需求背景

  • 用户下订单成功之后隔20分钟给用户发送上门服务通知短信
  • 订单完成一个小时之后通知用户对上门服务进行评价
  • 业务执行失败之后隔10分钟重试一次

    类似的场景比较多 简单的处理方式就是使用定时任务 假如数据比较多的时候 有的数据可能延迟比较严重,而且越来越多的定时业务导致任务调度很繁琐不好管理。

队列设计

目前可以考虑使用rabbitmq来满足需求 但是不打算使用,因为目前太多的业务使用了另外的MQ中间件。

开发前需要考虑的问题?

  • 及时性 消费端能按时收到
  • 同一时间消息的消费权重
  • 可靠性 消息不能出现没有被消费掉的情况
  • 可恢复 假如有其他情况 导致消息系统不可用了 至少能保证数据可以恢复
  • 可撤回 因为是延迟消息 没有到执行时间的消息支持可以取消消费
  • 高可用 多实例 这里指HA/主备模式并不是多实例同时一起工作
  • 消费端如何消费

    当然初步选用redis作为数据缓存的主要原因是因为redis自身支持zset的数据结构(score 延迟时间毫秒) 这样就少了排序的烦恼而且性能还很高,正好我们的需求就是按时间维度去判定执行的顺序 同时也支持map list数据结构。

简单定义一个消息数据结构

 
private String topic;/***topic**/
private String id;/***自动生成 全局惟一 snowflake**/
private String bizKey;
private long delay;/***延时毫秒数**/
private int priority;//优先级
private long ttl;/**消费端消费的ttl**/
private String body;/***消息体**/
private long createTime=System.currentTimeMillis();
private int status= Status.WaitPut.ordinal();

运行原理:

  1. Map来存储元数据。id作为key,整个消息结构序列化(json/…)之后作为value,放入元消息池中。
  2. id放入其中(有N个)一个zset有序列表中,以createTime+delay+priority作为score。修改状态为正在延迟中
  3. 使用timer实时监控zset有序列表中top 10的数据 。 如果数据score<=当前时间毫秒就取出来,根据topic重新放入一个新的可消费列表(list)中,在zset中删除已经取出来的数据,并修改状态为待消费
  4. 客户端获取数据只需要从可消费队列中获取就可以了。并且状态必须为待消费 运行时间需要<=当前时间的 如果不满足 重新放入zset列表中,修改状态为正在延迟。如果满足修改状态为已消费。或者直接删除元数据。

客户端

因为涉及到不同程序语言的问题,所以当前默认支持http访问方式。

  1. 添加延时消息添加成功之后返回消费唯一ID POST /push {…..消息体}
  2. 删除延时消息 需要传递消息ID GET /delete?id=
  3. 恢复延时消息 GET /reStore?expire=true|false expire是否恢复已过期未执行的消息。
  4. 恢复单个延时消息 需要传递消息ID GET /reStore/id
  5. 获取消息 需要长连接 GET /get/topic

用nginx暴露服务,配置为轮询 在添加延迟消息的时候就可以流量平均分配。

目前系统中客户端并没有采用HTTP长连接的方式来消费消息,而是采用MQ的方式来消费数据这样客户端就可以不用关心延迟消息队列。只需要在发送MQ的时候拦截一下 如果是延迟消息就用延迟消息系统处理。

消息可恢复

实现恢复的原理 正常情况下一般都是记录日志,比如mysqlbinlog等。

这里我们直接采用mysql数据库作为记录日志。

目前打算创建以下2张表:

  1. 消息表 字段包括整个消息体
  2. 消息流转表 字段包括消息ID、变更状态、变更时间、zset扫描线程Name、host/ip

定义zset扫描线程Name是为了更清楚的看到消息被分发到具体哪个zset中。前提是zset的key和监控zset的线程名称要有点关系 这里也可以是zset key。

举个栗子

假如redis服务器宕机了,重启之后发现数据也没有了。所以这个恢复是很有必要的,只需要从表1也就是消息表中把消息状态不等于已消费的数据全部重新分发到延迟队列中去,然后同步一下状态就可以了。

当然恢复单个任务也可以这么干。

关于高可用

分布式协调还是选用zookeeper吧。

如果有多个实例最多同时只能有1个实例工作 这样就避免了分布式竞争锁带来的坏处,当然如果业务需要多个实例同时工作也是支持的,也就是一个消息最多只能有1个实例处理,可以选用zookeeper或者redis就能实现分布式锁了。

最终做了一下测试多实例同时运行,可能因为会涉及到锁的问题性能有所下降,反而单机效果很好。所以比较推荐基于docker的主备部署模式。

扩展

支持zset队列个数可配置 避免大数据带来高延迟的问题。

目前存在日志和redis元数据有可能不一致的问题 如mysql挂了,写日志不会成功。

设计图:

欢迎关注我的微信公众号&lt;笑笑笑技术圈&gt; 我会不定期发布一些不限于技术的文章

基于redis的延迟消息队列设计的更多相关文章

  1. 基于redis的延迟消息队列设计(转)

    需求背景 用户下订单成功之后隔20分钟给用户发送上门服务通知短信 订单完成一个小时之后通知用户对上门服务进行评价 业务执行失败之后隔10分钟重试一次 类似的场景比较多 简单的处理方式就是使用定时任务 ...

  2. Delayer 基于 Redis 的延迟消息队列中间件

    Delayer 基于 Redis 的延迟消息队列中间件,采用 Golang 开发,支持 PHP.Golang 等多种语言客户端. 参考 有赞延迟队列设计 中的部分设计,优化后实现. 项目链接:http ...

  3. [转载] 基于Redis实现分布式消息队列

    转载自http://www.linuxidc.com/Linux/2015-05/117661.htm 1.为什么需要消息队列?当系统中出现“生产“和“消费“的速度或稳定性等因素不一致的时候,就需要消 ...

  4. laravel5.6 基于redis,使用消息队列(邮件推送)

    邮件发送如何配置参考:https://www.cnblogs.com/clubs/p/10640682.html 用到的用户表: CREATE TABLE `recruit_users` ( `id` ...

  5. ActiveMQ学习总结(8)——消息队列设计精要

    消息队列已经逐渐成为企业IT系统内部通信的核心手段.它具有低耦合.可靠投递.广播.流量控制.最终一致性等一系列功能,成为异步RPC的主要手段之一. 当今市面上有很多主流的消息中间件,如老牌的Activ ...

  6. Redis 学习笔记(六)Redis 如何实现消息队列

    一.消息队列 消息队列(Messeage Queue,MQ)是在分布式系统架构中常用的一种中间件技术,从字面表述看,是一个存储消息的队列,所以它一般用于给 MQ 中间的两个组件提供通信服务. 1.1 ...

  7. Redis+php-resque实现消息队列

      服务器硬件配置 Dell PowerEdge R310英特尔单路机架式服务器 Intel Xeon Processor X3430 2.4GHz, 8MB Cache 8GB内存(2 x 4GB) ...

  8. .NET Core微服务之基于EasyNetQ使用RabbitMQ消息队列

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.消息队列与RabbitMQ 1.1 消息队列 “消息”是在两台计算机间传送的数据单位.消息可以非常简单,例如只包含文本字符串:也可以更 ...

  9. Spring Cloud(7):事件驱动(Stream)分布式缓存(Redis)及消息队列(Kafka)

    分布式缓存(Redis)及消息队列(Kafka) 设想一种情况,服务A频繁的调用服务B的数据,但是服务B的数据更新的并不频繁. 实际上,这种情况并不少见,大多数情况,用户的操作更多的是查询.如果我们缓 ...

随机推荐

  1. linux 在jetty中部署web工程

    背景:公司中原有的项目需要在jetty中进行部署,所以要掌握相关知识. 1 部署步骤 首先要保证jdk环境变量配置正常,然后去官网下载对应版本号的jetty,解压缩即可. 将需要部署的web应用,wa ...

  2. Unity Shader入门教程(二)最基本的Diffuse和Normal样例

    本教程参考了<猫都能学会的Unity3dShaderLab教程.CHM>, 1.请上网搜索并下载此文件. 2.随后再下载里面提到的素材: http://vdisk.weibo.com/s/ ...

  3. JavaScript系统学习小结——Object类型、Array类型

    今天学习JavaSript中引用变量中的Object类型和Array类型: 1. Js中大多数引用类型值都是Object类型的实例,Object类型在应用程序中存储和传输数据时,是非常理想的选择: 创 ...

  4. [BZOJ 4832][lydsy 4月赛] 抵制克苏恩

    题面贴一发 [Lydsy2017年4月月赛]抵制克苏恩 Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 443  Solved: 164[Submit][ ...

  5. Vue2源码分析-逻辑梳理

    很久之前就看完vue1,但是太懒就一直没写博客,这次打算抽下懒筋先把自己看过了记录下来,否则等全部看完,估计又没下文了 看源码总需要抱着一个目的,否则就很难坚持下去,我并没做过vue的项目,我几乎很少 ...

  6. JAVA多线程之wait/notify

    本文主要学习JAVA多线程中的 wait()方法 与 notify()/notifyAll()方法的用法. ①wait() 与 notify/notifyAll 方法必须在同步代码块中使用 ②wait ...

  7. cve-2017-0199&metasploit复现过程

    CVE-2017-0199 WORD/RTF嵌入OLE调用远程文件执行的一个漏洞.不需要用户交互.打开文档即中招 首先更新msf到最新,据说最新版简化了利用过程,不需要开启hta这一步.但没测成功 还 ...

  8. TCP/IP拥塞控制

    TCP/IP拥塞控制包括:慢启动和拥塞避免.其操作流程如下所述: 初始化.拥塞窗口cwnd = 1,慢启动门限ssthresh = 65535 如果没有发生拥塞 若 cwnd < ssthres ...

  9. ios 监听TextField中内容

    @property (weak, nonatomic) IBOutlet UITextField *UserID; @property (weak, nonatomic) IBOutlet UITex ...

  10. C#快速入门

    一.简介 1.C#是由Anders Hejlsberg和他的团队在.Net框架开发期间开发的:是.Net框架的一部分. C#是专为公共语言基础结构(CLI)设计的,CLI由可执行代码和运行时环境组成, ...