【Java】 异步调用实践
本文要点:
- 为什么需要异步调用
- CompletableFuture 基本使用
- RPC 异步调用
- HTTP 异步调用
- 编排 CompletableFuture 提高吞吐量
BIO 模型
当用户进程调用了recvfrom 这个系统调用,kernel 就开始了 IO 的第一个阶段:准备数据。对于 network io 来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候 kernel 就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当 kernel 一直等到数据准备好了,它就会将数据从 kernel 中拷贝到用户内存,然后 kernel 返回结果,用户进程才解除 block 的状态,重新运行起来。所以,Blocking IO 的特点就是在 IO 执行的两个阶段都被 block 了。
同步调用
在同步调用的场景下,依次请求多个接口,耗时长、性能差,接口响应时长 T > T1+T2+T3+……+Tn。
减少同步等待
一般这个时候为了减少同步等待时间,会使用线程池来同时处理多个任务,接口的响应时间就是 MAX(T1,T2,T3):
线程池异步
大概代码如下
Future<String> future = executorService.submit(() -> {Thread.sleep(2000);return "hello world";
});
while (true) {if (future.isDone()) {System.out.println(future.get());break;}
}
同步模型中使用线程池确实能实现异步调用的效果,也能压缩同步等待的时间,但是也有一些缺陷:
- CPU 资源大量浪费在阻塞等待上,导致 CPU 资源利用率低。
- 为了增加并发度,会引入更多额外的线程池,随着 CPU 调度线程数的增加,会导致更严重的资源争用,上下文切换占用 CPU 资源。
- 线程池中的线程都是阻塞的,硬件资源无法充分利用,系统吞吐量容易达到瓶颈。
NIO 模型
为了解决 BIO 中的缺陷,引入 NIO 模型:
NIO 模型
当用户进程发出 read 操作时,如果 kernel 中的数据还没有准备好,那么它并不会 block 用户进程,而是立刻返回一个 error。从用户进程角度讲 ,它发起一个 read 操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个 error 时,它就知道数据还没有准备好,于是它可以再次发送 read 操作。一旦 kernel 中的数据准备好了,并且又再次收到了用户进程的 system call,那么它马上就将数据拷贝到了用户内存,然后返回。所以,用户进程其实是需要不断的主动询问 kernel 数据好了没有。
异步优化思路
我们知道了 NIO 的调用方式比 BIO 好,那我们怎么能在业务编码中使用到 NIO 呢?自己动手将 BIO 替换成 NIO 肯定不现实,已有组件支持 NIO 的可以直接使用,不支持的继续使用自定义线程池。
- 通过 RPC NIO 异步调用、 HTTP 异步调用的方式降低线程数,从而降低调度(上下文切换)开销。
- 没有原生支持 NIO 异步调用的继续使用线程池。
- 引入 CompletableFuture 对业务流程进行编排,降低依赖之间的阻塞。
简述CompletableFuture
CompletableFuture 是 java.util.concurrent 库在 java 8 中新增的主要工具,同传统的 Future 相比,其支持流式计算、函数式编程、完成通知、自定义异常处理等很多新的特性
常用 API 举例
supplyAsync
CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{ try{Thread.sleep(1000L);return "hello world";} catch (Exception e){return "failed";}
});
System.out.println(future.join());
// output
hello world
开启异步任务,到另一个线程执行。
complete
CompletableFuture<String> future1 = new CompletableFuture<>();
future.complete("hello world"); //异步线程执行
future.whenComplete((res, throwable) -> {System.out.println(res);
});
System.out.println(future1.join());
CompletableFuture<String> future2 = new CompletableFuture<>();
future.completeExceptionally(new Throwable("failed")); //异步线程执行
System.out.println(future2.join());
// output
hello world
hello worldException in thread "main"
java.util.concurrent.CompletionException:
java.lang.Throwable: failed
complete 正常完成该 CompletableFuture。
completeExceptionally 异常完成该 CompletableFuture。
thenApply
String original = "Message";
CompletableFuture<String> cf = CompletableFuture.completedFuture(original).thenApply(String::toUpperCase);
System.out.println(cf.join());
// output
MESSAGE
任务后置处理。
图示:
thenApply 图示
thenCombine
CompletableFuture<String> cf = CompletableFuture.completedFuture("Message").thenApply(String::toUpperCase);
CompletableFuture<String> cf1 = CompletableFuture.completedFuture("Message").thenApply(String::toLowerCase);
CompletableFuture<String> allCf = cf.thenCombine(cf1, (s1, s2) -> s1 + s2);
System.out.println(allCf.join());
// output
MSGmsg
合并任务,两个任务同时执行,结果由合并函数 BiFunction 返回。
图示:
thenCombine 图示
allOf
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Message1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Message2");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "Message3");
CompletableFuture<String> future = CompletableFuture.allOf(future1, future2, future3).thenApply(v -> {String join1 = future1.join();String join2 = future2.join();String join3 = future3.join();return join1 + join2 + join3;});
System.out.println(future.join());
// output
Msg1Msg2Msg3
allOf 会阻塞等待所有异步线程任务结束。
allOf 里的 join 并不会阻塞,传给 thenApply 的函数是在 future1, future2, future3 全部完成时,才会执行 。
图示:
CF 执行线程
下面有两个小demo,可以先试着想想输出的结果:
String original = "Message";
CompletableFuture cf = CompletableFuture.supplyAsync(() -> {System.out.println("supplyAsync thread: " + Thread.currentThread().getName());return original;
}).thenApply(r -> {System.out.println("thenApply thread: " + Thread.currentThread().getName());return r;
});
System.out.println(cf.join());
// output
supplyAsync thread: ForkJoinPool.commonPool-worker-1
thenApply thread: main
Message
String original = "Message";
CompletableFuture cf = CompletableFuture.supplyAsync(() -> {System.out.println("supplyAsync thread: " + Thread.currentThread().getName());try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}return original;
}).thenApply(r -> {System.out.println("thenApply thread: " + Thread.currentThread().getName());return r;
});
System.out.println(cf.join());
// output
supplyAsync thread: ForkJoinPool.commonPool-worker-1
thenApply thread: ForkJoinPool.commonPool-worker-1
Message
先看结论:
- 执行 complete 的线程会执行当前调用链上的所有CF。
- 如果 CF 提前 complete,后续 CF 由初始线程执行。
异步任务里没有 sleep 的时候,异步任务很快就会完成,意味着 JVM 执行到 thenApply 的时候,前置 CF 已经提前完成所以后续的 CF 会被初始线程 main 线程执行。
异步任务里有 sleep 的时候, JVM 执行到 thenApply 时,前置 CF 还没有完成,前置 CF complete 的线程会执行所有后续的 CF。
CF 嵌套join
ExecutorService executorService = Executors.newFixedThreadPool(2);
CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> {Thread.sleep(3000);return 1;
}, executorService);
CompletableFuture<Integer> cf2 = CompletableFuture.supplyAsync(() -> {Thread.sleep(3000);return 2;
}, executorService);
Integer join1 = cf1.thenApply((cf1Val) -> {System.out.println("cf1 start value:" + cf1Val);Integer cf2Val = cf2.join();System.out.println("cf2 end value:" + cf2Val);return 3;
}).join();
//output
cf1 start value:1
cf2 end value:2
代码很简单,有一个线程数为 2 的线程池,cf1、cf2 都使用这个线程执行异步任务,特别的是在 cf1.thenApply 中会调用 cf2.join(),当线程数是2的时候可以顺利输出
ExecutorService executorService = Executors.newFixedThreadPool(1);
CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> {Thread.sleep(3000);return 1;
}, executorService);
CompletableFuture<Integer> cf2 = CompletableFuture.supplyAsync(() -> {Thread.sleep(3000);return 2;
}, executorService);Integer join1 = cf1.thenApply((cf1Val) -> {System.out.println("cf1 start value:" + cf1Val);Integer cf2Val = cf2.join();System.out.println("cf2 end value:" + cf2Val);return 3;
}).join();
//output
cf1 start value:1
这时候我们将线程池的线程数调整为 1,这时只会输出 cf1 start value:1
,然后就一直阻塞。
使用 jstack -l pid 查看线程状态,发现是 WAITING
,等待的地方正是我们在代码里调用的cf2.join()
:
"pool-1-thread-1" #11 prio=5 os_prio=31 tid=0x00000001429f5000 nid=0xa903 waiting on conditionjava.lang.Thread.State: WAITING (parking)at sun.misc.Unsafe.park(Native Method)- parking to wait for <0x000000076ba5f7d0> (a java.util.concurrent.CompletableFuture$Signaller)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)at java.util.concurrent.CompletableFuture$Signaller.block(CompletableFuture.java:1707)at java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3323)at java.util.concurrent.CompletableFuture.waitingGet(CompletableFuture.java:1742)at java.util.concurrent.CompletableFuture.join(CompletableFuture.java:1947)at com.ppphuang.demo.threadPool.ExecutorsTest.lambda$main$2(ThreadPoolExecutorsTest.java:34)
原因是我们在唯一一个线程中调用 cf2.join()
,阻塞等待 cf2 完成,但是 cf2 需要等待 cf1 完成之后才有空闲线程去执行。这就类似于你右手正拿着一个水杯,然后等待右手拿水壶倒满水,这是不可能完成的。所以尽量不要嵌套join,不注意隔离线程池的话很容易造成’死锁‘(线程阻塞)。
CF 常用 API
API | 描述 |
---|---|
supplyAsync | 开启异步任务,到另一个线程执行,异步任务有返回值。 |
complete | 完成任务。 |
completeExceptionally | 异常结束任务。 |
thenCombine | 合并任务,两个任务同时执行,结果由合并函数 BiFunction 返回。 |
thenApply | 任务后置处理。 |
applyToEither | 会取两个任务最先完成的任务,上个任务和这个任务同时进行,哪个先结束,先用哪个结果。 |
handle | 后续处理。 |
whenComplete | 完成后的处理。 |
allOf | 等待所有异步线程任务结束。 |
join | 获取返回值,没有complete的 CF 对象调用join时,会等待complete再返回,已经 complete的 CF 对象调用join时,会立刻返回结果。 |
优化过程
异步 RPC 客户端
我们手写的这个 RPC 框架支持异步调用,如果你想看具体的实现,可以在文末找到源码链接。异步调用之前会设置一个 CallBack 方法,异步调用时会直接返回 null,不会等待服务端返回接果,服务端返回结果之后会通过 RPC 客户端自带的线程池执行设置的 CallBack 方法。
RPC 异步调用图示:
RPC 异步调用
包装异步RPC Client
通过 AsyncExecutor 包装 RPC的客户端,AsyncExecutor 类中的 client 属性值为创建的某个 RPC 服务的异步客户端代理类,这个代理类在构造方法中创建并赋值给 client 属性。
类中的 async 方法接受 Function 类型的参数 function,可以通过 function.apply(client)
来通过 client 执行真正的 RPC 调用。
在 async 方法中实例化一个 CompletableFuture, 并将 CompletableFuture 作为异步回调的上下文设置到 RPC 的异步回调中,之后将该 CompletableFuture 返回给调用者。
public class AsyncExecutor<C> {private C client;public AsyncExecutor(ClientProxyFactory clientProxyFactory, Class<C> clazz, String group, String version) {this.client = clientProxyFactory.getProxy(clazz, group, version, true);}public <R> CompletableFuture<R> async(Function<C, R> function) {CompletableFuture<R> future = new CompletableFuture<>();ClientProxyFactory.setLocalAsyncContextAndAsyncReceiveHandler(future, CompletableFutureAsyncCallBack.instance());try {function.apply(client);} catch (Exception e) {future.completeExceptionally(e);}return future;}
}
异步回调类
public class CompletableFutureAsyncCallBack extends AsyncReceiveHandler {private static volatile CompletableFutureAsyncCallBack INSTANCE;private CompletableFutureAsyncCallBack() {}@Overridepublic void callBack(Object context, Object result) {if (!(context instanceof CompletableFuture)) {throw new IllegalStateException("the context must be CompletableFuture");}CompletableFuture future = (CompletableFuture) context;if (result instanceof Throwable) {future.completeExceptionally((Throwable) result);return;}log.info("result:{}", result);future.complete(result);}
}
AsyncReceiveHandler 是 RPC 的异步回调抽象类,类中的 callBack、onException 抽象方法需要子类实现。
CompletableFutureAsyncCallBack 实现了这个 callBack 抽象方法,第一个参数是我们在包装异步 RPC Client 时设置的 CompletableFuture 上下文,第二个参数是 RPC 返回的结果。方法中判断 RPC 返回的结果是否异常,若异常通过 completeExceptionally 异常结束这个 CompletableFuture,若正常通过 complete 正常结束这个 CompletableFuture。
注册异步客户端Bean
@Component
public class AsyncExecutorConfig {@AutowiredClientProxyFactory clientProxyFactory;@Beanpublic AsyncExecutor<DemoService> demoServiceAsyncExecutor() {return new AsyncExecutor<>(clientProxyFactory, DemoService.class, "", "");}
}
异步 RPC 调用
@Autowired
AsyncExecutor<DemoService> demoServiceAsyncExecutor;CompletableFuture<String> pppName = demoServiceAsyncExecutor.async(service -> service.hello("ppp"));String name = pppName.join();
异步HTTP WebClient
WebClient 是从 Spring WebFlux 5.0 版本开始提供的一个非阻塞的基于响应式编程的进行 HTTP 请求的客户端工具。它的响应式编程的基于 Reactor 的。
WebClient VS RestTemplate
WebClient的优势在于:
- 非阻塞响应式 IO,单位时间内有限资源下支持更高的并发量。
- 支持使用 Java8 Lambda 表达式函数。
- 支持同步、异步、Stream 流式传输。
WebClient 使用
public CompletableFuture<String> asyncHttp(String url) {WebClient localhostWebClient = WebClient.builder().baseUrl("http://localhost:8080").build();Mono<HttpResult<String>> userMono = localhostWebClient.method(HttpMethod.GET).uri(url).retrieve().bodyToMono(new ParameterizedTypeReference<HttpResult<String>>() {})//异常处理 有onErrorReturn时doOnError不会触发,所以不需要后续在CompletableFuture中handle处理异常//如果不使用onErrorReturn,建议在后续CompletableFuture中handle处理异常.onErrorReturn(new HttpResult<>(201, "default", "default hello"))//超时处理.timeout(Duration.ofSeconds(3))//返回值过滤.filter(httpResult -> httpResult.code == 200)//默认值.defaultIfEmpty(new HttpResult<>(201, "defaultIfEmpty", "defaultIfEmpty hello"))//失败重试.retryWhen(Retry.backoff(1, Duration.ofSeconds(1)));CompletableFuture<HttpResult<String>> stringCompletableFuture = WebClientFutureFactory.getCompletableFuture(userMono);return stringCompletableFuture.thenApply(HttpResult::getData);}
WebClient 整合 CF
WebClientFutureFactory.getCompletableFuture 方法会把 WebClient 返回的结果组装成 CompletableFuture ,使用的是 Mono 类的 doOnError 和 subscribe 方法,当正常返回时通过 subscribe 来调用 completableFuture.complete,当异常时通过 doOnError 来调用 completableFuture.completeExceptionally:
public class WebClientFutureFactory {public static <T> CompletableFuture<T> getCompletableFuture(Mono<T> mono) {CompletableFuture<T> completableFuture = new CompletableFuture<>();mono.doOnError(throwable -> {completableFuture.completeExceptionally(throwable);log.error("mono.doOnError throwable:{}", throwable.getMessage());}).subscribe(result -> {completableFuture.complete(result);log.debug("mono.subscribe execute thread: {}", Thread.currentThread().getName());});return completableFuture;}
}
WebClient 对同一服务的多次调用:
public Flux<User> fetchUsers(List<Integer> userIds) {return Flux.fromIterable(userIds).parallel().flatMap(this::getUser).ordered((u1, u2) -> u2.id() - u1.id());
}
对返回相同类型的不同服务进行多次调用:
public Flux<User> fetchUserAndOtherUser(int id) {return Flux.merge(getUser(id), getOtherUser(id)).parallel().runOn(Schedulers.elastic()).ordered((u1, u2) -> u2.id() - u1.id());
}
对不同类型的不同服务的多次调用:
public Mono fetchUserAndItem(int userId, int itemId) {Mono<User> user = getUser(userId).subscribeOn(Schedulers.elastic());Mono<Item> item = getItem(itemId).subscribeOn(Schedulers.elastic());return Mono.zip(user, item, UserWithItem::new);
}
异步数据库调用
使用 CompletableFuture.supplyAsync 执行异步任务时,必须指定成自己的线程池,否则 CompletableFuture 会使用默认的线程池 ForkJoinPool,默认线程池数量为 cpus - 1:
WebClient<Boolean> dbFuture = CompletableFuture.supplyAsync(() -> getDb(id), ThreadPoolConfig.ASYNC_TASK_EXECUTOR);
编排 CF
构造了所有需要异步执行的 CompletableFuture 之后,使用 allOf 方法阻塞等待所有的 CompletableFuture 结果,allOf 响应之后可以通过 join 获取各个 CompletableFuture 的响应接口,这里的 join 是会立刻返回的,不会阻塞:
//RPC 的 CompletableFuture
CompletableFuture<String> pppName = demoServiceAsyncExecutor.async(service -> service.hello("ppp"));//RPC 的 CompletableFuture
CompletableFuture<String> huangName = demoServiceAsyncExecutor.async(service -> service.hello("huang"));//DB 操作的 CompletableFuture
WebClient<Boolean> dbFuture = CompletableFuture.supplyAsync(() -> getDb(id), ThreadPoolConfig.ASYNC_TASK_EXECUTOR);//allOf 方法阻塞等待所有的 CompletableFuture 结果
return CompletableFuture.allOf(pppName, huangName, dbFuture)//组装结果返回.thenApply(r -> pppName.join() && huangName.join() && dbFuture.join()).join();
超时处理
java9 中 CompletableFuture 才有超时处理,使用方法如下:
CompletableFuture.supplyAsync(() -> 6 / 3).orTimeout(1, TimeUnit.SECONDS);
java8 中需要配合 ScheduledExecutorService + applyToEither:
public class TimeoutUtils {private static final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(1);static {Runtime.getRuntime().addShutdownHook(new Thread(scheduledExecutor::shutdownNow));}public static <T> CompletableFuture<T> timeout(CompletableFuture<T> cf, long timeout, TimeUnit unit) {CompletableFuture<T> result = new CompletableFuture<>();scheduledExecutor.schedule(() -> result.completeExceptionally(new TimeoutException()), timeout, unit);return cf.applyToEither(result, Function.identity());}
}
TimeoutUtils 类与有一个静态属性,值为初始化的一个 ScheduledExecutorService ,还有一个静态方法 timeout ,这个方法将传入的 cf 用 applyToEither 接口与一个调度计时的 CompletableFuture 组合,哪个 CompletableFuture 先执行完成,就返回哪个的结果。
具体使用如下:
CompletableFuture<Integer> future = demoServiceAsyncExecutor.async(service -> service.getAge(18));
CompletableFuture<Integer> futureWithTimeout = TimeoutUtils.timeout(future, 3, TimeUnit.SECONDS);
futureWithTimeout.join();
异常与默认值处理
CompletableFuture 中可以处理异常有下面三个 API :
public <U> CompletableFuture<U> handle(java.util.function.BiFunction<? super T, Throwable, ? extends U> fn)
handle 接口不论 CompletableFuture 执行成功还是异常都会被处罚,handle 接受一个 BiFunction 参数,BiFunction 中的第一个参数为 CompletableFuture 的结果,另一个参数为 CompletableFuture 执行过程中的异常,Handle可以返回任意类型的值。可以给 handle 传入自定义函数,根据结果跟执行异常返回最终数据。
public CompletableFuture<T> whenComplete(java.util.function.BiConsumer<? super T, ? super Throwable> action)
whenComplete 接口与 handle 类似,whenComplete 接受一个 BiConsumer 参数,BiConsumer 中的第一个参数为 CompletableFuture 的结果,另一个参数为 CompletableFuture 执行过程中的异常,但是没有返回值。
public CompletableFuture<T> exceptionally(java.util.function.Function<Throwable, ? extends T> fn)
exceptionally 接口只有在执行异常的时候才会被触发,接受一个 Function 参会, Function 只有一个参数为 CompletableFuture 执行过程中的异常,可以有一个任意返回值。
下表是三个接口的对比:
handle() | whenComplete() | exceptionly() | |
---|---|---|---|
访问成功 | Yes | Yes | No |
访问失败 | Yes | Yes | Yes |
能从失败中恢复 | Yes | No | Yes |
能转换结果从T 到 U | Yes | No | No |
成功时触发 | Yes | Yes | No |
失败时触发 | Yes | Yes | Yes |
有异步版本 | Yes | Yes | Yes |
我们使用 handle 接口来处理异常与默认值,下面是封装的一个 handle 接口入参:
public class DefaultValueHandle<R> extends AbstractLogAction<R> implements BiFunction<R, Throwable, R> {public DefaultValueHandle(boolean isNullToDefault, R defaultValue, String methodName, Object... args) {super(methodName, args);this.defaultValue = defaultValue;this.isNullToDefault = isNullToDefault;}@Overridepublic R apply(R result, Throwable throwable) {logResult(result, throwable);if (throwable != null) {return defaultValue;}if (result == null && isNullToDefault) {return defaultValue;}return result;}
}
这个类实现了 handle 接口需要的 BiFunction 类型,在构造方法中有四个参数 boolean isNullToDefault, R defaultValue, String methodName, Object... args
第一个参数是决定执行结果为空值时,是否将我们传进来的第二个参数作为默认值返回。当异常时也会将第二个参数作为默认返回值。最后两个参数一个是方法名称,一个是调用参数,可以给父类用作日志记录。
与 CompletableFuture 配合使用如下:
CompletableFuture<String> pppName = demoServiceAsyncExecutor.async(service -> service.hello("ppp")).handle(new DefaultValueHandle<>(true, "name", "service.hello", "ppp"));
日志
封装了一个实现 BiConsumer 的 LogErrorAction 类,父类有个抽象类 AbstractLogAction 这个类就是简单使用 logReslut 方法记录日志,可以自己随意实现:
public class LogErrorAction<R> extends AbstractLogAction<R> implements BiConsumer<R, Throwable>{@Overridepublic void accept(R result, Throwable throwable) {logResult(result, throwable);}
}
与 CompletableFuture 配合使用如下
CompletableFuture<String> pppName = demoServiceAsyncExecutor.async(service -> service.hello("ppp")).whenComplete(new LogErrorAction<>("hello", "ppp"));
优化效果
优化前接口平均响应耗时 350ms,优化后平均响应耗时 180ms,下降 49% 左右。
最佳实践
- 禁止嵌套 join,避免“死锁”(线程阻塞)。
- 多个 CompletableFuture 聚合时建议使用 allOf。
- HTTP 使用无阻塞的 Spring webclient,避免自定义线程池线程阻塞。
- 使用 RPC 或者 HTTP 异步调用生成的 CompletableFuture, 后续的 thenAppply,handle 等禁止耗时操作,避免阻塞异步框架线程池。
- 禁止使用 CompletableFuture 的默认线程池,不同任务自定义线程池,不同级别业务线程池隔离,根据测试情况设置线程数,队列长度,拒绝策略。
- 异步执行的操作都加上超时,CF 超时后不会终止线程中的超时任务,不设置超时可能导致线程长时间阻塞。
- 建议使用异常、默认值、空值替换、错误日志等工具记录信息,方便排查问题。
相关文章:
【Java】 异步调用实践
本文要点: 为什么需要异步调用CompletableFuture 基本使用RPC 异步调用HTTP 异步调用编排 CompletableFuture 提高吞吐量BIO 模型 当用户进程调用了recvfrom 这个系统调用,kernel 就开始了 IO 的第一个阶段:准备数据。对于 network io 来说…...
园区智慧能源管理系统
实现对园区的用能情况实时、全方位监测,重点设备进行数据自动采集并智能统计、分析,根据需要绘制各种趋势曲线、能源流向图和分析报表。将物联网、大数据与全过程能源管理相融合,提供全生命周期的数字化用能服务,实现用能的精细化…...
基于卷积神经网络CNN的分类研究,基于卷积神经网络的手写体识别
目录 背影 卷积神经网络CNN的原理 卷积神经网络CNN的定义 卷积神经网络CNN的神经元 卷积神经网络CNN的激活函数 卷积神经网络CNN的传递函数 卷积神经网络CNN手写体识别 基本结构 主要参数 MATALB代码 结果图 展望 背影 现在生活,各种人工智能都要求对图像拥有识别…...
mybatis的增删改查运用
目录 一、总览图 二、运用 一、总览图 代码总览图 数据库总览图 二、运用 数据库的一张表对应一个封装类,一个mapper接口,一个mapper.xml文件, 一个实现类。表中的增删改查都在里面编写 但是配置xml文件整个数据库只要一个就好了 1.…...
centos8安装docker运行java文件
本文由个人总结,如需转载使用请标明原著及原文地址 这里是基于我前一篇搭的centos8服务器做的,如果yum baseos源或appstream源有问题可以去看看前一篇 https://blog.csdn.net/qq_36911145/article/details/129263830 1.安装docker 1.1配置docker yum…...
Docker容器化部署.net core API
1.为API集成Docker环境。(VS自带,傻瓜式操作) 1.1 点击项目,右键,添加,选择Docker支持 1.2 找到项目根目录中的Dockerfile文件,这是VS刚刚帮我们自动生成的。进入和做如图标红地方修改。 把文…...
springcloud 服务调用feign、熔断hystrix、网关gateway
回归cloud的学习,对于springcloud的架构与原理以及性能的分析我们都在之前的文章里写过:springcloud架构的认识我们之前测试过eureka服务注册功能,它能很好的保存服务之间的通讯关系,是维系微服务通讯网之间的电话本,同…...
《C++ Primer》 第十二章 动态内存
《C Primer》 第十二章 动态内存 动态内存与智能指针 shared_ptr允许多个指针指向同一个对象;unique_ptr则“独占”所指向的对象,weak_ptr指向shared_ptr所管理的对象。这三种类型都定义在memory头文件中。 shared_ptr类:默认初始化的智能…...
多个关键字用or、and、包含、不包含动态拼接为正则表达式和SQL查询条件
目录前言校验思路1、存储方式2、实现图一实现图二实现结果最后前言 不知道大家有没有做过这种需求:在某字符串中,根据多个关键字去判断这串字符串是否满足条件。如下图: 亦或是 如果说要根据图二的关键字去数据库中查询符合条件的数据&a…...
初始Linux操作系统
个人简介:云计算网络运维专业人员,了解运维知识,掌握TCP/IP协议,每天分享网络运维知识与技能。座右铭:海不辞水,故能成其大;山不辞石,故能成其高。个人主页:小李会科技的…...
【算法数据结构体系篇class12、13】:二叉树
一、判断二叉树是否是完全二叉树/*** 判断二叉树是否是完全二叉树** //判断层序遍历过程如果节点有右子树 没有左子树 那么就不是完全二叉树* //判断层序遍历过程如果遇到第一个节点是没有左或右子树的,也就是只有一个子节点或者没有,那么再往后层序遍历…...
数字IC手撕代码--联发科(总线访问仲裁)
题目描述当A、B两组的信号请求访问某个模块时,为了保证正确的访问,需要对这些信号进行仲裁。请用Verilog实现一个仲裁器,对两组请求信号进行仲后,要求:协议如图所示,请求方发送req(request&…...
白盒测试复习重点
白盒测试白盒测试之逻辑覆盖法逻辑覆盖用例设计方法1.语句覆盖2.判定覆盖(分支覆盖)3.条件覆盖4.判定条件覆盖5.条件组合覆盖6.路径覆盖白盒测试之基本路径测试法基本路径测试方法的步骤1.根据程序流程图画控制流图2.计算圈复杂度3.导出测试用例4.准备测试用例5.例题白盒测试总…...
学习C++这几个网站足矣
文章目录cppreferencecplusplusquick-bench[C 之父的网站](https://www.stroustrup.com/bs_faq.html)C提案[Cpp Core Guidelines](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines)[C Super-FAQ](https://isocpp.org/faq)[learn c](https://www.learncpp.com/)[A…...
第十四届蓝桥杯模拟赛(第三期)——C语言版
1.找最小数 问题描述: 请找到一个大于 2022 的最小数,这个数转换成十六进制之后,所有的数位(不含前导 0)都为字母(A 到 F)。 请将这个数的十进制形式作为答案提交。 #include <stdio.h> int main(…...
Flutter Button 实例
大家好,我是 17。 在上篇文章 使用 Flutter Button 介绍了如何修改 button 的样式,本文来具体实践一下。 本文列举一些常用的 button 效果,以便在用到的时候方便使用。因为 ElevatedButton 最常用,所以大多以 ElevatedButton 举…...
好玩的docker项目,盒子刷的海思nas,挂载外接硬盘。qb种子
玩法思路(5条消息) 群晖qb下载,tr辅种_屿兮的博客-CSDN博客_群晖辅种qbittorrent简介及设置_哔哩哔哩_bilibiliqb下载器下载Transmission最好用的BT(PT)下载神器/超简单上手教你在NAS轻松部署/告别简陋三步让你升级全中文最新Web界面(BT下载/PT下载/NAS/…...
RabbitMQ的使用
1.初识MQ1.1.同步和异步通讯微服务间通讯有同步和异步两种方式:同步通讯:就像打电话,需要实时响应。异步通讯:就像发邮件,不需要马上回复。两种方式各有优劣,打电话可以立即得到响应,但是你却不…...
Selenium如何隐藏浏览器页面?
Selenium隐藏浏览器页面 背景 在工作,学习中,我们常常会使用selenium来获取网页上的数据,编完完整程序之后,实现真正意义上的自动化获取,此时我们会发现在运行中往往会弹出浏览器页面,在调试过程中&…...
基于Ant DesignPro Vue实现通过SpringBoot后台加载自定义菜单- 前后端分离
基于Ant DesignPro Vue实现通过SpringBoot后台加载自定义菜单- 前后端分离 本文想基于Ant DesignPro Vue构建的前端SpringBoot实现的后端接口服务,实现前后端分离开发和独立运行,业务场景是登录认证,认证成功后返回该用户相应权限范围内可见的…...
Acwing---843. n-皇后问题
n-皇后问题1.题目2.基本思想3.代码实现1.题目 n−皇后问题是指将 n 个皇后放在 nn 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。 现在给定整数 n,请你输出所有的满足条件的棋子摆法。 …...
彻底搞清楚内存泄漏的原因,如何避免内存泄漏,如何定位内存泄漏
作为C/C开发人员,内存泄漏是最容易遇到的问题之一,这是由C/C语言的特性引起的。C/C语言与其他语言不同,需要开发者去申请和释放内存,即需要开发者去管理内存,如果内存使用不当,就容易造成段错误(segment fa…...
自动驾驶目标检测项目实战——基于深度学习框架yolov的交通标志检测
自动驾驶目标检测项目实战——基于深度学习框架yolov的交通标志检测 目前目标检测算法有很多,流行的就有faster-rnn和yolov,本文使用了几年前的yolov3框架进行训练,效果还是很好,当然也可以使用更高版本的Yolov进行实战。本代码使…...
flink兼容性验证
flink介绍:https://blog.csdn.net/weixin_43563705/article/details/107604693 一、安装启动 安装flink及其依赖 yum install java-1.8.0-openjdk curl tar mkdir -p /usr/local/flink wget https://mirrors.aliyun.com/apache/flink/flink-1.16.1/flink-1.16.1-bi…...
智慧工厂数字孪生可视化监测系统有效提升厂区安全管控效力
我国制造业正处于产业升级的关键时期,基于数据进行生产策略制定与管理是大势所趋,而数据可视化以更直观的方式成为数据分析传递信息的重要工具。 深圳华锐视点通过三维可视化手段对工厂各类设备进行三维建模,真实复现设备设施外观、结构、运转…...
c++中基本类型详细解释外加基本运算规则
👀👀#c中包括算数类型和空类型。 类型含义wchat_t宽字符bool布尔类型char字符chat16_tunicode字符chat_32unicode字符short短整型int整形long长整型longlong长整型float单精度浮点型double双精度浮点型longdouble扩展精度浮点型 👀…...
扬帆优配“机器人+”方案加码产业发展,这些股有望高增长
“机器人”发明新需求,2022年中国机器人市场规模约为174亿美元。 美国时刻3月1日,特斯拉在得克萨斯州超级工厂举办投资者日活动,展示了人形机器人Optimus的视频,更夸大的是,视频中的机器人好像在制作另一个机器人&…...
推送投票制作微信推送里投票制作教程在线投票活动制作
近些年来,第三方的微信投票制作平台如雨后春笋般络绎不绝。随着手机的互联网的发展及微信开放平台各项基于手机能力的开放,更多人选择微信投票小程序平台,因为它有非常大的优势。1.它比起微信公众号自带的投票系统、传统的H5投票系统有可以图…...
【架构师】跟我一起学架构——微服务分层监控
博客昵称:架构师Cool 最喜欢的座右铭:一以贯之的努力,不得懈怠的人生。 作者简介:一名Coder,软件设计师/鸿蒙高级工程师认证,在备战高级架构师/系统分析师,欢迎关注小弟! 博主小留言…...
Linux:https静态网站搭建案例
目录介绍httpshttps通信过程例介绍https 整个实验是在http实验基础上进行的 因为http协议在传输的时候采用的是明文传输,有安全隐患,所以出现了https(安全套接字层超文本传输协议) HTTPS并不是一个新协议, 而是HTTP…...
网站换域名了怎么办seo/重庆企业站seo
/** * 如何仅用递归函数和栈操作逆序一个栈 * 题目: * 一个栈依次压入1,2,3,4,5,那么从栈顶到栈底分别为5,4,3,2,1。 * 将这个栈转置后,从栈顶到栈…...
做网站banner分辨率设置多大/游戏推广可以做吗
支持向量机(SVM)的matlab的实现 支持向量机是一种分类算法之中的一个,matlab中也有对应的函数来对其进行求解;以下贴一个小例子。这个例子来源于我们实际的项目。 clc; clear; N10; %以下的数据是我们实际项目中的训练例子&#x…...
自己注册了个域名想做一个网站/输入搜索内容
Redis中list数据结构,具有“双端队列”的特性,同时redis具有持久数据的能力,因此redis实现分布式队列是非常安全可靠的。它类似于JMS中的“Queue”,只不过功能和可靠性(事务性)并没有JMS严格。 Redis中的队列阻塞时,整…...
泊头那家做网站/优化关键词有哪些方法
一、数值类型 1、整型 整数类型:TINYINT SMALLINT MEDIUMINT INT BIGINT 我们完全没必要为整数类型指定显示宽度,使用默认的就可以了 默认的显示宽度,都是在最大值的基础上加1 2、浮点型 浮点型:FLOAT DOUBLE 定点数:D…...
日照比较好的网站建设企业/口红的推广软文
1.在service层提示Could not autowire. No beans of StudenDao 并不能引进Dao接口 在dao层加入Repository注解即可 Component:这将 java 类标记为 bean。它是任何 Spring 管理组件的通用构造型。spring 的组件扫描机制现在可以将其拾取并将其拉入应用程序环境中。…...
网站开发要学的课程/网络营销活动方案
原文:http://zhacherui56688.blog.sohu.com/113669649.html 财 富 篇 》》根据生命的原理,我有权成为富翁,享受富裕的生活,这真是一件美好的事。>> 我一定会成为亿万富翁,能赐给我这个愿望的无限潜能,…...