大家好,我是君哥。今天分享批量任务的重叠问题。
面试官:聊聊你用过的任务调度框架?
我:目前任务调度框架的选择有很多,比如业内熟悉的 QuartZ,Spring Batch,xxl-job,以及新一代的任务框架 PowerJob 等。我本人使用最多的还是 xxl-job。
面试官:使用 xxl-job 遇到过任务重叠的问题吗?
我:任务重叠是批量任务调度中经常遇到的问题,主流的分布式调度框架是可以解决部分任务重复问题的,但并不能解决全部问题。
面试官:能说一下 xxl-job 解决了哪些任务重叠的问题,还有哪些问题没有解决吗?
我:(下午来自 xxl-job 官网)
- FIRST(第一个):固定选择第一个机器。
- LAST(最后一个):固定选择最后一个机器。
- ROUND(轮询):按照注册机器的列表顺序进行任务调度。
- RANDOM(随机):随机选择在线的机器。
- CONSISTENT_HASH(一致性HASH):每个任务按照Hash算法固定选择某一台机器,且所有任务均匀散列在不同机器上。
- LEAST_FREQUENTLY_USED(最不经常使用):使用频率最低的机器优先被选举。
- LEAST_RECENTLY_USED(最近最久未使用):最久未使用的机器优先被选举。
- FAILOVER(故障转移):按照顺序依次进行心跳检测,第一个心跳检测成功的机器选定为目标执行器并发起调度。
- BUSYOVER(忙碌转移):按照顺序依次进行空闲检测,第一个空闲检测成功的机器选定为目标执行器并发起调度。
- SHARDING_BROADCAST(分片广播):广播触发对应集群中所有机器执行一次任务,同时系统自动传递分片参数;可根据分片参数开发分片任务。
虽然策略比较多,但是常用的策略就是固定在一台机器上面跑或者轮询策略。假设有一个密集的定时调度任务,每隔两分钟跑一次,如果在同一个机器上跑,遇到调用下游接口响应慢,或者处理的业务数据暴增,可能出现前一次任务还没有完成,下一次任务已经发起了。为了防止任务重叠在一台机器上,可以采用 xxl-job 轮询的策略。
面试官:按照你刚才举的例子,定时任务每隔两分钟跑一次,如果选择固定在一台机器上跑,比如选择路由策略是 FIRST 或者 LAST,有多个任务重叠在一台机器上,xxl-job 是怎样解决的?
我:对于跑批间隔时间比较短的定时任务,因为调度很密集,执行器很容易造成任务阻塞。xxl-job 提供了 3 种阻塞处理策略:
- 单机串行(默认):调度请求进入单机执行器后,调度请求进入 FIFO 队列并以串行方式运行。
- 丢弃后续调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,本次请求将会被丢弃并标记为失败。
- 覆盖之前调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,将会终止运行中的调度任务并清空队列,然后运行本地调度任务。
采用单机串行的策略,如果前面的任务没有处理完成,后面的任务只能排队,等待被调度。
面试官:单机任务排队执行,这种策略看上去没有什么问题。你有遇到过相关问题吗?
我:一般场景下是不会有影响的,但如果一个任务跟日期相关,有任务排队到第二天才能执行,很可能会造成业务影响。
面试官:能举一个具体的场景吗?
我:举一个贷款业务的例子,系统通过跑批任务给应还款日是第二天的客户发送还款短信通知,处理逻辑是每个任务查出应还款日是第二天的一批用户,然后发送短信通知。这里的第二天需要用系统日期来判断。如果有排队的任务到第二天才执行,那应还款的部分用户会收不到通知。
面试官:这种场景 xxl-job 有解决方案吗?
我:采用轮询的路由策略,可以让排队的任务调度的不同的机器上,这样可以减少单机器的任务积压问题。
面试官:采用轮询的路由策略,表面看是可以解决单机器的任务积压问题。但如果任务积压是因为下游接口响应慢、sql 查询性能差等造成的任务执行慢,采用轮询策略可以解决吗?
我:这类问题造成的任务排队,改为轮询策略也是解决不了的,轮询策略只能解决类似单机资源紧张造成跑批慢的情况。
面试官:那这些问题有什么解决方案吗?
我:业务上是有解决方案的,比如金融行业系统里面一般有“切日”的概念,跟日期相关的业务不会取系统日期,而是取数据库保存的账务日期,所有需要使用账务日期的任务跑批完成后,才会把数据库保存的账务日期切换到第二天。
面试官:采用轮询路由策略,还可能带来哪些问题呢?
我:还可能造成冥等的问题。比如一个任务的处理逻辑是,每隔两分钟从数据库查询 100 条状态是“未处理”的数据进行处理,处理完成后更新状态为“已处理”。如果机器一上的任务还没有执行完成,机器二上的任务已经开始调度,那很可能会把机器一上正在执行的 100 条数据查出来重复处理。
面试官:这个问题该怎么解决呢?
我:我提供两种解决思路:
- 第一,最简单的方式就是采用单机执行;
- 第二,增加一个中间状态“处理中”,执行查询数据的时候采用排它锁,查出后更新成中间状态,提交事务后再执行处理逻辑,处理逻辑执行完成后更新成“已处理”
select * from xxx where statsu='未处理' limit yy,100 for update
update xxx set statsu='处理中' where id in(...)
面试官:那如果数据源不能加锁呢?比如数据源是邮件、接口查询。
我:可以对邮件或接口查到的数据唯一键进行保存,把唯一键做数据库主键,下一个任务可以根据主键做排除,再实现业务逻辑。
面试官:好的,恭喜你进入下一轮...