Linux 内核调优实战:从 sysctl 到 cgroup v2

背景与问题界定 某在线广告平台的实时竞价系统(RTB)运行在 100+ 台物理服务器上,Linux 内核版本已升级到 5.15 LTS,但性能表现始终达不到预期。一方面,netstat -s 显示大量的 TCPLoss 和 TCPTimeouts,网络协议栈参数均为操作系统默认值——这些默认值针对通用桌面场景设计,不适合高并发、低延迟的广告竞价场景。另一方面,容器技术从 Docker 切换到 containerd 后,cgroup v1 的层级复杂导致资源统计不准确,同一个 Pod 内的 Java 进程和 Sidecar 代理在 CPU 资源上相互争抢,缺乏精细化的限制能力。需要一套系统性的内核参数调优和 cgroup v2 迁移方案。 目标拆解与工程约束 网络协议栈极致优化:RTB 系统对网络延迟苛刻到微秒级,TCP 连接的延迟确认(delayed ack)、Nagle 算法、TIME_WAIT 累积等默认行为必须被调整,目标是单次请求的 TCP 握手延迟降低 30%。 内存管理精细化:广告竞价算法使用大量 mmap 文件映射和共享内存,需要调整 vm.swappiness、vm.dirty_ratio 和 NUMA 内存分配策略(numa_balancing)来减少内存回收抖动和跨 NUMA 节点访问延迟。 cgroup v2 统一资源管控:从 cgroup v1 迁移到 v2,利用其单一层级树(Single Hierarchy)消除 v1 中不同子系统可能挂在不同控制器下的混乱,实现 CPU、内存、IO 的统一 resource controller。 IO 优先级隔离:容器内的日志写入、监控数据采集和业务读写共享同一块 SSD,需要通过 cgroup v2 的 IO Controller(io.weight 和 io.max)实现 IO 带宽的 QoS 保障,避免日志洪峰冲垮数据库 IO。 方案设计 网络协议栈调优是我们投入最多的领域。根据 RTB 业务的流量特征(短连接、高并发、单次数据量小),我们将以下 sysctl 参数纳入集群基线配置: ...

2026年8月22日 · 2 分钟 · BvBeJ

Linux 性能诊断实战:从 perf 到 eBPF 追踪

背景与问题界定 某电商平台大促期间,核心下单接口的 P99 延迟从 200ms 飙升到 3.2s,CPU 饥渴地跑在 95% 但业务层看不出任何明显瓶颈。线上排查时,top 和 htop 只能看到进程级 CPU 使用率,无法定位到具体函数或内核调用路径。传统 strace 在生产环境会带来 10 倍以上的性能开销,无法直接使用。这暴露了一个典型问题:现代高并发系统中,性能瓶颈往往埋藏在系统调用层、内核协议栈或内存分配的微妙竞争点上,需要一套低开销、“显微镜级"的诊断工具组合。 目标拆解与工程约束 零入侵采样:诊断工具不能对生产业务进程造成显著的性能影响,采样开销必须控制在 1% CPU 以内,不能让诊断本身成为新的性能瓶颈。 全栈追踪能力:工具链必须覆盖从应用层函数、库调用、系统调用到内核函数、硬件 PMU 事件的全栈追踪,能在一个时间线上串联用户态和内核态的执行路径。 动态插桩:不能在业务代码中预先埋点,必须支持对运行中的进程进行动态插桩,且插桩点和追踪逻辑可以在不重启进程的情况下热插拔和调整。 安全与权限约束:生产环境上使用 eBPF 需要保证内核版本 >= 4.19(基础功能)或 >= 5.4(CO-RE 支持),同时需要控制 BPF 程序的复杂度(指令数 <= 4096),防止 BPF JIT 引发内核 panic。 方案设计 故障排查采用分层诊断策略。第一层使用 perf 进行快速的 CPU 热点定位,通过 perf top -p <pid> 查看进程级别热点函数,利用 CPU 硬件 PMU 中的 cycles 事件采样统计函数调用频率。当 perf 显示热点集中在内核态时(如 _raw_spin_lock 或 tcp_v4_rcv),使用 perf record -e sched:sched_switch -g 记录上下文切换的调用栈,确认是否为频繁的锁竞争或调度延迟。 ...

2026年8月18日 · 2 分钟 · BvBeJ

Linux CPU 隔离在低延迟服务中的实践

典型问题 同机混部下,关键服务线程会被后台任务、ksoftirqd 和内核 housekeeping 干扰,导致 p99 抖动长期无法收敛。 隔离手段 通过 cpuset 为关键进程绑定独占核心。 将中断亲和性避开关键核心。 把系统 housekeeping 任务集中到专用核。 验证方法 对比隔离前后 p99/p999。 观察调度切换次数与 run queue 长度。 结合 perf 看 cache miss 与上下文切换变化。 小结 CPU 隔离不是“调个参数”,而是资源编排策略。把关键路径从系统噪声里解耦,低延迟目标才有实现基础。

2026年5月26日 · 1 分钟 · BvBeJ

eBPF 持续 Profiling 在生产环境的落地边界

为什么要持续 Profiling 离线抓火焰图只能解释“当下问题”,无法覆盖版本演进中的渐进回归。持续 profiling 能提供趋势视角。 落地关键 采样频率按服务等级分层,避免全局高频。 只保留聚合后的符号栈,降低存储压力。 结合版本号维度做回归对比。 噪声治理 排除短命批处理进程。 对 JIT 语言补齐符号映射。 过滤启动期冷缓存阶段样本。 常见风险 盲目提高采样率导致 CPU 额外开销。 内核版本不一致引发采样偏差。 只看 Top 函数,忽视调用链变化。 小结 持续 profiling 不是“多收集”,而是“低扰动、可对比、可解释”的长期性能体检体系。

2026年5月10日 · 1 分钟 · BvBeJ

C++ 服务内存碎片治理:从分配器选择到线上观测

症状识别 业务负载稳定,但 RSS 只涨不降。 堆分析看不到明显泄漏对象。 进程重启后内存瞬间回落。 为什么会碎片化 多线程下不同 size class 高频分配释放。 短命与长命对象混放。 大量临时 buffer 导致 arena 难回收。 治理路径 分离对象生命周期:短命池与长命池分开。 统一热点对象尺寸,减少跨 class 抖动。 评估 jemalloc/tcmalloc 与默认 allocator 差异。 线上指标建议 allocated_bytes active_bytes resident_bytes fragmentation_ratio = resident / active 风险点 只看进程总内存,不看分配器内部统计。 盲目手写内存池,忽略线程本地缓存竞争。 回收策略和 NUMA 绑定冲突。 小结 碎片治理是系统工程:对象模型、分配器、线程调度、NUMA 拓扑都要协同。先观测后改造,收益才稳定。

2026年5月6日 · 1 分钟 · BvBeJ

io_uring 高吞吐下的背压设计:别让 SQ/CQ 成为黑洞

问题不在快,而在失控 很多 io_uring 服务在压测里吞吐漂亮,线上却出现尾延迟飙升。根因通常是提交队列无限推进,完成队列消费跟不上。 背压触发条件 SQ ring 使用率超过 80%。 CQ backlog 持续增长。 业务层处理耗时超过 IO 完成速率。 建议的三层背压 提交层:限制 in-flight 请求上限。 协程层:队列超阈值时暂停新任务调度。 入口层:对上游返回 retry-after 或降级响应。 伪代码 if (inflight > max_inflight || cq_backlog > cq_limit) { pause_accept(); shed_low_priority(); } while (io_uring_peek_cqe(&ring, &cqe) == 0) { handle_cqe(cqe); io_uring_cqe_seen(&ring, cqe); } 关键指标 submit_to_complete_latency cq_backlog inflight shed_count 小结 io_uring 的上限很高,但系统稳定性的上限由背压机制决定。先把“慢下来也不爆炸”做对,再追求“快”。

2026年5月1日 · 1 分钟 · BvBeJ

Linux 网络 IO:epoll 与 io_uring 选型笔记

先说结论 多数业务服务里,epoll 依然是稳妥选择。io_uring 在某些高并发 IO 场景能更快,但复杂度也更高。 epoll 的优势 生态成熟,排障资料多 编程模型稳定 与现有网络库兼容性好 对于大部分 API 服务,epoll 足够用。 io_uring 的价值点 减少系统调用次数 提升批量提交与完成处理效率 在特定负载下降低延迟 但它要求你理解提交队列、完成队列、内核版本差异等细节。 选型建议 先压测当前 epoll 方案,定位真实瓶颈 若瓶颈明确在 IO 提交/完成路径,再评估 io_uring 预留回退方案,避免一次性全量切换 团队视角 技术选型不只看 benchmark,还要看: 团队掌握程度 线上问题可定位性 长期维护成本 小结 新能力值得关注,但架构决策应以稳定收益为中心。对大多数团队来说,逐步引入比激进替换更现实。

2026年4月25日 · 1 分钟 · BvBeJ

Go 服务优雅重启:systemd 配合实践

背景 裸重启进程很简单,但线上会带来短暂不可用。优雅重启的目标是: 停止接收新连接 等待在途请求处理完 平滑切换到新进程 Go 侧的退出处理 srv := &http.Server{Addr: ":8080", Handler: mux} go func() { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatal(err) } }() sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT) <-sigCh ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() _ = srv.Shutdown(ctx) Shutdown 会先关闭监听,再等待连接收尾。 systemd 关键配置 [Service] ExecStart=/opt/app/server Restart=always RestartSec=2 TimeoutStopSec=20 KillSignal=SIGTERM KillSignal=SIGTERM 给应用机会走优雅退出逻辑 TimeoutStopSec 要大于应用 Shutdown 超时 发布建议 先在网关层摘流量 再重启实例 观察错误率与连接数回落 小结 优雅重启不是一个函数调用,而是应用与进程管理器协同设计。把退出路径做好,发布风险会明显下降。

2026年4月23日 · 1 分钟 · BvBeJ