阶段2(上):后端专题 Go/Gin/PostgreSQL/MySQL/Redis/MQ/gRPC 的课程、企业场景、面试题和可运行示例

This commit is contained in:
Shikui Wang committed 2026-10-23 15:24:57 +08:00
1 parent 0bae9916a9
commit 5eb67c5d08
65 files changed
+6457 -77

No files matched your search

@@ -0,0 +1,84 @@
# requires: aio_pika
"""RabbitMQ 延迟取消订单:消息先进带 TTL 的等待队列,过期后经死信交换机转到处理队列。
运行:AMQP_URL=amqp://guest:guest@localhost/ python delay_cancel.py
没有设置 AMQP_URL 时打印提示并正常退出。会创建并删除名字带随机后缀的交换机和队列。
"""
import asyncio
import json
import os
import sys
import time
import uuid
import aio_pika
DELAY_MS = 1000 # 演示用 1 秒;真实业务常见是 15~30 分钟
class OrderStore:
"""模拟数据库里的订单状态(真实项目里用条件 UPDATE 保证幂等)。"""
def __init__(self) -> None:
self.status: dict[str, str] = {}
def cancel_if_unpaid(self, order_id: str) -> bool:
# 等价于 UPDATE orders SET status='cancelled' WHERE id=? AND status='pending'
if self.status.get(order_id) == "pending":
self.status[order_id] = "cancelled"
return True
return False
async def main() -> int:
url = os.environ.get("AMQP_URL")
if not url:
print("未设置 AMQP_URL,跳过(示例:amqp://guest:guest@localhost/)")
return 0
suffix = uuid.uuid4().hex[:8]
conn = await aio_pika.connect_robust(url)
async with conn:
ch = await conn.channel()
await ch.set_qos(prefetch_count=10)
dlx = await ch.declare_exchange(f"order.dlx.{suffix}", aio_pika.ExchangeType.DIRECT, auto_delete=True)
# 处理队列:真正执行“超时取消”的消费者监听这里
work_q = await ch.declare_queue(f"order.cancel.{suffix}", durable=True, auto_delete=True)
await work_q.bind(dlx, routing_key="cancel")
# 等待队列:没有消费者,消息在这里过期后转到死信交换机
wait_q = await ch.declare_queue(f"order.wait.{suffix}", durable=True, auto_delete=True, arguments={
"x-message-ttl": DELAY_MS,
"x-dead-letter-exchange": dlx.name,
"x-dead-letter-routing-key": "cancel",
})
store = OrderStore()
store.status = {"o1": "pending", "o2": "pending"}
start = time.monotonic()
for oid in ("o1", "o2", "o1"): # o1 故意发两次,模拟重复消息
await ch.default_exchange.publish(
aio_pika.Message(json.dumps({"order_id": oid}).encode(),
delivery_mode=aio_pika.DeliveryMode.PERSISTENT, message_id=f"cancel-{oid}"),
routing_key=wait_q.name)
store.status["o2"] = "paid" # o2 在超时前支付了
results: list[tuple[str, bool]] = []
async with work_q.iterator() as it:
async for msg in it:
async with msg.process(): # 正常结束自动 ack,抛异常则 nack
oid = json.loads(msg.body)["order_id"]
results.append((oid, store.cancel_if_unpaid(oid)))
if len(results) == 3:
break
elapsed = time.monotonic() - start
print("处理结果:", results, f"耗时 {elapsed:.1f}s")
assert elapsed >= DELAY_MS / 1000 * 0.9, "消息应该在延迟之后才被处理"
assert store.status == {"o1": "cancelled", "o2": "paid"}, store.status
assert sorted(results) == [("o1", False), ("o1", True), ("o2", False)] # 重复消息不会重复取消
await wait_q.delete(if_unused=False, if_empty=False)
await work_q.delete(if_unused=False, if_empty=False)
print("延迟取消正确:未支付的被取消,已支付的不受影响,重复消息幂等")
return 0
if __name__ == "__main__":
sys.exit(asyncio.run(main()))