前言:最近帮公司把AI能力从单体应用迁移到微服务架构,踩了无数个坑。这篇文章不是Hello World,而是真实生产环境中的血泪总结。如果你也在考虑"怎么把ChatGPT接入Spring Cloud",建议先看完这篇再动手。
一、为什么你的AI项目总是"玩具级"?
去年用Spring Boot写了个AI问答Demo,本地跑得很爽,一上线就崩。问题在哪?
单体架构的AI应用三大痛点:
- 模型调用阻塞主线程 - 一个慢请求拖垮整个服务
- API Key裸奔在代码里 - 安全审计直接挂红灯
- 流式响应没法做负载均衡 - Nginx都懵圈了
直到把架构改成这样,才真正敢上生产环境:
┌─────────────┐ ┌─────────────┐ ┌─────────────────┐
│ Gateway │────▶│ AI-Service │────▶│ LLM Provider │
│ (流式支持) │ │ (熔断/限流) │ │ (OpenAI/Claude) │
└─────────────┘ └─────────────┘ └─────────────────┘
│
▼
┌─────────────┐
│ Vector-DB │
│ (RAG检索) │
└─────────────┘
核心思路:把AI能力拆成独立服务,别和业务代码耦合!
二、环境准备:别在版本上浪费时间
先给个能直接跑的pom.xml配置,这些版本我测过兼容性:
xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
</parent>
<properties>
<spring-cloud.version>2023.0.1</spring-cloud.version>
<spring-ai.version>0.8.1</spring-ai.version>
</properties>
<dependencies>
<!-- Spring Boot基础 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Cloud Alibaba Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Spring AI核心 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<!-- 流式响应必备 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>
避坑点:
- Spring Boot 3.x必须配合JDK 17+,别问,问就是踩过坑
- Spring AI 0.8+才支持函数调用(Function Calling),老版本别用
- Nacos 2.2+才支持gRPC,配置中心用HTTP就行
三、核心架构:AI-Service设计
3.1 配置隔离:别让Key泄露
生产环境千万别写死在application.yml里,用Nacos配置中心+加密:
java
@Configuration
@ConfigurationProperties(prefix = "ai.openai")
@Data
public class OpenAiProperties {
private String apiKey;
private String baseUrl = "https://api.openai.com";
private String model = "gpt-4-turbo-preview";
private Duration timeout = Duration.ofSeconds(30);
// 连接池配置,高并发必备
private int maxConnections = 100;
private int maxIdleTime = 20;
}
Nacos配置示例(记得开启加密插件):
yaml
ai:
openai:
api-key: "${ENC{your-encrypted-key-here}}"
base-url: "https://api.openai.com"
model: "gpt-4-turbo-preview"
3.2 服务层:流式响应+熔断器
这是核心代码,直接复制改改就能用:
java
@Service
@Slf4j
public class AiChatService {
@Autowired
private OpenAiChatClient chatClient;
@Autowired
private CircuitBreakerRegistry circuitBreakerRegistry;
/**
* 同步调用 - 适合简单问答
*/
@CircuitBreaker(name = "aiChat", fallbackMethod = "fallbackChat")
public String simpleChat(String message) {
log.info("收到请求: {}", message);
return chatClient.call(
new Prompt(message,
OpenAiChatOptions.builder()
.withTemperature(0.7)
.withMaxTokens(2000)
.build()
)
).getResult().getOutput().getContent();
}
/**
* 流式调用 - 生产环境主推,用户体验好
*/
public Flux<String> streamChat(String message, String sessionId) {
CircuitBreaker cb = circuitBreakerRegistry.circuitBreaker("aiStream");
return chatClient.stream(
new Prompt(message,
OpenAiChatOptions.builder()
.withStreamUsage(true) // 开启流式用量统计
.build()
))
.map(response -> response.getResult().getOutput().getContent())
.filter(Objects::nonNull)
.transformDeferred(RxReactiveStreams.toFlux())
.transform(cb::executeFlux) // 熔断器包装
.doOnError(e -> log.error("流式调用失败, session={}", sessionId, e))
.onErrorResume(e -> Flux.just("服务繁忙,请稍后重试"));
}
/**
* 降级方法
*/
private String fallbackChat(String message, Exception ex) {
log.warn("触发熔断, 异常: {}", ex.getMessage());
return "当前AI服务繁忙,请1分钟后再试";
}
}
关键设计说明:
- 用
CircuitBreaker防止LLM API挂掉时拖垮服务 streamChat返回Flux<String>,前端用SSE接收- 每个请求带
sessionId,方便链路追踪
3.3 控制器:SSE流式输出
前端要的是打字机效果,别用WebSocket,SSE更简单:
java
@RestController
@RequestMapping("/api/ai")
@RequiredArgsConstructor
public class AiChatController {
private final AiChatService aiChatService;
/**
* 流式聊天接口 - 前端EventSource直接连
*/
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> streamChat(
@RequestParam String message,
@RequestHeader("X-Session-Id") String sessionId) {
// 限流检查
if (!rateLimiter.tryAcquire()) {
return Flux.just(ServerSentEvent.builder("请求过于频繁").build());
}
return aiChatService.streamChat(message, sessionId)
.map(content -> ServerSentEvent.<String>builder()
.id(UUID.randomUUID().toString())
.event("message")
.data(content)
.build())
.concatWith(Flux.just(ServerSentEvent.<String>builder()
.event("complete")
.data("[DONE]")
.build()));
}
}
前端接收示例(Vue3):
javascript
const eventSource = new EventSource(`/api/ai/stream?message=${encodeURIComponent(msg)}`);
eventSource.onmessage = (e) => {
if (e.data === '[DONE]') {
eventSource.close();
return;
}
// 逐字显示效果
responseText.value += e.data;
};
四、微服务治理:网关+熔断+限流
4.1 Gateway路由配置
AI服务单独部署,通过Gateway暴露,这里配置流式响应支持:
yaml
spring:
cloud:
gateway:
routes:
- id: ai-service
uri: lb://ai-service
predicates:
- Path=/api/ai/**
filters:
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
注意点:
lb://表示用Nacos服务发现- 流式响应别加
ModifyResponseBody过滤器,会缓冲整个流 - 限流用Redis实现,集群部署时共享状态
4.2 熔断器配置
Resilience4j比Hystrix轻量,适合AI场景:
yaml
resilience4j:
circuitbreaker:
configs:
default:
slidingWindowSize: 10
minimumNumberOfCalls: 5
permittedNumberOfCallsInHalfOpenState: 3
automaticTransitionFromOpenToHalfOpenEnabled: true
waitDurationInOpenState: 30s
failureRateThreshold: 50
eventConsumerBufferSize: 10
instances:
aiChat:
baseConfig: default
waitDurationInOpenState: 60s # AI服务恢复慢,多给点时间
实战经验:
- LLM API偶尔抽风(429错误),熔断阈值别设太低
- 半开状态放3个请求测试,比默认值1个更稳妥
- 开启自动过渡,别手动干预
五、RAG增强:别让模型瞎编
生产环境AI必须接知识库,不然回答不靠谱。用Redis Stack做向量库,比Pinecone省钱:
5.1 向量存储配置
java
@Configuration
public class VectorStoreConfig {
@Bean
public VectorStore vectorStore(RedisTemplate<String, String> redisTemplate,
EmbeddingClient embeddingClient) {
// 使用RedisJSON + RediSearch
return RedisVectorStore.builder(redisTemplate, embeddingClient)
.withIndexName("kb-index")
.withPrefix("kb:")
.withMetadataFields(
MetadataField.text("category"),
MetadataField.numeric("timestamp")
)
.initializeSchema(true)
.build();
}
}
5.2 RAG检索服务
java
@Service
public class RagService {
@Autowired
private VectorStore vectorStore;
@Autowired
private ChatClient chatClient;
public String chatWithKnowledge(String question, String category) {
// 1. 向量化检索
SearchRequest searchRequest = SearchRequest.query(question)
.withTopK(5)
.withSimilarityThreshold(0.7)
.withFilterExpression("category == '" + category + "'");
List<Document> relevantDocs = vectorStore.similaritySearch(searchRequest);
// 2. 构造Prompt
String context = relevantDocs.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n---\n"));
String prompt = """
基于以下参考资料回答问题:
%s
问题:%s
要求:
1. 如果资料不足以回答,明确告知"根据现有资料无法确定"
2. 不要编造信息
3. 引用资料来源
""".formatted(context, question);
// 3. 调用模型
return chatClient.call(prompt);
}
}
数据导入脚本(Python辅助):
python
# 把公司文档导入向量库
from redis import Redis
from redis.commands.search.field import VectorField, TextField
def index_documents(docs):
redis_client = Redis(host='localhost', port=6379, decode_responses=True)
for i, doc in enumerate(docs):
# 用Spring AI的Embedding API生成向量
embedding = get_embedding(doc['content'])
redis_client.hset(f"kb:{i}", mapping={
"content": doc['content'],
"category": doc['category'],
"embedding": np.array(embedding).tobytes()
})
六、生产环境 checklist
上线前逐项检查:
| 检查项 | 工具/方法 | 合格标准 |
|---|---|---|
| API Key安全 | Vault/Nacos加密 | 代码中无明文Key |
| 流式响应超时 | Gateway配置 | 5分钟不断连 |
| 模型降级策略 | 多模型路由 | OpenAI挂了转Claude |
| Token消耗监控 | Micrometer + Prometheus | 按用户统计用量 |
| 敏感词过滤 | 本地敏感词库 | 政治/暴力内容拦截 |
| 并发压力测试 | JMeter | 100并发响应<3s |
多模型降级代码:
java
@Component
public class ModelRouter {
private final List<ChatClient> clients = new ArrayList<>();
private final AtomicInteger counter = new AtomicInteger(0);
public ChatClient getAvailableClient() {
// 简单轮询,生产环境用更复杂的健康检查
int index = counter.getAndIncrement() % clients.size();
return clients.get(index);
}
}
七、性能优化实录
最后分享几个压测后的优化点:
1. HTTP连接池必须调
java
@Bean
public ClientHttpRequestFactory requestFactory() {
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(5000);
factory.setReadTimeout(30000);
// 关键:连接复用,否则高并发会炸
factory.setHttpClient(HttpClientBuilder.create()
.setMaxConnTotal(200)
.setMaxConnPerRoute(50)
.build());
return factory;
}
2. 流式响应别用Jackson
默认Jackson会缓冲整个响应,改用StreamingResponseBody或者WebFlux。
3. 上下文压缩
长对话历史别全传,用摘要算法压缩:
java
public String compressHistory(List<Message> history) {
if (history.size() > 10) {
// 早期消息用LLM生成摘要
String earlyContext = summarize(history.subList(0, 5));
return earlyContext + recentMessages;
}
return format(history);
}
心得总结
Spring Boot + Spring Cloud + AI 不是简单对接API,而是完整的工程化问题。核心就三点:
- 服务隔离 - AI能力独立部署,别和业务耦合
- 流式优先 - 用户体验好,资源占用反而更低
- 防御编程 - 熔断、降级、限流一个不能少
代码已经脱敏,可以直接拿去改改用。有问题评论区见,看到都会回,也可以私信+关注。
转载请注明出处,商业使用请联系授权。