Redis学习之消息队列
JDK阻塞队列可能存在哪些问题?
-
服务器宕机,内存队列中的订单信息全部丢失
-
线程处理错误,已取出单个订单信息,但没有入库
-
受单JVM内存限制
所以,我们需要一个独立的队列来存管订单信息,也就是消息队列。
介绍
存放消息的队列。一种开发中常用的中间件
最简单的消息队列模型包括3个角色:
-
消息队列:存储和管理消息,也被称为消息代理(Message Broker)
-
生产者:发送消息到消息队列
-
消费者:从消息队列获取消息并处理消息
使用消息队列的优点
-
可以保证我们消息的安全、不会丢失(快递柜上锁)
-
可以解耦生产者和消费者(不用立刻去取快递)
-
独立组件,不影响 JVM
-
可以保证消息一定被接受,避免线程处理错误后订单丢失的问题
-
消息是有序的
实现方式
Redis List实现
使用 Redis List 的结构作为消息队列,使用 LPush 模拟生产者发送消息入队,使用 BRPOP(阻塞弹出)模拟消费者取出消息。没有消息时会保持阻塞状态,从而实现了类似 JVM 阻塞队列的效果。
优点:
-
利用Redis存储,不受限于JVM内存上限
-
基于Redis的持久化机制,数据安全性有保证
-
可以满足消息有序性
缺点:
-
无法避免消息丢失
-
只支持单消费者
Redis Pub/Sub实现
PubSub(发布订阅)是Redis2.0版本引入的消息传递模型。顾名思义,消费者可以订阅一个或多个channel,生产者向对应channel发送消息后,所有订阅者都能收到相关消息。
PUBLISH channel msg :向一个频道发送消息
SUBSCRIBE channel [channel] :订阅一个或多个频道
PSUBSCRIBE pattern[pattern] :订阅与pattern格式匹配的所有频道
基于PubSub的消息队列有哪些优缺点? 优点:
-
采用发布订阅模型,支持多生产、多消费
缺点:
-
不支持数据持久化
-
无法避免消息丢失
-
消息堆积有上限,超出时数据丢失
Redis Stream
Stream 是 Redis 5.0 引入的一种新数据类型,可以实现一个功能非常完善的消息队列。
核心命令:
-
XAdd:添加消息 / 创建队列,消息会自动持久化、不会丢失,每个消息都有唯一 id
-
XRead:读取消息,支持多消费者读、可从指定消息 id 开始读、支持阻塞读最新消息
注:只用这两个命令还是不够的,当我们指定起始ID为$时,代表读取最新的消息,如果我们处理一条消息的过程中,又有超过1条以上的消息到达队列,则下次获取时也只能获取到最新的一条,会出现漏读消息的问题
为解决上述问题,可以用 Stream 的以下特性:
-
消费组:同组内的多个消费者可以竞争消费(每个消息只有一个消费者能抢到),从而提高消费能力(并发度)。对应命令为 XGROUP、XREADGROUP 等。
-
消息标识:自动记录消费的进度,支持从上次未消费的地方开始接着消费,保证每条消息按顺序消费
-
消息确认机制:默认消费的消息为 pending 状态,会放到每个消费者的 pending list 中,只有消息由消费者确认(ACK),才会从 pending list 移除。这样如果消费业务处理异常,可以从 pending list 的开头依次读取未确认消息,重试处理。(也要避免无限重试,实在处理不成功就强制 ACK + 业务记日志)
消费者监听消息的基本思路:
在Java中操作Redis Stream有两种方法:
-
调用lua脚本
-
使用Redis Template 的opsForStream()
几种方案的对比: