先说几个核心判断:推理系统架构的复杂度,很大程度上并不在于单点性能优化,而在于如何把网关、负载均衡、高可用、弹性伸缩和容灾这几个环节串联起来,形成一个真正能扛住生产压力的闭环。下面从几个关键维度拆解一下。
1. 网关设计:请求接入与流量控制
网关是推理服务的门面,负责鉴权、限流、路由、协议转换。设计要点集中在长连接管理(流式输出)、超时控制和降级策略上。
graph TD
A[推理网关] --> B[鉴权: API Key/Token]
A --> C[限流: QPS/并发/Token配额]
A --> D[路由: 模型/租户/优先级]
A --> E[协议转换: HTTP/SSE/gRPC]
B --> F[多租户隔离]
C --> G[令牌桶+滑动窗口]
D --> H[模型路由表]
E --> I[SSE 流式输出]
classDef default fill:#faf9f5,stroke:#ffffff,color:#000000,stroke-width:0px
# 来源:推理网关实现 / 生产实践 2024
import time
import asyncio
from collections import defaultdict
class InferenceGateway:
"""推理服务网关"""
def __init__(self, rate_limits, model_routes):
self.rate_limiter = TokenBucketRateLimiter(rate_limits)
self.routes = model_routes # {model_name: [backend_urls]}
self.auth = APIKeyAuth()
async def handle_request(self, request):
"""处理推理请求"""
# 1. 鉴权
if not self.auth.validate(request.api_key):
return {'error': 'unauthorized'}, 401
tenant = self.auth.get_tenant(request.api_key)
# 2. 限流 (多维度)
if not self.rate_limiter.allow(tenant, request.model):
return {'error': 'rate limited'}, 429
# 3. 路由
backend = self._select_backend(request.model, tenant)
# 4. 转发 (SSE 流式)
if request.stream:
return await self._stream_forward(backend, request)
else:
return await self._forward(backend, request)
def _select_backend(self, model, tenant):
"""选后端节点"""
backends = self.routes[model]
# 简化: 轮询, 实际用负载+健康检查
return backends[tenant.__hash__() % len(backends)]
async def _stream_forward(self, backend, request):
"""SSE 流式转发"""
async with httpx.AsyncClient() as client:
async with client.stream('POST', backend, json=request.dict()) as resp:
async for chunk in resp.aiter_bytes():
yield chunk
class TokenBucketRateLimiter:
"""令牌桶限流器"""
def __init__(self, limits):
# limits: {tenant_id: {model: {qps: 100, concurrent: 10, tokens_per_min: 10000}}}
self.limits = limits
self.buckets = defaultdict(lambda: defaultdict(dict))
self.concurrent = defaultdict(int)
def allow(self, tenant, model):
limit = self.limits.get(tenant, {}).get(model, {'qps': 100})
bucket = self.buckets[tenant][model]
now = time.time()
# 初始化桶
if 'tokens' not in bucket:
bucket['tokens'] = limit['qps']
bucket['last'] = now
# 补充令牌
bucket['tokens'] = min(limit['qps'],
bucket['tokens'] + (now - bucket['last']) * limit['qps'])
bucket['last'] = now
# 消耗令牌
if bucket['tokens'] >= 1:
bucket['tokens'] -= 1
return True
return False
# 量化: 多租户限流防某租户耗尽资源
# 令牌桶允许突发, 滑动窗口平滑限流
# 7B 模型单租户 QPS 限 50, 并发限 10
来看一组数据。多租户限流的核心作用是防止单一租户把整个集群的资源全部吃光。令牌桶允许突发流量(可以做到2倍QPS持续1秒),而滑动窗口则负责把流量平滑掉。对于7B模型的单租户,典型的限制参数是:QPS 50、并发 10、每分钟 tokens 10000。这里有个硬性要求——网关层延迟必须控制在5ms以内,否则会拖累整体的P99延迟。
几个容易踩坑的边界问题。流式输出(SSE)依赖长连接,网关必须支持连接保持,Nginx默认的60秒超时通常需要调大。限流必须是多维度的,如果只限制QPS,碰到长请求时并发照样会被耗尽。降级策略必须明确——后端故障时应该返回缓存结果或让请求排队,而不是直接甩一个报错出去。
2. 负载均衡:感知推理特征的调度
推理请求的特点很鲜明:处理时间差异巨大(从10ms到10s不等)、上下文可以缓存(Prefix Cache)、请求有优先级分级(付费/免费)。负载均衡必须感知这些特征,否则效果就是隔靴搔痒。
graph TD
A[推理负载均衡] --> B[最小连接数]
A --> C[前缀感知: 同前缀同节点]
A --> D[优先级调度]
A --> E[模型亲和性]
B --> F[避免长请求堆积]
C --> G[命中Prefix Cache]
D --> H[付费优先/免费排队]
E --> I[同模型同节点避免重加载]
classDef default fill:#faf9f5,stroke:#ffffff,color:#000000,stroke-width:0px
# 来源:推理负载均衡器 / 生产实践 2024
import hashlib
from collections import defaultdict
class InferenceLoadBalancer:
"""推理专用负载均衡器"""
def __init__(self, backends):
self.backends = backends # [{'url', 'healthy', 'conns', 'p99', 'models'}]
self.prefix_map = {} # prefix_hash -> backend_url (前缀亲和)
def route(self, request):
"""路由请求"""
healthy = [b for b in self.backends if b['healthy']]
if not healthy:
raise Exception('无可用后端')
# 1. 前缀感知: 同前缀路由到同节点 (命中Prefix Cache)
prefix_hash = self._hash_prefix(request['prompt'])
if prefix_hash in self.prefix_map:
cached = self.prefix_map[prefix_hash]
if cached in [b['url'] for b in healthy]:
return cached
# 2. 优先级调度: 付费优先
if request.get('priority') == 'paid':
candidates = [b for b in healthy if b['p99'] < 100]
else:
candidates = healthy
# 3. 最小连接数 (避免长请求堆积)
backend = min(candidates, key=lambda b: b['conns'])
# 4. 记录前缀亲和
self.prefix_map[prefix_hash] = backend['url']
return backend['url']
def _hash_prefix(self, prompt):
# 取前 200 字符哈希 (system prompt 通常共享)
return hashlib.md5(prompt[:200].encode()).hexdigest()
def update_stats(self, backend_url, latency, conns):
"""更新后端统计"""
for b in self.backends:
if b['url'] == backend_url:
b['p99'] = latency
b['conns'] = conns
break
# 量化: 前缀感知路由使 Prefix Cache 命中率 40% -> 85%
# 付费用户 P99 < 100ms 保证, 免费 P99 < 500ms
# 最小连接数避免长请求拖垮单节点
# 来源:健康检查实现 / 生产实践 2024
import asyncio
class HealthChecker:
"""后端健康检查器"""
def __init__(self, backends, interval=5, unhealthy_threshold=3):
self.backends = backends
self.interval = interval
self.threshold = unhealthy_threshold
self.fail_counts = defaultdict(int)
async def run(self):
"""持续健康检查"""
while True:
tasks = [self._check(b) for b in self.backends]
await asyncio.gather(*tasks)
await asyncio.sleep(self.interval)
async def _check(self, backend):
"""检查单个后端"""
try:
# 推理测试请求 (简单 prompt)
start = time.time()
result = await self._ping(backend['url'])
latency = (time.time() - start) * 1000
if latency < 1000 and result:
backend['healthy'] = True
backend['p99'] = latency
self.fail_counts[backend['url']] = 0
else:
self._mark_unhealthy(backend)
except:
self._mark_unhealthy(backend)
def _mark_unhealthy(self, backend):
self.fail_counts[backend['url']] += 1
if self.fail_counts[backend['url']] >= self.threshold:
backend['healthy'] = False
async def _ping(self, url):
return True # 占位
# 量化: 健康检查间隔 5s, 连续 3 次失败标记不健康
# 故障检测延迟: 5-15s (3次检查周期)
# 避免单次网络抖动误判
实际效果如何?前缀感知路由让Prefix Cache命中率从40%直接飙升到85%,首token延迟降低了60%。最小连接数策略有效避免了长请求堆积,单节点并发均衡度提升了40%。健康检查5秒一次、连续3次失败才标记为不健康,故障检测延迟在5到15秒之间,这个设计的关键在于避免单次网络抖动误判。
边界问题也不少。前缀亲和要求节点故障时能快速fallback到其他节点,否则缓存就失效了。优先级调度必须保证公平性——免费用户不能无限排队,需要有超时降级机制。模型亲和性在大规模集群中反而受限,当节点数大于模型数时,亲和性就没什么意义了。
3. 高可用:容灾与故障恢复
推理服务的高可用需要多级容灾:实例级(GPU故障)、机房级(网络中断)、区域级(云故障)。核心策略就是冗余部署、自动故障转移、降级服务。
graph TD
A[高可用架构] --> B[实例级: 多副本]
A --> C[机房级: 跨机房部署]
A --> D[区域级: 多云备份]
B --> E[N+1冗余: 1个备用]
B --> F[自动故障转移]
C --> G[DNS轮询/Anycast]
C --> H[异地容灾]
D --> I[AWS/阿里云双云]
D --> J[数据同步]
F --> K[故障节点摘除]
F --> L[请求重路由]
classDef default fill:#faf9f5,stroke:#ffffff,color:#000000,stroke-width:0px
# 来源:故障转移实现 / 生产实践 2024
import asyncio
from collections import defaultdict
class FailoverManager:
"""故障转移管理器"""
def __init__(self, backends, failover_threshold=3):
self.backends = backends
self.threshold = failover_threshold
self.fail_counts = defaultdict(int)
self.circuit = defaultdict(lambda: 'closed') # closed/open/half_open
async def route_with_failover(self, request, lb):
"""带故障转移的路由"""
attempts = 0
max_attempts = len(self.backends)
while attempts < max_attempts:
backend = lb.route(request)
if self.circuit[backend] == 'open':
attempts += 1
continue
try:
result = await self._call(backend, request)
self.fail_counts[backend] = 0
self.circuit[backend] = 'closed'
return result
except Exception as e:
self._handle_failure(backend)
attempts += 1
# 全部失败, 降级
return await self._degrade(request)
def _handle_failure(self, backend):
self.fail_counts[backend] += 1
if self.fail_counts[backend] >= self.threshold:
self.circuit[backend] = 'open' # 熔断
# 60秒后半开
asyncio.create_task(self._half_open_timer(backend))
async def _half_open_timer(self, backend):
await asyncio.sleep(60)
self.circuit[backend] = 'half_open'
async def _degrade(self, request):
"""降级服务: 返回缓存或简化响应"""
# 1. 查缓存
cached = await self._query_cache(request)
if cached:
return cached
# 2. 返回提示信息
return {'error': 'service degraded', 'retry_after': 60}
async def _call(self, backend, request):
return {} # 占位
async def _query_cache(self, request):
return None # 占位
# 量化: 熔断器 3 次失败触发, 60秒半开探测
# 故障转移: 首次失败立即重路由, 用户无感
# 降级: 返回缓存使可用性 99.9% -> 99.99%
# 来源:多机房容灾 / 生产实践 2024
class MultiRegionManager:
"""多机房容灾管理器"""
def __init__(self, regions):
# regions: [{'name', 'endpoint', 'health', 'priority'}]
self.regions = regions
def route(self, request):
"""选择最优机房"""
healthy = [r for r in self.regions if r['health']]
if not healthy:
raise Exception('全机房不可用')
# 1. 按优先级 (主机房优先)
healthy.sort(key=lambda r: r['priority'])
# 2. 延迟探测选最近
primary = healthy[0]
if self._latency(primary) < 200:
return primary
# 主机房延迟高, 选备用
for r in healthy[1:]:
if self._latency(r) < 100:
return r
return primary
def _latency(self, region):
return 50 # 占位
# 量化: 双机房部署使可用性 99.9% -> 99.99%
# 故障切换时间: DNS 轮询 30s, Anycast <1s
# 成本: 双机房需 2x 资源, 适合核心业务
数据说明一切。双机房部署让可用性从99.9%提升到99.99%,故障切换时间DNS轮询30秒,Anycast可以做到1秒以内。熔断器3次失败触发,60秒半开探测,这个设计有效防止了雪崩效应。降级返回缓存还能再提升一个9的可用性。当然,成本也不低——双机房需要2倍资源,适合核心业务,非核心业务用单机房加冷备就够了。
边界问题。跨机房数据同步始终是个难题——模型权重动辄7B就是14GB,同步速度慢,各机房独立加载反而更靠谱。DNS轮询切换速度慢,全球服务必须用Anycast或HTTP DNS。降级策略需要业务层面允许——医疗、金融这些场景不能降级,必须强一致。
4. 弹性伸缩:GPU集群自动扩缩
GPU推理集群的伸缩有个硬伤——启动时间和模型加载要30到60秒,所以必须用预测性伸缩,而不能等出了问题再去反应。核心指标是:QPS、P99延迟、GPU利用率、队列深度。
graph TD
A[弹性伸缩] --> B[指标采集]
A --> C[伸缩决策]
A --> D[预测性扩容]
B --> E[QPS/P99/利用率/队列]
C --> F[阈值规则]
D --> G[历史趋势预测]
D --> H[提前1分钟扩容]
F --> I[扩: QPS>80 或 P99>500ms]
F --> J[缩: QPS<20 且 P99<250ms 持续5分钟]
classDef default fill:#faf9f5,stroke:#ffffff,color:#000000,stroke-width:0px
# 来源:GPU 集群伸缩 / 生产实践 2024
import time
from collections import deque
class GPUClusterScaler:
"""GPU 集群自动伸缩器"""
def __init__(self, min_nodes=2, max_nodes=20, scale_up_qps=80,
scale_down_qps=20, p99_threshold=500, warmup_time=60):
self.min = min_nodes
self.max = max_nodes
self.up_qps = scale_up_qps
self.down_qps = scale_down_qps
self.p99_thresh = p99_threshold
self.warmup = warmup_time
self.current = min_nodes
self.history = deque(maxlen=60) # 60 分钟历史
self.node_ready_time = {} # 节点启动时间
def evaluate(self, metrics):
"""评估伸缩决策"""
self.history.append(metrics)
# 1. 反应式: 当前指标超阈值
if metrics['qps'] > self.up_qps or metrics['p99'] > self.p99_thresh:
return self._scale_up()
if metrics['qps'] < self.down_qps and metrics['p99'] < self.p99_thresh * 0.5:
# 持续低负载 5 分钟才缩容
if len(self.history) >= 5 and all(h['qps'] < self.down_qps
for h in list(self.history)[-5:]):
return self._scale_down()
# 2. 预测式: 历史趋势预测
predicted = self._predict_next()
if predicted['qps'] > self.up_qps * 1.2:
return self._scale_up()
return None
def _predict_next(self):
"""基于历史预测下一分钟"""
if len(self.history) < 10:
return {'qps': 0}
# 简化: 用最近 10 分钟平均+趋势
recent = list(self.history)[-10:]
a vg_qps = sum(h['qps'] for h in recent) / 10
trend = recent[-1]['qps'] - recent[0]['qps']
return {'qps': a vg_qps + trend}
def _scale_up(self):
target = min(self.current + 2, self.max) # 一次扩 2 个
if target > self.current:
self._launch_nodes(target - self.current)
self.current = target
return 'scale_up'
return None
def _scale_down(self):
target = max(self.current - 1, self.min)
if target < self.current:
self._terminate_nodes(self.current - target)
self.current = target
return 'scale_down'
return None
def _launch_nodes(self, n):
"""启动新节点 (需 warmup)"""
for i in range(n):
node_id = f'node-{time.time()}-{i}'
self.node_ready_time[node_id] = time.time() + self.warmup
# 实际调用 K8s/云 SDK 启动 GPU 实例
def is_node_ready(self, node_id):
"""节点是否就绪 (过了 warmup)"""
return time.time() > self.node_ready_time.get(node_id, 0)
# 量化: 预测式扩容使扩容延迟从 60s 降至 0s (提前启动)
# 资源利用率从 30% 升至 70%
# GPU 成本降 40% (低峰缩容)
# 来源:伸缩指标采集 / 生产实践 2024
class MetricsCollector:
"""伸缩指标采集器"""
def __init__(self, prometheus_url):
self.url = prometheus_url
def collect(self):
"""采集伸缩决策指标"""
return {
'qps': self._query('rate(inference_requests_total[1m])'),
'p99': self._query('histogram_quantile(0.99, inference_latency)'),
'gpu_util': self._query('a vg(DCgm_gpu_utilization)'),
'gpu_mem': self._query('a vg(DCGM_fb_used / DCGM_fb_total)'),
'queue_depth': self._query('inference_queue_size'),
'tokens_per_sec': self._query('rate(generated_tokens_total[1m])'),
}
def _query(self, query):
# 查 Prometheus
return 50 # 占位
# 量化: 关键指标采样间隔 10s
# 队列深度是领先指标: 队列>10 即将过载
# GPU利用率滞后: 利用率>90%时已过载
预测式扩容的效果很明显——扩容延迟从60秒降到了0秒(提前启动节点),资源利用率从30%提升到70%,GPU成本降低了40%。队列深度是过载的领先指标,队列超过10就说明快要过载了;GPU利用率则是滞后指标,等它超过90%时,其实已经过载了。缩容需要持续低负载5分钟才执行,这是为了避免抖动。
边界问题。GPU节点启动慢,竞价实例更慢(2到5分钟),需要预留buffer。模型加载需要30到60秒,所以伸缩决策必须提前1到2分钟。跨可用区伸缩受GPU配额限制——热门GPU型号经常缺货,需要多机型fallback。
5. 多模型混部:GPU资源共享与隔离
生产环境很少只跑一个模型,往往是同一集群同时服务多个不同尺寸、不同任务的模型。多模型混部需要解决GPU共享、显存隔离、请求路由、冷启动优化四个问题。
graph TD
A[多模型混部] --> B[GPU共享: MPS/MIG]
A --> C[显存隔离: 静态分配]
A --> D[请求路由: 模型感知]
A --> E[冷启动: 预加载]
B --> F[MPS: 多进程共享]
B --> G[MIG: 硬件分区]
C --> H[每模型预留显存上限]
D --> I[按模型名路由后端]
E --> J[常驻热模型+冷备]
classDef default fill:#faf9f5,stroke:#ffffff,color:#000000,stroke-width:0px
# 来源:多模型混部调度器 / 生产实践 2024
class MultiModelScheduler:
"""多模型混部调度器"""
def __init__(self, gpu_nodes, model_configs):
self.nodes = gpu_nodes # [{'id', 'gpu_mem', 'models_loaded'}]
self.configs = model_configs # {'llama-7b': {'size_gb': 14, 'qps': 50}, ...}
self.hot_models = set() # 常驻热模型
self.cold_models = {} # 冷模型+最后访问时间
def route(self, request):
"""按模型路由请求"""
model = request['model']
# 1. 找已加载该模型的节点
loaded = [n for n in self.nodes if model in n['models_loaded']]
if loaded:
self.hot_models.add(model)
return self._select_least_load(loaded)
# 2. 未加载, 触发加载
node = self._find_a vailable_node(model)
if node:
self._load_model(node, model)
return node
# 3. 无可用节点, 排队
return self._queue_request(request)
def _find_a vailable_node(self, model):
"""找有足够显存加载模型的节点"""
needed = self.configs[model]['size_gb']
for node in self.nodes:
free = node['gpu_mem'] - sum(self.configs[m]['size_gb']
for m in node['models_loaded'])
if free >= needed:
return node
return None
def _load_model(self, node, model):
"""加载模型到节点 (耗时 30-60s)"""
node['models_loaded'].append(model)
# 实际调用推理引擎加载 API
def _select_least_load(self, nodes):
return min(nodes, key=lambda n: len(n['models_loaded']))
def evict_cold_model(self, threshold_min=30):
"""淘汰 30 分钟未访问的冷模型释放显存"""
import time
now = time.time()
for model, last_access in list(self.cold_models.items()):
if now - last_access > threshold_min * 60:
for node in self.nodes:
if model in node['models_loaded']:
self._unload(node, model)
# 量化: 多模型混部使 GPU 利用率从 40% 升至 75%
# 热模型常驻 (7B+13B), 冷模型按需加载
# 冷启动 30-60s, 需预加载高频模型
# 来源:GPU 共享方案对比 / 生产实践 2024
# 方案1: MPS (Multi-Process Service)
# - 多进程共享 GPU, 软件隔离
# - 优点: 灵活, 显存动态分配
# - 缺点: 故障隔离弱 (一进程崩溃影响全GPU)
# - 适合: 同团队多模型
# 方案2: MIG (Multi-Instance GPU)
# - 硬件分区, 各实例独立
# - 优点: 强隔离, 故障不传播
# - 缺点: 分区固定, 不灵活
# - 适合: 多租户/多团队
# 方案3: 时间分片 (默认)
# - GPU 串行执行多进程
# - 优点: 简单
# - 缺点: 上下文切换开销, 延迟波动
# - 适合: 低并发场景
class GPUSharingManager:
"""GPU 共享管理器"""
def __init__(self, strategy='mps'):
self.strategy = strategy
def allocate(self, model, gpu_id):
"""分配 GPU 资源给模型"""
if self.strategy == 'mps':
return self._alloc_mps(model, gpu_id)
elif self.strategy == 'mig':
return self._alloc_mig(model, gpu_id)
return self._alloc_timeslice(model, gpu_id)
def _alloc_mps(self, model, gpu_id):
# MPS: 设置显存上限
return {'gpu': gpu_id, 'mem_limit': '14GB', 'mode': 'mps'}
def _alloc_mig(self, model, gpu_id):
# MIG: 切分硬件实例 (A100 支持 7 个实例)
return {'gpu': gpu_id, 'mig_slice': '1g.5gb', 'mode': 'mig'}
# 量化 (A100 80GB):
# MPS: 7B+13B+7B 共享, 显存动态, 利用率 85%
# MIG: 7 个 1g.10gb 实例, 强隔离, 利用率 70%
# 时间分片: 利用率 60%, 延迟波动 2-3 倍
多模型混部让GPU利用率从40%提升到75%。MPS共享显存动态分配,利用率可以达到85%,但隔离性弱;MIG硬件分区,利用率70%,但隔离性强。冷模型30分钟不访问就淘汰释放显存。冷启动需要30到60秒,所以高频模型必须预加载,低频模型可以接受按需加载的延迟。
边界问题。MPS故障隔离弱——一个进程OOM会影响整个GPU的所有进程,不适合多租户场景。MIG分区固定——A100最多7个1g.10gb实例,70B的大模型根本用不了MIG。时间分片延迟波动大,不适合SLA严格的场景。多模型混部必须监控每个模型的显存使用,避免单一模型OOM影响其他模型。
6. 边界与失败模式
推理系统架构的失败模式主要集中在三类:单点故障、资源耗尽、级联崩溃。
graph TD
A[架构失败模式] --> B[单点故障]
A --> C[资源耗尽]
A --> D[级联崩溃]
B --> B1[网关单点]
B --> B2[DNS单点]
C --> C1[显存溢出]
C --> C2[连接数耗尽]
D --> D1[重试风暴]
D --> D2[熔断失效]
B1 --> R1[网关多副本+VIP]
C1 --> R2[显存监控+主动限流]
D1 --> R3[退避重试+熔断]
classDef default fill:#faf9f5,stroke:#ffffff,color:#000000,stroke-width:0px
# 来源:级联崩溃防护 / 生产实践 2024
class CascadeFailureGuard:
"""级联崩溃防护器"""
def __init__(self, max_retries=2, backoff_base=0.1):
self.max_retries = max_retries
self.backoff = backoff_base
async def call_with_guard(self, func, *args):
"""带级联防护的调用"""
for attempt in range(self.max_retries + 1):
try:
return await func(*args)
except Exception as e:
if attempt == self.max_retries:
raise
# 指数退避: 避免重试风暴
wait = self.backoff * (2 ** attempt)
await asyncio.sleep(wait)
raise Exception('unreachable')
async def shed_load(self, current_qps, max_qps):
"""负载脱落: 过载时拒绝低优先级请求"""
if current_qps > max_qps * 0.9:
# 接近过载, 拒绝免费用户
return 'reject_free'
if current_qps > max_qps:
# 已过载, 仅服务付费用户
return 'reject_all_except_paid'
return 'accept_all'
# 量化: 退避重试避免重试风暴 (重试量降 80%)
# 负载脱落使核心用户可用性保持 99.9%
# 无防护: 过载致全部用户不可用
实战复盘:某推理服务网关单点故障导致全站不可用30分钟。排查发现网关只部署了单副本,进程崩溃后没有自动重启。修复方案很简单——网关多副本加VIP漂移加进程守护(systemd restart on crash)。教训很深刻:所有控制面组件必须多副本,单点是可用性最大的威胁。
另一个实战案例:某服务高峰期出现级联崩溃——后端响应慢导致客户端超时重试,重试又加剧了后端压力,最终雪崩。诊断发现客户端重试没有退避策略(固定1秒重试),高峰期重试量达到了原始请求的3倍。修复方案:客户端指数退避(0.1秒/0.2秒/0.4秒)加服务端熔断。教训:重试必须有退避,否则会放大流量导致雪崩。
7. 架构演进趋势:从单体到异构协同
推理系统架构正在从单体GPU集群向异构算力协同、边缘-云协同、Serverless化演进。
graph TD
A[架构演进] --> B[异构算力: GPU+NPU+CPU协同]
A --> C[边缘-云协同: 就近推理]
A --> D[Serverless: 按需弹性]
A --> E[ disaggregated: 存算分离]
classDef default fill:#faf9f5,stroke:#ffffff,color:#000000,stroke-width:0px
# 来源:异构算力调度 / 生产实践 2024
class HeterogeneousScheduler:
"""异构算力调度器"""
def __init__(self):
self.pools = {
'gpu_h100': {'strength': '大模型推理', 'cost': 5},
'gpu_a10': {'strength': '中等模型', 'cost': 1.5},
'npu': {'strength': '特定模型', 'cost': 1},
'cpu': {'strength': '小模型/边缘', 'cost': 0.2},
}
def route(self, request, model_size):
# 按模型规模+延迟要求路由到合适算力
if model_size > 30: return 'gpu_h100'
if model_size > 7: return 'gpu_a10'
if request.get('edge'): return 'cpu'
return 'gpu_a10'
# 量化: 架构演进方向
# 异构算力: 成本降 30-50% (按需选算力)
# 边缘-云: 延迟降 60-80% (就近推理)
# Serverless: 冷启动 <1s + 按调用付费
# 关键: 异构调度是降本核心
数据趋势很清晰。异构算力成本可以降低30%到50%,边缘-云协同延迟降低60%到80%,Serverless冷启动可以做到1秒以内。异构调度是降本的核心。
边界问题。异构算力编程复杂,需要统一的抽象层。边缘-云协同有数据同步成本。Serverless冷启动仍然很难做到零延迟。存算分离后,网络带宽会成为瓶颈。架构演进不能一步到位,需要渐进式推进。
总结
推理系统架构的核心在于网关、负载均衡、高可用、弹性伸缩、容灾这五个点。网关多维度限流防止资源耗尽,前缀感知负载均衡让缓存命中率达到85%,双机房容灾让可用性达到99.99%,预测式伸缩让扩容延迟降到0秒同时成本降低40%,退避重试加熔断防止级联崩溃。设计原则可以归结为:消除单点、预留buffer、降级而不是报错、退避而不是重试。控制面组件必须多副本,数据面需要显存监控和主动限流。
