服务加载
从DB读取服务配置
根据配置动态构造Consumer对象并添加到Spring容器中
提交任务
业务系统通过dubbo或http接口提交任务
判断任务过期时间是否在一个扫描周期内
如果是,
设置分片号(从当前节点所负责的分片随机获取)
添加到内存队列
任务保存到job_delay_task表
如果否,
设置分片号(根据分片总数和随机算法算出分片号)
任务保存到delay_task表
定时器
由一个线程管理
根据配置的扫描间隔设置定时器的执行周期
根据当前时间和扫描间隔算出该时段的过期时间X-Delay
从DB获取过期时间在X-Delay之前的所有任务,并放到DelayQueue
调度任务
由一个线程池管理
所有线程都阻塞在DelayQueue的方法take
take到任务,从DB中获取任务,判断是否存在
如果不在,什么也不做(任务已执行成功或已被删除)
如果存在,判断调用次数是否超过设置
如果不超
调用业务回调服务
从任务中取出调用的服务配置
从容器中获取对应的Consumer对象
异步调用业务回调服务
设置下次重试时间,记录调用日志job_delay_task_execlog
如果超过,将任务转移到job_delay_task_backlog
任务反馈
更新任务调用结果
优点
功能全面,高可用、易伸缩、可重试
缺点
略微复杂
需要将服务配置动态生成为Consumer对象
增加新的服务需要通知所有调度节点刷新
存在一定的耦合性(直接调用业务服务,协议耦合),如果接入系统是thrift协议呢?
需要处理任务的重试
调度系统直接回调业务服务,如果业务服务不可用可能会造成盲目重试,不能很好的控制流量(调度系统不知道业务服务的处理能力)
如果引入MQ,使用MQ来解耦服务调用的协议,保证任务的重试,并由消费方根据自己的处理能力控制流量会不会更好呢?
另一种方案(DB/DelayQueue/ZooKeeper/MQ)
整体架构
数据库设计
主要流程
调度任务
由一个线程池管理
所有线程都阻塞在DelayQueue的take方法
take到任务,从DB中获取任务,判断是否存在
如果不在,什么也不做(任务已执行成功或已被删除)
如果存在,将任务转移到job_delay_task_execlog;往消息队列投递消息