1、背景介绍
供应链仓储域子域繁多,例如库存域,lpn域等,平时开发的过程中涉及很多分布式事务的场景,例如收货加库存,发货扣库存,拣货入箱,发货出箱等一些分布式事务场景,所以迫切需要出一套分布式事务处理方案,在调研了市场上的分布式事务解决方案,结合wms自身业务域不是强一致性的特色,选择了最终一致性,且使用本地消息表去实现它。
本地消息表这个方案最初是ebay提出的,核心就是将需要分布式处理的任务通过本地消息日志存储的方式来异步执行。该方案可以存到本地文本,数据库或消息队列,再通过异步线程或者自动job发起重试。
2、设计前的思考
在操作本地事务的同时,需要额外写入一张需要最终一致性业务记录的表,即本地消息表,且该业务记录是有状态的,在本地事务提交后,再执行需要最终一致性的方法,成功后更新记录状态为成功,如果失败了,还要引入兜底重试机制,下图能简单介绍它的功能:
为了实现以上最终一致性的功能。我们同样还需要做到以下几点:
- 无侵入:这个好理解,对于需要最终一致性的场景,可以很简单的实现
- 策略化:包括重试次数,重试的间隔时间,是否使用异步方式等
- 通用性:最好是无改动(或者很小改动)的支持绝大部分的场景,拿过来直接可用
- 复用性:对于异常场景存在需要重试场景,同时希望把正常逻辑和重试逻辑复用
3、架构设计
调研了大家对一致性框架的诉求,最终定义了如,入参自定义序列化,环境隔离,同步异步执行切换,注解支持,自定义执行拦截器,以及适配得物仓储业务的业务上下文订制以及持久化等一系列的核心能力,底层依赖了Spring的生态,在数据存储依赖了Mysql,Mongodb,缓存分布式锁上依赖了Redis等一系列主流的中间件,最终以jar包形式实现,尽可能做到即拿即用。
4、详细设计
基于在以上的架构设计后,做了以下设计
4.1 注解支持:@EnableConsistency
为了让用户更快,更方便的接入一致性框架,我们在早期的抽象类继承的方案上做了一版本升级,使用注解,使得使用方式跟@Transactional注解一样,只要加上@EnableConsistency就支持最终一致性的支持,非常方便。
4.2 自动重试&重试等待策略可配:
最终一致性有个天然的组成部分就是需要重试,一致性框架也不例外,引用了Spring的
ScheduledTask实现定时重试那些运行失败的记录,另外重试等待策略同样可配置:
4.2.1 重试等待策略
固定时间重试
支持配置固定时间间隔重试
延迟指数重试
底层采用Math.pow函数在重试次数越多次,执行间隔时间呈指数级增长
4.2.2 自定义重试次数
注解式支持重试次数的定义
4.3 自定义拦截器
在执行需要最终一致性方法的时候,我们同样提供了如Spring AOP一样被代理方法的前置,成功,异常后需要做的一些切面功能,非常方便的满足使用者的扩展,解耦了实现与扩展。
4.4 业务上下文&持久化
在业务系统的开发中,存在一个ThreadLocal的上下文,记录了用户的组织架构,签到等一系列用户信息。在设计一致性框架的时候我们考虑到用户上下文的存在,暴露了业务上下文扩展,以及存储业务上下文供重试时使用的能力。
4.5 环境隔离
由于得物的预发环境与生产环境采用的同一批数据库,故也将一致性框架记录采用了spring.profile.active的值做环境隔离,确保重试时不会预发的跑到生产的数据。
4.6 入参自定义序列化
由于需要在本地消息表中记录需要重试的方法的入参,故就涉及到入参序列化的问题,在思考良久之后,只提供默认的Json方式的序列化与反序列化,如果用户需要额外的序列化与反序列化方法,我们也支持,提供了暴露序列化与反序列化的口子供用户实现。
4.7 执行模式可配置
一般用本地消息执行最终一致性的部分,开始的设计是异步化执行,后续收到使用者用户的反馈,也有部分同步执行的场景,故增加了同步异步执行开关,开发者自行选择。
4.8 数据模型&状态机
4.9 核心代码展示
4.9.1ExecutorAutoConfiguration框架初始化
4.9.2 核心注解@EventualConsistency
使用最终一致性方法的核心注解:
4.9.3 核心实现Advice,MethodInterceptor的AnnotationAwareRetryOperationsInterceptor
4.9.4 延迟执行策略
4.9.5 AsyncConsistencyExecutor异步最终一致性执行
5、未来展望
(1)后台管理页面设计,支持报表查询,以及错误异常处理
(2)trace监控接入,方便定位问题
(3)适配业务支持类型DB
(4)自定义归档策略
最终一致性框架是由wms全组同学一起设计和开发完成,并且陪伴了得物快速发展的两年多,经历了2个618以及3个双十一,若干个情人节,圣诞节的考验。系统运行健康,无性能瓶颈,提升了很多场景下最终一致性的开发速度。目前仍在安全稳健的保障着仓储域服务的正常运转。