开放问答
同步改异步 offload 之后 P99 从 40ms 飙到 120ms 且偶发 OOM:背压没接住怎么定位
该问题的正文、回答与评论。
提问者 林亦舟-161 · 状态 open
高并发异步化背压JVM性能调优
【背景】 B 端订单服务,JDK17 + Spring Boot 3 + Netty 4.1,单机 8C16G,K8s cpu limit=4 / mem limit=8G。下游:Redis 7(3 主 3 从)+ MySQL 8.0.32(单实例)。QPS 峰值 ~3k,P99 目标 ≤50ms,改造前 P50=18ms、P99=42ms 达标。
【现象】 为压 P99,把原本同步阻塞的「查 Redis + 查 MySQL」改成 CompletableFuture + 自定义线程池 offload(核心 200 / 最大 800 / 队列 1024,CallerRunsPolicy)。上线一周后:
- P50 18ms → 16ms(略降)
- P99 42ms → 120ms(涨近 3 倍)
- 每天凌晨 ~2 次 Heap OOM,dump 里大量 CompletableFuture 节点 + 业务 DTO 堆积
- 多次 jstack:offload 池里 ~80% 线程在 get() 阻塞等下游 Redis 回包
【已尝试的排查与结果】
- 线程池 200→800→2000:P99 不降反升,OOM 从 2 次/天 → 5 次/天,怀疑 offload 池本身放大背压;
- 堆 4G→8G:OOM 延后但没消失,dump 对象组成没变;
- 换 ZGC(-XX:+UseZGC -XX:SoftMaxHeapSize=6g):GC pause <1ms,但 P99 依旧 110ms+,OOM 依旧;
- Redis 调用加 50ms 超时 + 熔断:P99 降到 80ms,但 OOM 仍在——说明不是单纯超时问题;
- 抓了 3 次 jstack + 1 次 heap dump,没看出死锁或单点泄漏。
【期望得到的答案形态】 希望有做过「同步改异步 offload」的朋友给一套可复现的诊断路径,而不是泛泛的「加监控」:
- jstack 采样如何区分「offload 线程饥饿」vs「堆对象积压」(采样间隔、看哪些状态、grep 哪些栈帧);
- heap dump 里 CompletableFuture / 业务 DTO 堆积分别对应哪种根因,用什么关键字过滤;
- 背压该在哪一层接(Netty EventLoop / offload 池 / 业务入口),各自的取舍;
- 该退回「同步 + 连接池」,还是继续 async 换 offload 模型(虚拟线程 / Reactive / 裸 NIO)?
谢谢!
评论
暂无评论。