Skip to content

四、阻塞机制详解 ​

4.1 阻塞 vs 轮询 ​

轮询模式(RPOP):
  客户端: 有没有?Redis: 没有。
  客户端: 有没有?Redis: 没有。
  客户端: 有没有?Redis: 没有。
  客户端: 有没有?Redis: 有!(第 0.3 秒)
  客户端: sleep(0.1)...(继续下一轮)
  → CPU 一直在跑,延迟 0.1 秒

阻塞模式(BRPOP):
  客户端: 有没有?我等 5 秒。Redis: ...
  (客户端挂起,不占 CPU)
  (3 秒后有人 LPUSH)
  Redis: 有!
  客户端: 立即处理
  → CPU 零占用,延迟 < 1 毫秒

4.2 Redis 服务端如何实现阻塞 ​

Redis 为每个阻塞命令维护一个等待链表:

key: task:queue
waiting_clients: [Client-A, Client-B, Client-C]
                   (按 BRPOP 调用时间排队)

当 LPUSH task:queue 6 执行时:

1. Redis 执行 LPUSH,列表变为 [6]
2. Redis 检查 waiting_clients,发现 Client-A 在等
3. Redis 直接把 "6" 发给 Client-A(不从列表弹出)
4. 从列表删除 "6",列表变为 []
5. 从 waiting_clients 移除 Client-A

关键:数据不经过列表中转,直接从 LPUSH 方传递给 BRPOP 方,O(1) 时间完成。

4.3 超时行为 ​

python
# timeout=5:最多等 5 秒
result = redis_client.brpop("task:queue", timeout=5)
# 5 秒内没来新元素 → 返回 None

# timeout=0:永久阻塞
result = redis_client.brpop("task:queue", timeout=0)
# 永远等到有元素才返回(不推荐,无法响应 Ctrl+C)

本项目为什么用 timeout=5:

python
while True:
    result = redis_client.brpop("task:queue", timeout=5)
    if result:
        run_task(...)
    # ← 每 5 秒回到循环顶部,检查 KeyboardInterrupt
  • timeout=0 永久阻塞 → Ctrl+C 无法中断
  • timeout=5 → 每 5 秒回到 while True 顶部,能响应 Ctrl+C
  • 5 秒是经验值,太短浪费 CPU(频繁循环),太长退出延迟

4.4 多 Worker 竞争 ​

多个 Worker 同时 BRPOP 同一个队列时,Redis 保证只有一个 Worker 能拿到元素:

Worker-1: BRPOP task:queue 5  ──┐
Worker-2: BRPOP task:queue 5  ──┤  三个 Worker 都在等
Worker-3: BRPOP task:queue 5  ──┘

API: LPUSH task:queue 6

Redis: 只有 Worker-1 拿到 "6"
       Worker-2、Worker-3 继续等待

等待顺序:先调 BRPOP 的 Worker 优先拿到(FIFO 等待链表)。