主题
三、优先级队列实战
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) 参数说明
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
key | str | 是 | 目标列表名 |
*values | str 或 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 调用只返回一个元素。想取完所有积压的,需要循环调用。