京东智能:智能客服备案登记技术解析——合规架构设计与实现指南
面向中高级开发者,把“备案”从政策名词拆成可落地的代码、配置与监控。
1. 背景痛点:对话系统也要“持证上岗”
《互联网信息服务算法推荐管理规定》第十四条明确要求:
“具有舆论属性或社会动员能力的算法推荐服务提供者,应当对算法机制机理、对话日志等进行备案。”
京东智能客服日均会话量 3.2 亿条,峰值 QPS 120 万,传统人工审核根本扛不住,工程侧必须解决三大难题:
- 实时过滤:敏感词、违规意图必须在 100 ms 内完成识别并阻断
- 全量备案:对话日志、模型版本、策略参数要可回溯、可验证、可重放
- 异地多活:华北、华东、华南三机房同时接受监管抽查,数据延迟 < 500 ms
一句话:既要“说得对”,又要“说得清”,还要“说得快”。
2. 架构对比:人工 VS 自动备案
| 维度 | 人工抽检 | NLP 自动备案(京东实践) |
|---|---|---|
| 吞吐量 | 2 k 会话/人/天 | 120 万 QPS,横向扩展无上限 |
| 敏感识别准确率 | 85%(受疲劳度影响) | 97.3%(模型+规则双引擎) |
| 备案延迟 | T+1 天 | 实时流式,平均 380 ms |
| 成本 | 随业务量线性增加 | 固定集群 + 15% 弹性伸缩 |
结论:自动备案不是“可选”,而是“唯一”解法。
3. 核心实现:把合规写进代码
3.1 合规流量拦截层(Spring Cloud Gateway)
所有对话请求统一走chat-gateway集群,在 Netty 层做前置过滤洗。
# bootstrap.yml spring: cloud: gateway: routes: - id: chat-compliance uri: lb://chat-compliance predicates: - Path=/api/v3/chat/** filters: - name: ComplianceFilter args: cache-size: 10_000 # 本地 LRU hot-load-interval: 300s # 热更新周期ComplianceFilter把请求体缓存到 DirectMemory,防止重复读取;随后把文本送入敏感词服务,返回riskTag后再决定是否向后端放行。
3.2 敏感词过滤模块(DFA + 多级缓存)
@Component public class SensitiveFilter { // 1. 本地一级缓存:Guava Cache,单机 10 ms 内返回 private final Cache<String, Boolean> localCache = CacheBuilder.newBuilder().maximumSize(10_000).expireAfterWrite(5, TimeUnit.MINUTES).build(); // 2. Redis 二级缓存:多机共享,失效时间 10 min @Autowired private StringRedisTemplate redisTemplate; // 3. 远程词库:MySQL + 版本号,增量更新 @Autowired private WordDao wordDao; private volatile DfaGraph dfaGraph; // DFA 根节点 @PostConstruct public void loadWords() { List<String> words = wordDao.listAll(); this.dfaGraph = DfaBuilder.build(words); } public boolean hit(String text) { return localCache.get(text, k -> { // 先读 Redis String redisKey = "sensitive:md5:" + DigestUtils.md5DigestAsHex(k.getBytes()); Boolean cached = redisTemplate.opsForValue().get(redisKey, Boolean.class); if (cached != null) return cached; // DFA 匹配 boolean hit = dfaGraph.match(k); redisTemplate.opsForValue().set(redisKey, hit, Duration.ofMinutes(10)); return hit; }); } }要点:
- DFA 构建复杂度 O(∑len(word)),内存占用 ≈ 1.2 GB(800 万词)
- 词库版本号写入 Redis,网关通过
Pub/Sub热更新,无需重启 - 本地缓存命中率 94%,P99 延迟 < 8 ms
3.3 对话日志 AES-GCM 字段级加密
监管要求“原始日志不可明文落盘”,但又要支持关键字检索。京东采用“字段级加密 + 列级哈希”混合方案:
- 敏感字段(用户 QQ、手机号、地址)走 AES-GCM,密钥托管在 KMS,每小时轮转
- 非敏感字段(商品 ID、时间戳)明文存储,方便 OLAP 查询
- 对手机号做一次
HMAC-SHA256保留前 6 位,用于客服二次确认,但不暴露完整号码
public String encrypt(String phone, String aad) { SecretKey key = kmsClient.deriveKey("phone", Instant.now().getEpochSecond()/3600); Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, generateIv())); cipher.updateAAD(aad.getBytes(StandardCharsets.UTF_8)); byte[] cipherText = cipher.doFinal(phone.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(cipherText); }存储示例:
| 列 | 值 |
|---|---|
| user_phone_cipher | gCM...(128 B) |
| user_phone_hash | 86f3b812ab...(前 6 位掩码) |
4. 性能考量:百万 QPS 也不掉链子
4.1 备案数据上报削峰
- 网关把命中“需备案”标记的对话写入 Kafka,Topic 按
partitionKey = userId % 1024均摊 - 消费端采用“微批 + 滑动窗口”:每 200 ms 或 4 MB 打包一次,调用监管接口
- 背压保护:当 Kafka 消费延迟 > 30 s,自动丢弃 10% 低危日志,优先保证高危完整
4.2 Flink 实时审计流水线
Source(Kafka) → 解析 → 敏感词检测 → 窗口聚合 → SideOutput(异常) → Sink(ES)- 使用
Cep.pattern识别“多人次同一敏感意图”事件 - 检查点 5 s 一次,端到端 exactly-semantic
- 120 万 QPS 时,集群 32 TaskManager * 8 GB,CPU 占用 68%
5. 避坑指南:踩过的坑,写进手册
备案接口幂等
监管侧按msgId + sessionId去重,京东侧在 Kafka Producer 端做idempotent=true,同时缓存已提交 ID 30 min,防止重启重推。敏感词库热更新
直接替换 DFA 对象会导致老请求仍用旧树。解决:采用“双缓冲 + 版本引用”,完成替换前,网关 Filter 先完成自身本地缓存失效,再切换引用,灰度 30 s。跨数据中心日志同步延迟
华北→华南专线 RTT 28 ms,但高峰期抖动到 200 ms。方案:- 写入时同步双写(同步复制因子=2),返回客户端前必须收到两地 ACK
- 对延迟敏感的策略参数走
Raft一致性组,牺牲吞吐换一致性
6. 小结与展望
把合规当成功能来做,而不是事后补丁,是京东智能客服能扛 3.2 亿条会话的最大心得。整套方案已在 GitHub 开源部分模块(搜索jd-chat-compliance),欢迎一起打磨。
下一步,我们想把“可解释性”也做进备案包:让监管同学不仅能重放对话,还能看到模型为什么给出这条回复。路还长,坑还多,先把今天的代码跑稳,再谈明天的故事。
如果你也在做对话系统,希望这篇笔记能帮你少踩几个坑,早点下班。