redis可以做消息队列吗
这个问题隐含着两个问题:
消息队列的消息存取需求是什么
redis如何实现消息队列的消息存取需求
消息队列的消息存取需求
在分布式系统中,当两个组件要基于消息队列进行通信的时候,一个组件会被要处理的请求放入消息队列中,自己就可以执行其他操作了,远端的另一个组件从队列中把消息队列取出来,再在本地进行处理
假设组件 1 需要对采集到的数据进行求和计算,并写入数据库,但是,消息到达的速度很快,组件 1 没有办法及时地既做采集,又做计算,并且写入数据库。所以,我们可以使用基于消息队列的通信,让组件 1 把数据 x 和 y 保存为 JSON 格式的消息,再发到消息队列,这样它就可以继续接收新的数据了。组件 2 则异步地从消息队列中把数据读取出来,在服务器 2 上进行求和计算后,再写入数据库。这个过程如下图所示:
我们一般把消息队列中发送消息的组件称为生产者(例子中的组件 1),把接收消息的组件称为消费者
在使用消息队列时,消费者可以异步读取生产者消息,然后再进行处理。这样一来,即使生产者发送消息的速度远远超过了消费者处理消息的速度,生产者已经发送的消息也可以缓存在消息队列中,避免阻塞生产者,这是消息队列作为分布式组件通信的一大优势。
消息保序
虽然消费者的信息是异步处理的,但是消息仍然需药按照生产者发送消息的顺序来处理消息,避免后发送的信息被先处理了
重复消息处理
消费者从消息队列读取信息时,有时会因为网络堵塞导致消息重传,此时如果消费者重复执行了相同的消息,如果消息是修改数据,就会导致数据出错。
消息可靠性保证
消费者在处理消息的时候,可能会出现故障或者宕机导致消息没处理的情况。也就是说,当消费者重启时,需要保证重新从消息队列里面读取信息再次处理。
基于List的消息队列解决方案
List本身是先进先出的顺序对数据进行存取,所以满足消息保序的需求。
不过,在生产者在写入数据时,list不会主动通知消费者有新消息写入了,如果消费者要处理消息,只能一直不停调用RPOP命令,带来了性能损失,为了解决这个问题,redis提供了阻塞形式的BRPOP,客户端在没有读到队列数据时,自动阻塞,知道队列有新数据写入队列,再开始读取新数据。
为了解决重复消息处理的问题,消息队列给每个消息提供全局ID,并且把处理过的ID记录下来
为了解决消息处理可靠性的问题,redis在消费者读取了消息后,会把这个消息存入另一个list里面去,这样如果在读取时候消费者挂了,重启后可以去备份里面读取消息处理。
问题出现了:生产者发送数据的数据太快了,但是消费者处理数据的速度太慢,会导致大量数据堆积在list中,给redis的内存带来很大的压力。
这个时候,我们希望启动多个消费者程序组成一个消费组,一起分担处理 List 中的消息。
因此我们引入了Stream
基于Streams的消息队列的解决方案
Streams是redis专门为消息队列设计的数据类型
XADD
XADD 命令可以往消息队列中插入新消息,消息的格式是键 - 值对形式。对于插入的每一条消息,Streams 可以自动为其生成一个全局唯一的 ID。
XREAD
XREAD 在读取消息时,可以指定一个消息 ID,并从这个消息 ID 的下一条消息开始进行读取。另外,消费者也可以在调用 XRAED 时设定 block 配置项,实现类似于 BRPOP 的阻塞读取操作。当消息队列中没有消息时,一旦设置了 block 配置项,XREAD 就会阻塞,阻塞的时长可以在 block 配置项进行设置。
XGROUP
Streams 本身可以使用 XGROUP 创建消费组,创建消费组之后,Streams 可以使用 XREADGROUP 命令让消费组内的消费者读取消息。
使用消费组的目的是让组内的多个消费者共同分担读取消息,所以,我们通常会让每个消费者读取部分消息,从而实现消息读取负载在多个消费者间是均衡分布的。
为了保障消费者宕机后,仍然可以处理读取为处理的数据。
Streams 会自动使用内部队列(也称为 PENDING List)留存消费组里每个消费者读取的消息,直到消费者使用 XACK 命令通知 Streams“消息已经处理完成”。如果消费者没有成功处理消息,它就不会给 Streams 发送 XACK 命令,消息仍然会留存。此时,消费者可以在重启后,用 XPENDING 命令查看已读取、但尚未确认处理完成的消息。