消息队列

基于 List 实现

  1. 生产者调用 LPUSH 往 list 存放消息,左插入,每次的消息都放在队列头
  2. 消费者调用 BRPOP 到 list 取消息,右弹出,弹出并返回队列末尾的一条消息
    • 每次取的消息都是队列末尾的一条,如果队列有多条消息,要 BRPOP 多次
    • BPROP 会阻塞获取消息,如果队列没有消息就等着,可以指定超时时间
LPUSH
list
BRPOP
# 生产者,发送一条消息(就是往 list 存入一个数据)
redis> lpush mylist 1
1
redis> 

# 消费者,阻塞式获取(第二个参数是超时时间,如果没有消息会阻塞 5 秒)
redis> brpop mylist 5
1) mylist
2) 1
redis> 

优点:

  • 实现起来非常简单
  • 因为 list 是一种数据类型,所以消息也能持久化
  • 可以满足消息有序消费

缺点:

  • 只支持单节点消费
  • 无法避免消息丢失(java 调用 rpop 拿到了消息后,java 服务挂了)

PubSub

  1. publish 发布,subscribe 订阅,Redis 2.0 开始支持
  2. 消费者可以订阅一个或多个 channel,生产者向 channel 发消息后,所有的订阅者都能接收到消息

常用命令

命令 作用 描述
SUBSCRIBE channel [channel] 订阅一个或多个频道
PUBLISH channel msg 向指定的频道发消息
PSUBSCRIBE pattern [pattern] 也是订阅频道,模糊匹配 PSUBSCRIBE h?ello 问号表示任意单个字符
PSUBSCRIBE h*ello 星号表示不限个数的任意字符
PSUBSCRIBE h[ae]llo 中括号表示里面其中的一个字符

优点:

  • 采用发布订阅模式,支持多生产者,多消费者

缺点:

  • 不支持数据持久化
  • 无法避免消息丢失
    • 如果生产者向一个频道发布消息,此时如果没有任何消费者订阅这个频道,这个消息就会丢失
    • PubSub 模式下,必须保证有消费者在线,生产这发的消息才会被消费到
  • 消息堆积有上限,超出上限的消息会丢失
    • 频道不会存放消息,只是起一个连接作用,生产者发一个消息,消费者就一定收到一个消息
    • 消费者会把接收到的消息缓存起来,如果处理消息速度过慢,缓存的消息逐渐过多,达到上限时会丢弃消息

Stream

Redis 5.0 引入的一种数据新的数据类型(意味着可以持久化),可以实现一个功能比较完善的消息队列

发送消息

# 命令完整参数
XADD key [NOMKSTREAM] [<MAXLEN | MINID> [= | ~] threshold
  [LIMIT count]] <* | id> field value [field value ...]
  
# eg:向 users 队列发送 {name = Rose, age = 22}
XADD users * name Rose age 22
  • [NOMKSTREAM]:如果队列不存在是否自动创建,默认会自动创建
  • [<MAXLEN | MINID> [= | ~] threshold [LIMIT count]] :阈值,表明当前队列最多存放的消息数量,当消息达到阈值时存活时间最长的消息将被丢弃
  • <* | id>:必须参数,* 和 id 二选一;* 表示 Redis 自动生成(时间戳-自增数字),id 表示用户自己指定
  • field value [field value ...]:消息体,键值对的形式

读消息

  • 消息读过不会删除,还存在队列中,所以读消息的时候如果想要读取最新的消息,要使用 $ 和具体消息id

  • 需要知道阻塞和不阻塞这两种读取方式,如果是非阻塞读,用 $ 是读不到消息的!因为如果不阻塞读取,表示没有消费者处于等待监听消息队列。生产者发送了消息,因为没有消费者监听队列,所以这个消息不会被任何消费者接收到

  • 如果阻塞方式读取消息成功,但是处理消息很耗时,处理消息的过程中生产者又发了多条消息,当消费者处理结束重新再次阻塞读取消息时,有可能消息漏读,因为每次读取都要指定本次读取的消息数量,消费者根本不知道在处理的过程中生产者发了多少条消息

# 命令完整参数
XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] id [id ...]

# eg:读取 user 队列中第一条消息,会返回队列名、消息ID、消息体
XREAD COUNT 1 STREAMS users 0

# eg:阻塞读取消息,如果没有消息就一直阻塞
XREAD COUNT 1 BLOCK 0 STREAMS users $

消息总数

语法比较简单,但是要注意读过的消息不会被删除

XLEN key

# 向 s1 发送一条消息 {k1=v1},返回的是消息ID
49.232.247.70:0>XADD s1 * k1 v1
"1726998044214-0"

# 查看队列消息条数
49.232.247.70:0>XLEN s1
"1"

# 从 0 开始读 s1 队列的消息,读 1 条
49.232.247.70:0>XREAD COUNT 1 STREAMS s1 0
1) 1) "s1"
   2) 1) 1) "1726998044214-0"
         2) 1) "k1"
            2) "v1"
            
# 再次查看 s1 中多少条消息
49.232.247.70:0>XLEN s1
"1"

消费者组

  • 消息分流:消费者加入消费者组,如果一个组中多个消费者,消息只会被组内的一个消费者消费,如果就是想要多个消费者这消费,可以创建多个消费者组(RocketMQ 和这个做法比较一致)
  • 消息标识:消费者组会维护一个标识,哪怕消费者宕机,下次重新启动会再次根据标识获取消息,确保每个消息都被消费
  • 确认机制:每个消费者有一个 pending list,消费者获取到消息后都会放入 pending list 中,当消费者处理完消息后,需要发送一个 XACK 来确认消息被消费,这时消息从 pendling list 中移除

常用命令

  1. 创建消费者组

    XGROUP CREATE key group <id | $> [MKSTREAM]
    
    • key:这个消费者组要监听的队列名称

    • group:消费者组名称

    • <id | $>:具体消息id 或 0 或 $ 三选一

      • id:大于这个id的消息开始读;0:从第一个开始读;$ 从最新的未读的开始读
      • 创建消费者组的时候如果队列已有很多消息了,这些消息又不想消费,就用 $,如果想消费就可以用 0
      • 如果消费者组创建时队列没有消息,这时 0 和 $ 就没区别了
    • [MKSTREAM]:当队列不存在是否自动创建队列,默认会创建

  2. 删除消费者组

    XGROUP DESTORY key group
    
  3. 删除消费者组中的消费者

    XGROUP DELCONSUMER key group consumer
    
  4. 创建消费者

    Redis6 新增的,这个很少会用到,因为在读取消息的时候要指定消费者,如果消费者不存在可以自动创建

    XGROUP CREATECONSUMER key group consumer
    
  5. 给消费者组添加消费者

    Redis6 新增的,这个也很少会用到,也是因为在读取消息的时候就可以指定消费者组里的消费者

    XGROUP CREATECONSUMER key group consumer
    
  6. 读取消息

    XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds]
      [NOACK] STREAMS key [key ...] id [id ...]
    
    • group:消费者组名称,如果组不存在会自动创建
    • consumer:消费者名称,如果消费者不存在会自动创建消费者
    • [COUNT count]:本次查询的最大数量
    • [BLOCK milliseconds]:是否阻塞读取,可以跟一个阻塞的毫秒时间
    • [NOACK]:如果写了表示消息不需要确认,也不会进 pending list
    • STREAMS key [key ...]:监听哪个消息队列,可以多个
    • id [id ...]:起始id,这里的id和上面的不一样
      • > :如果给的是大于符号,从下一个未消费的消息开始读
      • 数字:如果不是大于符号,这个id就只能是一个数字,表示下标,将会读取消费者 pending list[下标] 的消息(已消费但未确认的消息)
  7. 确认消息

    确认消息和消费者无关,和消费者组相关

    XACK key group id [id ...]
    
  8. 读取 pending list

    XPENDING key group [[IDLE min-idle-time] start end count [consumer]]
    
    • key:队列名称
    • group:消费者组名称
    • [IDLE min-idle-time]:在 pending list 存活的时间(如果业务上觉得超过10秒的才算异常,这里可以自定义时间,而不是所有的都算异常)
    • start end:开始和结束下标,pending list 中可能会有多条,如果是 - + 表示所有范围(注意这里只是确定范围)
    • count:读取多少条
    • [consumer]:消费者,这是可选参数,如果不指定表示读取消费者组下的所有消费者的 pending list
    # 向队列 myqueue 发送 4 条消息
    redis> XADD myqueue * k1 v1
    1727187879623-0
    redis> XADD myqueue * k2 v2
    1727187887663-0
    redis> XADD myqueue * k3 v3
    1727187891673-0
    redis> XADD myqueue * k4 v4
    1727187896144-0
    
    # 创建一个消费者组,这个组监听的队列是 myqueue,这个组读取的消息从第一个开始
    redis> XGROUP CREATE myqueue g1 0
    OK
    
    # 消费者组g1读取消息,同时自动创建消费者c1(消费者组g1下的消费者c1来读消息)
    redis> XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS myqueue >
    1) 1) myqueue
       2) 1) 1) 1727187879623-0
             2) 1) k1
                2) v1
    
    # 再都一条
    redis> XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS myqueue >
    1) 1) myqueue
       2) 1) 1) 1727187887663-0
             2) 1) k2
                2) v2
    
    # 再创建一个消费者c2来读(这次读取的是 k3,因为 k1,k2 被这个组下别的消费者c1读取了)
    redis> XREADGROUP GROUP g1 c2 COUNT 1 BLOCK 2000 STREAMS myqueue >
    1) 1) myqueue
       2) 1) 1) 1727187891673-0
             2) 1) k3
                2) v3
    
    # 确认消息(c1消费了2条消息,c2消费了1条消息)
    redis> XACK myqueue g1 1727187879623-0 1727187887663-0 1727187891673-0
    
    # 读取队列 myqueue 和 g1 这个组下所有消费者的 pending list,不限范围,读 10 条
    XPENDING myqueue g1 - + 10
    
    # 读取消费者 c1 中 pending list 的消息,从第一条开始,读一条
    XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS myqueue 0
    
    
posted @   CyrusHuang  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示