redis作为消息队列与专业对比

消息队列

++  支持消息阻塞等待拉取消息

++ 支持发布 / 订阅模式

++ 消费失败,可重复消费,消息不丢失

++ 实例宕机,消息不丢失,数据可持久化

++ 消息可堆积

Redis 消息队列

List队列

LPUSH/RPOP

业务需求足够简单,Redis当作消息队列,使用List数据类型,List底层是链表,在头部和尾部操作元素,时间复杂度O(1)

生产者使用LPUSH发布消息

        LPUSH queue msg1

        LPUSH queue msg2

消费者使用RPOP拉取消息

        RPOP queue     结果 msg1

        RPOP queue     结果 msg2

 出现问题

        1. 消费者逻辑是死循环一直RPOP,如果queue没有消息,还会RPOP,返回null

        2. 会造成cpu空转,浪费资源,还会对Redis造成压力

解决方法

使用阻塞式命令

阻塞式命令BRPOP/BLPOP

 拉取消息代码

while true:
    # 没有消息阻塞等待,0表示不设置超时时间,如果设置为0,则表示不设置超时,直到有新消息才返回,否则会在指定的超时时间后返回null
    msg = redis.brpop("queue", 0)
    if msg == null:
        continue
    # 处理消息
    handle(msg)

注意 : 如果设置超时时间太长,连接太久没活跃,可能会被redis Server判定为无效链接,会被强制下线,所以采用这种方式,需要有重连机制

List模型缺点

        1. 不支持重复消费:消费者拉取消息后,List就会把消息删除,无法被其他消费者再次消费,不支持多个消费者消费同一批数据

        2. 消息丢失 : 消费者拉取消息后,如果发生宕机,这条消息会丢失

发布/订阅模型: Pub/Sub

使用 PUBLISH/SUBSCRIBE 命令完成发布  订阅操作

 2个消费者使用SUBSCRIBE命令消费,并订阅同一队列

# 2个消费者  订阅一个队列
redis终端

SUBSCRIBE queue
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "queue"
3) (integer) 1

生产者

127.0.0.1:6379> PUBLISH queue [authors,zhangsan,sili,wangwu]
(integer) 1

2个消费者解除阻塞,收到生产者新消息

127.0.0.1:6379> SUBSCRIBE queue
// 收到新消息
1) "message"
2) "queue"
3) "msg1"

Pub/Sub : 既支持阻塞式拉取消息,还能满足多组消费者,消费同一批数据的业务需求

Pub/Sub提供[匹配订阅]模式,允许消费者根据一定规则,订阅[多个]自己想要订阅的队列

 消费者订阅

// 订阅符合规则的队列
127.0.0.1:6379> PSUBSCRIBE queue.*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "queue.*"
3) (integer) 1

生产者

127.0.0.1:6379> PUBLISH queue.p1 msg1
(integer) 1
127.0.0.1:6379> PUBLISH queue.p2 msg2
(integer) 1

消费者窗口

Reading messages... (press Ctrl-C to quit)
...
// 来自queue.p1的消息
1) "pmessage"
2) "queue.*"
3) "queue.p1"
4) "msg1"

// 来自queue.p2的消息
1) "pmessage"
2) "queue.*"
3) "queue.p2"
4) "msg2"

Pub/Sub优缺点

        1. 支持发布/订阅,支持多组生产者、消费者处理消息

        2. 消费者下线,数据会丢失

        3. 不支持数据持久化,Redis宕机,数据也会丢失

        4. 消息堆积, 缓冲区溢出,消费者会被强制下线,数据也会丢失

Pub/Sub 实现方式简单,不基于任何数据类型,没有数据存储,只是单纯的为生产者和消费者建立数据转发通道

发布/订阅消息处理流程

        1. 消费者订阅指定队列,Redis就会记录一个映射关系 : 队列 -> 消费者

        2. 生产者向队列发消息,Redis就从映射关系中找出对应的消费者,把消息转发出去

注意

        消费者必须先订阅队列,生产者才能发布消息,否则消息会丢失 

Pub/Sub 处理消息积压,为什么会丢数据?

        当消费者速度跟不上生产者,就会导致数据积压,Liat作为消息队列,消息积压导致链表很长,Redis内存就会持续增加,直到消费者取出,但Pub/Sub处理方式不一样,可能会导致消费失败和消息丢失。

        每一个消费者订阅队列时,Redis会在Server上给消费者分配一块内存[缓冲区],生产者发布消息时,Redis会把消息写到这块内存,之后消费者不断从这块内存[缓冲区]读取和处理消息,消息堆积过多,导致超过内存上限,Redis就会强制把消费者踢出去,就会消费失败,丢失数据

缓冲区默认配置:client-output-buffer-limit pubsub 32mb 8mb 60。

  • 32mb:缓冲区一旦超过 32MB,Redis 直接强制把消费者踢下线

  • 8mb + 60:缓冲区超过 8MB,并且持续 60 秒,Redis 也会把消费者踢下线

List属于[拉]模型,而Pub/Sub属于[推]模型

Pub/Sub实际应用场景

        目前只有哨兵集群和 Redis实时通信时,采用了 Pub/Sub 方案,因为哨兵模式正好符合即时通讯的业务场景

Stream : 趋于成熟的队列

Stream通过XADD和XREAD完成最简单的生产、消费模型

        ++ XADD : 发布消息

        ++ XREAD: 读取消息

生产者发布消息:

#  * 表示让redis自动生成消息ID
127.0.0.1:6379> XADD queue * name zhangsan
"161846965231315-0"
127.0.0.1:6379> XADD queue * name lisi
"1618469127777-0"

XADD命令发布消息,*代表自动生成唯一的消息ID,ID格式就是时间戳-自增序号

消费者拉去消息

// 从开头读取5条消息,0-0表示从开头读取
127.0.0.1:6379> XREAD COUNT 5 STREAMS queue 0-0
1) 1) "queue"
   2) 1) 1) "1618469123380-0"
         2) 1) "name"
            2) "zhangsan"
      2) 1) "1618469127777-0"
         2) 1) "name"
            2) "lisi"

继续拉取消息

127.0.0.1:6379> XREAD COUNT 5 STREAMS queue 1618469127777-0
(nil)   # 没有消息返回null

XREAD COUNT 5 STREAM queue  1618469127777-0

COUNT 5 : 代表读取5条消息,

queue : 队列名称

1618469127777-0 : 消息ID

消息ID位置也可换成 0-0  , 从头开始读取

stream是否支持阻塞式拉取消息

读取消息时,只需要增加BLOCK参数

// BLOCK 0 表示阻塞等待,不设置超时时间
127.0.0.1:6379> XREAD COUNT 5 BLOCK 0 STREAMS queue 1618469127777-0

XREAD COUNT 5 BLOCK 0 STREAM queue 1618469127777-0

BLOCK 0 : 表示不设置超时时间,一直阻塞等待消息,设置的话,超过时间就会被踢掉,可以设置重连机制

Stream 是否支持发布订阅模式

        ++  XGROUP : 创建消费者组

        ++  XREADGROUP : 在指定消费者下,开启消费者拉取消息

生产者

127.0.0.1:6379> XADD queue * name zhangsan age 14 s 15
"1618470740565-0"
127.0.0.1:6379> XADD queue * name lisi
"1618470743793-0"

消费者组

// 创建消费者组1,0-0表示从头拉取消息
127.0.0.1:6379> XGROUP CREATE queue group1 0-0
OK
// 创建消费者组2,0-0表示从头拉取消息
127.0.0.1:6379> XGROUP CREATE queue group2 0-0
OK

消费者组下面挂一个消费者,分别处理同一批数据

第一组消费者:

// group1的consumer开始消费,>表示拉取最新数据
127.0.0.1:6379> XREADGROUP GROUP group1 consumer COUNT 5 STREAMS queue >
1) 1) "queue"
   2) 1) 1) "1618470740565-0"
         2) 1) "name"
            2) "zhangsan"
      2) 1) "1618470743793-0"
         2) 1) "name"
            2) "lisi"

第二组消费者:
 

// group2的consumer开始消费,>表示拉取最新数据
127.0.0.1:6379> XREADGROUP GROUP group2 consumer COUNT 5 STREAMS queue >
1) 1) "queue"
   2) 1) 1) "1618470740565-0"
         2) 1) "name"
            2) "zhangsan"
      2) 1) "1618470743793-0"
         2) 1) "name"
            2) "lisi"

 消息处理异常,STREAM能否保证消息不丢失,重新消费

消息ID : 除了拉取消息用到消息ID,为了保证重新消费,也需要消息ID

当一组消费者处理完消息后,需要执行XACK命令告知redis,这时Redis会把这条消息标记为处理完成

// group1下的 1618472043089-0 消息已处理完成
127.0.0.1:6379> XACK queue group1 1618472043089-0

 建立多个消费者进行消费同一消息,一个消费者组下存在多个消费者用于防止消息堆积,当该群组下的消费者消费消息之后,其他消费者就不能接着消费该消息

如果消费者异常宕机,肯定不会发送XACK,Redis就会保留此消息,待消费者重新上线后,Redis就会把之前没有处理成功的数据,重新发给消费者

// 消费者重新上线,0-0表示重新拉取未ACK的消息
127.0.0.1:6379> XREADGROUP GROUP group1 consumer1 COUNT 5 STREAMS queue 0-0
// 之前没消费成功的数据,依旧可以重新消费
1) 1) "queue"
   2) 1) 1) "1618472043089-0"
         2) 1) "name"
            2) "zhangsan"
      2) 1) "1618472045158-0"
         2) 1) "name"
            2) "lisi"

Stream数据会写入到RDB和AOF做持久化

Stream是新增加的数据类型,他和其他数据类型一样,每个写操作,也都会写入RDB和AOF中,只需要配置号持久化策略,这样就算Redis宕机,Stream中的数据也可以从RDB或AOF中恢复过来

消费堆积时,Stream怎么处理的?

解决方案

        生产者限流 : 避免消费者处理不及时,导致持续积压

        丢弃消息 : 中间件丢弃旧消息,只保留固定长度的新消息

Redis在实现Stream时,采用了第二个方案

// 队列长度最大10000
127.0.0.1:6379> XADD queue MAXLEN 10000 * name zhangsan
"1618473015018-0"

当队列长度超过上限后,旧消息会被删除,只保留固定长度的新消息

        ++  查看消息长度  : XLEN

        ++   查看消费者状态 : XINFO

Steam原理

 

每个 Stream 都有唯一的名称,它就是 Redis 的 key,在我们首次使用 xadd 指令追加消息时自动创建。

上图解析:

Consumer Group :消费组,使用 XGROUP CREATE 命令创建,一个消费组有多个消费者(Consumer)。
last_delivered_id :游标,每个消费组会有个游标 last_delivered_id,任意一个消费者读取了消息都会使游标 last_delivered_id 往前移动。
pending_ids :消费者(Consumer)的状态变量,作用是维护消费者的未确认的 id。 pending_ids 记录了当前已经被客户端读取的消息,但是还没有 ack (Acknowledge character:确认字符)。
 

多个生产者和多个消费者可以创建多个消费者组和多个生产者

自述stream原理

首先生产者存入消息到stream会形成一个链表,并且每个消息会对应唯一的消息ID存储到链表中,之后创建消费者组,每一个消费者组会存在一个标识last_delivered_id 游标,当消费消息之后,这个游标就会向前移动,无论消息是否消费成功,消息都会持久化保存,并且未接收到ACK确认的消息会放到pending_ids列表中,当我们执行XREADGROUP GROUP group1 consumer COUNT 5 STREAMS queue 0,就会从pending_ids中读取消息,执行XREADGROUP GROUP group1 consumer COUNT 5 STREAMS queue >会从消息链表中读取消息

Redis与专业消息队列对比

专业消息队列,必须做到2点:

        1. 消息不丢

        2. 消息可堆积

从使用模型分析,怎么才能保证消息不丢失

消息队列 : 生产者、消费者、队列

 消息不丢,重点环节

        1. 生产者会不会丢消息

        2. 消费者会不会丢消息

        3. 队列中间件会不会丢消息

生产者会不会丢消息        

        1.消息没发出去 : 网络故障或其它问题导致发布失败,中间件直接返回失败

        2.不确定消息是否发布成功 : 网络问题导致发布超时,可能数据已发送成功,但读取响应结果超时了

解决办法

        情况一 : 消息发送失败,再发一次

        情况二 : 生产者没办法知道消息是否发布成功,所以为了避免消息丢失,只能继续重试,直到发布成功,生产者一般会设置一个最大重试次数,超过上限依旧失败,需要记录日志报警处理

重发消息导致问题

        生产者可以保证消息不丢,但会导致消息重复,这就需要消费者设计幂等逻辑,保证业务的正确性

注意 : 生产者方面和专业的消息队列一样,可以保证消息不丢失

消费者会不会丢消息

消费者拿到消息之后,redis宕机导致消息丢失,消费失败,解决--> 消费者处理完消息之后,必须告知队列中间件,队列会把消息标记为已消费,否则仍然会把消息发送给消费者。

Redis的Stream和专业的中间件,例如RabbitMQ、Kafka都这样解决

队列中间件会不会丢消息

redis未达到专业中间件要求

Redis在以下2个场景,会导致数据丢失

        1. AOF 持久化配置为每秒写盘,但这个写盘过程是异步的,Redis宕机时会存在数据丢失的可能

        2. 主从复制也是异步的,主从切换时,也存在数据丢失可能(从库还未同步主库发来的数据,就可能被提为主库)

基于以上原因,Redis本身无法严格保证数据的完整性

专业的消息队列如何处理队列中间件丢失消息

RabbitMQ和Kafka这类专业中间件,在使用时,一般部署集群,生产者在发布消息时,队列中间件通常会写[多个节点],以此保证消息的完整性,这样一来,即使其中一个节点挂了,也可以保证集群的数据不丢失

问题 : redis也集群部署,多个节点,不是也可以保证消息不丢失???

消息积压怎么办

Redis : 超过机器内存上限,就会面临OOM风险,Stream提供的最大长度,避免这种事情发生

Kafka/RabbitMQ :数据存在磁盘上,成本小,消息积压时,增加硬盘空间

综上所述:把Redis当作消息队列面临2个问题:

        1. redis本身可能丢失数据

        2. 面对消息积压时,Redis内存紧张

Redis作为消息队列

1.业务场景简单,数据丢失不敏感,消息积压概率小,选择redis

2.Redis部署运维更加轻量

Redis消息队列总结

 

​ 

技术方案选型

  1. 业务功能角度:上面说的内容,业务实现

  2. 技术资源角度 : 所处公司环境,技术资源能否支持这些技术方案

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Redis作为消息队列的使用方式有多种。其中一种是基于List结构的消息队列。在这种方式下,生产者将消息添加到List中,而消费者则从List中获取消息并进行处理。这种方式简单直接,但是存在一个问题,即消费者需要不停地轮询List来检查是否有新的消息到达,这会造成资源的浪费。为了解决这个问题,可以使用blpop/brpop命令,这些命令可以阻塞地等待消息的到达,从而避免了不必要的轮询。\[2\] 另外,Redis还支持基于PubSub的消息队列。在这种方式下,生产者将消息发布到指定的频道,而消费者则订阅这些频道并接收消息。这种方式适用于多个消费者同时接收消息的场景。\[1\] 此外,Redis还引入了基于Stream的消息队列。在这种方式下,生产者将消息添加到Stream中,而消费者则从Stream中获取消息并进行处理。与List结构相比,Stream结构提供了更多的功能,例如消费者组,可以实现多个消费者对消息的并行处理。\[1\] 综上所述,Redis作为消息队列可以通过List结构、PubSub机制和Stream结构来实现。具体选择哪种方式取决于实际需求和场景。 #### 引用[.reference_title] - *1* *3* [【Redis】使用Redis作为消息队列](https://blog.csdn.net/Decade_Faiz/article/details/128721072)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Redis消息队列](https://blog.csdn.net/qq_43647359/article/details/105755985)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值