持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第12天,点击查看活动详情
redis作为消息队列
谈到消息队列我们我们一般不会想到通常用来当作缓存的Redis也能当作消息队列来使用,一般都会想到Kafka、rabbitMq、等开源的消息队列。可以看出这些开源的消息队列都满足,异步、削峰、解耦。那么假如用redis当作消息队列的话它是否也会满足这三大特点呢?
消息队列是有由生产者、队列,消费者这三大组件组成。生产者发送消息给队列,消费者采用拉的方式从队列中获取数据、或者队列采用推的方式推送给消费者(一般是前者)。并且需要保证数据不丢失和队列中数据开源堆积一段时间,那么redis是否满足呢?
redis队列方式
- List类型数据结构
- 发布订阅模式
- Stream的消息队列(专业)
List队列
List队列是Redis中最简单的队列实现方式,生产者采用LPush或者RPUSH的方式往队列中中发送消息,消费者采用 RPOP 或者LPOP的方式从消费者中拉去消息。
生产者
LPUSH zzy 1,2,3,4
消费者
LPOP zzy
但是这样的发送的消息,消费者并不能实时的消费队列中的消息,需要一直调用LPOP指令来完成消费,这样就造成了不停的请求会占用CPU资源。那么redis是怎么避免这种情况的呢?redis提供了阻塞读取的命令, 消费者在在读取队列没有数据的时候自动阻塞,直到有新的消息写入队列,才会继续读取新消息执行业务逻辑。
无限阻塞直到弹出消息
BLPOP key 0
- 当队列中的元素消费完成后,key也就自动删除了。不会被重复消费。
- 消费者消费一个元素,队列中少一个元素。
- redis还提供
BRPOPLPUSH命令可以在消费该队列的时候,含义是消费最右边那个数据,并且把它写入到一个新的队列头部中。从 Redis 6.2.0 起,建议使用 BLMOVE 替代 BRPOPLPUSH 。
BRPOPLPUSH LIST1 ANOTHER_LIST TIMEOUT
发布订阅模式
Redis的发布订阅模式,生产者往一个通道里面发送消息,多个消费者可以订阅该通道,这样消费者就会收到来自生产者发送的消息。这样就解决了消息可以被多个消费者消费的问题,即消费重复消费。
生产者发送消息
PUBLISH zzy test2
消费者订阅通道
SUBSCRIBE zzy
先启动消费者订阅通道,再启动生产者发送消息。
这个通道是实时的,并不会作数据存储,当消费者出现异常后,再次启动只能收到最新的消息,之前的消息收不到。、生产者发送的消息太快,消费者跟不上会造成数据堆积导致可能会造成数据丢失。消费者订阅通道,redis会给它分配一个内存,当数据超过了这个内存上限也会丢失消息。
Stream模式
上面的发布订阅模式的消息是无法持久化的,针对这一点redis在5.0版本推出了新的队列Stream支持数据持久化、客户端可以访问任何时刻的数据。并且支持分组消费且保持消息有序,和kafka的类似,可以当作简单的消息队列来使用,但是如果对于数据的可靠性要求极高,比如设计到金钱内容的消息,建议还是使用kafka等专业的消息队列。
核心概念
-
Consumer Group :消费组,使用 XGROUP CREATE 命令创建,一个消费组有多个消费者(Consumer)。
- 同一条消息,只能被这个消费者组中的某个消费者获取。
- 多个消费者之间是相互独立的,互不干扰。
-
last_delivered_id :游标,每个消费组会有个游标 last_delivered_id,任意一个消费者读取了消息都会使游标 last_delivered_id 往前移动。保证了消费者组里的其他消费者不会再次读取该消息。
-
pending_ids :消费者(Consumer)的状态变量,作用是维护消费者的未确认的 id。 pending_ids 记录了当前已经被客户端读取的消息,但是还没有 ack (Acknowledge character:确认字符)。
生产者相关命令
生产者
XADD key ID field value [field value ...]
key :队列名称,如果不存在就创建
ID :消息 id,我们使用 * 表示由 redis 生成,可以自定义,但是要自己保证递增性。
field value : 记录。
XADD mystream * name Sara surname OConnor
删除消息XLEN key
统计长度XRANGE key start end [COUNT count]
独立消费
使用 XREAD 以阻塞或非阻塞方式获取消息列表
XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] id [id ...]
count :数量
milliseconds :可选,阻塞毫秒数,没有设置就是非阻塞模式
key :队列名
id :消息 ID
XREAD COUNT 2 STREAMS zzy 0-0
消费者组消费
首先创建消费者组,再启动消费者组里的一个消费者消费
创建消费者组
XGROUP [CREATE key groupname id-or-$] [SETID key groupname id-or-$] [DESTROY key groupname] [DELCONSUMER key groupname consumername]
key :队列名称,如果不存在就创建
groupname :组名。
$ : 表示从尾部开始消费,只接受新消息,当前 Stream 消息会全部忽略。
消费者组启动消费者消费
XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] ID [ID ..
group :消费组名
consumer :消费者名。
count : 读取数量。
milliseconds : 阻塞毫秒数。
key : 队列名。
ID : 消息 ID。
代码演示
创建消费者组
CREATE zzy group-0 0-0
启动消费者组里一个消费者消费
XREADGROUP group group-0 c1 count 3 streams zzy >
消费者执行完成后没有出现故障后需要执行ack手动提交这些消息id,xack zzy group-0 1666750665245-0表示这个消息确认收到。
Stream作为一个新的数据结构它与其它数据类型一样,每个写操作,也都会写入到 RDB 和 AOF 中。
消息堆积的处理 在发布消息时,你可以指定队列的最大长度,防止队列积压导致内存爆炸。当达到最大长度后数据消息仍然会有丢失的风险。