B

Byte's Lab

分布式系统 / 网络工程 / 编译器 / Rust / Go

Linux TCP 拥塞控制算法从 BBRv1 到 BBRv3 的演进与调优

2026-08-12约 12 分钟

BBR 系列算法是 Google 基于带宽-延迟模型设计的拥塞控制方案。BBRv1 在高丢包场景下存在 bufferbloat 问题,BBRv2 引入了丢包信号但收敛速度不理想。BBRv3 重新设计了 pacing rate 计算和瓶颈队列追踪机制,在 RTT 100ms+、丢包 1%-5% 的长肥管道场景下吞吐量相比 BBRv2 提升约 40%。

实际调优中,除了选择正确的算法,还需要关注以下几个内核参数的配合:

# sysctl 网络调优关键参数
net.ipv4.tcp_no_metrics_save = 1    # 不缓存路由度量,避免旧连接影响新连接
net.ipv4.tcp_moderate_rcvbuf = 1    # 启用自动接收缓冲区调节
net.ipv4.tcp_slow_start_after_idle = 0  # 空闲后不重置拥塞窗口
net.ipv4.tcp_fastopen = 3           # 启用客户端+服务端 TFO

其中 tcp_slow_start_after_idle 对长连接服务(如数据库连接池、gRPC 长连接)影响最大——关闭后可以避免每次空闲恢复时从初始拥塞窗口重新慢启动。

TCPBBRLinux内核性能调优

用 Rust 实现一个高性能零拷贝日志解析器

2026-08-05约 15 分钟

日志解析是后端基础设施中的高频操作。传统方案使用 BufReader 逐行读取后正则匹配,在 GB 级日志文件上 I/O 和内存拷贝成为瓶颈。本文展示如何利用 Rust 的 mmap + memchr 实现零拷贝的 SIMD 加速行扫描,实测解析速度达到每秒 2.3GB。

核心思路是将文件通过 mmap 映射到地址空间,省去用户态的 read/write 拷贝,再用 memchr 的 SIMD 实现(SSE2/AVX2)快速定位换行符:

use memmap2::MmapOptions;
use std::fs::File;

fn stream_lines(path: &str) -> anyhow::Result<()> {
    let file = File::open(path)?;
    let mmap = unsafe { MmapOptions::new().map(&file)? };

    for chunk in mmap.chunks(4096) {
        let mut start = 0;
        while let Some(pos) = memchr::memchr(b'\n', &chunk[start..]) {
            let line = &chunk[start..start + pos];
            process_line(line);
            start += pos + 1;
        }
    }
    Ok(())
}

在AMD EPYC 7763 上的 benchmark 显示,此方案比 BufReader + Lines 快 8.2 倍,内存分配次数减少 99.7%。

Rust零拷贝性能SIMD

Go GC 调优实战:P99 延迟从 12ms 降到 800μs 的完整过程

2026-07-28约 18 分钟

Go 的并发标记清除 GC 在大多数场景下表现优秀,但在高 QPS 低延迟的交易系统中,GC STW(Stop-The-World)导致的毛刺仍然不可忽视。本文记录了我们在一个日均 8000 万次请求的微服务上,将 GC P99 从 12ms 优化到 800μs 的完整过程。

关键优化手段:

1. 减少堆上指针数量——将高频路径上的 map[string]interface{} 替换为结构化 schema,GC 扫描对象数从 420 万降到 38 万。

2. GOGC 动态调整——在低峰期设 GOGC=200 节省内存,高峰期切到 GOGC=50 减少单次 GC 耗时。

3. offheap 分配——热路径上的 buffer pool 改用 mmap 管理,完全绕过 GC。

// GOGC 动态调整示例
import "runtime/debug"

func adjustGC(qps float64) {
    if qps > 100000 {
        debug.SetGCPercent(50)   // 高峰:频繁小GC
    } else {
        debug.SetGCPercent(200)  // 低峰:减少CPU开销
    }
}
GoGC延迟优化性能

TCP 半关闭状态(Half-Close)与 TIME_WAIT 的正确理解

2026-07-20约 8 分钟

很多开发者将 TIME_WAIT 视为性能问题的万恶之源,盲目设置 net.ipv4.tcp_tw_reuse=1。实际上 TIME_WAIT 是 TCP 协议保证可靠性的核心机制——它确保最后一个 ACK 到达对端,并防止旧连接的延迟数据包干扰新连接。

TIME_WAIT 真正需要关注的场景:

• 短连接高并发(如 HTTP/1.1 未开启 keep-alive)——解决方案是启用连接池或升级到 HTTP/2

• 主动关闭方大量出现 TIME_WAIT ——考虑使用长连接、调整 tcp_max_tw_buckets

对于被动关闭方(server 端),应该关注的是 FIN_WAIT2 超时而非 TIME_WAIT:

# 合理的内核参数配置
net.ipv4.tcp_fin_timeout = 15        # FIN_WAIT2 超时
net.ipv4.tcp_tw_reuse = 1            # 安全复用 TIME_WAIT(仅客户端)
net.ipv4.tcp_max_tw_buckets = 180000 # TIME_WAIT 数量上限
TCP网络编程Linux

从零手写一个 LRU Cache:为什么 std::HashMap 不够用

2026-07-12约 10 分钟

标准库的 HashMap 提供 O(1) 的插入和查找,但不支持 LRU 淘汰策略。在缓存系统、数据库 Buffer Pool 等场景下,我们需要 O(1) 的访问 + O(1) 的淘汰。经典方案是 HashMap + Doubly Linked List 的组合。

Rust 实现的关键在于自引用结构的处理——Rust 的所有权系统不允许一个 struct 同时拥有引用和被引用:

use std::collections::HashMap;
use std::ptr::NonNull;

struct Entry<K, V> {
    key: K,
    value: V,
    prev: Option<NonNull<Entry<K, V>>>,
    next: Option<NonNull<Entry<K, V>>>,
}

pub struct LruCache<K, V> {
    map: HashMap<K, Box<Entry<K, V>>>,
    head: Option<NonNull<Entry<K, V>>>,
    tail: Option<NonNull<Entry<K, V>>>,
    capacity: usize,
}

本文详细讲解了 unsafe 指针操作的正确姿势,以及如何用 Box::into_raw / Box::from_raw 安全地管理链表节点生命周期。

Rust数据结构LRUunsafe

深入理解 epoll:从 ET 到 LT 的性能差异与选型指南

2026-07-05约 14 分钟

epoll 是 Linux 上高性能网络编程的基石。它的两种触发模式——水平触发(LT)和边缘触发(ET)——在实际项目中有着截然不同的适用场景。

LT 模式下,只要 fd 处于可读/可写状态,每次 epoll_wait 都会通知。编程模型简单,但可能导致不必要的系统调用。ET 模式仅在状态变化时通知一次,性能更高但要求开发者必须一次性读完所有数据(用 EAGAIN 循环)。

实际选型建议:

用 LT:连接数多但活跃度低的场景(如 IM 长连接网关),编程简单不易出 bug

用 ET:连接少但吞吐要求极高的场景(如 RPC server),需要配合 O_NONBLOCK 和完善的错误处理

// ET 模式下必须循环读取直到 EAGAIN
loop {
    match fd.read(&mut buf) {
        Ok(n) if n > 0 => process(&buf[..n]),
        Ok(_) => break,  // EOF
        Err(e) if e.kind() == ErrorKind::WouldBlock => break,
        Err(e) => return Err(e.into()),
    }
}
epoll网络编程LinuxRust