分布式AI系统设计:高并发LLM推理服务的架构设计
2026/4/30大约 7 分钟
分布式AI系统设计:高并发LLM推理服务的架构设计
适读人群:需要在生产环境支撑高并发AI调用的Java架构师和高级工程师 阅读时长:约20分钟 文章价值:系统掌握高并发LLM推理服务的核心架构设计,解决吞吐量与延迟的核心矛盾
凌晨三点的告警
去年双十一前夕,我的朋友老陈在某大厂做AI推荐系统,凌晨三点给我发消息:"老张,完了,压测挂了,LLM接口QPS才到200就开始大量超时,明天就要上线……"
他们的场景是商品详情页的AI推荐语,预估峰值QPS 5000+。
我问他:你们现在是什么架构?
他说:就是Spring AI + OpenAI,一个请求过来就同步调LLM,等响应返回再返回给用户。
我说这就是问题所在。同步阻塞调用LLM,每个请求要等3-5秒,一台机器200个线程,并发上限就是200/5=40 QPS。要到5000 QPS,靠堆机器你得堆125台,成本炸了。
这篇文章就来聊聊,怎么把LLM推理服务做成真正能扛高并发的系统。
问题根源:LLM的特殊性
LLM推理和普通数据库查询有本质区别:
| 特性 | 普通API | LLM推理 |
|---|---|---|
| 延迟 | 10-100ms | 1000-10000ms |
| 响应模式 | 一次性返回 | 流式逐token输出 |
| 资源消耗 | 低 | 极高(GPU/内存) |
| 可并行度 | 极高 | 受GPU显存限制 |
| 失败模式 | 快速失败 | 慢速超时 |
正因为延迟高,同步阻塞模型的并发能力被严重限制。核心解法是:把同步变异步,把串行变并行。
整体架构设计
这个架构的核心思路:
- 解耦请求接收和LLM调用:网关快速接收请求,入队后立即返回taskId
- 异步处理:Worker从队列消费,不受上游并发压力
- 多层缓存:语义缓存减少重复调用
- 多模型路由:主备切换,负载均衡
核心组件实现
1. 响应式接入层(WebFlux)
用WebFlux代替SpringMVC,同等机器线程数下可以支撑更高并发:
@RestController
@RequestMapping("/ai")
@Slf4j
public class LLMProxyController {
private final LLMRequestService requestService;
private final SemanticCacheService cacheService;
/**
* 提交AI推理任务(异步模式)
* 立即返回taskId,客户端轮询或WebSocket获取结果
*/
@PostMapping("/infer")
public Mono<InferResponse> submitInfer(@RequestBody InferRequest request,
ServerHttpRequest httpRequest) {
String clientId = extractClientId(httpRequest);
return cacheService.get(request.getPrompt())
.map(cached -> InferResponse.cached(cached))
.switchIfEmpty(
requestService.submitTask(request, clientId)
.map(taskId -> InferResponse.submitted(taskId))
)
.doOnError(e -> log.error("推理请求失败: clientId={}", clientId, e));
}
/**
* 流式推理(Server-Sent Events)
* 不经过队列,直接流式返回,适合交互式场景
*/
@GetMapping(value = "/infer/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> streamInfer(
@RequestParam String prompt,
@RequestParam(defaultValue = "gpt-4o-mini") String model) {
return requestService.streamInfer(prompt, model)
.map(token -> ServerSentEvent.<String>builder()
.event("token")
.data(token)
.build())
.concatWith(Flux.just(
ServerSentEvent.<String>builder()
.event("done")
.data("[DONE]")
.build()
));
}
/**
* 查询任务结果
*/
@GetMapping("/tasks/{taskId}")
public Mono<TaskResult> getTaskResult(@PathVariable String taskId) {
return requestService.getResult(taskId);
}
}2. 语义缓存层
语义缓存的关键是:语义相似的问题共用缓存,而不是只做精确匹配:
@Service
@Slf4j
public class SemanticCacheService {
private final EmbeddingModel embeddingModel;
private final VectorStore vectorStore;
private final RedisTemplate<String, String> redisTemplate;
private static final float SIMILARITY_THRESHOLD = 0.92f;
private static final Duration CACHE_TTL = Duration.ofHours(2);
/**
* 语义相似度缓存查询
*/
public Mono<String> get(String prompt) {
return Mono.fromCallable(() -> {
// 1. 先查精确匹配缓存(最快)
String exactKey = "exact:" + DigestUtils.md5Hex(prompt);
String exactResult = redisTemplate.opsForValue().get(exactKey);
if (exactResult != null) {
log.debug("精确缓存命中");
return exactResult;
}
// 2. 语义相似度搜索
List<Document> similar = vectorStore.similaritySearch(
SearchRequest.query(prompt)
.withTopK(1)
.withSimilarityThreshold(SIMILARITY_THRESHOLD)
);
if (!similar.isEmpty()) {
String cachedAnswer = similar.get(0).getMetadata()
.getOrDefault("answer", "").toString();
if (!cachedAnswer.isEmpty()) {
log.debug("语义缓存命中,相似度: {}",
similar.get(0).getScore());
return cachedAnswer;
}
}
return null;
});
}
/**
* 写入缓存(同时写精确缓存和向量缓存)
*/
public void put(String prompt, String answer) {
// 精确缓存
String exactKey = "exact:" + DigestUtils.md5Hex(prompt);
redisTemplate.opsForValue().set(exactKey, answer, CACHE_TTL);
// 向量缓存(用于语义相似匹配)
Document doc = new Document(answer, Map.of(
"prompt", prompt,
"answer", answer,
"timestamp", System.currentTimeMillis()
));
vectorStore.add(List.of(doc));
}
}3. 任务队列与Worker
@Component
@Slf4j
public class LLMInferWorker {
private final ChatClient chatClient;
private final RedisTemplate<String, TaskResult> resultStore;
private static final Duration RESULT_TTL = Duration.ofMinutes(10);
@KafkaListener(
topics = "llm-infer-tasks",
groupId = "llm-worker-group",
concurrency = "4" // 4个并发消费者
)
public void processInferTask(InferTask task) {
String taskId = task.getTaskId();
log.info("开始处理推理任务: taskId={}", taskId);
// 更新任务状态
updateTaskStatus(taskId, TaskStatus.PROCESSING);
long startTime = System.currentTimeMillis();
try {
// 选择模型(基于优先级和可用性)
String model = selectModel(task.getModelPreference());
String result = chatClient.prompt()
.user(task.getPrompt())
.options(OpenAiChatOptions.builder()
.withModel(model)
.withMaxTokens(task.getMaxTokens())
.withTemperature(task.getTemperature())
.build())
.call()
.content();
long latency = System.currentTimeMillis() - startTime;
// 存储结果
TaskResult taskResult = TaskResult.builder()
.taskId(taskId)
.status(TaskStatus.COMPLETED)
.content(result)
.latencyMs(latency)
.completedAt(LocalDateTime.now())
.build();
resultStore.opsForValue().set("result:" + taskId, taskResult, RESULT_TTL);
// 写入语义缓存
// cacheService.put(task.getPrompt(), result);
log.info("推理任务完成: taskId={}, latency={}ms", taskId, latency);
} catch (Exception e) {
log.error("推理任务失败: taskId={}", taskId, e);
TaskResult failResult = TaskResult.builder()
.taskId(taskId)
.status(TaskStatus.FAILED)
.errorMessage(e.getMessage())
.build();
resultStore.opsForValue().set("result:" + taskId, failResult, RESULT_TTL);
}
}
private String selectModel(String preference) {
// 简单的模型选择逻辑,生产环境要配合健康检查
return switch (preference) {
case "fast" -> "gpt-4o-mini";
case "quality" -> "gpt-4o";
default -> "gpt-4o-mini";
};
}
}4. 多模型负载均衡与故障转移
@Service
@Slf4j
public class ModelLoadBalancer {
private final Map<String, ChatClient> modelClients;
private final CircuitBreakerRegistry circuitBreakerRegistry;
// 各模型权重配置
private final Map<String, Integer> modelWeights = Map.of(
"openai-gpt4o", 60,
"azure-gpt4o", 30,
"zhipu-glm4", 10
);
/**
* 带熔断的模型调用
*/
public String callWithFallback(String prompt, Map<String, Object> options) {
List<String> models = selectModelsInOrder();
for (String modelName : models) {
CircuitBreaker cb = circuitBreakerRegistry.circuitBreaker(modelName);
try {
return cb.executeSupplier(() -> {
ChatClient client = modelClients.get(modelName);
return client.prompt()
.user(prompt)
.call()
.content();
});
} catch (CallNotPermittedException e) {
log.warn("模型熔断,跳过: model={}", modelName);
} catch (Exception e) {
log.error("模型调用失败,尝试下一个: model={}", modelName, e);
}
}
throw new AllModelsUnavailableException("所有模型均不可用");
}
/**
* 按权重加随机选择模型顺序
*/
private List<String> selectModelsInOrder() {
// 基于权重的随机排序,权重高的优先
List<String> models = new ArrayList<>(modelWeights.keySet());
models.sort((a, b) -> {
int weightA = modelWeights.getOrDefault(a, 0);
int weightB = modelWeights.getOrDefault(b, 0);
return weightB - weightA;
});
return models;
}
}关键性能指标与监控
Prometheus指标埋点:
@Component
public class LLMMetrics {
private final Counter requestCounter;
private final Counter cacheHitCounter;
private final Histogram latencyHistogram;
private final Counter tokenCounter;
public LLMMetrics(MeterRegistry registry) {
this.requestCounter = Counter.builder("llm.requests.total")
.description("LLM请求总数")
.tag("type", "infer")
.register(registry);
this.cacheHitCounter = Counter.builder("llm.cache.hits")
.description("缓存命中次数")
.register(registry);
this.latencyHistogram = Histogram.builder("llm.latency.ms")
.description("LLM响应延迟(毫秒)")
.serviceLevelObjectives(500, 1000, 2000, 5000, 10000)
.register(registry);
this.tokenCounter = Counter.builder("llm.tokens.total")
.description("Token消耗总数")
.register(registry);
}
public void recordRequest(boolean cacheHit, long latencyMs, int tokens) {
requestCounter.increment();
if (cacheHit) cacheHitCounter.increment();
latencyHistogram.record(latencyMs, TimeUnit.MILLISECONDS);
tokenCounter.increment(tokens);
}
}容量规划参考
根据实际项目经验,给出一个容量规划参考表:
| 场景 | 单次延迟 | 缓存命中率 | 推荐架构 | 估算QPS |
|---|---|---|---|---|
| 简单问答(短prompt) | 0.5-1s | 40-60% | 单层代理+缓存 | 500-1000 |
| RAG问答(长context) | 2-5s | 20-40% | 队列+多Worker | 100-300 |
| 实时对话 | 流式 | 10-20% | WebFlux流式 | 200-500并发连接 |
| 批量生成(离线) | 不敏感 | 可忽略 | Kafka+批处理 | 10000+/小时 |
小结
高并发LLM系统的核心设计原则:
- 同步变异步:接收请求和LLM调用解耦,是突破并发瓶颈的根本
- 缓存是第一道防线:能缓存的都缓存,语义缓存比精确缓存命中率高3-5倍
- 多模型备份:单一模型是单点故障,生产系统必须有备用链路
- 监控先行:不知道瓶颈在哪,优化就是盲人摸象
老陈后来把他们的系统按这套架构重构,上线后峰值扛到了4800 QPS,没有再在凌晨被叫起来。
AI系统的高并发,本质还是分布式系统的老问题——只是LLM的高延迟特性,让这些问题更加突出。
