延迟队列:一种带有 延迟功能 的消息队列

  1. 延时 → 未来一个不确定的时间
  2. mq → 消费行为具有顺序性

这样解释,整个设计就清楚了。你的目的是 延时,承载容器是 mq。

背景

列举一下我日常业务中可能存在的场景:

  1. 建立延时日程,需要提醒老师上课
  2. 延时推送 → 推送老师需要的公告以及作业

为了解决以上问题,最简单直接的办法就是定时去扫表:

服务启动时,开启一个异步协程 → 定时扫描 msg table,到了事件触发事件,调用对应的 handler

几个缺点:

  1. 每一个需要定时/延时任务的服务,都需要一个 msg table 做额外存储 → 存储与业务耦合
  2. 定时扫描 → 时间不好控制,可能会错过触发时间
  3. 对 msg table instance 是一个负担。反复有一个服务不断对数据库产生持续不断的压力

最大问题其实是什么?

调度模型基本统一,不要做重复的业务逻辑

我们可以考虑将逻辑从具体的业务逻辑里面抽出来,变成一个公共的部分。

而这个调度模型,就是 延时队列 。

其实说白了:

延时队列模型,就是将未来执行的事件提前存储好,然后不断扫描这个存储,触发执行时间则执行对应的任务逻辑。

那么开源界是否已有现成的方案呢?答案是肯定的。Beanstalk (https://github.com/beanstalkd/beanstalkd) 它基本上已经满足以上需求

设计目的

  1. 消费行为 at least
  2. 高可用
  3. 实时性
  4. 支持消息删除

依次说说上述这些目的的设计方向:

消费行为

这个概念取自 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 数目过多,会把磁盘从顺序读写会变成随机读写从导致性能衰减,同时也会带来其他类似重启或者恢复时间过长的问题。

  1. topic 过多 → 存储压力
  2. topic 存储的是现实时间,在调度时对不同时间 (topic) 的读取,顺序读 → 随机读
  3. 同理,写入的时候顺序写 → 随机写

架构设计

API 设计

producer

  1. producer.At(msg []byte, at time.Time)
  2. producer.Delay(body []byte, delay time.Duration)
  3. producer.Revoke(ids string)

consumer

  1. consumer.Consume(consume handler)

使用延时队列后,服务整体结构如下,以及队列中 job 的状态变迁:

  1. service → producer.At(msg []byte, at time.Time) → 插入延时job到 tube 中
  2. 定时触发 → job 状态更新为 ready
  3. consumer 获取到 ready job → 取出 job,开始消费;并更改状态为 reserved
  4. 执行传入 consumer 中的 handler 逻辑处理函数

生产实践

主要介绍一下在日常开发,我们使用到延时队列的哪些具体功能。

生产端

  1. 开发中生产延时任务,只需确定任务执行时间

    1. 传入 At() producer.At(msg []byte, at time.Time)
    2. 内部会自行计算时间差值,插入 tube
  2. 如果出现任务时间的修改,以及任务内容的修改
    1. 在生产时可能需要额外建立一个 logic_id → job_id 的关系表
    2. 查询到 job_id → producer.Revoke(ids string) ,对其删除,然后重新插入

消费端

首先,框架层面保证了消费行为的 exactly once ,但是上层业务逻辑消费失败或者是出现网络问题,亦或者是各种各样的问题,导致消费失败,兜底交给业务开发做。这样做的原因:

  1. 框架以及基础组件只保证 job 状态的流转正确性
  2. 框架消费端只保证消费行为的统一
  3. 延时任务在不同业务中行为不统一
    1. 强调任务的必达性,则消费失败时需要不断重试直到任务成功
    2. 强调任务的准时性,则消费失败时,对业务不敏感则可以选择丢弃

这里描述一下框架消费端是怎么保证消费行为的统一:

分为 cluster 和 node。cluster

https://github.com/tal-tech/go-queue/blob/master/dq/consumer.go#L45

  1. cluster 内部将 consume handler 做了一层再封装
  2. 对 consume body 做hash,并使用此 hash 作为 redis 去重的key
  3. 如果存在,则不做处理,丢弃

node

go-queue/consumernode.go at master · tal-tech/go-queue · GitHub

  1. 消费 node 获取到 ready job;先执行 Reserve(TTR),预订此job,将执行该job进行逻辑处理
  2. 在 node 中 delete(job);然后再进行消费
    1. 如果失败,则上抛给业务层,做相应的兜底重试

所以对于消费端,开发者需要自己实现消费的幂等性。

高可用延迟队列设计与实现相关推荐

  1. 服务高可用:幂等性设计

    转载自 服务高可用:幂等性设计 什么是幂等性? 一般在服务调用时,读服务如果调用失败了,会自动按配置次数转移到别的服务上去请求.而写服务就不能重复请求,如果因为超时或者网络故障等原因被调用服务并没有返 ...

  2. 高可用网站架构设计与实现

    word完整版可点击如下下载>>>>>>>> 高可用网站架构设计与实现.rar-互联网文档类资源-CSDN下载内容包括详细设计文档word版,附带开题报 ...

  3. 高可用系统架构设计 技术方案

    背景 可靠的系统是业务稳定.快速发展的基石. 那么,如何做到系统高可靠.高可用呢? 高可用方法论 下面的表格里,列出了高可用常见的问题和应对措施. 可扩展 扩展是最常见的提升系统可靠性的方法,系统的扩 ...

  4. eclipse经常高占用_高可用系统的设计指南

    可靠的系统是业务稳定.快速发展的基石.那么,如何做到系统高可靠.高可用呢?下面从技术方面介绍几种提高系统可靠性.可用性的方法. 扩展 扩展是最常见的提升系统可靠性的方法,系统的扩展可以避免单点故障,即 ...

  5. 高可用架构的设计方法

    概述 高可用(High Availability),简称HA,是衡量IT系统服务质量的一个极其重要的参考,高可用一直是IT系统设计中需要重点关注的点.本文总结高可用架构中的一些关键设计思想. 衡量指标 ...

  6. TA大数据分析系统的高可用架构从设计到实现

    随着大数据时代的到来,对海量数据进行数据分析,并依据分析结果进行精细化运营成为各大企业的重要课题.但大数据行业门槛高,自建平台成本高.难度大.效率低,因此企业越来越需要专业的大数据分析工具. 针对市场 ...

  7. Golang 高性能高可用消息队列框架go-nsq使用

    为什么要使用Nsq 最近一直在寻找一个高性能,高可用的消息队列做内部服务之间的通讯.一开始想到用zeromq,但在查找资料的过程中,意外的发现了Nsq这个由golang开发的消息队列,毕竟是golan ...

  8. win server 缓冲区队列不足_有赞延迟队列设计

    延迟队列,顾名思义它是一种带有延迟功能的消息队列. 那么,是在什么场景下我才需要这样的队列呢? 背景 我们先看看以下业务场景: 当订单一直处于未支付状态时,如何及时的关闭订单,并退还库存? 如何定期检 ...

  9. 实力分享,聚焦分布式高可用消息队列

    消息队列(Message Queue),是分布式系统中非常重要的组件,其通用的使用场景可以简单地描述为: 当不需要立即获得结果,但是并发量又需要进行控制的时候,差不多就是需要使用消息队列的时候. 消息 ...

最新文章

  1. GO语言教程3:杂类
  2. 时间序列分析及应用r语言pdf_R语言:时间序列经典分析法(二)
  3. 有趣但是没有用的linux命令
  4. 【Error】Provider com.sun.xml.stream.ZephyrParserFactory not found
  5. java 中的访问修饰符
  6. 私有属性和方法-通过父类方法间接访问
  7. uva 12105——Bigger is Better
  8. [golang]如何看懂调用堆栈
  9. python怎么清理垃圾_【原创】python实现清理本地缓存垃圾
  10. 【OpenCV】矩阵掩模操作
  11. mysql_fetch_array()/ mysql_fetch_assoc()/ mysql_fetch_row()/ mysql_num_rows等…期望参数1为资源或结果
  12. lintcode :Count and Say 报数
  13. 多片段时序数据建模预测实践
  14. Java开发笔记(一百三十五)Swing的文件对话框
  15. JAVA中如何创建一个二维数组,然后给二维数组赋值!
  16. java Swing实现考试系统
  17. KVM地址翻译流程及EPT页表的建立过程
  18. 应用数理统计之概率论复习与补充
  19. BUUCTF web(三)
  20. dropna()函数

热门文章

  1. VC++/MFC中调用CHM帮助文档的方法--ShellExecute
  2. X 039 0203 039 mysql_2020年寒假假期总结0203
  3. 帆软日期控件变灰_FineReport-JS脚本常见日期使用整理
  4. 服务器跑python程序后还能安装网站吗_如何在服务器上跑python程序
  5. access开发精要(14)-货币与数字类型格式(2)
  6. python3精要(7)-集合,集合运算,集合解析
  7. 【NLP】从整体视角了解情感分析、文本分类!
  8. 【论文解读】EfficientNet强在哪里
  9. 【实战】使用Python部署机器学习模型的10个实践经验
  10. BERT源码分析(PART III)