敏感词过滤在Gin中慢的主因是未优化I/O和内存分配,应使用AC自动机配合sync.Pool复用缓冲区、流式JSON解析及ResponseWriter包装层实现零拷贝过滤。

敏感词过滤在 Gin 中跑得慢,八成不是因为词库大,而是没用对算法或没绕开 JSON 解析陷阱。Aho-Corasick(AC 自动机)是目前最稳的方案,但直接套用 ahocorasick 包仍可能卡在 I/O 或内存分配上。
为什么 string.Contains 或正则匹配在 Gin 里不适用
HTTP body 是字节流,string.Contains 每次都要把整个 body 转成字符串再扫一遍,遇到长文本(比如 10KB 的 JSON)就触发大量内存拷贝;正则更糟,regexp.MustCompile 编译开销高,且无法做前缀共享优化。实际压测中,单次过滤耗时从 0.8ms(AC)飙升到 12ms(正则),QPS 直接掉 60%。
- JSON 字段名、数字、空格都会被误判为“敏感词”,必须先解析结构再只过滤
string类型字段 - 不能对原始字节流做全局替换,否则会破坏 JSON 结构(比如把
"price": 999里的"999"当成词干误删) - 生产环境每秒几百个 POST 请求,每次新建
bytes.Buffer和json.Decoder会显著抬高 GC 压力
用 ahocorasick.Trie + sync.Pool 降低 GC 开销
AC 自动机本身是零拷贝的,但 Go 生态里多数封装包默认每次 new 一个 bytes.Buffer 来写过滤结果,这是性能瓶颈主因。必须手动复用缓冲区和解码器。
- 初始化全局
sync.Pool:var bufferPool = sync.Pool{New: func() interface{} { return bytes.NewBuffer(make([]byte, 0, 4096)) }} - 过滤函数内优先从池取:
buf := bufferPool.Get().(*bytes.Buffer); defer bufferPool.Put(buf); buf.Reset() - JSON 解析不用
json.Unmarshal全量反序列化,改用json.NewDecoder流式读取,配合json.RawMessage延迟解析非字符串字段 - 构建 trie 时禁用大小写敏感(
ahocorasick.WithCaseInsensitive(false)),避免 runtime 多一次strings.ToLower调用
响应体过滤必须走 ResponseWriter 包装层
如果在中间件里直接改 c.Writer.Body,会导致后续中间件(比如日志、监控)读不到原始响应,而且 Gin 默认的 ResponseWriter 不支持重写已写内容。正确做法是用自定义 writer 包装 c.Writer,在 Write 方法里拦截并过滤。
- 实现
ResponseWriter接口,重写Write([]byte),仅当Content-Type包含"application/json"时才触发过滤 - 不要在
WriteHeader后再尝试修改 body,HTTP header 已发出去,客户端可能已开始解析 - 过滤失败时记录错误但不 panic,用
io.MultiWriter把原始输出同时写进日志文件,方便事后审计
真正卡顿的地方往往不在 trie 构建,而在 buffer 分配和 JSON 解析路径。别急着换算法,先用 pprof 看一眼 runtime.mallocgc 和 encoding/json.unmarshal 占比——超过 35%,说明该加 pool 了。











