限流
限制请求流量,防系统被冲垮
类比:景区门口限流,一次只放一批人进。
- 作用:挡住突发海量请求,保护服务不卡死
- 常见方式:按 QPS、并发数、IP 拦截多余请求
限流简单实现
- 基于 SlowAPI 实现接口限流,
from slowapi import Limiter
limiter = Limiter(key_func=get_remote_address)
@app.post("/workflow/start")
@limiter.limit("10/minute") # 每分钟最多10次
async def start_workflow():
pass
熔断
下游崩了就直接断路,不再调用
类比:家里跳闸,短路直接断电,避免起火。
-
- 触发:下游频繁超时、报错率飙升
-
- 行为:直接拒绝调用,快速返回兜底结果
-
- 恢复:隔段时间试探,好转再慢慢恢复调用
熔断简单实现
class ContentService:
熔断器装饰器:连续失败5次触发熔断,熔断冷却时长30秒
@circuit_breaker(fail_max=5, reset_timeout=30)
async def _call_llm(self, prompt: str):
"""真正的LLM调用,受熔断器保护"""
发起异步大模型请求,属于易出错的第三方调用
return await self.llm.ainvoke(prompt)
async def generate_article(self, topic: str):
"""对外暴露的方法,永远不会失败,对外统一稳定入口"""
try:
优先执行正常业务逻辑,调用大模型生成内容
return await self._call_llm(topic)
except CircuitBreakerOpen:
捕获熔断异常,此时不再请求故障服务
logger.warning(f"LLM熔断中,返回降级结果")
切换兜底降级策略,保障接口正常响应
return self._get_fallback(topic)
def _get_fallback(self, topic: str):
"""降级兜底方案,服务异常时返回友好占位数据"""
return {
"title": f"关于{topic}的精彩内容",
"content": "内容生成服务正在维护中,请稍后再试。",
"is_degraded": True # 标记当前为降级返回结果
}
熔断流程
用户请求 generate_article()
↓
尝试调用 _call_llm() ← 被 @circuit_breaker 保护
↓
正常 → 返回文章
失败 → 累计失败次数
↓
失败达到 5 次 → 熔断器【打开】
↓
接下来 30 秒内,直接抛出 CircuitBreakerOpen
↓
代码捕获异常 → 走降级 _get_fallback()
↓
返回友好提示,不崩溃、不雪崩
错误边界和降级策略
请求1 ──▶ 尝试主服务 ──▶ 失败(等了10秒) ──▶ 走备用 ──▶ 失败计数=1
请求2 ──▶ 尝试主服务 ──▶ 失败(等了10秒) ──▶ 走备用 ──▶ 失败计数=2
请求3 ──▶ 尝试主服务 ──▶ 失败(等了10秒) ──▶ 走备用 ──▶ 失败计数=3 ──▶ 触发熔断!
────────────────────────────────────────────────────────────────
请求4 ──▶ 熔断器拦截 ──▶ 直接走备用(0秒)
请求5 ──▶ 熔断器拦截 ──▶ 直接走备用(0秒)
请求6 ──▶ 熔断器拦截 ──▶ 直接走备用(0秒)
...
30秒内都不尝试主服务
────────────────────────────────────────────────────────────────
30秒后 ──▶ 半开状态 ──▶ 放一个请求试试 ──▶ 成功了!──▶ 恢复正常
熔断降级策略和普通降级区别
┌────────────────────────────────────────────────────────────────┐
│ │
│ 【简单降级】 │
│ │
│ 请求 ──▶ 主服务(每次都试) ──▶ 失败 ──▶ 备用服务 │
│ │ │
│ │ 超时10秒 │
│ ▼ │
│ 用户等了10秒才拿到结果 │
│ │
│ 问题:主服务挂了,每个用户都要等10秒 │
│ │
├────────────────────────────────────────────────────────────────┤
│ │
│ 【熔断降级】 │
│ │
│ 请求 ──▶ 熔断器检查 ──▶ 开着 ──▶ 直接备用服务 │
│ │ │
│ │ 0秒 │
│ ▼ │
│ 用户秒拿到结果 │
│ │
│ 好处:快速失败,不浪费时间 │
│ │
└────────────────────────────────────────────────────────────────┘
兜底方案简单实现
async def generate_content_with_fallback(topic: str):
try:
return await llm_primary.invoke(topic) # 主模型
except Exception:
logger.warning("Primary LLM failed, fallback to secondary")
return await llm_secondary.invoke(topic) # 备用模型
except Exception:
return get_cached_template(topic) # 兜底模板
熔断和限流的区别
用户请求 generate_article()
↓
尝试调用 _call_llm() ← 被 @circuit_breaker 保护
↓
正常 → 返回文章
失败 → 累计失败次数
↓
失败达到 5 次 → 熔断器【打开】
↓
接下来 30 秒内,直接抛出 CircuitBreakerOpen
↓
代码捕获异常 → 走降级 _get_fallback()
↓
返回友好提示,不崩溃、不雪崩
消息队列限流
消息队列 MQ,是限流的常用解决方案,同时还能削峰、解耦、异步缓冲
核心作用
-
削峰限流
突发海量请求先存入队列,消费端按固定速率慢慢处理,不会瞬间压垮业务 / LLM 服务,把瞬时高峰流量拉平。
-
和原有熔断降级区别
-
熔断:故障止损,下游坏了直接断路兜底
-
限流 + MQ:流量缓冲,请求太多先排队排队消化
消息队列设计模式 : 生产者消费者模型
MQ 到底怎么实现限流
-
来多少请求,都先丢队列
-
队列有最大容量(20),满了直接拒绝 → 限流
-
后台只有一个消费者 ,2 秒处理一个
-
不管你并发 10、100、1000,处理速度永远可控
-
LLM 永远不会被瞬间流量冲垮
这就是 MQ 削峰限流 的本质!
MQ简易代码实现
import asyncio
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# ==================== MQ 核心 ====================
# 消息队列:用来缓冲、排队、限流
class ContentMQ:
def __init__(self):
# 异步队列 = 简易 MQ
# maxsize=20 → 最多排队20个请求 → 限流
self.queue = asyncio.Queue(maxsize=20)
# 生产者:接收请求 → 扔进队列
async def produce(self, topic: str, request_id: str):
try:
await self.queue.put((topic, request_id))
logger.info(f"[{request_id}] 已加入队列 | 当前排队:{self.queue.qsize()}")
except asyncio.QueueFull:
# 队列满了 = 限流触发
logger.warning(f"[{request_id}] 队列已满,触发限流!")
raise Exception("系统繁忙,请稍后再试")
# 消费者:**匀速、慢速** 从队列取任务处理
# 这就是限流的关键!不管多少请求,都慢慢处理
async def consume(self):
while True:
topic, request_id = await self.queue.get()
logger.info(f"[{request_id}] 开始生成内容:{topic}")
# 👇 你的 LLM 调用(慢任务)
try:
await asyncio.sleep(2) # 模拟 LLM 耗时
logger.info(f"[{request_id}] 生成成功!")
except Exception as e:
logger.error(f"[{request_id}] LLM 失败:{e}")
finally:
self.queue.task_done()
# ==================== 你的业务 ====================
class ContentService:
def __init__(self):
self.mq = ContentMQ() # 服务自带 MQ
# 对外接口:只入队,不处理 → 永远不卡
async def generate_article(self, topic: str, request_id: str):
await self.mq.produce(topic, request_id)
return "任务已排队,正在生成中..."
# 启动后台消费线程
async def start_consumer(self):
asyncio.create_task(self.mq.consume())
# ==================== 测试 ====================
async def main():
service = ContentService()
await service.start_consumer()
# 模拟 30 个并发请求
tasks = [service.generate_article(f"AI科普{i}", f"请求{i}") for i in range(30)]
await asyncio.gather(*tasks, return_exceptions=True)
asyncio.run(main())