
最近在做一个智能客服系统的重构项目原来的单体架构在高并发下实在是撑不住了响应慢得像蜗牛资源还经常打架。经过一番折腾我们最终基于微服务架构搞了一套新的方案效果拔群。今天就来聊聊我们是怎么做的把吞吐量提上去还把系统搞得稳稳当当的。1. 为啥要拆聊聊单体架构的那些“痛”最开始我们的智能客服系统是个大单体所有功能——用户接入、意图识别、知识库查询、对话管理、历史记录——都打包在一个应用里。平时流量不大还好一到促销或者活动问题就全暴露出来了。最明显的就是响应延迟。想象一下1000个用户同时问问题所有请求都挤在同一个服务实例里排队数据库连接池很快就被占满后面的请求只能干等着。我们监控到的平均响应时间RT从平时的200ms直接飙到2秒以上用户体验非常糟糕。另一个问题是资源竞争和利用率低。对话管理模块可能只需要少量CPU但知识库检索模块却是内存和IO大户。它们挤在一起资源分配很不均衡。为了应对峰值我们不得不按照最高负载来配置服务器资源导致大部分时间资源都是闲置的成本很高。而且任何一个小功能的bug发布都需要重启整个庞大的应用风险高上线周期也长。这种架构显然已经跟不上业务发展的速度了。2. 怎么拆微服务拆分的心得与策略拆服务不是乱拆我们的核心原则是“高内聚、低耦合”按业务域来划分边界。经过梳理我们把原来的大单体拆分成了以下几个核心服务网关服务 (api-gateway)所有流量的统一入口负责路由、认证、限流。用户会话服务 (session-service)管理用户的对话上下文和状态这是有状态的需要特别注意。意图识别服务 (nlu-service)核心AI能力分析用户问题背后的意图。这部分我们计划未来接入更复杂的NLP模型。知识库服务 (kb-service)提供问答对、文章、文档的检索功能对查询性能要求极高。消息处理服务 (message-service)处理异步消息比如离线消息、消息推送、客服坐席分配等。历史记录服务 (history-service)专门负责存储和查询对话历史与核心流程解耦。这样拆分后每个服务都可以独立开发、部署和伸缩。比如大促时我们可以单独给kb-service和nlu-service多扩几个实例而history-service可以保持不变资源利用一下子就合理了。3. 核心组件选型与实现三板斧稳住基本盘架构定了技术栈怎么选我们用了Spring Cloud全家桶搭配几个关键中间件。第一板斧Spring Cloud Gateway 统一网关所有外部请求先到这里。我们在这里做了几件事路由根据路径将请求分发到对应的微服务。认证鉴权集成JWT验证用户Token。限流使用Redis的令牌桶算法对每个API进行限流防止突发流量打垮下游服务。熔断降级集成Resilience4j当下游服务响应慢或失败时快速失败并返回兜底响应比如“系统繁忙请稍后再试”。第二板斧Redis 缓存高频问答知识库查询是性能瓶颈。我们分析发现80%的用户问题集中在20%的热点问答上。于是我们用Redis做了个二级缓存策略服务本地内存Caffeine缓存极热数据超快。分布式Redis缓存热点问答对和部分用户会话上下文。 这样大部分简单问答请求根本不用走到数据库RT直接降到毫秒级。第三板斧Kafka 处理异步消息不是所有操作都需要实时完成。比如对话结束后的历史记录落盘。用户对客服的评价统计。复杂的数据分析任务。 我们都通过Kafka发个消息由专门的消息处理服务去异步消费。这样核心链路问-答就非常轻快不会因为这些后台任务而阻塞。4. 代码实战保证问答接口的幂等性在高并发下用户可能因为网络问题重复提交相同问题。为了避免产生重复的对话记录或执行重复操作问答接口的幂等性至关重要。我们利用Redis实现了简单的幂等控制。核心思路为每个用户会话的每个请求生成一个唯一令牌token在处理请求前先检查这个token是否已被使用。Service Slf4j public class QaServiceImpl implements QaService { Autowired private StringRedisTemplate redisTemplate; private static final String IDEMPOTENT_KEY_PREFIX idempotent:qa:; private static final long TOKEN_EXPIRE_SECONDS 300L; // 令牌5分钟有效 Override public ResponseDTOAnswerVO getAnswer(QuestionDTO questionDTO) { String sessionId questionDTO.getSessionId(); String idempotentToken questionDTO.getIdempotentToken(); // 1. 幂等性校验 String redisKey IDEMPOTENT_KEY_PREFIX sessionId : idempotentToken; Boolean isAbsent redisTemplate.opsForValue().setIfAbsent(redisKey, PROCESSING, TOKEN_EXPIRE_SECONDS, TimeUnit.SECONDS); if (Boolean.FALSE.equals(isAbsent)) { // Token已存在属于重复请求 log.warn(重复请求sessionId:{}, token:{}, sessionId, idempotentToken); return ResponseDTO.success(null, 请求已处理请勿重复提交); } try { // 2. 核心业务逻辑意图识别 - 知识库查询 - 组织答案 String intent recognizeIntent(questionDTO.getContent()); AnswerVO answer queryKnowledgeBase(intent, questionDTO.getContent()); // 3. 处理成功更新Redis状态 redisTemplate.opsForValue().set(redisKey, SUCCESS, TOKEN_EXPIRE_SECONDS, TimeUnit.SECONDS); return ResponseDTO.success(answer); } catch (Exception e) { // 4. 处理失败删除令牌允许重试 redisTemplate.delete(redisKey); log.error(处理问答请求失败, e); throw new BusinessException(系统处理失败); } } // ... 其他方法recognizeIntent, queryKnowledgeBase实现 ... }时间复杂度分析上述幂等性校验主要涉及一次Redis的SETNXsetIfAbsent操作其时间复杂度为 O(1)。后续的SET和DELETE操作也是 O(1)。因此幂等控制逻辑对接口性能的影响是常数级的几乎可以忽略不计。5. 效果如何压力测试数据说话架构改造完不上线心里没底。我们用JMeter模拟了1000 TPS每秒事务数的高并发场景持续压测10分钟对比了新旧架构。指标单体架构微服务架构提升平均响应时间 (RT)2150 ms85 ms降低约96%吞吐量 (TPS)约 450约 1800提升300%错误率12.5% (超时为主)0.1%显著降低CPU利用率单实例95% (瓶颈)各服务均衡在60-75%更均衡资源成本需预留大量资源应对峰值支持动态扩缩容利用率高预计节省30%数据不会骗人。微服务化后系统吞吐量有了质的飞跃响应时间变得稳定且快速错误率也控制在了极低水平。6. 要上线了生产环境注意事项光压测好还不够生产环境才是真正的考场。我们总结了几个必须注意的点熔断策略配置Resilience4j不要用默认配置。我们根据每个服务的SLA服务等级协议调整了熔断器参数。例如对于核心的kb-service我们配置了较慢的调用失败率阈值50%和较短的熔断持续时间5秒避免轻易熔断导致服务不可用。而对于非核心的history-service则可以配置得激进一些快速失败保护核心链路。resilience4j.circuitbreaker: instances: kbService: failure-rate-threshold: 50 sliding-window-size: 10 minimum-number-of-calls: 5 wait-duration-in-open-state: 5s日志聚合方案服务多了日志查起来就是噩梦。我们引入了ELK栈Elasticsearch, Logstash, Kibana。所有服务通过Logback将日志输出到Kafka再由Logstash消费并存入Elasticsearch。在Kibana里我们可以根据traceId轻松追踪一个用户请求在所有微服务间的完整调用链排查问题效率大大提升。灰度发布流程决不能全量一次性发布。我们基于Kubernetes和Spring Cloud Gateway的权重路由功能实现了灰度发布。例如新版本nlu-service-v2上线时先部署一个实例在网关上配置5%的流量导入v295%的流量仍走v1。观察监控指标错误率、RT稳定后再逐步扩大灰度比例直至全量。这个过程完全自动化降低了发布风险。结尾与思考这次从单体到微服务的重构就像给系统做了一次深度手术和健身。效果是显而易见的系统更健壮能扛住流量冲击开发更敏捷各团队可以专注自己的服务运维也更清晰扩容监控都更有针对性。当然微服务也带来了分布式事务、链路追踪、服务治理等新的复杂性这就需要我们引入相应的工具和规范来应对。最后留一个我们正在思考的开放性问题如何结合NLP模型实现意图识别服务的动态扩容现在的nlu-service可能只是基于规则或简单的模型。未来如果接入大型深度学习模型进行意图识别模型加载耗内存、推理耗GPU。怎么实现像无状态服务那样秒级扩容是不是需要把模型服务单独拆出来利用模型服务网格Model Mesh或者专门的推理服务框架根据实时请求队列的长度自动拉起或释放带GPU的推理容器这里面的水还挺深欢迎有经验的朋友一起交流。