通用流程基础框架设计

背景

在如应用打包和广告投放等场景都有明显的流程分多步骤处理过程,在开发时除了要考虑业务逻辑外,还需要考虑其他业务无关的通用功能,比如编排,异常处理,重试,监听回调等等,耗费较多精力和成本。

目标

建设通用流程基础框架,提供编排、重试、监听回调等基础能力,这样开发时只需要关心业务逻辑即可,有效提高开发效率。

框架特点

  1. 基于责任链模式把流程内的多个步骤串联起来,通过流程上下文传递信息。
  2. 拦截器能力,在流程或步骤前后执行,可以实现统一校验或统一异常处理等通用功能。
  3. 不同的流程之间通过转换器串联起来,实现流程间的松解耦。
  4. 通过配置线程池,同类流程允许并行执行,提高效率。
  5. 可以设置步骤级别异步重试策略。
  6. 支持流程的超时重试,一般用于服务重启后流程自动恢复。
  7. 步骤可以注册监听策略,使步骤拥有异步监听的能力,常用于那些异步创建,需通过不断反查来确定最终创建状态的场景。
  8. 与 Spring 框架相结合,方便利用 Spring 框架的强大特性,例如依赖注入等等。

方案设计

类&接口设计

  1. BaseStep 为步骤基类,继承该类类封装步骤逻辑。里面提供基础能力,可以控制流程执行。
  2. Flow 类主要用于装配步骤和连接下一流程。
  3. FlowContext 为流程上下文基类,里面包含流程的基本信息,通过该类的实例决定执行哪一流程。
  4. FlowKeyGenerator 接口,流程 key 生成器,每个流程实例都有一个全局唯一的 key。
  5. FlowFactory 接口,流程工厂,主要用于生产流程,定义运行流程的线程池以及应用的流程名。
  6. FlowManager 接口,流程管理器,用于注册流程工厂,流程拦截器等等,还是流程调用的入口。
  7. FlowExecutor 接口,流程执行器,控制流程执行的核心
  8. FlowInterceptor 接口,在流程执行前后提供埋点。
  9. StepInterceptor 接口,在步骤执行前后提供埋点。
  10. FlowContextConverter 接口,FlowContext 转换器,流程结束后通过转换器连接下一流程。

流程拦截器调用顺序

流程拦截器提供三个埋点,分别是:

  1. 流程提交到线程池前(beforeSubmit):由于流程从提交到线程池到被线程池调度有一定的时延,所以特地提供此埋点。
  2. 流程执行前(beforeRun)
  3. 流程执行完后(afterRun):该埋点会有一个 Exception 类型的参数,接收步骤或 StepInterceptor 或 FlowInterceptor#beforeRun方法产生的异常

具体调用过程如下图:

流程转换器

流程转换器相当于一个中转站,把不同流程连接起来,支持分裂(一对多)。

加上流程转换器后的流程执行模型就变成这样了:

异步重试机制

调研了 Spring-retry 与 Guava-retry 开源框架发现都是同步阻塞重试,浪费线程资源,影响性能。

于是重新设计了一个异步重试模块,默认实现基于时间轮+线程池,

支持多种延时策略:

  • 固定延时
  • 随机时间延时
  • 递增式延时
  • 指数级递增延时

和多种重试判断器:

  • 最大重试次数判断
  • 特定异常判断
  • 混合判读
  • 自定义判断

并且支持注解的方式配置。

异步监听步骤

考虑一个常见的场景:一个步骤申请了某个请求,请求需要审核比较耗时,通常需要不断重试监听请求的审核状态,直到审核通过或者失败,此时步骤才算完成。

流程框架也提供这种步骤的能力。前面提到的重试监听通常需要到另外的线程中处理,此时流程会暂停往下进行,直到重试结束,再根据监听结果来自行决定流程是否继续。

处理模型大致如下:

流程执行超时重试

常用于服务器重启后自动恢复流程。

设计方案分为两大模块:

第一个模块:记录流程上下文和过期时间

  1. 可以在流程处设置默认的步骤重试策略,也可以在步骤处配置特定的超时重试策略。
  2. 利用流程拦截器和步骤拦截器,在流程提交时和步骤执行前,把当前流程上下文保存到 Redis hash 类型key里,以及把步骤的过期时间存到 Redis 的有序队列里。
  3. 流程结束时删除 Redis 里的流程上下文和有序队列里的记录。

第二个模块,轮询监听过期流程

  1. 超时监听器轮询 Redis 有序队列,获取第一条记录(第一条记录代表最先过期的流程)

  2. 判断这条记录是否已经过期

    1. 如果未过期,忽略,睡眠等待下一次轮询
    2. 如果已过期,从有序队列删除记录,获取流程上下文并提交给流程管理器,该流程将从超时的步骤开始重新执行
  3. 开始下一次轮询

答疑

  1. 疑问:超时的流程上下文可能会被多个实例同时发现,如何保证只有一个实例拿到这个流程的重试权?

答: 发现超时流程上下文时,对该流程上下文从有序队列里删除,利用 Redis 单线程执行命令的特性,只会有一个实例删除成功,删除成功的实例才会去重试流程。


  1. 疑问:假如步骤仍在运行,但是由于运行时间过长超过了超时时间,导致超时监听器认为该流程超时并且重新分发该流程,此时会不会出现同一流程(flowKey 一样)多处运行的情况?如何解决的?

答: 这种场景下,确实会出现同一流程多处运行,但仅限当前步骤,从下一步骤开始只会单处运行。框架里尽最大程度避免出现这种问题:

  1. 步骤拦截器会在步骤执行前:更新当前流程上下文的更新时间。步骤执行后:比较当前流程上下文的更新时间与远端(Redis)保存的流程上下文的更新时间,如果当前流程的较小,说明当前流程已经过时,直接抛出异常 结束流程;
  2. 步骤内每次重试都会更新流程上下文的更新时间,避免重试太多太久让监听器误以为流程已经超时。

需要注意的点

  1. 因为有重试的存在,所以要求每个步骤的逻辑必须保证幂等。
  2. 因为整个流程可能不在同一线程里执行,所以可以不使用 ThreadLocal 就不要使用 ThreadLocal,可以使用 FlowContext 来代替。
  3. 如果某些场景只能使用 ThreadLocal,也有开源方案可以解决,例如下面这个: github.com/alibaba/tra...
相关推荐
机器之心1 小时前
图学习新突破:一个统一框架连接空域和频域
人工智能·后端
.生产的驴2 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven
顽疲2 小时前
springboot vue 会员收银系统 含源码 开发流程
vue.js·spring boot·后端
机器之心2 小时前
AAAI 2025|时间序列演进也是种扩散过程?基于移动自回归的时序扩散预测模型
人工智能·后端
hanglove_lucky3 小时前
本地摄像头视频流在html中打开
前端·后端·html
皓木.4 小时前
(自用)配置文件优先级、SpringBoot原理、Maven私服
java·spring boot·后端
i7i8i9com5 小时前
java 1.8+springboot文件上传+vue3+ts+antdv
java·spring boot·后端
秋意钟5 小时前
Spring框架处理时间类型格式
java·后端·spring
我叫啥都行5 小时前
计算机基础复习12.22
java·jvm·redis·后端·mysql
Stark、5 小时前
【Linux】文件IO--fcntl/lseek/阻塞与非阻塞/文件偏移
linux·运维·服务器·c语言·后端