Redis的消息队列学习笔记

消息队列的作用?

消息队列在项目中使用的目的是降低多机服务器的耦合度,降低了耦合度之后也会降低从单机升级到多机带来的代码改动量。

Redis中的消息队列

Redis是基于键值对存储的非关系型数据库,其特点为单线程+I/O多路复用,其访问数据的效率非常高。在Redis数据库中一共有三种消息队列的实现方式:

1-基于List实现的消息队列

List是Redis中的一种基本数据类型,其底层数据结构是双向链表。双向链表可以在两个端口分别进行插入和删除数据的操作,非常适合用于实现消息队列:

  • 用户1使用LPUSH从左边向List中插入一个消息;
  • 用户2使用RPOP从右边读取List中的一个消息。
    • RPOP是非阻塞的获取一个消息。使用BRPOP 可以实现阻塞的获取消息。阻塞的获取消息对于大多数的服务器程序设计,都是一种性能更加优秀的使用方式。

使用List实现的消息队列具有如下的特点:

  • 消息有序:只在一个端口插入消息,只在另一个端口消费消息,所以消费消息的顺序一定是严格按照消息消息插入的顺序。
  • 持久化:List是可以实现持久化的。通过命令BRPOPLPUSH,在获取消息的时候,可以使用一个辅助的List存储消费到的消息。如果用户还没来得及消费消息就发生了宕机,在重启之后可以访问这个“备份”的List重新获取之前得到的消息。

使用List实现的消息队列具有下面两个不足:

  • 同一条消息不能被多个用户消费:因为消费消息是基于POP命令实现的,所有List在不做其他设计的情况下,一条消息只能被一个用户消费,其他用户是无法获取到同一条消息的。
    • 如果要让多个消费者消费同一个消息,那么在设计消息队列的时候,就需要给每一个用户分配一个单独List。换句话说,有多少个用户订阅了同一个频道,就需要“拷贝”多少个相同的List。这样设计的缺点也很明显:资源太浪费,对Redis的存储空间带来了不小的压力。
  • 无法保证消息不会被重复消费:List本身是不会对消息是否被重复消费作任何的检查的。
    • 如果我们要实现不重复消费同一条消息,可以给每一条消息都设置一个不会重复的全局ID,用户在获取完消息后,通过全局ID来判断此条消息是否被重复消费了。

总言之,使用List实现的消息队列,通过一定得设计和拓展,是可以满足消息保序、不重复消费、持久化这三个基本要求的。

2-基于发布-订阅服务实现的消息队列

我们要知道,消息传播的方式有两种:点对点模式、发布/订阅模式。上面的List其实就是点对点的传播消息。在Redis中,内置了一个发布-订阅服务。

用户可以向某个频道发布消息,凡是订阅了该频道的用户都可以接收到该消息。也就是多播的模式。从这来看,Redis的发布-订阅服务可以算作是“根正苗红”的消息队列模型。

发布-订阅的底层数据结构是基于字典实现的。简单来讲,每一个频道是key,所有的订阅频道的用户是value,订阅相同频道的用户,使用一个链表组织在一起。发布消息时,查找到频道的key,然后向这个key上的所有订阅者都发送消息。

使用发布-订阅具有以下好处:

  • 官方支持,非常方便的就能得到一个多播的消息传播模方式。
  • 实时性高。

发布-订阅服务的缺点:

  • 不支持持久化:如果服务器宕机了,之前发送的没有被消费的消息就会被丢弃;
  • 无法判断消息是否会被重复消费。发布-订阅服务本身是不会对消息是否被重复消费作检查的。例如,多个发布者,向同一个频道写入消息,有可能会发生消息重复消息的情况。
  • 可能会产生消息堆积:如果订阅者没有及时获取到消息,就会造成消息的堆积。(这个问题对于List消息队列也是同样存在的)在消息堆积达到默认值32MB后,Redis会强制断开该订阅者的连接,用于清空内存。

总的来说,发布-订阅服务使用非常简单,原生支持多播。但是不具备持久化技术,同时无法保证消息不被重复消息。
有人说发布-订阅服务应用于日志推送比较好。

3-基于Steam实现的消息队列

Stream是Redis在5.0之后针对消息队列服务专门设计的一种数据结构。在操作命令上就非常支持消息队列的各种操作:

  • XADD:向stream中写入一条消息:自动返回一个全局ID
  • XREAD:从stream中赌气u一条消息。
    • 使用BLOCK选项可以实现阻塞读取;
      Stream的基础命令和List实现的消息队列是很类似的,都是生产者发布消息,消费者获取消息。Stream会给每一个消息都设置一个全局ID,这就避免了消息的重复读取。

Stream独特的地方在于,支持群组的概念:

  • 创建一个消费组,组内可以有多个用户。但是,一条消息被组内某用户获取了之后,其他消费者是不能够获取到了。
    • 不是同一个消费组的用户,是可以获取同一条消息的。
    • 基于这个特点,可以让一个消费组的用户,轮流对组内的消息进行获取。
      此外,Stream是原生支持持久化的。Stream中有一个PENDING List会保存组内的消息。只有当组内的用户成功消费了消息,返回一个XACK确认消息,PENDING List中才会删除该消息。所以,如果消费消息的中间,发生了服务器宕机等时间,重启之后,可以通过XPENDING命令恢复需要读取的消息。
  • 如果客户端忘记回复XACK怎么办?如果客户端忘记回复的话,PEL(PENDING List)中堆积的消息会越来越多,占用Redis的内存空间。如果积累到允许的上限的话,肯定是会被主动释放的吧。

上面提到的群组概念、PENDING List持久化技术,可以说是借鉴了著名的中间件Kfaka的设计思想实现的。

总结一下
Stream设计Redis5.0之后设计的一种满足消息队列服务的数据结构,使用Stream实现的消息队列,其特点有:

  • 消息保序:
    • 阻塞读取:XREAD BLOCK
  • 不重复消息:每一个消息都有全局的ID,避免重复的消费消息;
  • 持久化:内部有PEL保存发送了的消息,实现消息的持久化;
  • 支持以消费组的形式消费消息。

Stream在功能上,相较于List和发布-订阅服务都更加的完善。但是和专业的消息队列相比,还是存在一定的不足,主要表现在两个方面:

  • 消息存在丢失的可能性:AOF持久化的写盘过程中,如果发生了服务器宕机事件,数据会丢失;此外主从复制的过程也会产生数据丢失的可能性
  • 消息不允许大量的堆积:Redis是消息是写在内存上的,其允许的写入大小是有限制的,如果超过了这个限制,就有可能会发生数据丢失的可能。

除了Redis的消息队列服务,你还知道其他消息队列组件吗?

其实Redis的消息队列服务并不是业界主流的,应对个人小型应用开发,没有特别高的性能需求,Redis的消息队列服务是满足要求。但是如果是企业的商用项目,动辄几十万上百万的高并发需求,Redis的性能就会显得有些捉襟见肘了。

业界有名的消息队列服务有:

  • RabbitMQ;
  • RocketMQ;
  • Kafka;
  • ActiveMQ;

上述这些个消息队列服务都是在实际的工业生产活动中得到过检验的,能够承担起一个公司的消息队列服务需求的。

然而,这些消息队列需要独立安装部署,作为一个中间件来提供服务,虽然有着高性能、高可靠的优点,但是额外部署这些中间件也会增加运维成本,和服务器成本。所以,对于我这么一个独立开发,更多的是基于学习的目的的项目,我选择了使用和维护都比较简单的Redis来作为我的集群服务器的消息队列中间件。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值