前言
延时队列是一种特殊类型的队列,其中的消息或任务会被延迟处理,直到特定的时间到达。
在Redis
中,可以使用ZSET
(有序集合)实现延时队列。
关于ZSET
的内容,可以看这里 ——> Redis 中的 ZSET
一、举例
比方说,现在有一个业务场景:用户创建了一个表单,该表单将于一分钟后自动提交,如何用ZSET
实现呢?
下面会用python
代码实现一个这样的功能。
二、步骤
1、连接到 redis
首先获取一个 redis 实例对象。
定义一个 ZSET :用于实现延时队列。
定义一个 HASH :用于存储表单信息。
# redis 相关配置
REDIS_HOST = 'localhost'
REDIS_PORT = 6379
REDIS_DB = 0
REDIS_PASSWORD = 'localhost'
# 获得一个 redis 实例对象
r = redis.client.Redis(host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB, charset="utf-8", decode_responses=True, password=REDIS_PASSWORD)
# 定义一个 HASH 和 一个 ZSET 实现功能
REDIS_JOB_QUEUE = 'JOB:DELAY-QUEUE'
REDIS_JOB_HASH = 'JOB:HASH'
2、推入延时队列逻辑
写一个推入延时队列的逻辑,当创建出表单时,将表单 id 和 创建内容放到 HASH 中;再将 表单 id 和 一分钟后的时间戳放入 ZSET 中,实现延迟队列。
def push_to_delayed_queue(formId, create_msg):
key = formId
value = {'formId': formId, 'data': created_record}
# 序列化value为JSON字符串
value_json = json.dumps(value)
# 将 formId 作为 key,将 formId, data 放在字典中作为 value,存入一个 hash 中
r.hset(REDIS_JOB_HASH, mapping={key: value_json})
# 将 formId 作为 member,一分钟以后的时间戳作为 score,存入一个 zset 中
score = int(time.time()) + 60
r.zadd(REDIS_JOB_QUEUE, {key: score})
3、消费延时队列逻辑
用一个 while True 去循环检查延时队列,当发现有数据的时间戳小于当前时间戳时,将该数据取出,进行消费。同时要注意并发情况,最终只能有一个进程抢到消息。
并且防止循环中断,用 try 去捕获可能的异常。
# 返回一个 redis 示例对象
def issue_redis_client():
return redis.client.Redis(host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB, charset="utf-8", decode_responses=True, password=REDIS_PASSWORD)
def loop():
logging.info("start delay_queue loop")
rc = issue_redis_client()
while True:
try:
# 取已达到执行时间的任务列表,但是每次列表里只取一条数据
values = rc.zrangebyscore(REDIS_JOB_QUEUE, 0, time.time(), start=0, num=1)
if not values:
# 如果没有到达执行时间的任务就休息 1 秒
time.sleep(1)
continue
# 获取第一条记录,这是一个二进制字符串
value = values[0]
# 试图从 zset 中移除这条记录
success = rc.zrem(REDIS_JOB_QUEUE, value)
# 因为有多进程并发的可能,最终只会有⼀个进程可以抢到消息,如果移除成功,则该记录没有被其他进程处理
if success:
# 处理消息
handle_msg(value)
except Exception as e:
print(f"An error occurred: {e}")
# 这里根据需要实现错误日志记录和处理逻辑
time.sleep(1)
4、处理消息逻辑
处理消息的逻辑。
根据 表格 id 从 HASH 将创建信息提出来,然后提交表单。
def handle_msg(msg):
key = msg
value_json = r.hget(REDIS_JOB_HASH, key)
r.hdel(REDIS_JOB_HASH, key)
if value_json:
# 反序列化
value = json.loads(value_json)
# todo: 根据表单内容提交的方法,此处省略