Skip to content

Latest commit

 

History

History
441 lines (328 loc) · 8.84 KB

File metadata and controls

441 lines (328 loc) · 8.84 KB

Tokio 运行时性能调优指南

🎯 问题诊断

如果您发现运行时配置后性能下降,可能的原因包括:

常见性能问题

  1. 线程数过多

    • 症状:CPU 利用率低,上下文切换频繁
    • 原因:I/O 密集型任务不需要大量工作线程
    • 解决:减少工作线程数
  2. 线程栈过大

    • 症状:内存占用高
    • 原因:2MB × 线程数 = 过多内存
    • 解决:使用默认栈大小(~1MB)
  3. 事件间隔过大

    • 症状:响应延迟增加
    • 原因:I/O 事件轮询不够频繁
    • 解决:降低 event_interval

🔧 优化方案

当前配置(已优化)

// I/O 密集型优化配置
let worker_threads = std::cmp::max(4, num_cpus::get() / 2);

tokio::runtime::Builder::new_multi_thread()
    .worker_threads(worker_threads)      // 8 线程 (16核系统)
    .thread_name("sni-proxy-worker")
    // 使用默认栈大小 (~1MB)
    .enable_all()
    .global_queue_interval(31)
    .event_interval(31)                  // 降低到 31 提高响应
    .build()?

适用场景

  • ✅ I/O 密集型(流媒体代理)
  • ✅ 长连接场景
  • ✅ 大量并发连接

性能特点

  • 🔻 减少上下文切换
  • 🔻 降低内存占用
  • ⬆️ 提高 I/O 响应速度

📊 性能对比

配置对比表

配置 工作线程 栈大小 event_interval 适用场景
默认 核心数 默认 61 通用
旧配置 16 2MB 61 CPU 密集型
当前配置 8 默认 31 I/O 密集型 ✅
极简配置 4 默认 31 低负载

内存占用对比

配置 线程栈内存 说明
旧配置 16 × 2MB = 32MB 过高
当前配置 8 × 1MB = 8MB 适中 ✅
极简配置 4 × 1MB = 4MB 最低

🧪 测试方案

方案 A:当前配置(推荐)

配置

  • 工作线程:CPU 核心数 / 2 (最少 4)
  • 栈大小:默认
  • 事件间隔:31

测试步骤

# 1. 编译
cargo build --release

# 2. 启动服务
./target/release/sni-proxy config.json

# 3. 观察日志
🚀 Tokio 运行时配置:
  工作线程数: 8 (CPU 核心: 16 物理, 16 逻辑)
  线程栈大小: 默认 (~1MB)
  事件间隔: 31 (I/O 响应优化)

# 4. 监控性能
# 观察 CPU 使用率
top -H -p $(pgrep sni-proxy)

# 观察上下文切换
pidstat -w -p $(pgrep sni-proxy) 1

预期结果

  • ✅ CPU 利用率适中 (40-60%)
  • ✅ 上下文切换减少
  • ✅ 响应延迟降低
  • ✅ 内存占用降低

方案 B:极简配置(低负载场景)

如果当前配置仍然慢,尝试此配置:

修改 src/main.rs

fn main() -> Result<()> {
    let runtime = tokio::runtime::Builder::new_multi_thread()
        .worker_threads(4)              // 固定 4 线程
        .thread_name("sni-proxy-worker")
        .enable_all()
        .global_queue_interval(31)
        .event_interval(31)
        .build()?;

    runtime.block_on(async_main())
}

适用场景

  • 并发连接 < 1000
  • 单机部署
  • 低负载环境

方案 C:最大化 I/O 性能

如果需要极致 I/O 性能:

修改 src/main.rs

fn main() -> Result<()> {
    let runtime = tokio::runtime::Builder::new_multi_thread()
        .worker_threads(4)
        .thread_name("sni-proxy-worker")
        .enable_all()
        .global_queue_interval(31)
        .event_interval(1)              // 最频繁的 I/O 轮询
        .build()?;

    runtime.block_on(async_main())
}

警告

  • ⚠️ 增加系统调用开销
  • ⚠️ CPU 使用率会上升
  • ✅ 延迟最低

方案 D:回退到默认配置

如果自定义配置导致问题,使用默认配置:

修改 src/main.rs

#[tokio::main]
async fn async_main() -> Result<()> {
    // ... 原有代码
}

fn main() -> Result<()> {
    // 简单启动(使用默认运行时)
    async_main()
}

或者保持自定义运行时但使用默认参数:

fn main() -> Result<()> {
    let runtime = tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()?;

    runtime.block_on(async_main())
}

📈 性能基准测试

测试工具

1. 连接测试

# 使用 wrk 测试 HTTP 代理性能
wrk -t4 -c100 -d30s https://example.com

# 使用 ab 测试
ab -n 10000 -c 100 https://example.com/

2. CPU 分析

# 实时监控 CPU 使用
top -H -p $(pgrep sni-proxy)

# 查看线程 CPU 分布
ps -Lf -p $(pgrep sni-proxy) -o pid,tid,psr,pcpu,comm

3. 上下文切换分析

# 监控上下文切换(越低越好)
pidstat -w -p $(pgrep sni-proxy) 1

# 预期值:
# cswch/s < 1000  (非常好)
# cswch/s < 5000  (良好)
# cswch/s > 10000 (需要优化)

4. 延迟测试

# 使用 ping 测试延迟
time curl -I https://example.com

# 查看连接建立时间
curl -w "@curl-format.txt" -o /dev/null -s https://example.com

# curl-format.txt 内容:
# time_namelookup:  %{time_namelookup}\n
# time_connect:     %{time_connect}\n
# time_starttransfer: %{time_starttransfer}\n
# time_total:       %{time_total}\n

🎛️ 调优参数说明

1. worker_threads(工作线程数)

作用:异步任务执行线程池大小

选择指南

I/O 密集型(流媒体代理):
- 推荐: CPU 核心数 / 2 到 1/4
- 范围: 4 - 8 线程
- 原因: I/O 操作主要等待,不需要大量线程

CPU 密集型(加密、压缩):
- 推荐: CPU 核心数
- 范围: 8 - 16 线程
- 原因: 需要充分利用 CPU 计算能力

混合负载:
- 推荐: CPU 核心数 × 0.75
- 范围: 6 - 12 线程
- 原因: 平衡 I/O 和 CPU 使用

测试方法

# 测试不同线程数的性能
for threads in 4 8 12 16; do
    echo "测试 $threads 线程"
    # 修改配置,重启服务,运行基准测试
    # 记录吞吐量和延迟
done

2. event_interval(事件间隔)

作用:轮询 I/O 事件的频率

参数说明

event_interval = 1:
- 每次循环都轮询 epoll/kqueue
- 延迟最低,但 CPU 开销最高
- 适合低延迟要求

event_interval = 31:
- 每 31 次任务轮询一次
- 延迟适中,CPU 开销适中
- 推荐用于流媒体代理 ✅

event_interval = 61:
- 每 61 次任务轮询一次
- 延迟稍高,CPU 开销最低
- 适合低负载环境

权衡

  • 值越小:延迟越低,但 CPU 使用率越高
  • 值越大:CPU 效率越高,但延迟越高

3. global_queue_interval(全局队列间隔)

作用:工作窃取调度器的公平性

参数说明

global_queue_interval = 10:
- 任务分配更公平
- 适合任务执行时间差异大的场景

global_queue_interval = 31: (默认)
- 平衡公平性和性能
- 推荐值 ✅

global_queue_interval = 100:
- 性能最高
- 可能导致任务分配不均

建议:保持默认值 31


🔍 故障排查

问题 1:性能仍然慢

检查项

# 1. 确认使用 release 构建
cargo build --release
./target/release/sni-proxy config.json

# 2. 检查 CPU 使用率
top -p $(pgrep sni-proxy)
# 如果 CPU 很低(< 20%),减少线程数
# 如果 CPU 很高(> 80%),增加线程数

# 3. 检查上下文切换
pidstat -w -p $(pgrep sni-proxy) 1
# 如果 cswch/s > 10000,减少线程数

# 4. 检查内存
ps aux | grep sni-proxy
# 检查 RSS(内存)是否过高

问题 2:延迟增加

可能原因

  1. event_interval 过大
  2. 线程数过少
  3. 全局队列间隔不合适

解决方案

// 降低 event_interval
.event_interval(1)  // 最激进

// 或者增加线程数
.worker_threads(num_cpus::get())

问题 3:CPU 使用率过高

可能原因

  1. event_interval 过小
  2. 线程数过多

解决方案

// 增加 event_interval
.event_interval(61)

// 减少线程数
.worker_threads(4)

💡 推荐配置总结

小型部署(< 1000 连接)

tokio::runtime::Builder::new_multi_thread()
    .worker_threads(4)
    .enable_all()
    .event_interval(31)
    .build()?

中型部署(1000-5000 连接)

let worker_threads = std::cmp::max(4, num_cpus::get() / 2);

tokio::runtime::Builder::new_multi_thread()
    .worker_threads(worker_threads)
    .enable_all()
    .event_interval(31)
    .build()?

大型部署(> 5000 连接)

tokio::runtime::Builder::new_multi_thread()
    .worker_threads(num_cpus::get())
    .enable_all()
    .event_interval(31)
    .build()?

📚 参考资料


最后更新: 2025-12-04 版本: 1.1.0