消息队列
- 存放消息的队列,最简单的消息队列模型包括三个角色:
消息队列:存储和管理消息,也称为消息代理
生产者: 发送消息到消息队列
消费者: 从消息队列获取消息并处理消息
- Redis提供了三种不同的方式来实现消息队列:
list结构:基于List结构模拟消息队列
PubSub:基本的点对点消息模型
Stream: 比较完善的消息队列模型
Redis中的消息队列
基于List结构模拟消息队列
- 我们可以利用LPUSH结合RPOP、或者RPUSH结合LPOP来实现。
- 但是要注意的是,当队列中没有消息时RPOP或者LPOP操作会返回null,并不像JVM的阻塞队列那样会阻塞并等待消息。
- 因此这里应该使用BRPOP或者BLPOP来实现阻塞效果。
优点: - 利用Redis存储,不受限于JVM内存上限
- 基于Redis的持久化机制,数据安全性有保证
- 可以满足消息有序性
缺点 - 无法避免消息丢失
- 只支持单消费者
基于PubSub的消息队列
PubSub(发布订阅)时Redis2.0版本引入的消息传递模型。顾名思义,消费者可以订阅一个或多个channel,生产者向对应channel发送消息后,所有订阅者都能收到相关消息。
- SUBSCRIBE channel[channel]:订阅一个或多个频道
- PUBLISH channel msg:向一个频道发送消息
- PSUBSCRIBE pattern[pattern]:订阅与pattern格式匹配的所有频道
优点:
采用发布订阅模型,支持多生产、多消费
缺点:
不支持数据持久化
无法避免消息丢失。发完消息如果没人收直接丢了。
消息堆积有上限,超出时数据丢失。发布消息之后如果有消费者在监听,在消费者边会有缓存区域。在消费者没有处理完消息时,如果又来消息,这些消息都会缓存在消费者端。但是消费者端的缓存空间是有限的。
Stream
单消费者
Stream是Redis5.0引入的一种新数据类型,可以实现一个功能非常完善的消息队列。
发送消息命令参数:
- key 队列名称
- [NOMKSTREAM] 如果队列不存在,是否自动创建队列,默认自动创建
- [MAXLEN | MINID [= | ~] threshold [LIMIT count] 设置消息队列的最大消息数量,默认不设置上限
- *|ID 消息的唯一id,*代表由Redis自动生成,格式是“时间戳-递增数字”,例如“1644804662707-0”
- field value [field value …] 发送到队列中的消息,称为Entry,格式就是多个keu-value键值对
例如
创建名为users的队列,并向其中发送一个消息,内容是:{name=jack,age=21},并且使用Redis自动生成ID
XADD users * name jack age 21
读取消息:
- COUNT count 每次读取消息的最大数量
- BLOCK milliseconds 当没有消息时,是否阻塞,阻塞时长。0代表永久阻塞
- STREAMS key [key …] 要从哪个队列读取消息,key就是队列名
- ID [ID …] 其实id,只返回大于该ID的消息
-
- 0:代表从第一个消息开始
-
- $:代表从最新的消息开始
XREAD阻塞方式,读取最新消息
- $:代表从最新的消息开始
XREAD COUNT 1 BLOCK 1000 STREAMS users $
在业务开发中,我们可以寻暖的调用XREAD阻塞方式来查询最新消息,从而实现持续监听队列的效果,伪代码如下
while (true){
//尝试读取队列中的消息,最多阻塞2秒
Object msg = redis.execute("XREAD COUNT 1 BLOCK 2000 STREAMS users $");
if (msg == null) {
continue;
}
//处理消息
handleMessage(msg);
}
【注意】当我们指定其实ID为$时,代表读取最新的消息,如果我们处理一条消息的过程中,又有超过1条以上的消息到达队列,则下次获取时也只能获取到最新的一条,会出现漏读消息的问题。
STREAM类型消息队列的XREAD命令特点
- 消息可回溯
- 一个消息可以被多个消费者读取
- 可以阻塞读取
- 有消息漏读的风险
消费者组
消费者组(Consumer Group):将多个消费者划分到一个组中,监听同一个队列。具有以下特点
- 消息分流:队列中的消息会分流给组内的不同消费者,而不是重复消费,从而加快消息处理的速度
- 消息提示:消费者会维护一个标识,记录最后一个被处理的消息,哪怕最后消费者宕机重启,还会从标识之后读取消息,确保每一个消息都会被消费
- 消息确认:消费者获取消息后,消息处于pending状态,并存入一个pending-list。当处理完成后,需要通过XACK来确认消息,标记消息为已处理,才会从pending-list移除。
创建消费者组:
XGROUP CREATE key groupName ID [MKSTREAM]
- key:队列名称
- groupName:消费者组名称
- ID:起始ID标识,$代表队列中最后一个消息,0则代表队列中第一个消息
- MKSTREAM:队列不存在时自动创建队列,不给补自动创建
其他常见命令
- 删除指定的消费者组
XGROUP DESTROY key groupName
- 给指定的消费者组添加消费者
一般不需要自己添加消费者,当我们从组中指定消费者并监听消息的时候,如果发现消费者不存在,会自动创建。
XGROUP CREATECONSUMER key groupName consumername
- 删除消费者组中的指定消费者
XGROUP DELCONSUMER key groupname consumername
- 从消费者组读消息
- group:消费组名称
- consumer:消费者名称,如果消费者不存在,会自动创建一个消费者
- count:本次查询的最大数量
- BLOCK milliseconds:当没有消息时最长等待时间
- NOACK:无需手动ACK,获取消息后自动确认。不建议设置,有可能漏掉消息。
- STREAMS key:指定队列名称
- ID:获取消息的其实ID:
-
- ”>“ : 从下一个未消费的消息开始
-
- 其他:根据指定id从pending-list中获取已消费单未确认的消息,例如0,是从pending-list中的第一个消息开始
-
- 一般情况下我们都配置”>“,当出了异常之后,再去读pending-list中 的消息。
确认消息:
XACK key group ID [ID …]
返回值为确认的消息数量
查看pending-list
XPENDING key group [ [ IDLE min-idle-time ] start end count [ consumer ] ]
- key:队列名称
- group:组的名称
- IDLE min-idle-time :空闲时间,指获取消息以后,处理消息之前的时间,超过空闲时间的消息会被放进pending-list
- start end:获取的数量。- +代表所有的。
- consumer:获取哪个消费者的pending-list,每个消费者都有自己的pending-list
STREAM类型消息队列的XREADGROUP命令的特点:
- 可回溯
- 可以多消费者争抢消息,加快消费速度
- 可以阻塞读取
- 没有消息漏读的风险
- 有i消息确认机制,保证消息至少被消费一次
独立于JVM,不会收到JVM内存限制,可以持久化,有消息确认机制,不用担心消息的漏读问题。