redis之十九 -- 消息队列终极解决方案——Stream(上)

在 Redis 5.0 Stream 没出来之前,消息队列的实现方式都有着各自的缺陷,例如:

  • 发布订阅模式 PubSub,不能持久化也就无法可靠的保存消息,并且对于离线重连的客户端不能读取历史消息的缺陷;
  • 列表实现消息队列的方式不能重复消费,一个消息消费完就会被删除;
  • 有序集合消息队列的实现方式不能存储相同 value 的消息,并且不能阻塞读取消息。

并且以上三种方式在实现消息队列时,只能存储单 value 值,也就是如果你要存储一个对象的情况下,必须先序列化成 JSON 字符串,在读取之后还要反序列化成对象才行,这也给用户的使用带来的不便,基于以上问题,Redis 5.0 便推出了 Stream 类型也是此版本最重要的功能,用于完美地实现消息队列,它借鉴了 Kafka 的设计思路,它支持消息的持久化和消息轨迹的消费,支持 ack 确认消息的模式,让消息队列更加的稳定和可靠。

接下来我们先来了解 Stream 自身的一些特性,然后在综合 Stream 的特性,结合 Java 代码完整的实现一个完美的消息队列示例。

基础使用

Stream 既然是一个数据类型,那么和其他数据类型相似,它也有一些自己的操作方法,例如:

  • xadd 添加消息;
  • xlen 查询消息长度;
  • xdel 根据消息 ID 删除消息;
  • del 删除整个 Stream;
  • xrange 读取区间消息
  • xread 读取某个消息之后的消息。

具体使用如下所述。

添加消息

127.0.0.1:6379> xadd key * name redis age 10
"1580880750844-0" #结果返回的是消息 id

其中 * 表示使用 Redis 的规则:时间戳 + 序号的方式自动生成 ID,用户也可以自己指定 ID。

相关语法:

xadd key ID field string [field string ...]

查询消息的长度

127.0.0.1:6379> xlen key
(integer) 1

相关语法:

xlen key

删除消息

127.0.0.1:6379> xadd key * name redis
"1580881585129-0" #消息 ID
127.0.0.1:6379> xlen key
(integer) 1
127.0.0.1:6379> xdel key 1580881585129-0 #删除消息,根据 ID
(integer) 1
127.0.0.1:6379> xlen key
(integer) 0

相关语法:

xdel key ID [ID ...]

此命令支持删除一条或多条消息,根据消息 ID。

删除整个 Stream

127.0.0.1:6379> del key #删除整个 Stream
(integer) 1
127.0.0.1:6379> xlen key
(integer) 0

相关语法:

del key [key ...]

此命令支持删除一个或多个 Stream。

查询区间消息

127.0.0.1:6379> xrange mq - +
1) 1) "1580882060464-0"
   2) 1) "name"
      2) "redis"
      3) "age"
      4) "10"
2) 1) "1580882071524-0"
   2) 1) "name"
      2) "java"
      3) "age"
      4) "20"

其中:- 表示第一条消息,+ 表示最后一条消息。

相关语法:

xrange key start end [COUNT count]

查询某个消息之后的消息

127.0.0.1:6379> xread count 1 streams mq 1580882060464-0
1) 1) "mq"
   2) 1) 1) "1580882071524-0"
         2) 1) "name"
            2) "java"
            3) "age"
            4) "20"

在名称为 mq 的 Stream 中,从消息 ID 为 1580882060464-0 的,往后查询一条消息。

相关语法:

xread [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]

此命令提供了阻塞读的参数 block,我们可以使用它读取从当前数据以后新增数据,命令如下:

127.0.0.1:6379> xread count 1 block 0 streams mq $

其中 block 0 表示一直阻塞,$ 表示从最后开始读取,这个时候新开一个命令行插入一条数据,此命令展示的结果如下:

127.0.0.1:6379> xadd mq * name sql age 20 #新窗口添加数据
"1580890737890-0"
#阻塞读取到的新数据
127.0.0.1:6379> xread count 1 block 0 streams mq $
1) 1) "mq"
   2) 1) 1) "1580890737890-0"
         2) 1) "name"
            2) "sql"
            3) "age"
            4) "20"
(36.37s)

python版基础增删查

from redis import StrictRedis

redis_cli = StrictRedis(host="xx", port=xx, password="xx", db=xx, decode_responses=True)

# 新增
stream_id = redis_cli.xadd("test_key", {"name": "test", "age": 10})
print("%s add success" % stream_id)

# 查看长度
stream_len = redis_cli.xlen("test_key")
print("len is:", stream_len)

# 查看所有
all_detail = redis_cli.xrange("test_key")
print("all_detail is: ", all_detail)
all_detail_2 = redis_cli.xread({"test_key": "0-0"})
print("all_detail_2 is:", all_detail_2)
# 查询指定id后面一条
id_detail = redis_cli.xread({"test_key": "1639561709762-0"})
print("id_detail is :", id_detail)
# 从最后一条消息后查询
last_detail = redis_cli.xread({"test_key": "$"})
print("last_detail is: %s" % last_detail)


# 删除整个key
redis_cli.delete("")

# 删除某个key的某条消息
redis_cli.xdel("test_key", "1639561709762-0")

基础版消息队列

使用 Stream 消费分组实现消息队列的功能和列表方式的消息队列比较相似,使用 xadd 命令和 xread 循环读取就可以实现基础版的消息队列,具体代码如下:

# 生产者

from redis import StrictRedis

redis_cli = StrictRedis(host="", port=xx, password="xx", db=xx, decode_responses=True)


def pub():
    if redis_cli.exists("test_stream"):
        redis_cli.delete("test_stream")
    for i in range(3):
        stream_id = redis_cli.xadd("test_stream", {"name": "mrli", "province": "beijing", "flag": i})
        print("%s add success! " % stream_id)
    print("the length is: %s" % redis_cli.xlen("test_stream"))


if __name__ == '__main__':
    pub()

结果:

1639562319392-0 add success! 
1639562319633-0 add success! 
1639562319810-0 add success! 
the length is: 6 

# 消费者

from redis import StrictRedis

redis_cli = StrictRedis(host="", port=xx, password="xx", db=xx, decode_responses=True)


def sub():
    while True:
        msg = redis_cli.xread({"test_stream": "$"}, block=6000)
        print("msg is %s: " % msg)


if __name__ == '__main__':
    import threading
    t = threading.Thread(target=sub)
    t.start()

结果:

msg is [['test_stream', [('1639562319392-0', {'name': 'mrli', 'province': 'beijing', 'flag': '0'})]]]: 
msg is [['test_stream', [('1639562319719-0', {'name': 'mrli', 'province': 'beijing', 'flag': '1'})]]]: 
msg is [['test_stream', [('1639562319849-0', {'name': 'mrli', 'province': 'beijing', 'flag': '2'})]]]: 
msg is []: 
msg is []: 
msg is []: 

以上代码需要特殊说明的是,我们使用 "$" 来实现读取当前时间以后新增的消息,如果要从头读取历史消息需要更换读取方式。

还有一点需要注意,在 StrictRedis 框架中如果使用r.xread() 方法来阻塞读取消息队列,第二个参数 long block 必须设置大于 0,如果设置小于 0,就会报错

所以 block 属性我们可以设置一个比较大的值来阻塞读取消息。

所谓的阻塞读取消息指的是当队列中没有数据时会进入休眠模式,等有数据之后才会唤醒继续执行。

小结

本文介绍了 Stream 的基础方法,并使用 xadd 存入消息和 xread 循环阻塞读取消息的方式实现了简易版的消息队列,交互流程如下图所示:

Stream简易版交互图.png

然后这些并不是 Stream 最核心的功能,下文我们将带领读者朋友们,使用消费分组来实现一个完美的消息队列。

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值