延迟对列公共服务的设计

适用场景:

延时任务有别于定时任务,定时任务往往是固定周期的,有明确的触发时间。而延时任务一般没有固定的开始时间,它常常是由一个事件触发的,而在这个事件触发之后的一段时间内触发另一个事件。也就是说,任务事件生成时并不想让消费者立即拿到,而是延迟一定时间后才接收到该事件进行消费。定时任务首选 Medusa CronJob , 有DAG需求或者大数据任务的选Airflow

  • 在订单系统中,一个用户某个时刻下单之后通常有30分钟的时间进行支付,如果30分钟之内没有支付成功,那么这个订单将自动进行过期处理;
  • 车后养护订单下单后会锁库存,7天后用户还不来店就需要释放库存;
  • 车后订单,15分钟后提醒店家下单。

一、技术架构

延时队列架构如上图所示,重要组件说明如下:

  • gRPC server:获取client发送过来的请求,根据请求(request)中的触发时间(triggerAt)与设置的最大延时时间(max_delay)进行比较,把超过max_delay的request中的延时任务信息(task)中的(triggerAt, taskId)存储到redis的有序集合(SortedSet)"xn_delay_wait_queue"中。把没有超过max_delay的task(triggerAt, taskId)存储到redis的有序集合"xn_delay_queue"中,同时把task详细信息写入到redis的hash中,key值为taskId。把task信息以及request_id等信息发送至kafka的"kafka.default.topic"中,后续持久化到MySQL中;
  • consumer(runloop):获取"kafka.default.topic"中的消息,将task信息写入MySQL的task表中,或者将task的状态信息更新至task表中对应的task中;
  • fetcher(runloop):获取redis中有序集合"xn_delay_wait_queue"中的满足条件的task,根据这些task的taskId获取MySQL task表中的task详情,并把这些task详情写入至redis的hash中,key为taskId,同时把task(triggerAt, taskId)写入至有序集合"xn_delay_queue"中;
  • worker(runloop):获取redis中有序集合"xn_delay_queue"中的满足条件的task,根据taskId获取redis hash中的task详情,然后执行这些task对应的任务(kafka任务、gRPC任务、HTTP任务);
  • redis:有序集合存储延时队列中的任务、等待进入延时队列的任务。hash存储延时队列中的task的详情信息,使用完成后删除task详情;
  • mysql:持久化所有task信息及request_id。

二、详细说明

2.1 创建延时任务

通过gRPC或者HTTP方式创建延时任务,延时队列的处理原理相同,因此下面章节不进行单独描述。

2.1.1 创建gRPC延时任务

1)发送gRPC延时请求至gRPC server

client发送request到gRPC server,调用CreateGrpcTask(),其中request信息如下表所示:

字段名

类型

是否必须

说明

trigger_atint64Y触发时间(时间戳毫秒值)
servicestringY请求的serviceName或packageName.serviceName
retryobjectNRetryInfo,任务运行失败后最大重试次数count和重试间隔interval(秒)
projectstringY项目名
methodstringYgrpc请求的methodName
hoststringY任务请求的grpc服务host
groupstringY组名
formatintY

GrpcBodyFromat

bodybytesY根据Format指定编码器编码后的二进制数据

gRPC server将接收到的request进行处理,生成task_info,包括task_id、group、project、trigger_at、retry、payload(host、service、method、body、format),对trigger_at与配置的max_delay进行比较,若trigger_at大于max_delay,则把task_id、trigger_at加入到延时等待队列中(redis的有序集合"xn_delay_wait_queue");若trigger_at小于max_delay,则把task_id、trigger_at加入到延时队列中(redis的有序集合"xn_delay_queue"),同时将taskinfo信息写入到redis hash中,key为task_id,field为basic_info(group、project、trigger_at、retry、type)进行encode后的值,value为payload(host、service、method、body、format)进行encode后的值。xn_delay_queue中的task与hash中的task一一对应。

gRPC server将task写入redis的示意图如下所示:

然后gRPC server将task_info发送至kafka的"kafka_default_topic",用于后续的持久化。

2)consumer从kafka topic消费消息

consumer从kafka topic中获取task_info,然后对task_info中的payload信息进行decode操作(与上面encode对应),获取task_info后将其写入至MySQL task表中,column["task_id"|"task_type"|"group_name"|"project"|"payload"|"trigger_at"|"retry_count"|"retry_interval"|"task_status"|"created_at"|]。

3)fetcher将xn_delay_wait_queue中满足条件的加入至xn_delay_queue

fetcher不断的轮询xn_delay_wait_queue,找到满足trigger_at<server.wait_delay的task,根据这些task的task_id获取MySQL task表中的task_info信息,然后将task_info写入到redis hash中,key为task_id,field为basic_info(group、project、trigger_at、retry、type)进行encode后的值,value为payload(host、service、method、body、format)进行encode后的值。接着,把task_id、trigger_at加入到延时队列中(redis的有序集合"xn_delay_queue"),同时删除延时等待队列中(redis的有序集合"xn_delay_wait_queue")的task_id对应的记录。

fetcher将xn_delay_wait_queue满足trigger_at<server.wait_delay的task加入值xn_delay_queue的示意图如下所示,

4)worker执行xn_delay_queue中满足触发条件的task

worker不断的轮询xn_wait_queue,找到满足now大于等于trigger_at的task,拿到task_id后删除延时队列中(redis的有序集合"xn_delay_queue"的task_id对应的记录。根据task_id获取redis hash中对应的field[basic_info(group、project、trigger_at、retry、type)],value[payload(host、service、method、body、format)]进行decode操作,然后根据type类型执行对应的任务(kafka任务、http任务、gRPC任务),执行完成后删除redis hash中task_id对应的记录,同时更新MySQL task表中task_id对应的状态为task执行成功。

注:若decode操作或者任务执行失败,worker将会删除redis hash中task_id对应的记录,同时更新MySQL task表中task_id对应的状态为task执行失败,以及更新retry次数为当前retry次数。若retry次数小于等于request中的设置的retry次数时,将task_id,trigger_at加入至延时队列中(redis的有序集合"xn_delay_queue"),然后将task_info写入到redis hash中;若retry次数大于request中的设置的retry次数时,则删除redis hash中task_id对应的记录。

worker执行示意图如下所示

2.1.2 创建HTTP延时任务

原理与创建gRPC延时任务一致,仅task类型不一致,为HTTP任务,后续具体执行为HTTP任务,这里不再赘述。

2.1.3 创建kafka延时任务

原理与创建gRPC延时任务一致,仅task类型不一致,为kafka任务,后续具体执行为kafka任务,这里不再赘述。

2.2 取消延时任务

用户通过gRPC或者http方式发送取消延时任务请求后,gRPC server根据task_id执行删除redis中hash中对应的task_info,以及redis的有序集合"xn_delay_queue"或者"xn_delay_wait_queue"中的task信息(task_id,trigger_at),同时更新MySQL task表中task_id对应的状态为task取消。

2.3 查询延时任务状态

用户通过gRPC或者http方式发送取消延时任务请求后,gRPC server根据task_id查询MySQL task表中task_id对应的状态为task信息,返回task的状态信息(task_type, trigger_at, task_status)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值