—— Redis/RabbitMQ:解耦与削峰填谷
"""消息队列三大作用:
1. 解耦 —— 生产者与消费者互不依赖
2. 异步 —— 不需要同步等待处理结果
3. 削峰 —— 高峰期消息排队,消费者按能力消费
常见实现:
┌──────────────┬──────────────┬──────────────┐
│ 方案 │ 适合场景 │ 复杂度 │
├──────────────┼──────────────┼──────────────┤
│ Redis List │ 简单任务队列 │ ★☆☆ │
│ Redis PubSub │ 实时通知 │ ★★☆ │
│ RabbitMQ │ 企业级消息 │ ★★★ │
│ Kafka │ 大数据流 │ ★★★★ │
└──────────────┴──────────────┴──────────────┘
"""
"""使用 Redis List 作为消息队列:
- LPUSH:生产者从左端推入消息
- BRPOP:消费者从右端阻塞弹出(阻塞等待,节省 CPU)
FIFO 顺序:LPUSH → ... → BRPOP(先进先出)
"""
import redis
import json
import threading
import time
class SimpleQueue:
"""基于 Redis List 的简单队列"""
def __init__(self, name, redis_url="redis://localhost:6379/0"):
self.name = name
self.redis = redis.from_url(redis_url)
def push(self, message):
"""生产消息"""
data = json.dumps(message, ensure_ascii=False)
self.redis.lpush(self.name, data)
def pop(self, timeout=0):
"""消费消息(阻塞等待)"""
result = self.redis.brpop(self.name, timeout=timeout)
if result:
_, data = result
return json.loads(data)
return None
def size(self):
"""队列长度"""
return self.redis.llen(self.name)
def clear(self):
"""清空队列"""
self.redis.delete(self.name)
# 内存模拟版本(无需 Redis 服务)
class MemoryQueue:
"""纯 Python 内存队列(演示用)"""
def __init__(self, name="default"):
self.name = name
self._queue = []
self._lock = threading.Lock()
self._not_empty = threading.Condition(self._lock)
def push(self, message):
with self._not_empty:
self._queue.append(message)
self._not_empty.notify()
def pop(self, timeout=30):
with self._not_empty:
if not self._queue:
self._not_empty.wait(timeout=timeout)
if self._queue:
return self._queue.pop(0)
return None
def size(self):
return len(self._queue)
# 演示
queue = MemoryQueue("tasks")
# 生产者
for i in range(5):
queue.push({"id": i, "task": f"process_data_{i}"})
print(f"生产了 {queue.size()} 条消息")
# 消费者
while queue.size() > 0:
msg = queue.pop(timeout=1)
print(f" 消费: {msg}")
"""发布/订阅(Pub/Sub)模式:
- 发布者将消息发到频道(channel)
- 所有订阅了该频道的消费者都能收到消息
- 一对多广播,适合实时通知场景
"""
class PubSub:
"""简单的发布/订阅实现"""
def __init__(self):
self._channels = {} # channel -> [callback, ...]
def subscribe(self, channel, callback):
"""订阅频道"""
if channel not in self._channels:
self._channels[channel] = []
self._channels[channel].append(callback)
def unsubscribe(self, channel, callback):
"""取消订阅"""
if channel in self._channels:
self._channels[channel] = [
cb for cb in self._channels[channel] if cb != callback
]
def publish(self, channel, message):
"""发布消息"""
if channel in self._channels:
for callback in self._channels[channel]:
callback(channel, message)
def list_channels(self):
"""列出所有频道"""
return list(self._channels.keys())
# 使用
bus = PubSub()
# 订阅者1:日志处理器
def log_handler(channel, message):
print(f" 📋 [{channel}] 日志: {message}")
# 订阅者2:告警处理器
def alert_handler(channel, message):
print(f" 🚨 [{channel}] 告警: {message}")
bus.subscribe("system", log_handler)
bus.subscribe("system", alert_handler)
bus.subscribe("deploy", log_handler)
# 发布消息
bus.publish("system", "CPU 使用率 92%")
bus.publish("system", "内存不足")
bus.publish("deploy", "v2.0 部署成功")
"""生产级消息队列需要解决的问题:
1. 消息丢失 → 消息确认(ACK)+ 持久化
2. 重复消费 → 幂等性设计
3. 消息积压 → 死信队列 + 告警
4. 顺序消费 → 分区/队列分区
"""
class ReliableQueue:
"""带确认机制的消息队列"""
def __init__(self, name="reliable"):
self._queue = [] # 待消费
self._processing = {} # 处理中(id → message)
self._dead_letter = [] # 死信
self._counter = 0
self._lock = threading.Lock()
def produce(self, message):
"""生产消息"""
with self._lock:
self._counter += 1
msg = {"id": self._counter, "data": message, "retries": 0}
self._queue.append(msg)
return msg["id"]
def consume(self):
"""消费消息(取出并标记为处理中)"""
with self._lock:
if self._queue:
msg = self._queue.pop(0)
self._processing[msg["id"]] = msg
return msg
return None
def ack(self, msg_id):
"""确认消息处理成功"""
with self._lock:
self._processing.pop(msg_id, None)
def nack(self, msg_id, max_retries=3):
"""处理失败,重新入队或进死信"""
with self._lock:
msg = self._processing.pop(msg_id, None)
if msg:
msg["retries"] += 1
if msg["retries"] <= max_retries:
self._queue.append(msg)
else:
self._dead_letter.append(msg)
def stats(self):
return {
"pending": len(self._queue),
"processing": len(self._processing),
"dead_letter": len(self._dead_letter),
}
# 使用
rq = ReliableQueue()
msg_id = rq.produce({"action": "send_email", "to": "user@example.com"})
msg = rq.consume()
rq.ack(msg["id"])
print(f"队列状态: {rq.stats()}")
#!/usr/bin/env python3
"""第31课 消息队列验证"""
import threading
def test_memory_queue():
class MemoryQueue:
def __init__(self):
self._queue = []
self._lock = threading.Lock()
def push(self, msg):
with self._lock:
self._queue.append(msg)
def pop(self):
with self._lock:
return self._queue.pop(0) if self._queue else None
def size(self):
return len(self._queue)
q = MemoryQueue()
q.push("msg1")
q.push("msg2")
q.push("msg3")
assert q.size() == 3
assert q.pop() == "msg1"
assert q.pop() == "msg2"
assert q.size() == 1
print("✅ 内存队列测试通过")
def test_pubsub():
class PubSub:
def __init__(self):
self._channels = {}
def subscribe(self, ch, cb):
self._channels.setdefault(ch, []).append(cb)
def publish(self, ch, msg):
for cb in self._channels.get(ch, []):
cb(ch, msg)
received = []
bus = PubSub()
bus.subscribe("test", lambda ch, msg: received.append(msg))
bus.publish("test", "hello")
bus.publish("test", "world")
assert received == ["hello", "world"]
print("✅ 发布/订阅测试通过")
def test_reliable_queue():
class ReliableQueue:
def __init__(self):
self._queue = []
self._processing = {}
self._dead = []
self._counter = 0
def produce(self, data):
self._counter += 1
msg = {"id": self._counter, "data": data, "retries": 0}
self._queue.append(msg)
return msg["id"]
def consume(self):
if self._queue:
msg = self._queue.pop(0)
self._processing[msg["id"]] = msg
return msg
def ack(self, mid):
self._processing.pop(mid, None)
def nack(self, mid, max_retries=2):
msg = self._processing.pop(mid, None)
if msg:
msg["retries"] += 1
if msg["retries"] <= max_retries:
self._queue.append(msg)
else:
self._dead.append(msg)
rq = ReliableQueue()
mid = rq.produce("task1")
msg = rq.consume()
rq.ack(msg["id"])
assert rq._queue == []
assert rq._processing == {}
mid2 = rq.produce("task2")
msg2 = rq.consume()
rq.nack(msg2["id"], max_retries=1)
msg2 = rq.consume()
rq.nack(msg2["id"], max_retries=1) # 超过重试次数
assert len(rq._dead) == 1
print("✅ 可靠队列测试通过")
if __name__ == "__main__":
test_memory_queue()
test_pubsub()
test_reliable_queue()
print("\n🎉 第31课全部验证通过!")
"""RabbitMQ 核心概念:
- Producer(生产者):发送消息的程序
- Queue(队列):存储消息的缓冲区
- Consumer(消费者):接收消息的程序
- Exchange(交换机):接收生产者消息,推送到队列
Exchange 类型:
1. direct -> 精确匹配 routing key
2. topic -> 通配符匹配 (log.* 匹配 log.info, log.error)
3. fanout -> 广播到所有绑定队列
4. headers -> 基于消息头匹配
安装:apt install rabbitmq-server
Docker:docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:management
Python:pip install pika
"""
import heapq
import threading
import time
class PriorityMessageQueue:
"""优先级消息队列"""
def __init__(self):
self._heap = [] # (-priority, id, message) 负号让高优先级先出
self._counter = 0
self._lock = threading.Lock()
def push(self, message, priority=0):
with self._lock:
self._counter += 1
heapq.heappush(self._heap, (-priority, self._counter, message))
def pop(self):
with self._lock:
if self._heap:
_, _, message = heapq.heappop(self._heap)
return message
return None
# 演示
pq = PriorityMessageQueue()
pq.push("low task", priority=1)
pq.push("high task", priority=10)
pq.push("medium task", priority=5)
print(pq.pop()) # high task
print(pq.pop()) # medium task
print(pq.pop()) # low task
| 特性 | Redis List | RabbitMQ | Kafka |
|---|---|---|---|
| 吞吐量 | 10万/s | 万/s | 百万/s |
| 延迟 | 亚毫秒 | 毫秒 | 毫秒 |
| 持久化 | 可选 | 支持 | 强持久化 |
| 顺序保证 | 单队列有序 | 单队列有序 | 分区有序 |
| 重复消费 | 不支持 | 支持 | 支持 |
| 运维复杂度 | 低 | 中 | 高 |
| 适用场景 | 简单任务 | 企业消息 | 大数据流 |
"""幂等性:同一条消息消费多次,结果和消费一次相同
常见方案:
1. 唯一消息 ID + 去重表
2. 业务层幂等(如 UPDATE SET balance = balance + 100 WHERE id = ? 已幂等)
3. 乐观锁(版本号控制)
"""
import hashlib
import threading
class IdempotentConsumer:
"""幂等消费者"""
def __init__(self):
self._processed = {} # msg_id -> result
self._lock = threading.Lock()
def consume(self, message):
"""消费消息(幂等)"""
msg_id = message.get("id")
if not msg_id:
msg_id = hashlib.md5(str(message).encode()).hexdigest()
with self._lock:
if msg_id in self._processed:
print(f" ⏭️ 跳过重复: {msg_id[:8]}")
return self._processed[msg_id]
# 实际处理
result = self._process(message)
with self._lock:
self._processed[msg_id] = result
return result
def _process(self, message):
print(f" ✅ 处理: {message}")
return f"processed_{message.get('id', 'unknown')}"
# 使用
consumer = IdempotentConsumer()
msg = {"id": "msg_001", "action": "send_email", "to": "user@example.com"}
consumer.consume(msg) # 处理
consumer.consume(msg) # 跳过重复