问题场景
去年“双十一”前,公司把客服系统从人工全部切到智能客服,结果流量一冲,接口超时率飙到 18%,用户吐槽“机器人只会说‘正在为您转接’”。复盘发现,痛点集中在三点:
- 单节点 Dialogflow 代理直连,高峰 QPS 400 时平均延迟 1.8 s,直接触发前端 5 s 熔断。
- 多轮对话靠 HTTP 头带
sessionId,负载均衡一到就丢上下文,用户问“订单呢?”机器人回“请问您要查哪一笔?”。 - 运营配了 1 200 条敏感词,结果“特价”被误杀,整条营销链路瘫痪。
一句话:智能客服不是“插上就灵”,高并发场景下,接口、状态、敏感词,每一步都可能踩坑。
架构设计
1. 总体思路
把“对话”当成“订单”处理:统一入口、异步驱动、状态外置、水平可扩。整体分四层:
- 接入层:Spring Cloud Gateway 做聚合,统一鉴权、限流、灰度。
- 对话服务:无状态微服务,只负责 NLU 调用与业务规则,本地不存会话。
- 状态仓库:Redis Cluster 存会话快照,带 TTL + 互斥锁,解决“上下文丢失”。
- 缓冲层:RocketMQ 削峰填谷,把“高峰 5 k QPS”匀成“平均 800 QPS”喂给下游。
2. 技术选型对比
| 方案 | 实测 QPS | P99 延迟 | 会话保持成本 | 备注 |
|---|---|---|---|---|
| Dialogflow ES | 420 | 1.8 s | $0.002/条 | 谷歌墙+额度,高峰 429 |
| Rasa Pro | 680 | 650 ms | 自建 16C32G 集群 | 需要调优 TensorFlow |
| 自研 NLU | 1 100 | 120 ms | 0.06 元/条 | 语料需自己标,迭代重 |
结论:Rasa 性价比最高,但高峰仍可能被打爆;自研 NLU 延迟最低,适合“问答库封闭”场景。最终采用“Rasa + 自研 FAQ”双路召回,Gateway 按灰度比例分流。
3. 长连接保活
Gateway ↔ 客户端 建立 WebSocket,心跳 30 s;Gateway ↔ 对话服务 使用 HTTP/2 连接池,maxConnections 500,keep-alive 60 s,减少三次握手开销。
代码实现
1. 分布式会话锁
会话维度:conv:{userId},SETNX 抢锁失败即重试,防止并发写脏。
private static final String LOCK_KEY_PREFIX = "conv:lock:"; private static final long LOCK_EXPIRE_MS = 3_000; public boolean tryLock(String userId) { String key = LOCK_KEY_PREFIX + userId; Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", Duration.ofMillis(LOCK_EXPIRE_MS)); return Boolean.TRUE.equals(flag); } public void unlock(String userId) { redisTemplate.delete(LOCK_KEY_PREFIX + userId); }2. 消息队列削峰
Gateway 收到提问后先落库,发 Half 消息到 RocketMQ,返回“正在思考”占位;下游 Rasa 消费后回写答案,WebSocket 推送。Half 消息机制保证“下游超时”可回查,避免用户白等。
3. 敏感词过滤
采用 DFA + 白名单双缓存。误判时,运营可在后台把词加入white:{word},5 min 内生效;代码里优先匹配白名单,再跑 DFA。
if (whiteSet.contains(word)) { return false; // 直接放过 } return dfaMatch(word);4. 编码规范
- 所有 public 方法入口必做
Preconditions.checkNotNull - 日志用
MDC.put("userId", userId),链路追踪 - 异常统一转译成
BizException,返回码 4xx/5xx 清晰区分
性能数据
JMeter 5.5 压测环境:8C16G × 5 节点,100 并发线程循环 15 min。
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 平均 RT | 1 650 ms | 380 ms |
| P99 RT | 3 200 ms | 620 ms |
| 超时率 | 18 % | 0.4 % |
| CPU 峰值 | 92 % | 55 % |
| Redis 读 QPS | 0 | 12 k |
关键优化点:异步化 + 连接池 + 本地缓存热点 FAQ,整体请求响应时间下降 40% 以上,达成目标。
生产实践
1. 灰度切流
按用户尾号灰度,先 5% → 20% → 100%,每步观察错误日志与业务指标,发现“白名单未同步”导致 0.7% 误判,立即回滚配置,10 min 修复。
2. 超时上下文恢复
对话服务宕机重启时,从 Redis 恢复conv:{userId},若 TTL 剩余 < 30 s,主动推送“会话已过期,请重新描述问题”,避免机器人答非所问。
3. 压测常态化
把 JMeter 脚本集成到 Jenkins,每周跑 30 min 性能回归;一旦平均 RT > 500 ms 或错误率 > 1%,自动钉钉告警。
4. 踩坑小结
- Dialogflow 额度 600 QPS/项目,多项目分流也要提前申请。
- Rasa 模型热更新会阻塞请求,用双模型 + 流量切换才能 0 中断。
- 敏感词 DFA 构建在容器启动时做,别放在第一次请求,否则 2 w 条规则要 7 s 编译,直接超时。
延伸思考
智能客服的“会话”不再局限于一端:用户可能在小程序发起,又跑到 App 继续问,甚至电话客服也要无缝接手。如何设计跨渠道的客服会话同步,才能保证:
- 消息顺序不颠倒
- 渠道特有字段(如微信
encryptOpenId)不冲突 - 断网重连后自动续上历史
这是下一期想和大家一起探讨的开放题,欢迎评论区聊聊你的做法或踩过的坑。