来自:架构之美

1.业务场景剖析

公司业务系统(比如:电商系统)中有大量涉及定时任务的业务场景,例如:实现买卖双方在线沟通的IM系统,为了确保接收方能够收到消息,服务端一般都会有重试策略,即服务端在消息发出的一段时间内,如果没收到接收方的确认信息,则重新发送消息。这就是一个典型的定时任务场景—消息发出等待固定的时间后,触发消息重发逻辑,重发逻辑首先判断所发消息是否收到确认信息,如果没有就将对应的消息再发送一次。类似的场景有很多,例如:自动取消长时间未支付的订单、买家收货一段时间以后自动确认打款等等。

应对上述场景比较粗暴的解决方案是定时扫库,例如:业务将订单的支付超时时间定义为2小时。可以每1分钟扫一次订单库,将超时订单取消。显然,此方案不够优雅,主要问题如下:

1.增加数据库读压力;

2.不够精确,会有最长1分钟的滞后;

扫库的方案一般体量不大时可以使用,当业务发展到一定规模后就不再适用。对IM消息重发秒级别的定时需求,只能增加扫库的频率,但过于频繁的扫库很可能会将数据库拖垮。显然需要更优雅的技术方案解决定时任务问题。

2.时间轮算法剖析

时间轮算法可以高效的处理定时任务,并且有非常高的精度。我们以IM的消息重发功能为例介绍下时间轮算法的应用。假设消息发出15秒后触发重发逻辑,可以设计如图1所示的数据结构:

图1 时间轮算法

1.一个包含15个元素的数组,数组每个元素指向一个链表,可以理解为15个桶;

2.Current Pos指向数组中某个桶,每秒钟向下移动一次,指向下个桶;

3.如果Current Pos已经指向最后一个桶,移动时返回数组头部,指向第一个桶;

4.发消息时将相关信息放入Current Pos指向的桶中(作为链表中的一个元素)。

根据图1可以看出,Current Pos的下一个桶(图1数组中下标5)中的数据,就是所有已经发出15秒的消息,我们可以遍历链表,取出数据,逐个触发消息重发逻辑。

需要注意的是Current Pos是一个循环指针(指向数组末端后,下次偏移会重新指向数组头)。由此我们可以用更形象的方式描述这个结构,把数组首尾相连,形成一个“轮子”,也就是时间轮。如图2所示:

图2 时间轮

3.基于Redis实现时间轮

上面介绍的时间轮是将数据放在应用进程内存中的,可靠性比较差,我们可以进一步优化,将时间轮放到公共的存储中,很自然的会想到使用Redis。可以用Redis中的List和String两种数据类型实现时间轮。设计多个List,每个List相当于时间轮中的一个桶,再用一个String保存当前List的Key。如图3所示:

图3 Redis实现时间轮

应用程序通过修改CurrentPos的Value实现时间轮指针的移动。很轻松的将进程内存中的时间轮放到了Redis中,提高了数据可靠性,同时可以多个实例访问时间轮,避免了单点问题。

4.长时间跨度定时需求实现

新的问题来了,现在我们看到的时间轮,可以用来触发秒级别的定时任务,但如果时间跨度比较大,例如小时或者天级别的定时场景,我们就需要一个非常“大”的轮子,将会占用非常多的内存资源。显然不是最优的方案,我们可以继续优化,使用磁盘文件+内存时间轮结合的方案,如图4所示:

图4 长时间跨度定时需求实现方案

1.将数据(需要触发的事件)按触发时间分散存储在多个文件中;

2.每个文件负责存储触发时间在指定区间内的事件,例如:文件A负责区间为2019年11月21日14点~2019年11月21日14点30,则所有在这个时间区间内触发的事件都会存储在这个文件中;

3.内存中只装载最近半小时要触发的事件,并以时间轮形式组织。

应用程序需要选择合适的时机将文件装载到内存,并建立时间轮索引,控制好时间轮转动,将到期事件触发即可。

5.延时服务中台化

到现在为止,上面介绍的模型已经可以满足业务的定时任务需求,但它还只是一个功能逻辑,我们不能让每个有需求的业务方都去实现一个时间轮,重复造轮子。所以需要将方案进一步地下沉,抽象为一个基础的中台服务,提供通用的延时触发能力,对外提供服务。系统架构设计如图5所示:

图5 延时服务中台化

业务模块与延时服务的交互可以使用RPC Over TCP,但是对于延时服务来说,需要调用业务模块的RPC接口来触发延时任务,延时服务与业务模块耦合,不利于系统的稳定性,同时业务也需要实现回调接口,侵入比较大,易用性也不强。我们自然可以想到使用消息队列解耦,新的架构如图6所示:

  图6 消息队列解耦

6.延时消息

看到这里很多同学会说,直接用延时消息不是更好嘛?为什么还要花这么大的篇幅,把事情搞这么复杂(架构设计哲学在于大道至简)。确实是这样,但问题在于不是所有的消息队列都支持延时消息,更不是都能支持任意时间的延时,例如:现在使用非常广泛的RocketMQ对延迟消息的支持就不是很友好:只能支持固定几个档位,不能支持任意时间的延迟。所以为了能够满足业务需求,我们使用外部服务+Redis+MQ的方案(图6),以较低的投入快速实现任意时间的延时消息。

7.改造MQ实现延时消息

图6架构设计依赖了外部服务以及Redis等来实现延时消息,由于引入过多的组件,整体服务稳定性会受影响,并不是最好的实现方案。更优雅的方案可以通过改造MQ来实现,把时间轮逻辑做到MQ内部。下面以RocketMQ为例介绍延时消息的实现方案,RocketMQ消息存储模型如图7所示:

图7 RocketMQ消息存储模型

1.消息按顺序存储在CommitLog文件中;

2.Dispatch线程将消息按主题分发到不同的Queue中。

8.思考

基于RocketMQ实现延时消息,除了实现时间轮算法外还会涉及哪些改动?

1.延时消息的特殊处理;

2.主从Broker之间的数据同步;

3.Broker的故障恢复;

4.......

可见,实际情况要复杂的多,还有很多点需要注意,这里也没有全部列出,欢迎大家在留言区讨论补充。

9.总结

针对同一业务需求,会有多种技术方案,单从技术角度看很容易判断出方案的好坏,但我们看问题需要多角度和多维度,不能只关注于方案本身,从上面延时消息实现来看,最优雅的方案同时也是最复杂、实现难度最大方案。反之,借助外部组件可以用较低的投入实现同样的使用效果,虽然有缺陷,但对业务来说感受不到差别,所以我们选择技术方案时不一定要过于追求完美,要结合公司实际情况和团队技术实力,计算投入产出比(ROI),作出合理选择。

架构师最核心的能力是根据场景给出优雅的解决方案,适合就是最好的,既不保守设计又不过度设计。NX的架构师,是把复杂问题简单化,简单问题做没;SB(SomeBody)的架构师,刚好相反,是把简单问题复杂化,复杂问题搞不定!

希望您是一个NX的架构师!那么问题来了,做一个NX的架构师,需要具备哪些思维方式?欢迎留言交流。


特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

长按订阅更多精彩▼如有收获,点个在看,诚挚感谢

构建企业级业务高可用的延时消息中台相关推荐

  1. 如何构建一套高可用的 APP 消息推送平台

    转载自  如何构建一套高可用的 APP 消息推送平台 消息推送作为移动 APP 运营中的一项关键技术,已经被越来越广泛的运用.本文追溯了推送技术的发展历史,剖析了其核心原理,并对推送服务的关键技术进行 ...

  2. 如何构建一套高可用的移动消息推送平台?

    消息推送作为移动 APP 运营中的一项关键技术,已经被越来越广泛的运用.本文追溯了推送技术的发展历史,剖析了其核心原理,并对推送服务的关键技术进行深入剖析,围绕消息推送时产生的服务不稳定性,消息丢失. ...

  3. 阿里云AHAS Chaos:应用及业务高可用提升工具平台之故障演练

    简介: 阿里云AHAS Chaos:应用及业务高可用提升工具平台之故障演练 应用高可用服务AHAS及故障演练AHAS Chaos 应用高可用服务(Application High Availabili ...

  4. 大众点评账号业务高可用进阶之路

    引言 在任何一家互联网公司,不管其主营业务是什么,都会有一套自己的账号体系.账号既是公司所有业务发展留下的最宝贵资产,它可以用来衡量业务指标,例如日活.月活.留存等,同时也给不同业务线提供了大量潜在用 ...

  5. 架构师修炼系列【业务高可用】

    无论高可用计算架构,还是高可用存储架构,其本质的设计目的都是为了解决部分服务器 故障的场景下,如何保证系统能够继续提供服务.但在一些极端场景下,有可能出现所有服务 器都出现故障,如果业务期望达到即使在 ...

  6. 拒绝空谈 AI 设想!手把手教你构建实时、高可用的 AI 调度平台

    当前人工智能飞速发展,机器学习的精度和性能也在不断提高,由机器学习引导的技术正在默默改变着大家的生活,并创造出很多新的商业机会和价值. 人工智能目前最直接的出发点就是让计算机能将人眼看到的.人耳听到的 ...

  7. 【NoSQL】抛弃VIP,使用consul和sentinel构建redis的高可用系统

    背景: 传统redis高可用方案只加了sentinel,在主库故障后,虽然可以选主,然后切换只读参数,但是,对 应用来说却需要更改连接的IP或者在hosts中更改解析.算得上是半自动. 利用consu ...

  8. 基于CAP模型设计企业级真正高可用的分布式锁

    来自:架构之美 1.CAP定律剖析 2000年Eric Brewer教授提出CAP猜想,2年后CAP猜想被Seth Gilbert和Nancy Lynch从理论上证明.CAP是Consitency(强 ...

  9. python消息队列celery高可用_分布式消息队列-Celery

    怎么能不恨呢,在我发现自己是恶鬼的时候,在我最绝望最虚弱的时候,这个世上最该跟我在一起的人却用刀把我的心刺穿了 Celery 是 Distributed Task Queue,分布式任务队列.分布式决 ...

最新文章

  1. cesium加载百度地图_四大Webgis地图框架的对比选择
  2. boost::detail::atomic_count相关的测试程序
  3. 【机器学习】对于特征离散化,特征交叉,连续特征离散化非常经典的解释
  4. matlab库存点仿真教程,基于MATLABSimulink库存系统建模与仿真.doc
  5. 在资本寒冬下,程序员为何也能迅速找到好工作
  6. bzoj 1643: [Usaco2007 Oct]Bessie's Secret Pasture 贝茜的秘密草坪(DP)
  7. pytorch与Keras对应模型Sequential()和add()
  8. OPPO手机刷机解锁救砖解账户锁方法
  9. Learning Modality-Specific Representation with Self-Supervised Mulit-Task Learning for MSA
  10. 细数SAP Business One主流实施与服务商
  11. 【转】大数据【五十八】探索MapReduce过程及分组详解
  12. 尚硅谷kylin单机版之安装kylin
  13. NOI 4.3 1538: Gopher II(匈牙利算法求最大匹配)
  14. react-native打包失败: Expiring daemon because jvm heap space is exhausted
  15. 【Excel神技】之 区域命名
  16. 基于vue-router的从后端动态加载菜单的实现
  17. python(for in 用法)
  18. 用ADI官方源码构建任意Xilinx的ZYNQ平台下的ADI芯片控制程序(1)——硬件平台搭建篇
  19. 利用计算机教学的体会,利用多媒体教学设备心得体会
  20. 我爱SQL之数据查询

热门文章

  1. 为什么要选择html5,5分钟告诉你,为什么要学HTML5大前端
  2. 哈尔滨商业大学计算机与信息工程学院地址,计算机与信息工程学院
  3. mysql5.6允许远程连接_mysql允许远程连接的方法
  4. 数据管理DMS企业版接入蚂蚁金融云售卖
  5. WindowsAPI每日一练(2) 使用应用程序句柄
  6. Debian手动修改ip地址
  7. TeamCity 和 Nexus 的使用
  8. DFS(6)——hdu1342Lotto
  9. Linux下USB suspend/resume源码分析【转】
  10. [转] Java快速教程