我们首先调研了公司内外的定时器实现,避免重复造轮子 。调研了诸如例如公司外部的Quartz、有赞的延时队列等,以及公司内部的PCG tikker、TDMQ等,以及微信支付内部包括营销、代扣、支付分等团队的一些实现方案 。最后从可用性、可靠性、易用性、时效性以及代码风格、运维代价等角度考虑,我们决定参考前人的一些优秀的技术方案,并根据我们团队的技术积累和组件情况,设计和实现一套定时器方案 。
首先要确定定时器的存储数据结构 。这里借鉴了时间轮的思想,基于微信团队最常用的存储组件tablekv进行任务的持久化存储 。使用到tablekv的原因是它天然支持按uin分表,分表数可以做到千万级别以上;其次其单表支持的记录数非常高,读写效率也很高,还可以如MySQL一样按指定的条件筛选任务 。
我们的目标是实现秒级时间戳精度,任务到期只需要单次通知业务方 。故我们方案主要的思路是基于tablekv按任务执行时间分表,也就是使用使用方指定的start_time(时间戳)作为分表的uin,也即是时间轮bucket 。为什么不使用多轮时间轮?主要是因为首先kv支持单表上亿数据,其二kv分表数可以非常多,例如我们使用1000万个分表需要约115天的间隔才会被哈希分配到同一分表内 。故暂时不需要使用到多轮时间轮 。
最终我们采用的分表数为1000w,uin=时间戳mod分表数 。这里有一个注意点,通过mod分表数进行Key收敛, 是为了避免时间戳递增导致的key无限扩张的问题 。示例图如下所示:
文章插图
kv时间轮
任务持久化存储之后,我们采用一个Daemon程序执行定期扫表任务,将到期的任务取出,最后将请求中带的业务信息(biz_data添加任务时带来,定时器透传,不关注其具体内容)回调通知业务方 。这么一看流程还是很简单的 。
这里扫描的流程类似上面讲的时间轮算法,会有一个指针(我们在这里不妨称之为time_pointer)不断向后移动,保证不会漏掉任何一个bucket的任务 。这里我们采用的是commkv(可以简单理解为可以按照key-value形式读写的kv,其底层仍是基于tablekv实现)存储CurrentTime,也就是当前处理到的时间戳 。每次轮询时Daemon都会通过GetByKey接口获取到CurrentTime,若大于当前机器时间,则sleep一段时间 。若小于等于当前机器时间,则取出tablekv中以CurrentTime为uin的分表的TaskList进行处理 。本次轮询结束,则CurrentTime加一,再通过SetByKey设置回commkv 。这个部分的工作模式我们可以简称为Scheduler 。
Scheduler拿到任务后只需要回调通知业务方即可 。如果采用同步通知业务方的方式,由于业务方的超时情况是不可控的,则一个任务的投递时间可能会较长,导致拖慢这个时间点的任务整体通知进度 。故而这里自然而然想到采用异步解耦的方式 。即将任务发布至事件中心(微信内部的高可用、高可靠的消息平台,支持事务和非事务消息 。
由于一个任务的投递到事件中心的时间仅为几十ms,理论上任务量级不大时1s内都可以处理完 。此时time_pointer会紧跟当前时间戳 。当大量任务需要处理时,需要采用多线程/多协程的方式并发处理,保证任务的准时交付 。broker订阅事件中心的消息,接受到消息后由broker回调通知业务方,故broker也充当了Notifier的角色 。整体架构图如下所示:
文章插图
架构图
主要模块包括:
任务扫描Daemon:充当Scheduler的角色 。扫描所有到期任务,投递到事件中心,让它通知broker,由broker的Notifier通知业务方 。
定时器broker:集业务接入、Notifier两者功能于一身 。
任务状态机图如下所示,只有两种状态 。当任务插入kv成功时即为pending状态,当任务成功被取出并通知业务方成功时即为finish状态 。
文章插图
6.实现细节与难点思考下面就上面的方案涉及的几个技术细节进行进一步的解释 。
6.1 业务隔离通过biz_type定义不同的业务类型,不同的biz_type可以定义不同的优先级(目前暂未支持),任务中保存biz_type信息 。业务信息(主键为biz_type)采用境外配置中心进行配置管理 。方便新业务的接入和配置变更 。业务接入时,需要在配置中添加诸如回调通知信息、回调重试次数限制、回调限频等参数 。业务隔离的目的在于使各个接入业务不受其他业务的影响,这一点由于目前我们的定时器用于支持本团队内部业务的特点,仅采取对不同的业务执行不同业务限频规则的策略,并未做太多优化工作,就不详述了 。
推荐阅读
- 数据频繁变化的情况下,如何高效检索?
- SQL小技巧解决大问题:如何统计字符出现次数?
- 深入了解VLAN工作原理,不能错过干货
- Spring Boot 中如何统一 API 接口响应格式?
- 物业消防安全管理制度如何实施
- 金桔如何做饮品,青金桔发酵茶如何做
- 美国红枫,美国红枫小苗如何购买
- 古代是如何选妃的 看古代皇帝是如何选妃的?感觉身体被掏空
- 如何在个月内瘦30斤,月瘦10斤是否靠谱
- 新鲜玫瑰花瓣如何利用,菊花茶如何挑选