复杂任务中,流程的解耦设计
做事不能急,得一步异步的来;
一、业务场景
在系统开发的过程中,必然存在耗时极高的动作,是基于请求响应模式无法解决的问题,通常会采用解耦的思维,并基于异步或者事件驱动的方式去调度整个流程的完整执行;
文件任务:在系统解析大文件数据时,在获取任务之后,会异步处理后续文件读写流程;
中间表:执行复杂场景的数据分析时,收集完待分析的对象之后,会并发执行各个维度的采集动作,并依次将数据写入临时的中间表中,方便数据查询动作;
在上述场景中,基于单次请求响应无法执行整个过程,必须对流程分段分步和异步推进,在流程中根据场景去判断,是异步有序驱动,还是异步并发处理,并基于各个节点的执行状态判断动作是否成功。
二、任务管理
复杂任务的执行周期相对偏长,要确保稳定的执行则需要对任务做精细的设计和管理,通常会基于如下几个因素去描述任务:
02-1.png- 场景:定义任务的主题场景,便于将多种任务做统一管理和调度,例如:文件、数据、报表等;
- 计划:对任务做好步骤的拆分,并制定和推进相应的执行计划,例如:有序调度、并发执行等;
- 状态:针对任务和节点的执行计划,都要提供细节的状态定义,例如:开始/结束,进行中/已完成,成功/失败等;
设计合理的任务结构,以便更高效的管理流程,根据主题场景做任务分类,添加相应的执行计划,根据状态跟踪任务执行过程,并对失败动作进行捕捉和重试;
三、设计思路
1、同步请求响应
服务之间的通信模式一般分为:同步和异步两种;同步是指在请求端发出动作之后,会一直等待响应端完成,或者响应超时导致熔断,即在一次请求调用中耦合所有的处理流程;
02-2.png服务中大部分的请求都是同步响应模式,可以提高系统的响应速度;但是在分布式中,首先要控制超时熔断的时间,避免在流量高峰期请求堆积,拖垮整个服务;另外对于被大量调用的公共服务,要提高并发的支撑能力,降低对请求链路的性能影响。
2、异步解耦模式
异步模式的最大优点就是实现请求和响应的完全解耦,任务只需要触发一次开始动作,后续的流程就会逐步的推进直到结束;各个服务节点处理逻辑不会受到整个请求链路的耗时限制;
02-3.png实现异步有多种方式,例如:请求回调、发布订阅、Broker代理等;在之前异步章节中有详细描述,这里不再赘述;异步消除了服务节点之间的依赖关系,但是也同样提高了流程的复杂性;
3、事件驱动设计
事件驱动是一个抽象的概念,即通过事件的方式实现多个服务间的协同,驱动整个流程的处理逻辑;在业务层面是一种设计思想,在技术层面通常采用发布订阅的方式,同样也可以消除服务间的强依赖关系;
02-4.png事件和异步在模式上很类似,事件驱动在设计上更加精细,例如在订单场景中:将订单的状态变化作为一个事件,服务间通过消息传递的方式,依次处理库存服务、物流服务等;由于事件携带了一定的业务信息和状态,流程解耦更加彻底的同时复杂度也会更高。
四、实践总结
1、结构设计
在结构设计中围绕任务、节点、数据三个核心要素,以确保对任务的执行过程有完整的跟踪和管理,要实现对任务的节点及相关的操作,具备执行重试或者直接取消撤回的控制;
02-5.png状态管理是一项很复杂的工作,要衡量任务中各个状态标识是否合理,就要实时监控状态的变化,并且基于各种极端情况去验证流程,例如:重试设计、任务取消、任务暂停。
2、高并发管理
任务型的场景加上复杂的管理流程,执行时间自然也很长,如果场景中涉及到大文件的解析、或者数据调度,自然会引入任务分割与并发执行的机制;
比较常用的思路:根据任务调度的集群数,对数据核心编号进行哈希计算,可以采用取模和分段两种算法,然后基于多线程的方式并发处理各自服务内的分管任务。
3、管理模型
不管是观察者模式,或者发布订阅模型,又或者说事件驱动设计,都可以理解为生产/消费的关系模型,围绕生产、存储、消费三个节点做管理;
02-6.png- 生产端:负责创建具体的消息主体,在总线模式中,通常将消息进行入库存储,然后再执行队列推送,并跟踪该过程的状态变化,保证库和队列的一致性;
- 消息体:描述动作的发布方和消费方,关键的状态信息变化,唯一标识和创建时间及版本,其余则根据场景需要定义即可;
- 消费端:在消费时要关注的核心问题即失败重试,要避免重试机制引起数据不一致的问题,可以对消费进行加锁或者消息状态校验,以实现幂等的效果;
- 存储端:通常采用数据库和消息中间件双存储的模式,并且需要保证二者动作的同时成功或者失败,顺序为先入库再执行队列推送;
整个模型在设计思路上比较合理,但是架构的复杂性也变的很高,比如数据一致性问题、状态机制、事务、幂等性、流程中断等;整个链路需要详细的追踪记录并且可视化管理,开发补偿动作的接口,用来及时解决可能出现的突发问题。
4、组件案例
Spring框架本身就极具复杂度,这里单看事件模型的设计,包含三个核心角色:事件、发布、监听;与观察者设计模式在理念上相同;
02-7.png事件:ApplicationEvent基础抽象类继承自JDK中EventObject类,具体事件要继承该类;source事件源,timestamp发生的系统时间;
public class OrderState {
// 基础要素
private Integer eventId ;
private String version ;
private Long createTime ;
// 消息定位
private String source ;
private String target ;
// 状态变化
private Integer orderId ;
private Integer stateFrom ;
private Integer stateTo ;
}
public class OrderStateEvent extends ApplicationEvent {
public OrderStateEvent (OrderState orderState){
super(orderState);
}
}
发布者:Spring定义的顶级接口ApplicationEventPublisher,提供事件发布的能力;
@Service
public class EventService implements ApplicationContextAware, ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
public void changeState (Integer orderId,Integer stateFrom,Integer stateTo){
OrderState orderState = new OrderState() ;
OrderStateEvent orderStateEvent = new OrderStateEvent(orderState) ;
logger.info(Thread.currentThread().getName()+";"+orderStateEvent);
applicationEventPublisher.publishEvent(orderStateEvent);
}
}
监听者:实现JDK中顶级接口EventListener,Spring扩展了多种事件监听器,以实现各种场景的需求,例如:有序无序、同步异步等;
@Component
public class OrderStateListener implements ApplicationListener<OrderStateEvent> {
private static final Logger logger = LoggerFactory.getLogger(OrderStateListener.class) ;
@Async
@Override
public void onApplicationEvent(OrderStateEvent orderStateEvent) {
logger.info(Thread.currentThread().getName()+";"+orderStateEvent);
}
}
五、参考源码
应用仓库:
https://gitee.com/cicadasmile/middle-ware-parent
编程文档:
https://gitee.com/cicadasmile/butte-java-note