JAiRouter 架构揭秘:一个面向 AI 时代的响应式网关设计
关键词:Spring WebFlux、响应式、AI 网关、负载均衡、熔断、限流、可观测性
1. 为什么需要 JAiRouter?
在 大模型即服务(MaaS) 的时代,一个业务系统往往需要同时对接 GPUStack / Ollama / vLLM / OpenAI 等多种后端。
传统做法是在每个业务模块里各自维护一套调用逻辑,带来的痛点显而易见:
痛点 | 场景示例 |
---|---|
协议差异 | GPUStack 要求 HTTP+JSON,OpenAI 兼容 SSE,ollama 仅支持 chat,向量化 |
流量不均 | 某一台 GPU 机器被突发流量打满,其余机器空闲 |
故障放大 | 当 Ollama 实例异常时,上游服务无熔断,拖垮整站 |
配置漂移 | 每上线一台新机器,需要到 N 个业务系统改配置 |
JAiRouter 的定位就是 “AI 世界的流量中枢”:
通过统一的、响应式的、可观测的网关,把后端差异、流量调度、故障自愈、动态配置全部收敛到一个平台。
2. 一张图看懂整体架构
- 客户端层:任何符合 OpenAI 格式的 HTTP 调用方即可接入,零改造。
- 网关层:负责统一入口、路由、负载、限流、熔断。
- 适配器层:把 JAiRouter 的请求翻译成后端能听懂的“方言”。
- 后端服务:真正的 GPU 算力节点或第三方 SaaS。
3. 核心模块全景
下面按“从北到南”的顺序,拆解 7 个核心模块。
3.1 控制器层(Controller Layer)
- UniversalController 对外暴露 100% OpenAI 兼容 的 RESTful 接口,让前端/业务方无感切换。
- ModelManagerController 提供
/admin/models
等运维端点,支持 热更新 实例列表。 - AutoMergeController 监听 本地配置文件,秒级合并 配置文件并推送到所有节点,无需重启。
3.2 服务层(Service Layer)
一句话总结:“所有策略都是插件”。
- LoadBalancerFactory 随机 / 轮询 / 最少连接 / IP Hash,四种算法随时切换。
- RateLimiterFactory 令牌桶 / 漏桶 / 滑动窗口 / 预热,支持 按 IP、按模型 双重维度隔离。
- CircuitBreakerFactory 基于 失败率 + 半开探测 的经典熔断,30 秒无心跳自动摘除。
3.3 适配器层(Adapter Layer)
推理引擎 | 适配器 |
---|---|
GpuStack | GpuStackAdapter |
Ollama | OllamaAdapter |
vLLM | VLLMAdapter |
OpenAI | OpenAIAdapter |
所有适配器实现 BaseAdapter
接口,开发者在 10 行代码 内即可新增一家后端。
3.4 负载均衡层(Load Balancer Layer)
- LeastConnections 在 GPU 机器显存/Token 吞吐量不均时尤其有效。
- IPHash 用于“长会话”场景,保证同一客户端多次请求落到同一实例,避免重复加载 LoRA。
3.5 限流层(Rate Limiting Layer)
- TokenBucket 适合突发流量(如秒杀问答)。
- WarmUp 在新节点加入集群时缓慢放量,防止冷启动打爆显存。
3.6 熔断层(Circuit Breaker Layer)
- 状态机完全基于 Reactor 实现,无锁、无阻塞。
- 支持 按模型、按实例 双维度熔断,粒度更细。
3.7 存储层(Storage Layer)
- MemoryConfigStore:纳秒级读取,用于运行时策略。
- FileConfigStore:基于 Git 的版本化管理,支持 一键回滚。
4. 技术栈 & 设计原则
类别 | 选型 | 理由 |
---|---|---|
语言 | Java 17 | LTS + 虚拟线程预览 |
框架 | Spring Boot 3.5.x + WebFlux | 原生响应式,背压友好 |
构建 | Maven 3.8 + Wrapper | CI/CD 零依赖 |
文档 | SpringDoc OpenAPI | 自动生成,可在线调试 |
监控 | Micrometer + Actuator | 对接 Prometheus + Grafana |
代码质量 | Checkstyle + SpotBugs + JaCoCo | PR 即检测,覆盖率 80%+ |
设计原则 4 句话 讲完:
- 响应式:所有 I/O 非阻塞。
- 模块化:每个策略都是接口 + 自动装配,拔插无重启。
- 可观测:指标、追踪、日志三位一体,出问题 5 分钟定位。
5. 扩展点实战:10 分钟接入「Xinference」
本节以 Xorbits Inference(Xinference) 为例,完整演示“零侵入”接入一个新的推理后端。
只要 3 步,不重启、不发布、不改动业务代码。
5.1 需求背景
业务方已有 3 条 GPU 机器,希望把 Xinference 作为第 4 条算力池,并满足:
- 复用现有
/v1/chat/completions
接口,前端无感; - 支持 流式/非流式 双模式;
- 沿用 JAiRouter 的负载、限流、熔断策略。
5.2 步骤 1:实现 Adapter(3 分钟)
新建 XinferenceAdapter.java
,核心代码 60 行:
package org.unreal.modelrouter.adapter.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.stereotype.Component;
import org.unreal.modelrouter.adapter.AdapterCapabilities;
import org.unreal.modelrouter.adapter.BaseAdapter;
import org.unreal.modelrouter.dto.ChatDTO;
import org.unreal.modelrouter.model.ModelServiceRegistry;
import org.unreal.modelrouter.monitoring.collector.MetricsCollector;
/**
* Xinference Adapter
* Xinference 与 OpenAI v1 协议 100% 兼容,仅需补充缺失字段
*/
@Component
public class XinferenceAdapter extends BaseAdapter {
private final ObjectMapper om = new ObjectMapper();
public XinferenceAdapter(ModelServiceRegistry registry, MetricsCollector collector) {
super(registry, collector);
}
@Override
public AdapterCapabilities supportCapability() {
return AdapterCapabilities.all(); // 支持 chat / embedding / tts / stt ...
}
@Override
protected String getAdapterType() {
return "xinference";
}
/* ---------- 请求转换 ---------- */
@Override
protected Object transformRequest(Object req, String adapterType) {
if (req instanceof ChatDTO.Request r) {
ObjectNode node = om.createObjectNode();
node.put("model", r.model());
node.set("messages", om.valueToTree(r.messages()));
if (r.temperature() != null) node.put("temperature", r.temperature());
if (r.maxTokens() != null) node.put("max_tokens", r.maxTokens());
if (r.stream() != null) node.put("stream", r.stream());
return node;
}
return req; // embedding / tts / stt 同理
}
/* ---------- 响应增强 ---------- */
@Override
protected String transformStreamChunk(String chunk) {
// 若 chunk 中无 system_fingerprint,补充
if (chunk.startsWith("data: ")) {
try {
String json = chunk.substring(6);
if ("[DONE]".equals(json.trim())) return chunk;
ObjectNode node = (ObjectNode) om.readTree(json);
if (!node.has("system_fingerprint")) {
node.put("system_fingerprint", "xinference-adapter");
}
return "data: " + node;
} catch (Exception ignored) {}
}
return chunk;
}
}
要点
- 继承
BaseAdapter
即可拿到 负载、限流、熔断、重试 等全部能力; - 只实现差异处:Xinference 与 OpenAI 协议一致,只需 补全缺失字段(如
system_fingerprint
)。
5.3 步骤 2:声明式配置(1 分钟)
在 Git 仓库的 application-xinference.yml
中新增:
jairouter:
adapters:
xinference:
enabled: true
hosts:
- http://10.0.0.21:9997 # GPU-4
- http://10.0.0.22:9997 # GPU-5
load-balancer: round-robin # 可选 random / least-connections / ip-hash
rate-limit:
permits-per-second: 100
burst-capacity: 200
circuit-breaker:
failure-rate-threshold: 0.5
wait-duration: 30s
5.4 步骤 3:Git Push → 30 秒生效(无需重启)
git add application-xinference.yml
git commit -m "feat: add xinference backend"
git push origin main
- JAiRouter 监听配置仓库的 Webhook,30 秒内把新配置热加载到全部节点;
- 业务方立即可以通过
/v1/chat/completions
访问 Xinference 算力,前端 0 改动。
5.5 验证
curl -X POST https://gateway.example.com/v1/chat/completions \
-H "Authorization: Bearer sk-xxx" \
-d '{
"model": "xinference-llama3-8b",
"messages": [{"role":"user","content":"你好"}],
"stream": true
}'
返回:
data: {"id":"xinference-123","object":"chat.completion.chunk",...,"system_fingerprint":"xinference-adapter"}
...
data: [DONE]
5.6 效果
指标 | 结果 |
---|---|
新增代码行数 | 60 行 |
上线耗时 | 1 次 Git Push ≈ 30 秒 |
业务改动 | 0 |
是否重启 | 否 |
只要遵循 Adapter + 配置即代码 范式,任何推理引擎 都可以在 10 分钟内接入 JAiRouter。
6. 结语:面向未来的 AI 网关
JAiRouter 把 “协议差异、流量调度、故障自愈、配置管理” 四大痛点抽象成四大模块,通过响应式编程 + 配置即代码,让 AI 算力像自来水一样随开随用。
- 开源仓库:https://github.com/Lincoln-cn/jairouter
- 商业支持:发送邮件至 sodlinken@gmail.com
如果这篇文章对你有帮助,别忘了 点赞 + 收藏 + 关注三连!
评论区欢迎交流落地经验,下一个版本的功能可能由你决定!