Redis实现消息队列

在我们很多开发场景中可能会需要消息队列的存在,实现消息队列的方式有很多,上篇文章也大概了解了一下Kafka,Rebbitmq等等消息队列,但是如果我们想要特别简单的消息队列,我们可以使用redis来实现

引用:redis之如何实现消息队列_redis实现消息队列_wang0907的博客-CSDN博客

Redis实现消息队列最简单版

我们可以先回顾一下redis是用来干嘛的,redis是非关系型的数据库,他是以key:value形式存在数据库中,redis数据库的常用数据类型有字符串,列表,哈希,集合,有序集合,当我们简单的了解一下redis的列表类型的时候,我们就可以实现一个简单版的消息队列

Redis中列表的插入值有多种方法,但是我们选择其中两个

rpush key value1 value2 value3      表示从右侧插入,每次插入都在上个值的右侧

lpush key vlaue value2 value3        表示从左侧插入,每次插入都在上个值的左侧

Reis的取值方法

lpop key   从列表左侧弹出一个值

rpop key  从列表右侧弹出一个值

那么我们看到了列表的插入以及取值,是不是可以联想到我们的消息队列呢,消息队列其实可以简单的理解为我们生产者在某一个位置放入值,而我们的消费者在同样的位置去拿值,那结合redis的插入值,如我们的lpush,他每次所插入的值都会在上一个值的左侧,所以最右边的值是第一个插入的,再依次向左,那这个时候如果我们用rpop去弹出列表最后面的值,那么就拿的是到消息队列第一个放入的值,那再弹第二个,就是第二个放入的,这样我们可以实现一个简单的消息队列

List用自身的特性保证了有序性,但是有一个问题在出队时消费者需要轮询消息队列,无论消息队列是否有值都需要轮询显然这是对资源的消耗不太合理,所以Redis中可以使用阻塞式读取命令BRPOP当消息队列是空时会阻塞等待,当消息队列有后会重新出队

消息的重复消费

生产者可以创建唯一性ID来标识这个消息,消费者需要存储已经消费的消息ID列表,每处理一个消息需要对比消息ID在已消费列表中是否存在,如果ID已存在那么该消息不会再处理,这也是幂等性的保证

消息的可靠性

消息的可靠性保证可以在消费者出队一个消息后将这个消息保存到未完成的队列中,只有当消费者处理完毕才从未完成的队列中移除,其核心思想就是采用BRPOPLPUSH备份

对于List可以简单满足消息队列的需求,但需要注意的是List处理消息队列肯定不能多个消费者一起消费,不然消息的有序性得不到保证,如果生产者的速度过快,而消费者的速度慢就会造成消息堆积,在Redis内存带来巨大的压力,所以在这种场景下需要考虑多个消费者共同分担压力的问题,这就需要依靠5.0 版本开始提供的Stream解决。 

Streams

Streams是Redis5.0新增的数据结构,它提供了消息的持久化以及主备复制功能,可以让任何客户端访问任何时刻的数据,它有一个消息链表可以将所有加入的消息串起来,每个消息都存在一个唯一ID,并且这个ID是递增的,是一种redis专门为消息队列定义的一种数据结构,

自然的我们是先要看如何定义这种数据结构了,和其它的数据结构一样,我们不需要显式的创建,在执行第一次数据添加的时候自动创建,添加数据的命令是XADD,语法格式是XADD key ID field value [field value ...],参数说明如下:

key:redis的key
ID:消息的唯一标识,可以指定,也可以设置为*,设置为*时id会自动生成,id是递增
field value:消息的字段和值

如下生产(创建)若干条消息:

redis> XADD mystream * name Sara surname OConnor
"1601372323627-0"
redis> XADD mystream * field1 value1 field2 value2 field3 value3
"1601372323627-1"
redis> XLEN mystream
(integer) 2
redis> XRANGE mystream - +
1) 1) "1601372323627-0"
   2) 1) "name"
      2) "Sara"
      3) "surname"
      4) "OConnor"
2) 1) "1601372323627-1"
   2) 1) "field1"
      2) "value1"
      3) "field2"
      4) "value2"
      5) "field3"
      6) "value3"
redis> 

其中XLEN用来查看消息的个数,XRANGE用来通过范围查询基于递增ID获取消息,-相当于是负无穷,+相当于是正无穷,即获取所有消息。我们接着再来看下其它一些命令

XDEL :根据ID删除消息

> XADD mystream * a 1
1538561698944-0
> XADD mystream * b 2
1538561700640-0
> XADD mystream * c 3
1538561701744-0
> XDEL mystream 1538561700640-0
(integer) 1
127.0.0.1:6379> XRANGE mystream - +
1) 1) 1538561698944-0
   2) 1) "a"
      2) "1"
2) 1) 1538561701744-0
   2) 1) "c"
      2) "3"

XLEN:获取消息的数量 

redis> XADD mystream * item 1
"1601372563177-0"
redis> XADD mystream * item 2
"1601372563178-0"
redis> XADD mystream * item 3
"1601372563178-1"
redis> XLEN mystream
(integer) 3
redis>

XRANGE:查询指定范围的消息

key :队列名
start :开始值, - 表示最小值
end :结束值, + 表示最大值
count :数量

redis> XADD writers * name Virginia surname Woolf
"1601372577811-0"
redis> XADD writers * name Jane surname Austen
"1601372577811-1"
redis> XADD writers * name Toni surname Morrison
"1601372577811-2"
redis> XADD writers * name Agatha surname Christie
"1601372577812-0"
redis> XADD writers * name Ngozi surname Adichie
"1601372577812-1"
redis> XLEN writers
(integer) 5
redis> XRANGE writers - + COUNT 2
1) 1) "1601372577811-0"
   2) 1) "name"
      2) "Virginia"
      3) "surname"
      4) "Woolf"
2) 1) "1601372577811-1"
   2) 1) "name"
      2) "Jane"
      3) "surname"
      4) "Austen"
redis>

 XREVRANGE:从后往前获取消息

语法格式XREVRANGE key end start [COUNT count]

key :队列名
end :结束值, + 表示最大值
start :开始值, - 表示最小值
count :数量

 redis> XADD writers * name Virginia surname Woolf
"1601372731458-0"
redis> XADD writers * name Jane surname Austen
"1601372731459-0"
redis> XADD writers * name Toni surname Morrison
"1601372731459-1"
redis> XADD writers * name Agatha surname Christie
"1601372731459-2"
redis> XADD writers * name Ngozi surname Adichie
"1601372731459-3"
redis> XLEN writers
(integer) 5
redis> XREVRANGE writers + - COUNT 1
1) 1) "1601372731459-3"
   2) 1) "name"
      2) "Ngozi"
      3) "surname"
      4) "Adichie"

XREAD 

以阻塞或者是非阻塞的方式获取消息,即消费消息的命令,语法格式XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] id [id ...],解释如下:

count :数量
milliseconds :可选,阻塞毫秒数,没有设置就是非阻塞模式
key :队列名
id :消息 ID

# 从 Stream 头部读取两条消息
> XREAD COUNT 2 STREAMS mystream writers 0-0 0-0
1) 1) "mystream"
   2) 1) 1) 1526984818136-0
         2) 1) "duration"
            2) "1532"
            3) "event-id"
            4) "5"
            5) "user-id"
            6) "7782813"
      2) 1) 1526999352406-0
         2) 1) "duration"
            2) "812"
            3) "event-id"
            4) "9"
            5) "user-id"
            6) "388234"
2) 1) "writers"
   2) 1) 1) 1526985676425-0
         2) 1) "name"
            2) "Virginia"
            3) "surname"
            4) "Woolf"
      2) 1) 1526985685298-0
         2) 1) "name"
            2) "Jane"
            3) "surname"
            4) "Austen" 

XGROUP CREATE 

创建消费者组,使用消费者可以对消息进行并发的消费,解决消费者消费能力不足的问题,语法格式为XGROUP [CREATE key groupname id-or-$] [SETID key groupname id-or-$] [DESTROY key groupname] [DELCONSUMER key groupname consumername],解释如下:
 

key :队列名称,如果不存在就创建
groupname :组名。
$ : 表示从尾部开始消费,只接受新消息,当前 Stream 消息会全部忽略。

如下从头开始消费: 

XGROUP CREATE mystream consumer-group-name 0-0  

如下从尾部开始消费: 

XGROUP CREATE mystream consumer-group-name $ 

在实际的场景中我们可以通过设置多个消费者组的不同开始消费的位置来实现并发消费的效果,此时可能如下图:

 图中主要元素解释如下:

每个 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:确认字符)。

XREADGROUP GROUP 

读取消费者组中的消息,语法格式如下:

XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] ID [ID ...]

解释如下:

group :消费组名
consumer :消费者名。
count : 读取数量。
milliseconds : 阻塞毫秒数。
key : 队列名。
ID : 消息 ID。 

如下测试:

XREADGROUP GROUP consumer-group-name consumer-name COUNT 1 STREAMS mystream > 

总结 

Redis消息队列的实现真正能运用到生产环境的是List和Streams,两者区别如下所示

  • 11
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Redis可以通过使用Stream数据结构来实现消息队列。在Redis中,消息队列可以使用XADD命令将消息发送到队列中,使用XREADGROUP命令从队列中读取消息,并使用XACK命令确认消息已被处理。 首先,可以使用XADD命令将消息发送到队列中。该命令的语法如下: XADD key [MAXLEN|MINID [=|~] threshold [LIMIT count]] field value [field value ...] 其中,key是队列的名称,field和value是消息的键值对。可以使用*作为field来自动生成消息的唯一ID。 然后,可以使用XREADGROUP命令从队列中读取消息。该命令的语法如下: XREADGROUP GROUP 消费组名称 消费者名称 COUNT 每次读取消息的数量 BLOCK 阻塞时间毫秒 STREAMS 队列名称 > 其中,消费组名称和消费者名称用于标识消费者,每次读取的消息数量和阻塞时间可以根据需求进行设置,队列名称是要读取消息的队列。 在消费者代码中,可以使用一个无限循环来监听消息,并使用XREADGROUP命令来获取消息。如果没有消息,可以继续循环等待。处理完消息后,需要调用XACK命令确认消息已被处理。 以下是一个Java代码示例,手动模拟消费者监听消息的过程: ```java while (true) { Object message = redis.call("XREADGROUP GROUP 消费组名称 消费者名称 COUNT 每次读取消息的数量 BLOCK 阻塞时间毫秒 STREAMS 队列名称 >"); if (message == null) { continue; } try { // 处理消息的逻辑 handleMessage(message); // 处理完毕后确认消息已被处理 redis.call("XACK 队列名称 消费组名称 消息ID"); } catch (Exception e) { while (true) { Object unAckMessage = redis.call("XREADGROUP GROUP 消费组名称 消费者名称 COUNT 每次读取消息的数量 BLOCK 阻塞时间毫秒 STREAMS 队列名称 >"); if (unAckMessage == null) { continue; } try { handleMessage(unAckMessage); redis.call("XACK 队列名称 消费组名称 消息ID"); } catch (Exception e1) { continue; } } } } ``` 在上述代码中,handleMessage方法用于处理消息的逻辑。如果处理消息时发生异常,可以将消息放回队列中等待重新处理。 总结起来,Redis可以通过使用Stream数据结构和相关命令来实现消息队列。通过XADD命令将消息发送到队列中,使用XREADGROUP命令从队列中读取消息,并使用XACK命令确认消息已被处理。以上是一个简单的示例代码,可以根据实际需求进行修改和扩展。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值