Java中的异步
何为异步?
首先必须强调一个基础概念,异步是一种设计方式,异步操作不等于多线程。异步处理不用阻塞当前线程来等待处理完成,而是允许后续操作,直至其它线程将处理完成,并回调通知此线程。注:不是所有的异步都需要回调,异步通常处理 I/O 场景,即读写。读当然需要回调,不然干嘛读?可写就不必回调啦。所以,有一半场景需要回调,另一半则不需要。
同步处理和异步处理相对,需要实时处理并响应,一旦超过时间会结束会话,在该过程中调用方一直在等待响应方处理完成并返回。举个不恰当的例子:同步就像最初的快递,快递小哥给你打电话得等你本人来后亲自签收;异步则是使用快递站点的模式,快递到后快递小哥给你发个短信告知你到哪取、取件码多少,收到短信后按短信内容去相应站点取你的快递就行了。
异步处理的实现方式有很多种,常见多线程,消息中间件,发布订阅的广播模式,其根本逻辑在于先把请求承接下来,放入存储容器中,在从容器中把请求取出,统一调度处理。
使用异步的优劣
- 异步可以解耦业务间的流程关联,降低耦合度;
- 降低接口响应时间,例如用户注册,异步生成相关信息表;
- 异步可以提高系统性能,提升吞吐量;
- 流量削峰即把请求先承接下来,然后在异步处理;
- 异步用在不同服务间,可以隔离服务,避免雪崩;
注意:一定要监控任务是否产生积压过度情况,任务如果积压到雪崩之势,你会感觉每一片雪花都想勇闯天涯。
异步处理模式
异步流程处理的实现有很多方式,但是实际开发中常用的就那么几种,例如:
- 基于接口异步响应,常用在第三方对接流程;
- 基于消息生产和消费模式,解耦复杂流程;
- 基于发布和订阅的广播模式,常见系统通知
注:异步适用的业务场景,对数据实时性、强一致性的要求不高,异步处理的数据更多时候追求的是最终一致性。关于“最终一致性有空在给大家介绍”。
下面结合不同的开发场景简单介绍下各种方式的使用
与第三方对接
大致流程如下:
- 地服务发起请求,调用第三方服务接口;
- 请求包含业务参数,和成功或失败的回调地址;
- 第三方服务实时响应流水号,作为该调用的标识;
- 之后第三方服务处理请求,得到最终处理结果;
- 如果处理成功,回调本地服务的成功通知接口;
- 如果处理失败,回调本地服务的失败通知接口;
注:如果本地服务多次请求第三方服务,需要根据流水号判断该请求的状态,业务的状态设计也是极其复杂,要根据流水号和状态追溯整个流程的执行进度,避免错乱。(保证请求一致性,避免相同请求数据重复请求重复处理)
生产消费模式
- 消息生成之后,写入Kafka/MQ队列 ;
- 消息处理方获取消息后,进行流程处理;
- 消息在中间件提供的队列中持久化存储 ;
- 消息发起方如果挂掉,不影响消息处理 ;
- 消费方如果挂掉,不影响消息生成;
注:基于这种消息中间件模式,完成业务解耦,提高系统吞吐量,是(分布式)架构中常用的方式。即使有多个消息消费方,也只会在一个消费方处理消息,这就是该模式的特点。
发布订阅模式
- 提供一个消息传递频道channel;
- 多个订阅频道的客户端client;
- 消息通过PUBLISH命令发送给频道channel ;
- 客户端就会收到频道中传递的消息 ;
注:广播模式更注重通知下发,流程交互性不强。实际开发场景:运维总控系统,更新了某类服务配置,通知消息发送之后,相关业务线上的服务在拉取最新配置,更新到服务中。(Nacos使用的就是该种模式)
使用注意
任务积压监控
生成一个消息,就因为有一个处理该消息的任务要执行,这就导致任务可能出现积压的情况,常见原因大致有如下几个:
- 任务产生的服务过多,任务处理的服务过少,不均衡;
- 任务处理时间太长,也导致生产过剩;
- 中间件本身容量偏小,需要扩容或集群化管理;
如果任务积压过多,可能要对任务生成进行流量控制,或者提升任务的处理能力,从而避免雪崩情况。
Spring中使用
- Spring管理的异步线程数量有限,如果是web项目的话,线程数量由tomcat的线程池配置有关系,所以最好自己配置线程配置类。
- 异步回调,异步方法执行完之后,我怎么才能让用户知道我已经调用成功了呢?异步方法返回Future类型的结果对象,然后通过websocket将执行消息发送给用户界面
- 在生产环境一定要设置合理的超时时间,防止程序无限期等待下去。另外就是要考虑异步任务执行过程中报错抛出异常的情况,需要捕获future的异常信息。Future没有提供通知的机制,就是回调,我们无法知道它什么时间完成任务。
有时间结合源码介绍下Spring内置的异步Async的原理及实现