赞
踩
金融、支付类公司,易产生资损的业务当属代发、转账、卡券权益兑换类等出金交易。每一位致力于此的架构师、开发工程师最担心重复代发、重复兑换的问题,尤其对于批量的出金类业务,由于设计不当导致的大量的资金、资产损失后果惨重。因此批处理任务的防重设计极为重要。
2.1 古代
以每5分钟执行一次批量代发交易为例,早期大部分系统都是单体应用,通常采用Spring+Cron表达式来实现定时任务:
- spring-quartz.xml:
- <bean id="issue" ...>
- <property name="cronExpression" value="* 0/5 * * * ?"></property>
- </bean>
为了防止定时任务重复启动,开发工程师们需要注意两点。
- <!--concurrent : false表示等上一个任务执行完后再开启新的任务-->
- <property name="concurrent" value="false"></property>
2.2 近代
随着集群部署的广泛使用,单体应用逐渐被替代。为了防止集群内多个实例的定时任务同时启动:
2.3 现代
通过docker容器化部署时,应用实例的IP地址不固定,会散落部署在云平台内,采用配置“白名单服务器”的方式来防重已不可行。通过集中式的调度中心来发起调度任务是解决问题的一种思路。可使用elastic-job,xxl-job等开源框架,也可自研。
基本思路是:
潜在风险点
开发工程师在规避定时任务重复执行的同时,往往忽视了dubbo等组件,以及nginx、HAproxy等中间件带来的自动重试问题。想从根本上解决问题,开发工程师还需从系统设计与实现入手:即使定时任务重复启动也能确保交易不重复。
3.1 原始需求
产品经理:我要在代发工资系统内,实现每5分钟给200名员工发放工资,要求本系统不能重复发放工资。
3.2 需求分析
关键诉求:不能重复。
3.3 数据结构
注:以下所有的实现,都默认有重复执行定时任务的可能。
思路一:一查二发
实现过程大致是一查二发的步骤,伪代码:
- //1.查出最多200条
- select * from t_order where status=‘0’ and rownum<=200;
- //2.循环单笔发,并逐笔更新订单状态
- foreach i++{
- Send(i);
- update t_order set status=#{status} where id=#{id} and status='0'
- }
不难发现,当两个定时任务同时执行,步骤1的sql可能抓取到重复的数据,继而在步骤2造成重复代发工资。那么在执行send()前先校验下订单状态?不可行,可能会存在脏读。
思路二:幂等表
实现步骤:代发前,插入幂等表,如主键冲突记录异常,并终止本笔代发。
- //1.查出最多200条
- select * from t_order where status=‘0’ and rownum<=200;
- //2.循环单笔发
- foreach i++{
- insert into mideng(…) values(…);
- if 主键冲突 then 记录异常 return;
- Send(i);
- update t_order set status=#{status} where id=#{id} and status='0'
- }
通过幂等表可以做到防重,但有洁癖的开发工程师会觉得:
是否可以从根本上杜绝多个定时任务取同样的数据?
思路三:一锁二发
实现步骤:在每个定时任务的线程内生成唯一标识UUID,先把UUID更新至数据库中,再根据UUID做为查询条件取数进行后续的代发。
- //1.锁定数据
- String uuid=createUUID();//a73266fc0aa411eaae330221860e9b7e
- upate t_order set batch_id=#{uuid} where status=‘0’ and batch_id is null and rownum<=200;
- //2.取出本线程锁定的数据
- select * from t_order where status=‘0’ and batch_id=#{uuid};
- foreach i++{
- Send(i);
- update t_order set status=#{status} where id=#{id} and status='0' and batch_id=#{uuid}
- }
数据结构
通过一锁二发的步骤可以保证每个定时任务只执行当前线程锁定的数据。
开发工程师也可以根据实际的业务需求,同时使用一锁二发+幂等表。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。