这是典型的伪共享问题——多个cpu核心频繁修改同一cache line内的不同变量,导致cache line在核间反复无效化与同步;需用perf采集硬件事件、检查cache line大小与结构体布局、开启锁日志定位热点,再通过字段对齐隔离、增大分桶数、用原子操作替代锁等手段优化,并以cache-miss ratio下降为关键验证指标。

这不是“总线行污染”,而是典型的伪共享(false sharing)问题——多个 CPU 核心频繁修改位于同一 cache line(通常 64 字节)内的不同变量,导致该 cache line 在核间反复无效化与同步,拖慢性能。
确认是否真由伪共享引发性能下降
先别急着调锁,用数据验证问题存在:
- 用 perf record -e cycles,instructions,cache-misses -a sleep 30 采集压测时的硬件事件,重点关注 cache-misses 升幅是否显著高于基线
- 检查 /proc/cpuinfo 确认 L1 cache line size(通常是 64 字节),再对照 Nginx 共享内存结构体布局,看锁变量、计数器、哈希桶头等关键字段是否挤在同一 cache line 内
- 观察 nginx -V 输出中是否启用 --with-debug,若有,可配合 NGX_DEBUG_LOCK 宏开启锁争抢日志,定位高冲突 bucket
拆分热点字段,强制隔离 cache line
Nginx 的 ngx_shmtx_t 和关联状态(如计数器、链表头)必须错开对齐:
- 在自定义共享结构体中,为每个锁或高频写字段前后插入 char pad[64],确保其独占一个 cache line
- 若使用 ngx_slab_pool_t 分配器,避免把多个锁对象紧挨着分配;改用独立 mmap 区或按 cache line 对齐 malloc(需模块内手动控制)
- 对 limit_req_zone 或 ssl_session_cache 这类内置结构,无法直接改源码,但可通过增大分桶数(如 zone=xxx:10m buckets=2048)间接降低单 bucket 冲突密度
减少锁频次,用原子操作替代部分写场景
不是所有写都需要锁——能用原子操作就不用自旋锁:
- 单纯递增/递减计数(如失败次数、命中数)一律用 ngx_atomic_fetch_add(&counter, 1),它生成 LOCK XADD 指令,不触发 cache line 无效广播
- 读-改-写复合操作(如更新时间戳+计数)才需加锁;且锁区内只做必要修改,避免嵌套计算或内存分配
- 对 SSL session 缓存,key 查找和过期判断可无锁,仅插入/删除链表节点时锁定对应 slot
验证优化效果的关键指标
改完不能只看 QPS,要盯住底层信号:
- perf stat -e cycles,instructions,cache-references,cache-misses 对比前后 cache-miss ratio 是否下降(理想值
- 用 pstack $(pgrep nginx) 抽样,看 worker 是否长时间卡在 ngx_shmtx_lock 循环里(说明自旋未退避)
- 检查 error log 中 "ngx_shmtx_lock: spin timeout" 出现频率,若高频出现,说明当前自旋阈值(默认 1024)已不够,需调大 NGX_SHMTX_SPIN 并重编译











