Redis 队列

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 队列的实现举例: 队列主要用在系统解耦、流量削峰、异步处理、数据顺序处理等场景。新手在使用时可能会犯一些常见的错误。下面讲一个新手容易犯的错误,在这个示例中把队列的入队、出队和Redis存储节点的主从关系给混淆了,示例如下存储:Redis主节点M, 使用数据List类型做为队列,列表名称M (标记为M.

队列的实现

举例:
队列主要用在系统解耦、流量削峰、异步处理、数据顺序处理等场景。新手在使用时可能会犯一些常见的错误。下面讲一个新手容易犯的错误,在这个示例中把队列的入队、出队和Redis存储节点的主从关系给混淆了,示例如下

存储:
Redis主节点M, 使用数据List类型做为队列,列表名称M (标记为M.L,意为主节点上的List)
Redis从节点S, 做为Redis主节点的备,有和主节点相同的列表名称L (标记为S.L,意为从节点上的List

应用程序:
应用名称App

操作:
入队命令: LPUSH M.L Data
出队命令: RPOP S.L

我们知道,Redis主从节点数据的流向是主节点->从节点,队列中一般数据也是队尾(入队)-> 队头(出队),这两个数据流向混淆后就会出现以上的错误。

img_94067114c6dab15cd9c84ef8670aa7e8.png
image.png

上图显然不是我们想要的结果,这种设计导致的问题是Redis主节点使用的内存会不断增长直至触发Redis的LRU策略导致数据丢失或者无法入队。Redis的从节点S仅仅是做为主节点M的一个备份节点存在的。

img_c3237aa5cd9cc5980ff8fbef542bb95b.png
image.png

阻塞队列

阻塞队列是一种特殊的队列,具体是指对出队动作在队列为空时的阻塞行为以及在有元素入队后对出队的通知行为.我们知道事件通知机制是服务端通过一定的途径向客户端发送事件消息来实现的,服务端和客户端通常保持长链接。生产者(通知方)向队列中发送事件消息,消费者(接收方)从队列中拿走(POP)事件消息,当队列中没有事件消息的时候,消费者(接收方)阻塞,消费者(接收方)和队列之间保持长链接。

消费者(接收方)的代码(Java)逻辑应该大致如下:

    while (true){
            try {
                EventMessage em = client.brpop(list, timeout);
                doSomething(em);
            }catch(Exception e){
                continue;
            }
      }

使用循环是因为每次brpop命令只能拿到一个队列中的元素,为了得到消息的连续性必须循环brpop。在循环中使用try...catch...是防止在brpop的过程中由于网络闪断、连接池等因素导致的连接不可用抛出异常致使循环不可继续不能连续获取事件消息。

生产者(通知方)的Redis命令逻辑大致如下:

MULTI
      some command...
      LPUSH list x
      EXEC

阻塞队列与PUB/SUB在实现事件通知上的区别

  • 1.PUB/SUB方式在消息消费者(被通知方)订阅一个或多个话题后和Redis保持长链接等待通知,之后可以连续的得到事件通知消息;阻塞队列brpop/blpop方式每次只能获取一个事件消息(即便pop多个队列也是一样),客户端需要循环使用brpop/blpop来获取事件消息,直至队列为空阻塞。
  • 2.PUB/SUB方式实现的通知只有在消息消费者(被通知方)和Redis服务有在线连接的情况下才能收到,一旦连接断开消费者(被通知方)将不能收到通知,即便重新成功连接也无法收取丢失的通知消息;而阻塞队列在客户端断开后重新连接成功后可以收到通知消息。
  • 3.PUB/SUB方式事件消息消费者可以有多个并且每个消费者都能得到相同的消息;而阻塞队列虽然事件消费者可以有多个但是消息只是分发给其中一个消费者,消息无法重复消费。

可靠队列

在Redis的列表(List)实现的队列中,一般一个客户端通过LPUSH命令将消息放入队列中,而另一个客户端通过RPOP/BRPOP 命令有顺序的取出队列中的消息进行消费。但是这种方式却不一定安全,因为在这个过程中,一个客户端可能在取出一个消息之后在处理这个消息之前崩溃,而未处理完的消息也就因此而丢失,并且无法找回。在一些希望是更可靠的消息传递系统中的应用上,这可能会导致一些问题。使用 RPOPLPUSH/BRPOPLPUSH 命令可以解决这个问题:因为它不仅返回一个消息,同时还将这个消息添加到另一个备份列表当中,当一个客户端完成某个消息的处理之后,可以从备份表中把消息删除。

img_4069e3feed5eda1f22ca3b0e3293236f.png
image.png

对于备份列表一般有两种设计方式:每个客户端各自私有一个备份列表和所有客户端公用一个备份列表。两种方式实现机制不同,举例如下

    1. 每个客户端各自私有一个备份队列
  • 客户端从队列里获取消息之前首先检查自己的私有备份队列。
  • 如果备份队列中有消息说明这条消息没有处理完,使用RPOPLPUSH/BRPOPLPUSH把这个消息取出来的同时再次放入备份队列(再次放入是防止从备份队列中取出后没处理完丢失),处理消息完成后把备份队列中的消息删除。
  • 如果备份队列中没有消息,从消息队列中使用RPOPLPUSH/BRPOPLPUSH获取消息并把消息放入备份队列,处理完消息后把备份队列中的消息删除。

按照规则形成的处理逻辑,对每一个客户端分别使用自己私有的备份队列从根本上规避了所有客户端公用备份队列中由于“后发先至”等问题导致的复杂逻辑。

img_7dd28003e266ef381f2dc3a17adf6241.png
image.png
  • 2.所有客户端公用一个备份队列,专门客户端做确认检查

  • 所有客户端从队列中取出消息数据并通过RPOPLPUSH/BRPOPLPUSH放入备份队列

  • 所有客户端处理消息完成后再从队列中取出下一个消息重复以上动作

  • 专门客户端处理备份消息队列校对消息是否做完,如果做完则删除,否则重做消息。专门客户端起到一个补漏的作用。

img_d5ef25ea7dd4cbae3affb9d69979e0f1.png
image.png

旋转队列

在使用RPOPLPUSH命令的时候,它的两个参数如果是相同的队列键,客户端就可以一个接一个的获取从队头到队尾的所有元素并且把获取的元素放置到队尾。我们称作队列的旋转。这种情况在多个客户端对同一个队列进行旋转时也有效。甚至有客户端在旋转时增加队列的元素也是可行的。

此种模型对于队列和客户端来说都是易于扩展的。因为即便客户端获取元素后失败旋转到下一个周期后仍能获取到元素因此是安全的。这个模型使得我们可以很容易实现这样一类系统:有多个客户端,需要连续不断地对一些元素进行周期性的遍历轮训处理。一个典型的例子就是数据定时采集系统:采集点保存在队列中,客户端从队列中拿采集点然后采集数据。


img_51be91c432fc568cc94fe0a267b1942e.png
image.png

优先权队列

优先队列和一般队列有所不同,它不完全遵循先进先出的规则,而是根据队列中元素的优先权,优先权最大的先被取出。队列中的元素不仅存储元素本身还存储元素的优先权数据。使用Redis数据结构实现的方式是按照优先权建队列(列表),相同优先权的元素在同一个队列中,客户端在使用BRPOP/RPOP命令使队列中的元素出队的时候参数按照优先权从高到低的顺序进行。

例如,

  • 队列1:包含0~9,10个元素
  • 队列2:包含0~3,4个元素
  • 队列3:包含0~5,6个元素

优先权:队列1>队列2>队列3
客户端使用命令为:RPOP/BRPOP 队列1 队列2 队列3

img_262e5c8869ffbbbba3fb1406a95534c5.png
image.png

个人介绍:

高广超:多年一线互联网研发与架构设计经验,擅长设计与落地高可用、高性能、可扩展的互联网架构。

本文首发在 高广超的简书博客 转载请注明!

img_31e2e3075b097cabfd9b3643cd9abaa5.png
简书博客
img_3b1610566b09db3358ca5dcb3e015a52.png
头条号
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
3月前
|
消息中间件 NoSQL Java
别再用 Redis List 实现消息队列了,Stream 专为队列而生
别再用 Redis List 实现消息队列了,Stream 专为队列而生
66 0
|
2月前
|
存储 NoSQL API
【小小思考】Redis实现去重任务队列
【2月更文挑战第1天】思考一下如何用Redis实现去重的任务队列,主要有List 、List + Set/Hash/Bloom Filter、ZSet、Lua和开源库等方式。
77 1
|
4月前
|
NoSQL Redis
redis队列
redis队列
29 0
|
6月前
|
消息中间件 NoSQL Java
Redis实现延迟队列,我研究了两种方案,发现并不简单
前段时间有个小项目需要使用延迟任务,谈到延迟任务,我脑子第一时间一闪而过的就是使用消息队列来做,比如RabbitMQ的死信队列又或者RocketMQ的延迟队列,但是奈何这是一个小项目,并没有引入MQ,我也不太想因为一个延迟任务就引入MQ,增加系统复杂度,所以这个方案直接就被pass了。
|
8月前
|
NoSQL Go Redis
Redis与异步队列
使用Redis可以很方便地实现异步队列。
62 0
|
8月前
|
NoSQL Go Redis
Asynq: 基于Redis实现的Go生态分布式任务队列和异步处理库
Asynq: 基于Redis实现的Go生态分布式任务队列和异步处理库
284 0
|
9月前
|
消息中间件 监控 NoSQL
RocketMq普通消息,死信队列,消息幂等性(redis)
RocketMQ作为一款纯java、分布式、队列模型的开源消息中间件,支持事务消息、顺序消息、批量消息、定时消息、消息回溯等。
172 0
|
9月前
|
存储 NoSQL Go
基于redis实现延迟队列
基于redis实现延迟队列
|
12月前
|
消息中间件 NoSQL JavaScript
用 Redis 实现延迟队列,我研究了两种方案,发现并不简单 下
用 Redis 实现延迟队列,我研究了两种方案,发现并不简单 下
|
12月前
|
消息中间件 NoSQL JavaScript
用 Redis 实现延迟队列,我研究了两种方案,发现并不简单 上
用 Redis 实现延迟队列,我研究了两种方案,发现并不简单 上