第1975篇:混合云AI架构——敏感数据留本地、通用能力用公有云的拆分策略
2026/4/30大约 9 分钟
第1975篇:混合云AI架构——敏感数据留本地、通用能力用公有云的拆分策略
上一篇讲了数据不出境的合规方案,这一篇把视角拉高,讲整体的混合云 AI 架构怎么设计。
混合云不是新概念,但用在 AI 上有它自己的特殊性:AI 调用本质上是"把数据发给模型",数据和模型在哪里,决定了你的架构边界在哪里。如果设计不好,要么数据合规出问题,要么成本失控,要么性能很差。
这篇从实际项目经验出发,讲一个可以落地的混合云 AI 架构。
为什么混合云是更好的选择
纯本地部署和纯公有云各有明显缺陷:
纯本地部署的问题:
- GPU 资源成本高,算个账:一台 A100 80GB 机器大概 30 万+,一个小团队很难支撑
- 模型版本更新要自己维护,跟不上公有云的速度
- 弹性差,峰值时撑不住,平时资源浪费
纯公有云的问题:
- 敏感数据合规风险
- 长期成本随用量线性增长,高并发时费用惊人
- 网络延迟不可控
混合云方案把两者结合:
- 敏感数据和高频通用请求走本地(成本可控,合规)
- 复杂推理和低频高质量请求走公有云(效果好,弹性好)
架构全貌
核心决策:请求应该去哪里
混合云架构的核心是路由决策,要在毫秒级别完成,且不能出错。
@Service
@Slf4j
public class HybridRouterService {
@Autowired
private SensitiveDataDetector sensitiveDetector;
@Autowired
private ComplexityAnalyzer complexityAnalyzer;
@Autowired
private CostEstimator costEstimator;
@Autowired
private LoadMonitor loadMonitor;
public RoutingDecision route(AIRequest request) {
// 决策树:多维度综合判断
RoutingContext ctx = buildContext(request);
// 第一优先级:数据安全(硬约束)
if (ctx.hasSensitiveData()) {
return RoutingDecision.local(
selectLocalModel(ctx),
"含敏感数据,强制本地"
);
}
// 第二优先级:缓存命中
Optional<String> cached = checkSemanticCache(request.getMessage());
if (cached.isPresent()) {
return RoutingDecision.cache(cached.get(), "语义缓存命中");
}
// 第三优先级:复杂推理任务
if (ctx.getComplexityScore() > 0.8) {
return RoutingDecision.cloud(
CloudProvider.DEEPSEEK_R1,
"复杂推理,使用R1模型"
);
}
// 第四优先级:本地负载判断
if (loadMonitor.getLocalLoadPercent() < 70) {
// 本地有余量,优先用本地(省钱)
return RoutingDecision.local(
selectLocalModel(ctx),
"本地有余量,优先本地"
);
}
// 第五优先级:成本估算
double localCost = costEstimator.estimateLocal(ctx);
double cloudCost = costEstimator.estimateCloud(ctx);
if (cloudCost < localCost * 1.5) {
// 云端成本不超过本地的1.5倍,走云端
return RoutingDecision.cloud(
selectCloudModel(ctx),
"负载高且云端成本可接受"
);
}
return RoutingDecision.local(selectLocalModel(ctx), "默认本地");
}
private String selectLocalModel(RoutingContext ctx) {
// 根据任务类型和长度选择本地模型
if (ctx.getInputTokens() < 512) {
return "qwen2.5-7b"; // 短问题用小模型,快
} else {
return "qwen2.5-14b"; // 长文本用大模型,准
}
}
private CloudProvider selectCloudModel(RoutingContext ctx) {
if (ctx.isCodeTask()) return CloudProvider.ALIBABA_QWEN_CODER;
if (ctx.isChineseTextTask()) return CloudProvider.ALIBABA_QWEN_MAX;
return CloudProvider.ALIBABA_QWEN_PLUS;
}
}负载监控:知道本地资源是否有余量
路由决策依赖实时的负载数据,这个模块必须轻量、准确:
@Component
@Slf4j
public class LocalLoadMonitor {
// GPU 利用率(通过 NVML 库获取)
@Scheduled(fixedRate = 5000) // 每5秒更新一次
public void refreshMetrics() {
try {
// 调用 nvidia-smi 获取 GPU 使用率
ProcessBuilder pb = new ProcessBuilder(
"nvidia-smi", "--query-gpu=utilization.gpu,memory.used,memory.total",
"--format=csv,noheader,nounits"
);
Process process = pb.start();
String output = new String(process.getInputStream().readAllBytes()).trim();
// 解析输出
String[] parts = output.split(",\\s*");
int gpuUtilization = Integer.parseInt(parts[0]);
long memUsed = Long.parseLong(parts[1]);
long memTotal = Long.parseLong(parts[2]);
this.currentGpuUtil = gpuUtilization;
this.currentMemUtil = (int)(memUsed * 100 / memTotal);
} catch (Exception e) {
log.warn("获取GPU指标失败,使用估算值: {}", e.getMessage());
}
}
private volatile int currentGpuUtil = 0;
private volatile int currentMemUtil = 0;
public int getLocalLoadPercent() {
// 取GPU利用率和显存使用率的最大值作为整体负载
return Math.max(currentGpuUtil, currentMemUtil);
}
public boolean isOverloaded() {
return getLocalLoadPercent() > 85;
}
}语义缓存:最便宜的"模型"
相似问题大量重复是企业 AI 应用的普遍现象。语义缓存可以大幅降低实际调用量:
@Service
public class SemanticCacheService {
@Autowired
private EmbeddingModel embeddingModel;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private VectorStore cacheVectorStore; // 专门用于缓存的向量库
private static final double SIMILARITY_THRESHOLD = 0.93;
private static final Duration CACHE_TTL = Duration.ofHours(24);
/**
* 查缓存
*/
public Optional<String> get(String query) {
List<Document> similar = cacheVectorStore.similaritySearch(
SearchRequest.query(query)
.withTopK(1)
.withSimilarityThreshold(SIMILARITY_THRESHOLD)
);
if (similar.isEmpty()) return Optional.empty();
String cacheKey = (String) similar.get(0).getMetadata().get("cache_key");
String cached = redisTemplate.opsForValue().get("ai_cache:" + cacheKey);
if (cached == null) {
// 向量索引存在但 Redis 里已过期,清理向量索引
cacheVectorStore.delete(List.of(similar.get(0).getId()));
return Optional.empty();
}
log.info("语义缓存命中,相似问题: {}", similar.get(0).getContent());
return Optional.of(cached);
}
/**
* 写缓存
*/
public void put(String query, String response) {
String cacheKey = UUID.randomUUID().toString();
// 存向量索引
Document doc = new Document(
query,
Map.of("cache_key", cacheKey)
);
cacheVectorStore.add(List.of(doc));
// 存响应内容
redisTemplate.opsForValue().set(
"ai_cache:" + cacheKey,
response,
CACHE_TTL
);
}
}语义缓存的相似度阈值是个关键参数。0.93 是我实测的合理值:
- 高于 0.95:缓存命中率低,效果不明显
- 低于 0.90:可能返回语义相近但答案不同的错误缓存
对于问题答案会随时间变化的场景(比如"今天天气"),要么不缓存,要么加时间戳判断。
本地模型的高可用设计
本地 GPU 节点出问题是迟早的事,必须设计高可用:
@Service
public class LocalModelCluster {
private final List<LocalModelNode> nodes;
private final LoadBalancer loadBalancer;
@Autowired
public LocalModelCluster(List<LocalModelNode> nodes) {
this.nodes = nodes;
this.loadBalancer = new WeightedRoundRobinLoadBalancer(nodes);
}
public String inference(String prompt, String modelName) {
// 尝试所有节点
List<LocalModelNode> availableNodes = nodes.stream()
.filter(LocalModelNode::isHealthy)
.toList();
if (availableNodes.isEmpty()) {
throw new LocalModelUnavailableException("所有本地节点不可用");
}
LocalModelNode selectedNode = loadBalancer.select(availableNodes);
try {
return selectedNode.inference(prompt, modelName);
} catch (Exception e) {
log.warn("节点 {} 推理失败,尝试其他节点: {}", selectedNode.getId(), e.getMessage());
selectedNode.markUnhealthy();
// 故障转移到下一个节点
return availableNodes.stream()
.filter(n -> !n.getId().equals(selectedNode.getId()))
.filter(LocalModelNode::isHealthy)
.findFirst()
.map(n -> n.inference(prompt, modelName))
.orElseThrow(() -> new LocalModelUnavailableException("本地集群全部不可用"));
}
}
}
// 健康检查
@Component
@Scheduled(fixedRate = 30000)
public class LocalNodeHealthChecker {
@Autowired
private LocalModelCluster cluster;
public void checkHealth() {
cluster.getNodes().forEach(node -> {
try {
// 发送简单的 ping 请求
String response = node.inference("你好", "qwen2.5-7b");
if (response != null && !response.isEmpty()) {
node.markHealthy();
}
} catch (Exception e) {
log.warn("节点 {} 健康检查失败: {}", node.getId(), e.getMessage());
node.markUnhealthy();
}
});
}
}弹性策略:本地压力大时自动溢出到云端
这是混合云架构最有价值的特性——当本地资源撑不住时,自动把请求溢出到公有云:
@Service
public class ElasticAIService {
@Autowired
private LocalLoadMonitor loadMonitor;
@Autowired
private LocalModelCluster localCluster;
@Autowired
private ChatClient cloudClient;
@Autowired
private SensitiveDataDetector sensitiveDetector;
/**
* 弹性路由:本地优先,超载时溢出云端
*/
public String elasticChat(String message) {
boolean isSensitive = sensitiveDetector.detect(message) == LEVEL_1_MUST_LOCAL;
if (isSensitive) {
// 敏感数据:本地必须处理,排队等待
return localCluster.inferenceWithQueue(message, "qwen2.5-14b");
}
if (loadMonitor.isOverloaded()) {
// 本地超载,溢出到云端
log.info("本地负载{}%,溢出到云端处理", loadMonitor.getLocalLoadPercent());
return cloudClient.prompt().user(message).call().content();
}
return localCluster.inference(message, "qwen2.5-7b");
}
}成本监控与预算控制
混合云的成本是动态变化的,必须实时监控:
@Service
public class CostControlService {
@Autowired
private CostRepository costRepository;
@Autowired
private AlertService alertService;
// 月度预算(元)
@Value("${ai.monthly-budget:5000}")
private double monthlyBudget;
private AtomicDouble currentMonthCost = new AtomicDouble(0);
@PostConstruct
public void loadCurrentMonthCost() {
double cost = costRepository.sumCostByMonth(YearMonth.now());
currentMonthCost.set(cost);
}
/**
* 检查是否还有预算
*/
public BudgetStatus checkBudget() {
double spent = currentMonthCost.get();
double remaining = monthlyBudget - spent;
double usagePercent = spent / monthlyBudget * 100;
if (usagePercent >= 100) {
return BudgetStatus.EXHAUSTED;
} else if (usagePercent >= 90) {
alertService.sendWarning(String.format("AI预算已用%.1f%%,剩余%.2f元", usagePercent, remaining));
return BudgetStatus.CRITICAL;
} else if (usagePercent >= 80) {
return BudgetStatus.WARNING;
}
return BudgetStatus.NORMAL;
}
/**
* 预算告急时的降级策略
*/
public ChatClient selectClientByBudget(boolean isSensitive) {
if (isSensitive) return localClient; // 敏感数据永远走本地
BudgetStatus status = checkBudget();
return switch (status) {
case NORMAL -> cloudClient; // 正常,走云端
case WARNING -> localClient; // 预警,切回本地
case CRITICAL -> localClient; // 严重,切回本地
case EXHAUSTED -> {
// 预算用完,只能用本地,云端请求全部拒绝
log.error("AI云端预算已耗尽,本月剩余请求全部走本地模型");
yield localClient;
}
};
}
}RAG 的混合架构
知识库也需要混合部署:
@Service
public class HybridRAGService {
@Autowired
@Qualifier("localVectorStore") // 本地 Milvus,存敏感知识
private VectorStore localVS;
@Autowired
@Qualifier("cloudVectorStore") // 阿里云向量服务,存通用知识
private VectorStore cloudVS;
@Autowired
private ComplianceAIGateway gateway;
public String ragQuery(String question, String userId) {
// 1. 并行检索两个知识库
CompletableFuture<List<Document>> localFuture = CompletableFuture.supplyAsync(
() -> localVS.similaritySearch(SearchRequest.query(question).withTopK(3))
);
CompletableFuture<List<Document>> cloudFuture = CompletableFuture.supplyAsync(
() -> cloudVS.similaritySearch(SearchRequest.query(question).withTopK(3))
);
List<Document> localDocs = localFuture.join();
List<Document> cloudDocs = cloudFuture.join();
// 2. 合并结果,本地文档优先(可能包含更新的业务信息)
List<Document> allDocs = new ArrayList<>();
allDocs.addAll(localDocs);
allDocs.addAll(cloudDocs);
// 3. 构建上下文
String context = allDocs.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n---\n"));
// 4. 根据检索结果中是否有敏感内容,决定用哪个模型
boolean hasLocalSensitiveDoc = localDocs.stream()
.anyMatch(d -> Boolean.TRUE.equals(d.getMetadata().get("sensitive")));
String prompt = buildRAGPrompt(context, question);
if (hasLocalSensitiveDoc) {
return gateway.chatLocal(prompt);
} else {
return gateway.chatCloud(prompt);
}
}
}部署拓扑建议
根据团队规模和业务量,有三种规模的推荐配置:
小型团队(并发 < 10,月请求 < 100万):
- 1台 RTX 4090(24GB)跑 Qwen2.5-14B
- 公有云作为主要承载
- 本地主要用于合规请求
中型团队(并发 10-50,月请求 100-1000万):
- 2台 A10(24GB×2),分别跑 7B 和 14B
- 公有云分担峰值
- 语义缓存命中率目标 40%+
大型团队(并发 50+,月请求 > 1000万):
- 4+ 台 A100/H100,跑 Qwen2.5-72B INT4
- 公有云仅用于复杂推理(R1)
- 自建向量库集群(Milvus 分布式)
一个实际的效果数据
我们一个项目,改成混合云架构后的变化(对比纯公有云):
- 月度 API 费用降低约 65%(高频重复问题走本地或缓存)
- P99 延迟从 3.2s 降到 1.8s(本地模型不受公网抖动影响)
- 合规风险:从"不确定"变为"可以出具技术证明"
- GPU 利用率平均在 60-70%(合理区间,没有浪费也没有超载)
当然,前期 GPU 硬件投入是真实成本,需要根据业务规模算 ROI 再决策。
