前言
现在市面上有很多好用的开源的消息队列中间件,例如RocketMq、Kafka以及Rabbitmq等等,它们都可以在应用程序之间增加异步消息传递功能。但是要使用这些中间件都包含很多组件,在你的消费队列很少的情况下就显得不是很好。
Redis做队列就可以处理这种甚至只有一组消费者的消费队列。当然要注意的是,Redis的消息队列不是专业的消息队列,没有那么多的高级特性,没有ack保证,如果对消息的可靠性有着极高的要求,那么它就不适合使用。
1、异步消息队列
Redis的list(列表)数据结构是常用来作为异步的消息队列使用,使用rpush和lpush操作入队,用lpop和rpop命令操作出队,如图所示:

它可以支持多个生产者和多个消费者并发进出消息,每个消费者拿到的消息都是不同的元素,如果所示:

下面是rpush和lpop结合使用的例子。lpush和rpop同理。

2、队列之阻塞读
消费端通过队列的pop操作来获取消息,然后进行相应的业务处理。然后再循环的获取消息。这就是作为队列消费者的客户端的生命周期。
但是如果队列空了的话,消费端就会陷入pop的死循环,这样既会增加机器的CPU消耗,Redis的QPS也会同步提升,这样显然是不合理的。
最简单的办法就是让消费线程sleep一会,这样是可以解决上述的问题,但是会增加消费端的延迟。Redis为我们提供了相应的阻塞pop操作,对应的命令是blpop/lrpop。这两个命令的前缀b代表的是blocking,也就是阻塞。阻塞读在队列没有数据的时候,会立即进入休眠状态,一但数据到来,则立即唤醒。消息的延迟几乎为0。这样就可以完美解决队列为空的场景。
但是这种使用还会存在一个问题,就是空闲连接的问题。如果线程一直阻塞在哪里,Redis的客户端连接就成了闲置连接,当闲置时间过长的时候,服务器会主动断开连接,减少资源占用。这是blpop/brpop会抛出异常,所以要特别注意。
3、延时队列的实现
延时队列可以通过Redis的zset(有序集合)来实现。我们把具体的消息数据序列化成字符串作为一个value,将消息到期处理时间的时间戳作为score,然后启动线程去轮训zset获取到期的任务进行处理。
# coding:utf-8
import uuid
import json
import time
import redis
delay_queue_key = 'delay-queue'
def delay(client, msg):
msg['id'] = str(uuid.uuid4())
value = json.dumps(msg)
retry_ts = time.time() + 5
#新版redis模块传参是一个dict,key是要存储的val,value为score
client.zadd(delay_queue_key, {value: retry_ts})
def loop(client):
while True:
#每次拉去一条
values = client.zrangebyscore(delay_queue_key, 0, time.time(), start=0, num=1)
if not values:
#m没有数据 sleep
time.sleep(1)
continue
value = values[0]
#如果多线程消费 存在并发 只有一个线程可以抢到该消息
success = client.zrem(delay_queue_key, value)
if success:
msg = json.loads(value)
print("consume msg : ", msg)
#连接redis
pool = redis.ConnectionPool(host='127.0.0.1', port=6379)
client = redis.Redis(connection_pool=pool)
#清空数据库
client.flushall()
msg_body = {'name':'llq', 'age':36}
delay(client, msg_body)
loop(client)
本文介绍了使用Redis作为简单消息队列的场景,强调在低需求时的适用性。讨论了Redis列表作为异步消息队列的操作,并提出阻塞读取以避免空循环导致的资源浪费,利用blpop/lrpop命令实现阻塞读。接着,文章阐述了如何利用Redis的有序集合(zset)来创建延时队列,通过设置score为到期时间戳来管理到期任务。
662

被折叠的 条评论
为什么被折叠?



