高可用延迟队列设计与实现
<!-- 高可用延迟队列设计与实现 -->
延迟队列:一种带有 延迟功能 的消息队列
- 延时 → 未来一个不确定的时间
- mq → 消费行为具有顺序性
这样解释,整个设计就清楚了。你的目的是 延时,承载容器是 mq。
背景
列举一下我日常业务中可能存在的场景:
- 建立延时日程,需要提醒老师上课
- 延时推送 → 推送老师需要的公告以及作业
为了解决以上问题,最简单直接的办法就是定时去扫表:
服务启动时,开启一个异步协程 → 定时扫描 msg table,到了事件触发事件,调用对应的 handler
几个缺点:
- 每一个需要定时/延时任务的服务,都需要一个 msg table 做额外存储 → 存储与业务耦合
- 定时扫描 → 时间不好控制,可能会错过触发时间
- 对 msg table instance 是一个负担。反复有一个服务不断对数据库产生持续不断的压力
最大问题其实是什么?
调度模型基本统一,不要做重复的业务逻辑
我们可以考虑将逻辑从具体的业务逻辑里面抽出来,变成一个公共的部分。
而这个调度模型,就是 延时队列 。
其实说白了:
延时队列模型,就是将未来执行的事件提前存储好,然后不断扫描这个存储,触发执行时间则执行对应的任务逻辑。
那么开源界是否已有现成的方案呢?答案是肯定的。Beanstalk (https://github.com/beanstalkd/beanstalkd) 它基本上已经满足以上需求
设计目的
- 消费行为 at least
- 高可用
- 实时性
- 支持消息删除
依次说说上述这些目的的设计方向:
消费行为
这个概念取自 mq 。mq 中提供了消费投递的几个方向:
at most once
→ 至多一次,消息可能会丢,但不会重复at least once
→ 至少一次,消息肯定不会丢失,但可能重复exactly once
→ 有且只有一次,消息不丢失不重复,且只消费一次。
exactly once
尽可能是 producer + consumer 两端都保证。当 producer 没办法保证是,那 consumer 需要在消费前做一个去重,达到消费过一次不会重复消费,这个在延迟队列内部直接保证。
最简单:使用 redis 的 setNX 达到 job id 的唯一消费
高可用
支持多实例部署。挂掉一个实例后,还有后备实例继续提供服务。
这个对外提供的 API 使用 cluster 模型,内部将多个 node 封装起来,多个 node 之间冗余存储。
为什么不使用 kafka?
考虑过类似基于 kafka/rocketmq 等消息队列作为存储的方案,最后从存储设计模型放弃了这类选择。
举个例子,假设以 Kafka 这种消息队列存储来实现延时功能,每个队列的时间都需要创建一个单独的 topic(如: Q1-1s, Q1-2s..)。这种设计在延时时间比较固定的场景下问题不太大,但如果是延时时间变化比较大会导致 topic 数目过多,会把磁盘从顺序读写会变成随机读写从导致性能衰减,同时也会带来其他类似重启或者恢复时间过长的问题。
- topic 过多 → 存储压力
- topic 存储的是现实时间,在调度时对不同时间 (topic) 的读取,顺序读 → 随机读
- 同理,写入的时候顺序写 → 随机写
架构设计
API 设计
producer
producer.At(msg []byte, at time.Time)
producer.Delay(body []byte, delay time.Duration)
producer.Revoke(ids string)
consumer
consumer.Consume(consume handler)
使用延时队列后,服务整体结构如下,以及队列中 job 的状态变迁:
- service →
producer.At(msg []byte, at time.Time)
→ 插入延时job到 tube 中 - 定时触发 → job 状态更新为 ready
- consumer 获取到 ready job → 取出 job,开始消费;并更改状态为 reserved
- 执行传入 consumer 中的 handler 逻辑处理函数
生产实践
主要介绍一下在日常开发,我们使用到延时队列的哪些具体功能。
生产端
- 开发中生产延时任务,只需确定任务执行时间
- 传入 At()
producer.At(msg []byte, at time.Time)
- 内部会自行计算时间差值,插入 tube
- 传入 At()
- 如果出现任务时间的修改,以及任务内容的修改
- 在生产时可能需要额外建立一个 logic_id → job_id 的关系表
- 查询到 job_id →
producer.Revoke(ids string)
,对其删除,然后重新插入
消费端
首先,框架层面保证了消费行为的 exactly once
,但是上层业务逻辑消费失败或者是出现网络问题,亦或者是各种各样的问题,导致消费失败,兜底交给业务开发做。这样做的原因:
- 框架以及基础组件只保证 job 状态的流转正确性
- 框架消费端只保证消费行为的统一
- 延时任务在不同业务中行为不统一
- 强调任务的必达性,则消费失败时需要不断重试直到任务成功
- 强调任务的准时性,则消费失败时,对业务不敏感则可以选择丢弃
这里描述一下框架消费端是怎么保证消费行为的统一:
分为 cluster 和 node。cluster:
https://github.com/tal-tech/go-queue/blob/master/dq/consumer.go#L45
- cluster 内部将 consume handler 做了一层再封装
- 对 consume body 做hash,并使用此 hash 作为 redis 去重的key
- 如果存在,则不做处理,丢弃
node:
https://github.com/tal-tech/go-queue/blob/master/dq/consumernode.go#L36
- 消费 node 获取到 ready job;先执行 Reserve(TTR),预订此job,将执行该job进行逻辑处理
- 在 node 中 delete(job);然后再进行消费
- 如果失败,则上抛给业务层,做相应的兜底重试
所以对于消费端,开发者需要自己实现消费的幂等性。
项目地址
go-queue
是基于 go-zero
实现的,go-zero
在 github 上 Used by
有300+,开源一年获得11k+ stars.
-
go-zero: https://github.com/zeromicro/go-zero
-
go-zero: https://gitee.com/kevwan/go-zero
-
go-stash: https://github.com/tal-tech/go-queue
欢迎使用并 star 支持我们!
微信交流群
关注『微服务实践』公众号并点击 交流群 获取社区群二维码。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
容器神话 Docker 是如何一分为二的
译者点评: 最近听了很多资深的人士关于开源,以及商业化的分析。。开源与商业化,听起来就是一对矛盾的所在,似乎大家都在尝试做其二者的平衡。是先有开源,还是先有商业化?俗话说“谈钱不伤感情”,近几年背靠开源的创业公司如雨后春笋般涌现,即使是开发人员也是需要生活的。 容器神话 Docker 曾经无比风光,盛极一时。即使这样一个备受瞩目,大获风投的热捧的独角兽也未能免俗,并付出了不小的代价。 今天这篇文章讲述了 Docker 这家公司从诞生到巅峰到没落,这一路上所做的抉择,并最终做了开源与商业的分离,再一次从开源踏上找寻商业化之路。这些都是值得我们参考和思考的,不管是已经开源或者准备从事开源的。 这篇文章翻译自How Docker broke in half 这家改变游戏规则的容器公司是其昔日的外衣。作为云时代最热门的企业技术业务之一的它到底发生了什么? Docker 并没有发明容器——将计算机代码打包成紧凑单元的方法,可以轻松地从笔记本电脑移植到服务器——但它确实通过创建一套通用的开源工具和可重用的镜像使其成为主流,这使所有开发人员只需构建一次软件即可在任何地方运行。 Docker 使开发人...
- 下一篇
Universal Media Server 10.12.0 发布,媒体文件传输工具
Universal Media Server 是一个兼容 DLNA,UPnP 和 HTTP/S 的媒体服务器,支持所有主要操作系统,包括 Windows,Linux 和 macOS 版本,几乎不需要配置就可以流式传输或转码许多不同的媒体格式。 Universal Media Server 10.12.0 发布,该版本更新内容如下: 常规: 提高了启动扫描的性能并减少了内存的使用 渲染器的配置现在默认启用了 MediaInfo 修正了文件在下载过程中或刚下载完时被锁定的问题 修复了关闭时进程挂起的问题 使得构建项目的速度更快 开始使用 DeepSource 进行静态代码分析 依赖性: 将 h2database 从 1.4.199 恢复到 1.4.197 在 macOS 上将 JRE 升级到 8u302+8 将 junit5.version 从 5.7.2 升级到 5.8.0 将 maven-javadoc-plugin 从 3.3.0 升级到 3.3.1 将 maven-pmd-plugin 从 3.14.0 升级到 3.15.0 将 oshi-core 从 5.8.1 升级到 5.8....
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS8编译安装MySQL8.0.19
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Docker使用Oracle官方镜像安装(12C,18C,19C)