15 消息队列的考验:Redis 有哪些解决方案?

消息队列支持的各个组件之间的快速通信,而 Redis 高效的读写特性则满足信息读写的要求,但是对于消息队列而言还需要支持其他的要求,那么Redis 能否支持做消息队列,Redis 如何实现一个消息队列?

一、消息队列的消息存取需求

消息的发送组件被称之为生产者,接收消息的组件称之为消费者,生产者将生产的消息保存为 JSON 的格式,然后将保存好的消息发送给消息队列,之后消费者异步地从消息队列中获取信息,之后将获取的信息本地进行处理,之后再将数据写入到数据库中,其中如果不使用消息队列就会导致数据地接收方在收到到数据后进行处理后还需要写入数据库,导致执行地速度赶不上数据的提供方,数据的提供方在发送数据前就有可能因为数据地接收方还未处理完前一个数据使得不得不被阻塞在数据地发送环节,而数据地接收方同样可能会因为数据地提供方长时间未能发送数据,导致数据的接收方被阻塞在接收数据这一环节。而消息队列则解决了这一方面的阻塞问题,将传输的数据缓存在队列中,这样双发都不会被阻塞在发送和接收数据这一环节中,这是消息队列在作为分布式组件通信的优势。

1、消息队列在保存消息的时候需要满足三个需求:消息的保序,重复消息的处理以及消息的可靠性。

  • 消息保序:消息的消费者接收到的消息的顺序与生产者发送的消息的顺序保持一致,避免因为消费者接收的消息的顺序错乱导致其最后执行的业务逻辑出现错误。
  • 重复消息的处理:消息的传输可能会存在由于网络的阻塞出现消息重传的现象,因此在对于重复的消息进行处理,避免消息的消费者读取到多条一样的消息,而重复执行操作,最终可能由于重复的执行一些修改操作导致业务逻辑执行的错误。
  • 消息的可靠性:消费者在处理消息的时候,可能会因为消费者这边发生了宕机或者故障,导致正在处理的消息并没有完成处理,此时为了保证消息的可靠性,消费者仍然而已从消息队列中读取到未完成的消息,重新地执行,避免出现消息漏处理的问题。

二、基于 List 消息队列解决方案

(1)消息保序

  • 由于 List 集合就具备数据先入先出的特性,因此使用 List 能够满足消息的保序的需求。生产者使用LPUSH 命令将消息放入到 List 集合中,而为了避免消费者循环判断 List 中是否有消息,有则调用 RPOP 命令获取,没有则继续循环等到获取信息导致消费者被阻塞在接收信息这一环节,然而消费者循环等待 List 中获取消息会大量的消耗消费者的 CPU 的开销,因此 Redis 中让消费者调用 BRPOP 命令,该命令又称之为阻塞获取命令,当客户端从 List 中没有获取到消息的时候就阻塞客户端从 List 中获取消息操作,直至 List 中放入了新的消息,消费者就能从 List 中读取消息了。这样一来就避免了消费者循环等到获取消息带来的 CPU 损耗。

(2)重复消息的处理

  • 首先 Redis 每次在生成一个消息的时候为每条消息生成一个 ID,并将这个 ID 同消息数据一同写入到 List 中去,但是就需要借助客户端这边需要记录自己已经接收到的消息的所有 ID 集合,当接收到一条消息的时候就去已经接收执行的消息的集合中进行遍历比较,判断是否是重复的消息,这也称之为“幂等性”(对于同一条消息,消费者收到一次和收到多次的执行结果是一样的)

(3)消息的可靠性

  • 需要 Redis 对消费者接收到的消息开辟额外的空间进行存储,List 类型提供了BRPOPLPUSH 命令,当消费者从 List 集合的右边获取消息的时候,将其获取的信息重复保存在新的 List 集合 mqback 中,并从 List 集合的左边重新插入,因此当消费者发生宕机之后,重启之后根据从 mqback 集合重新获取未执行完的消息

三、基于 Streams 消息队列的解决方案

虽然使用 List 来实现消息队列完成了消息队列对消息的的三大需求,但是一般而言消费者接收消息和处理消息的过程比生产者发送消息要慢很多,这样就会造成大量的消息积压在 Redis 中占用了大量的内存,因此就需要将多个消费者组成消费组,但是 List 的实现中并不支持消费组,因此 Redis 5.0 版本中提供了 Streams 数据类型。

(1)Streams 操作命令

Streams 是 Redis 专门用于实现消息队列的数据类型,其中含有大量的消息队列的操作命令

  • XADD:插入消息,并且保证有序,同时自动生成一个全局唯一 ID。
    • 使用该命令插入的数据是 key-value 的形式,消息的键及其消息数据信息 value。下面为消息队列 mqstream 插入消息键为 “repo”,消息数据为 “5” 的数据,其中在队列名称后面添加 “" 符号代表让系统自动生成一个全局唯一 ID,当然也可以不使用 "”,在命令的最后自定义一个全局唯一的 ID,但是使用 “*” 起始会更加方便且高效,同时生成的全局唯一 ID 号由 “-” 分成了两部分,前面表示的是该消息插入消息队列时系统的当前时间的毫秒数,后面的数字表示是在当前毫秒数内的第几条消息,如果为 1 表示是当前这毫秒内第一条消息。
XADD mqstream * repo 5
"1599203861727-0"
  • XREAD:用于读取消息,也可以通过 ID 进行读取。
    • 可以直接使用该命令从指定的消息队列中读取消息,或者在后面指定读取的消息的 ID,然后从该 ID 的下一条在指定消息队列中的消息开始读取消息。
    • 使用 XREAD 命令的时候还可以使用 block 配置项,类似于 List 中的 RBPOP 命令,当消费者读取消息队列中没有消息的时候就会被阻塞,其被阻塞的时长也在 block 配置项中配置。
  • XREADGROUP:按消费组形式读取消息。
    • 使用 XREADGROUP create 创建指定某一消息队列的消费组,消费组中的消费者读取过的消息,其他的的该消息队列的消费组中的其他消费者就不能重复读取,使用消费组的目的就是为了使得读取负载均匀的分布到各个消费者中,避免使得 Redis 中因为消息不能及时处理导致消耗大量内存。
  • XPENDING 和 XACK:XPENDING 命令用于查询消费组中所有的消费者读取但未确认的消息;XACK 命令用于向消息队列中确认消息已经处理完成。
    • Redis 为了避免消费者发生宕机之后对接收了但由于宕机未处理完的消息保证能够重新从消息队列中获取保证消息的可靠性,Streams 会使用内部队列(也称 PENDING List)来留存消费者获取但是未确认的所有消息,然后宕机后恢复的消费者调用 XPENDING 命令查看 PENDING List 内部队列中以获取但是尚未确认处理完毕的消息,直至消费者执行处理完毕之后,才会向消息队列发送 XACK 命令,PENDING List 才会将该消息从队列中移除。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值