Skip to content

三、优先级队列实战 ​

3.1 业务场景 ​

AI 漫剧项目需要支持 VIP 用户优先:

VIP 用户提交     ──► task:queue:vip    (优先消费)
普通用户提交     ──► task:queue:normal  (有空再消费)

3.2 API 端:推入不同队列 ​

python
# creative.py — 提交接口
if user.is_vip:
    redis_client.lpush("task:queue:vip", task.id)
else:
    redis_client.lpush("task:queue:normal", task.id)

lpush(key, *values) 参数说明 ​

参数类型必填说明
keystr是目标列表名
*valuesstr 或 int是一个或多个要推入的值。Redis 内部统一转为字符串存储。多个值时按参数顺序逐个推入左端

返回值:int(推入后的列表长度)

常见误解:lpush 的返回值不是你推入的值(如 101),而是推入后列表的元素个数。因为你已经知道推入了什么(就是你传的参数),Redis 选择返回更有用的信息——列表当前长度。

你传的值列表状态返回值含义
101[101]1列表现在有 1 个元素
102[102, 101]2列表现在有 2 个元素
103[103, 102, 101]3列表现在有 3 个元素

如果想要推入的值,直接用你传给 lpush 的变量即可,不需要从返回值取。

推入方向:左端(列表头部)

单个值推入:

python
result = redis_client.lpush("task:queue:vip", 101)
# 列表内容: [101]          ← 101 被存入列表
# result = 1                ← 返回的是列表长度,不是 101

多个值推入:

python
result = redis_client.lpush("task:queue:vip", 101, 102, 103)
# 推入过程:
#   推入 101 → [101]
#   推入 102 → [102, 101]
#   推入 103 → [103, 102, 101]
# 最终列表: [103, 102, 101]   ← 存入的值
# result = 3                   ← 返回的是列表长度,不是 101/102/103

注意:参数顺序和最终列表顺序是反的。先传的 101 在列表最右端,后传的 103 在最左端。

与 brpop 返回值的对比:

python
# lpush 返回长度
length = redis_client.lpush("task:queue", 101)
# length = 1                    ← 列表长度

# brpop 返回弹出的值
result = redis_client.brpop("task:queue", timeout=5)
# result = ("task:queue", "101")  ← 队列名 + 弹出的元素值
命令返回值含义示例
lpush推入后的列表长度1
rpush推入后的列表长度1
brpop弹出的元素值 + 队列名("task:queue", "101")
rpop弹出的元素值"101"
llen列表长度1

3.3 Worker 端:优先消费 ​

python
# orchestrator.py — process_queue
def process_queue() -> None:
    logger.info("编排 Worker 启动,等待任务...")
    while True:
        try:
            # 先查 VIP 队列,再查普通队列
            result = redis_client.brpop(
                ["task:queue:vip", "task:queue:normal"],
                timeout=5
            )
            if result:
                queue_name, task_id = result
                task_id = int(task_id)
                logger.info("从队列 %s 取到任务 %s", queue_name, task_id)
                run_task(task_id)
        except KeyboardInterrupt:
            logger.info("Worker 收到退出信号,停止")
            break
        except Exception as e:
            logger.exception("Worker 处理异常: %s", e)
            time.sleep(1)

3.4 BRPOP 多 key 的行为规则 ​

规则 1:左优先

BRPOP 按 key 列表的声明顺序从左到右检查。只要左边的队列有元素,就不会弹右边的。

VIP 队列: [101, 102]          ← 有 2 个 VIP 任务
普通队列: [201, 202, 203]      ← 有 3 个普通任务

第1次 BRPOP → 取 102(VIP)
第2次 BRPOP → 取 101(VIP)
第3次 BRPOP → 取 201(普通)     ← VIP 空了才取普通
第4次 BRPOP → 取 202(普通)
第5次 BRPOP → 取 203(普通)

规则 2:阻塞时哪个先来取哪个

当所有队列都为空时,BRPOP 阻塞等待。任意队列推入新元素时立即返回:

时间 0s: BRPOP 开始阻塞(VIP 空,普通空)
时间 3s: 有人 LPUSH task:queue:normal 42
时间 3s: BRPOP 立即返回 ("task:queue:normal", "42")

但如果两个队列几乎同时有元素,仍然左优先:

时间 0s: BRPOP 开始阻塞
时间 3.001s: LPUSH task:queue:normal 42
时间 3.002s: LPUSH task:queue:vip 101
时间 3.002s: BRPOP 返回 ("task:queue:vip", "101")  ← VIP 优先

规则 3:一次只弹一个元素

即使多个队列都有元素,一次 BRPOP 调用只返回一个元素。想取完所有积压的,需要循环调用。