3. 基于 List 的简单消息队列:LPUSH + BRPOP 实现生产者-消费者模式
Redis 的 List 数据结构是构建消息队列最直接、最轻量的方式。其核心操作 LPUSH(从左侧推入)与 BRPOP(从右侧阻塞弹出)天然适配生产者-消费者模型。本节将深入剖析这一经典模式的实现原理、代码示例及适用边界。
3.1 核心原理与数据流
生产者将消息序列化为字符串后,通过 LPUSH 写入 List 头部;消费者通过 BRPOP 从 List 尾部阻塞式取出消息。当队列为空时,BRPOP 会挂起连接,直到新消息到达或超时,从而避免空轮询带来的 CPU 浪费。
关键特性: List 本身是一个双向链表,
LPUSH 与 BRPOP 的时间复杂度均为 O(1),在消息量较大时仍能保持稳定的入队与出队性能。
3.2 代码示例:Python 实现
以下示例使用 redis-py 客户端,展示一个简单的任务队列。生产者推送任务 ID,消费者阻塞获取并处理。
生产者代码
import redis
import json
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
def produce_task(task_id, payload):
message = json.dumps({"id": task_id, "data": payload})
r.lpush("task_queue", message)
print(f"Produced: {task_id}")
# 模拟生产 3 个任务
for i in range(1, 4):
produce_task(f"task_{i}", f"payload_{i}")
消费者代码
import redis
import json
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
def consume_task(timeout=5):
# BRPOP 返回 (key, value) 元组,超时返回 None
result = r.brpop("task_queue", timeout=timeout)
if result:
_, message = result
task = json.loads(message)
print(f"Consumed: {task['id']} - {task['data']}")
# 此处执行实际业务逻辑
return task
else:
print("No task available within timeout")
return None
# 持续消费
while True:
consume_task(timeout=3)
注意事项: 生产环境中建议将消息体序列化为 JSON 或 MessagePack,并显式设置
decode_responses=True 以避免字节串与字符串的混淆。消费者应捕获异常,防止单条消息处理失败导致进程退出。
3.3 优缺点分析
基于 List 的消息队列虽然简单,但在功能完备性与可靠性上存在明显短板。下表从多个维度进行对比:
| 维度 | 评价 | 说明 |
|---|---|---|
| 实现复杂度 | 极低 | 仅需两个 Redis 命令,无需中间件 |
| 吞吐性能 | 高 | O(1) 入队出队,单节点可达 10w+ QPS |
| 消息可靠性 | 低 | 消费者崩溃导致消息丢失,无 ACK 机制 |
| 消息去重 | 不支持 | 原生 List 无去重能力,需业务层配合 Set |
| 延迟消息 | 不支持 | 无法直接实现定时/延迟投递 |
| 消费确认 | 无 | BRPOP 取出即删除,无法重新消费 |
| 多消费者负载 | 互斥 | 每条消息只能被一个消费者消费(天然负载均衡) |
3.4 适用场景与避坑指南
基于 List 的队列最适合对消息可靠性要求不高、追求极致简单与吞吐的场景。以下是典型用例与常见陷阱:
- 适用场景:
- 异步日志收集(允许少量丢失)
- 非关键性任务分发(如邮件发送、图片缩略图生成)
- 内部统计数据的暂存与消费
- 常见陷阱:
- 消息丢失: 消费者拉取后未处理即崩溃,消息永久丢失。解决方案:引入备份 List 或使用
RPOPLPUSH实现可靠队列。 - 队列积压: 消费者速度跟不上生产者时,List 无限增长,可能导致 Redis 内存溢出。建议监控
LLEN并设置告警。 - 阻塞连接管理: 大量消费者同时 BRPOP 会占用 Redis 连接池资源,需合理配置连接超时与重连策略。
- 消息丢失: 消费者拉取后未处理即崩溃,消息永久丢失。解决方案:引入备份 List 或使用
生产环境警示: 切勿将 List 队列用于金融交易、订单状态流转等需要严格 ACK 与重试的场景。若必须使用 Redis,请考虑 Redis Stream(后续章节详解),它提供了消费者组、消息持久化与 ACK 机制。
3.5 性能调优要点
虽然 List 队列本身简单,但在高并发下仍有一些调优空间:
- 批量生产: 使用 Pipeline 或
LPUSH变参一次推入多条消息,减少网络往返。 - 合理设置超时: BRPOP 的 timeout 不宜过小(避免频繁空返回),也不宜过大(影响消费者扩缩容响应),建议 3~5 秒。
- 内存控制: 对 List 设置
MAXMEMORY与淘汰策略(如allkeys-lru),防止队列积压撑爆内存。 - 客户端连接池: 为消费者配置独立的连接池,避免与业务操作争抢连接。