概述当业务系统需要同时处理海量用户请求、调用大模型LLM完成 AI 推理时单请求逐一调用的模式会迅速触及 LLM API 的 Rate Limit 和成本上限。请求排队合并Request Batch Merge策略是解决这一矛盾的核心方案它将同类请求在内存中暂存、相似度检测、批量打包在保证单请求延迟可接受的前提下显著提升系统吞吐、降低 Token 消耗和调用成本。一、问题背景高并发对接 LLM 的三大挑战1.1 Rate Limit 瓶颈主流 LLM 提供商OpenAI GPT-4o、Claude 3.5、Kimi、通义千问等均对 API 调用施加 QPS 或 RPM 限制。以 GPT-4o 为例Tier 4 账户的 RPM 上限为 10000 RPM、TPM 上限为 300000 Token/min。当业务 QPS 达到数千甚至上万时如果每个用户请求都直接转发给 LLM将不可避免地触发 429 Too Many Requests 错误。1.2 成本失控大模型按 Token 计费。假设业务日活 100 万用户平均每用户每次会话消耗 500 Token按 OpenAI GPT-4o-mini $0.15/1M Input Token 计算日均调用成本约 $75。如果不做请求合并每次调用各自携带系统提示词System Prompt——一个 2000 Token 的系统提示词被重复发送 100 万次等同于每天额外浪费近 20 亿无效 Token。1.3 尾部延迟剧增在无排队的情况下高并发请求竞争 LLM 连接池资源P99 延迟可能从单请求的 500ms 飙升到数秒甚至超时。排队合并通过批量调用将竞争摊平使延迟分布更加可预测。二、请求合并策略原理2.1 核心思想请求合并的本质是空间换时间在内存中维护一个短时等待队列将时间窗口内到达的多个请求暂存待满足合并条件后一次性批量发送。合并后的单个 LLM 调用可以同时处理多个子请求LLM 返回的结果再按请求 ID 分发回各个调用方。2.2 合并条件请求能否被合并取决于两个维度- 语义相似度通过 Embedding 模型将用户 prompt 转为向量计算余弦相似度。相似度 0.85 的请求视为可合并。- 业务兼容性合并的请求不能有冲突的上下文比如不同的 system prompt、不同的模型参数 temperature/top_p。2.3 合并窗口合并窗口Merge Window是决定合并效果的核心参数- 时间窗口从第一个请求入队开始计时窗口期内不断接纳新请求。典型值 50ms~200ms。- 数量窗口队列达到指定请求数量即触发发送典型值 20~100。- 混合策略时间窗口和数量窗口任一触发即发送取更早到达的条件。窗口越大合并率越高更多请求被纳入同一批次但单个请求的等待延迟也越高。这是一个需要在业务 SLA 和系统吞吐之间做权衡的关键参数。2.4 强制发送机制为防止窗口永不触发导致请求hung必须设置兜底机制- 超时强制发送窗口超时即使未满立即发送当前批次。- 队列满强制发送队列积压超过阈值如 200 请求时强制发送并告警。- 熔断发送LLM 错误率超过阈值时暂停合并退化为单请求模式。三、系统架构设计3.1 整体分层用户流量→ API网关限流/鉴权→请求合并层核心→ LLM连接池→ LLM提供商↓Sentinel/Hystrix(熔断/限流/隔离)3.2 请求合并层职责请求合并层是整个架构的核心引擎承担以下职责- 请求接入接收来自网关的 AI 请求为每个请求生成唯一 IDcorrelation ID立即返回 CompletableFuture 给调用方。- 相似度检测调用 Embedding 服务或使用本地模型如 all-MiniLM-L6-v2计算向量相似度将相似请求加入同一批次。- 队列管理维护多级优先级队列高优先级实时交互低优先级离线批处理按 FIFO 顺序出队。- 批量调用攒满窗口或超时后将批次内的所有 prompt 拼接为一次 LLM 批量调用。- 响应分发LLM 返回后按 correlation ID 拆分结果异步完成各 CompletableFuture。- 指标暴露实时上报合并率、批次大小、队列深度、P99 延迟等核心指标。3.3 连接池设计与 LLM 的交互推荐使用 HTTP 连接池如 Apache HttpClient、OkHttp- 连接复用避免每次请求新建 TLS 连接三次握手开销在高频场景下不可忽视。- 最大连接数控制根据 LLM 方的 Rate Limit 动态调整防止触发限流。- 读写超时建议设置 readTimeout maxTokens / tokenRate 5swriteTimeout 10s。3.4 熔断降级当 LLM 响应超时率 30% 或错误率 10% 时熔断器打开- 暂停合并直接透传请求退化模式。- 定时探测每 10s 放行一个探测请求若成功则逐步关闭熔断器。- 降级响应对用户返回AI 服务繁忙请稍后重试。四、实战实现4.1 基于 Guava 实现请求合并Guava 的 ListeningExecutorService 配合 AsyncFunction 是实现请求合并的最简方案//定义带合并能力的执行器ListeningExecutorService executor MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(50));//批量请求入口public ListenableFutureListChatResponse batchChat(ListChatRequest requests) {// 1.等待窗口最多等100ms或凑满20个请求ListChatRequest batch requestQueue.poll(100, TimeUnit.MILLIS);// 2.相似度检测按Embedding聚类ListListChatRequest clusters clusterByEmbedding(batch);// 3.批量提交ListListenableFutureListChatResponse futures clusters.stream().map(cluster - batchInvoke(cluster, llmClient)).collect(toList());// 4.合并结果return Futures.allAsList(futures);}//异步批量调用private ListenableFutureListChatResponse batchInvoke(ListChatRequest cluster, LLMClient client) {String systemPrompt cluster.get(0).getSystemPrompt();String combinedUser cluster.stream().map(r - [请求 r.getId() ] r.getPrompt()).collect(Collectors.joining(\n---\n));ChatRequest combined ChatRequest.builder().systemPrompt(systemPrompt).userPrompt(combinedUser).build();return transform(client.chatAsync(combined), response - {//按请求ID拆分响应return splitResponse(response, cluster);});}4.2 基于 Sentinel 实现流量控制Sentinel 不仅能实现熔断还能提供多维度的流量控制//定义LLM调用的熔断规则DegradeRule rule new DegradeRule(llm-batch).setGrade(CircuitBreakerStrategy.SLOW_RATIO_RULE).setCount(0.5) // 50%慢调用2s比例触发熔断.setSlowRatioThreshold(0.8) // 80%请求超过阈值视为慢调用.setMinRequestAmount(10) //至少10个请求才计算熔断.setStatIntervalMs(30_000); // 30s窗口统计DegradeRuleManager.loadRules(Collections.singletonList(rule));//使用Sentinel API保护批量调用try (Entry entry SphU.entry(llm-batch, EntryType.IN, 1, batchArgs)) {//批量调用带流量整形ListChatResponse responses llmClient.batchChat(batch);//正常处理dispatch(responses);} catch (BlockException e) {//限流/熔断触发降级处理fallbackToCacheOrReject(batch);} catch (Throwable t) {// LLM调用异常熔断器会计入Tracer.traceEntry(t, entry);}4.3 Embedding 相似度检测实现相似度检测是合并策略的核心环节推荐使用轻量级 Embedding 模型//使用本地模型计算Embedding推荐all-MiniLM-L6-v2public ListListChatRequest clusterByEmbedding(ListChatRequest requests) {//批量获取向量float[][] embeddings embeddingModel.encode(requests.stream().map(ChatRequest::getPrompt).toList());//聚类简单贪心相似度阈值ListListChatRequest clusters new ArrayList();boolean[] used new boolean[requests.size()];for (int i 0; i requests.size(); i) {if (used[i]) continue;ListChatRequest cluster new ArrayList();cluster.add(requests.get(i));used[i] true;for (int j i 1; j requests.size(); j) {if (used[j]) continue;double sim cosineSimilarity(embeddings[i], embeddings[j]);if (sim SIMILARITY_THRESHOLD) {cluster.add(requests.get(j));used[j] true;}}clusters.add(cluster);}return clusters;}private double cosineSimilarity(float[] a, float[] b) {double dot 0, normA 0, normB 0;for (int i 0; i a.length; i) {dot a[i] * b[i];normA a[i] * a[i];normB b[i] * b[i];}return dot / (Math.sqrt(normA) * Math.sqrt(normB));}五、生产环境关键考量5.1 缓存命中对于完全相同的 promptCache Hit可以直接返回缓存结果无需调用 LLM。OpenAI 的 cache-control 指令和 Anthropic 的缓存机制均支持毫秒级返回成本为零。建议在请求合并前先做精确哈希匹配将缓存命中率作为重要指标监控。5.2 模型参数差异合并的请求必须使用相同的模型参数model、temperature、top_p、max_tokens。如果业务允许建议将 temperature 默认设为 0确定性输出不仅更容易合并还能提升 Token 利用率。5.3 系统提示词处理各请求的 system prompt 可能不同。推荐策略- 统一系统提示词多个请求共用相同的 system prompt 时直接合并。- 差异标记system prompt 不同时在 combined prompt 中用 XML/JSON 标签区分由 LLM 自行解析归类——这实际上是把合并粒度从请求级降到 prompt 级。5.4 监控指标上线后必须监控以下核心指标5.5 冷启动问题合并层重启后连接池需要预热。建议- 启动时先发送 10~20 个探测请求暖热连接池。- 使用 Keep-Alive 保持长连接避免超时断连。- 监控连接池活跃连接数预判容量。六、总结请求排队合并是 Java 后端在高并发场景下对接大模型的核心架构模式。它通过在网关与 LLM 之间插入合并层利用时间窗口和相似度检测将多个请求合并为一次批量调用在 QPS、Token 成本、尾部延迟三个维度同时获得数量级的改善。Guava 提供了简洁的异步合并能力Sentinel 提供了生产级的熔断限流保障二者结合再加上 Embedding 相似度检测能够构建一套完整可靠的请求合并系统。实际落地时需要根据业务 SLA 选择合适的窗口参数并通过完善的监控指标持续调优。---*本文为 Java 程序员大模型系列第 39 阶段内容专注 Java 后端工程实践。*图1请求排队合并策略原理图图2高并发流量控制架构图图3批量请求合并流程图图4实战Guava Sentinel请求合并实现