最近在做一个电商智能客服项目其中“货盘”系统是核心难点。简单来说货盘就是客服与用户对话时系统能实时、精准地推荐的商品列表。这背后涉及到海量商品数据的实时查询、用户意图的快速匹配以及在高并发下保证推荐准确性和系统稳定性的挑战。今天就来聊聊我们是如何从零开始设计并优化这套系统的。1. 背景与核心痛点为什么传统方案行不通在电商大促期间智能客服的咨询量会呈指数级增长。我们的货盘系统最初是单体架构直接耦合在客服主应用中很快就暴露了几个致命问题高并发查询瓶颈每次客服发起商品推荐请求系统都需要从庞大的商品库中进行多维度筛选如关键词、类目、用户画像、库存状态数据库连接池瞬间被打满响应时间从几十毫秒飙升到数秒严重影响客服效率。数据一致性问题商品的价格、库存、上下架状态是实时变化的。客服端看到的推荐货盘必须和用户在前端看到的商品信息严格一致。单体应用里我们靠频繁轮询数据库来更新缓存不仅延迟高还给数据库带来了巨大压力。实时推荐准确性用户的意图可能在对话中快速转变比如从“红色连衣裙”问到“有优惠吗”系统需要结合最新的对话上下文和实时商品数据如秒杀价进行推荐。旧系统数据处理链路长导致推荐的商品可能已经售罄或涨价体验很差。这些问题迫使我们思考必须对系统进行彻底的重构。2. 技术选型为什么是微服务事件驱动我们首先否定了在单体架构上修修补补的方案决定拥抱微服务。将货盘系统拆分为独立服务好处显而易见资源隔离、独立伸缩、技术栈灵活。但微服务带来了新的挑战服务间如何高效、可靠地通信我们对比了同步的RPC调用和异步的事件驱动模式同步RPC如Feign调用链路过长时一个下游服务抖动就会导致整个推荐链路超时形成级联故障可用性低。异步事件驱动当商品信息发生变更如价格更新、库存扣减时只需发布一个事件。货盘服务订阅这些事件异步更新自己的数据视图。这样查询链路和更新链路解耦推荐查询不再直接依赖核心商品库而是依赖自己维护的、最终一致的数据副本抗压能力极大提升。因此我们确定了“微服务 事件驱动”的核心架构。技术栈上选择了成熟的Spring Cloud Alibaba生态和Kafka消息队列。3. 核心实现拆解关键模块与代码整个系统可以划分为几个核心微服务商品中心、货盘计算服务、推荐引擎服务和缓存与事件中心。3.1 服务治理与发现我们使用Nacos作为服务注册与配置中心。每个服务启动时向Nacos注册货盘计算服务通过OpenFeign声明式客户端调用推荐引擎服务负载均衡由Ribbon自动完成。// 货盘服务中通过Feign调用推荐服务 FeignClient(name recommendation-service, configuration FeignConfig.class) public interface RecommendationServiceClient { PostMapping(/api/v1/recommend) ResponseDTOListProductDTO getRecommendation(RequestBody QueryContext context); } // 使用示例 Service public class PaletteService { Autowired private RecommendationServiceClient recommendationClient; public ListProductDTO getRealTimePalette(String sessionId, String query) { // 构建查询上下文包含会话历史、用户ID等 QueryContext context buildContext(sessionId, query); // Feign调用内部已完成负载均衡 return recommendationClient.getRecommendation(context).getData(); } }3.2 事件驱动的数据同步这是保证货盘数据实时性的关键。商品中心任何核心变更都会向Kafka发送事件。// 商品服务中发布库存变更事件 Service public class ProductStockService { Autowired private KafkaTemplateString, Object kafkaTemplate; public void updateStock(Long skuId, Integer delta) { // 1. 更新数据库库存 // ... 数据库更新逻辑 // 2. 发布领域事件 ProductStockChangedEvent event new ProductStockChangedEvent(skuId, newStock); kafkaTemplate.send(product-stock-topic, event); } } // 货盘服务中消费事件更新本地缓存/存储 Component public class ProductStockConsumer { KafkaListener(topics product-stock-topic, groupId palette-group) public void consumeStockEvent(ProductStockChangedEvent event) { // 更新Redis中的商品库存缓存 String cacheKey product:stock: event.getSkuId(); redisTemplate.opsForValue().set(cacheKey, event.getStock()); // 同时可以更新本地Caffeine缓存如果有一级缓存的话 localCache.put(cacheKey, event.getStock()); } }3.3 智能推荐的核心逻辑推荐引擎服务内部我们实现了基于向量检索的快速匹配。将商品特征和用户查询同时嵌入到向量空间通过近似最近邻搜索快速召回。# 推荐引擎服务示例 (Python Faiss) import faiss import numpy as np class VectorRecommendationEngine: def __init__(self, index_path): # 加载预构建的商品向量索引 self.index faiss.read_index(index_path) # 加载商品ID映射表 self.id_map self.load_id_map() def recommend(self, query_vector, top_k10): # query_vector: 用户查询句子的向量表示 query_vector np.array([query_vector]).astype(float32) # 搜索最相似的top_k个商品向量 distances, indices self.index.search(query_vector, top_k) # 将向量索引转换为商品ID product_ids [self.id_map[i] for i in indices[0]] return product_ids4. 性能优化实战从千级到百万级QPS架构搭好了性能优化才是真正的战场。我们主要做了以下几件事4.1 多级缓存设计这是提升读性能最有效的手段。我们设计了“本地缓存(Caffeine) 分布式缓存(Redis) 数据库”的三级缓存。本地缓存存储热点商品的静态信息如标题、图片过期时间短如30秒用于抵挡瞬时的超高频请求。Redis缓存策略一存储完整的货盘结果Key由“会话ID查询关键词”构成设置短过期时间如2分钟。适用于同一会话内重复查询。策略二存储商品维度的实时动态信息如库存、价格由Kafka事件驱动更新保证强实时性。缓存更新采用“事件驱动更新为主惰性更新为辅”的策略。商品变更事件触发Redis缓存更新缓存失效后从数据库加载并回填缓存。4.2 数据库分片与读写分离货盘服务需要维护一份商品数据的查询视图。我们使用MySQL并进行了垂直和水平分片。垂直分片将商品基础信息标题、类目和动态信息库存、价格分到不同库表减少单表宽度。水平分片按商品ID取模进行分库分表将数据均匀分布到多个实例上。读写分离货盘服务的数据同步作业从事件中恢复数据写入主库而推荐计算时的查询全部走从库极大减轻主库压力。4.3 压力测试数据对比优化前后我们使用JMeter进行了压测模拟1000并发用户持续请求指标优化前单体优化后微服务事件驱动缓存平均响应时间~1200ms~85msP99响应时间3000ms~200ms系统吞吐量(QPS)~800~9500数据库CPU使用率持续 90%峰值 ~40%可以看到核心接口的QPS提升了一个数量级响应时间变得平滑可控。5. 避坑指南那些我们踩过的“坑”5.1 事件顺序性保障Kafka单个分区内能保证顺序但商品事件如创建、更新、下架如果被发到不同分区消费时可能乱序。我们通过将同一商品ID的所有事件发送到同一个Kafka分区来解决。在生产者处指定分区键Partition Key为商品ID即可。5.2 分布式事务与最终一致性“扣减库存并发送事件”这不是一个本地事务。我们采用了“本地事务表消息表”的经典方案类似Transactional Outbox Pattern。在商品数据库中同一事务内完成库存扣减和事件记录插入event_outbox表。有一个单独的定时任务扫描event_outbox表将新事件发送到Kafka发送成功后标记事件为已发送。 这样就保证了“只要库存扣减成功事件最终一定会被发出”。5.3 冷启动与数据预热新服务上线或缓存全量失效时大量请求穿透到数据库会导致雪崩。我们的方案是启动预热服务启动时从数据库分批加载核心商品数据到Redis。热Key探测在网关层或Redis代理层实时统计请求频率对突然出现的热Key如爆款商品主动延长其本地缓存和Redis缓存的过期时间并提前加载更丰富的维度数据。6. 总结与展望回顾整个项目从单体架构的泥潭中走出通过微服务和事件驱动构建出一个弹性、高可用的智能客服货盘系统是一次非常有益的技术实践。核心经验是将同步的、紧耦合的调用转变为异步的、基于事件的数据流从而解耦服务、削峰填谷、提升韧性。未来还可以从以下几个方向深入推荐算法升级引入更复杂的深度学习模型并利用向量数据库如Milvus替代Faiss支持更灵活的实时向量检索与过滤。流量治理精细化结合Sentinel对不同的推荐策略接口进行细粒度流控在极端流量下保护核心链路。成本优化分析缓存命中率和数据热度对不常访问的商品数据采用成本更低的存储方案如从Redis迁移到SSD硬盘存储。架构没有银弹最好的架构永远是适合业务发展阶段、并能快速迭代的架构。希望我们这套“微服务事件驱动多级缓存”的组合拳能给大家在设计高并发、实时性要求高的系统时带来一些具体的参考思路。