服务器性能优化:调优不够用了之后

这篇记录的是怎么从监控数据倒推系统层调优,而不是堆更多机器。

接口 P99 从 200ms 漂到 800ms,应用层代码没动,查下来是 nginx worker_connections 和文件描述符上限先撞墙了。

性能评估

系统监控

# CPU 使用率
top
htop
mpstat 1

# 内存使用
free -h
vmstat 1

# 磁盘 I/O
iostat -x 1
iotop

# 网络流量
iftop
nethogs

应用性能

# 查看进程状态
ps aux
lsof -p <pid>

# 查看打开的文件数
lsof -p <pid> | wc -l

# 查看网络连接
netstat -anp | grep <pid>
ss -tunp | grep <pid>

操作系统优化

内核参数

# /etc/sysctl.conf
# 网络优化
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30

# 文件描述符
fs.file-max = 1000000

# 内存优化
vm.swappiness = 10
vm.dirty_ratio = 15
vm.dirty_background_ratio = 5

# 应用更改
sysctl -p

文件描述符限制

# /etc/security/limits.conf
* soft nofile 65535
* hard nofile 65535
* soft nproc 65535
* hard nproc 65535

# 验证
ulimit -n
ulimit -u

应用优化

Nginx 优化

# nginx.conf
worker_processes auto;
worker_rlimit_nofile 65535;

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

http {
    # 连接优化
    keepalive_timeout 65;
    keepalive_requests 10000;
    
    # 缓存优化
    open_file_cache max=100000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    
    # Gzip 压缩
    gzip on;
    gzip_comp_level 6;
    gzip_types text/plain text/css application/json application/javascript;
    
    # Buffer 优化
    client_body_buffer_size 128k;
    client_max_body_size 10m;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 4k;
    output_buffers 1 32k;
    postpone_output 1460;
}

MySQL 优化

# my.cnf
[mysqld]
# 连接优化
max_connections = 500
max_connect_errors = 10000

# 缓存优化
innodb_buffer_pool_size = 4G
innodb_log_file_size = 256M
innodb_flush_log_at_trx_commit = 2

# 查询缓存
query_cache_type = 1
query_cache_size = 64M

# 慢查询
slow_query_log = 1
long_query_time = 2

Redis 优化

# redis.conf
# 内存优化
maxmemory 2gb
maxmemory-policy allkeys-lru

# 持久化优化
save ""
appendonly no

# 网络优化
tcp-backlog 511
tcp-keepalive 300

# 连接数
maxclients 10000

Node.js 优化

// 增加 worker 线程数
process.env.UV_THREADPOOL_SIZE = 4;

// 使用 cluster 模式
const cluster = require('cluster');
const os = require('os');

if (cluster.isMaster) {
  const numCPUs = os.cpus().length;
  
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
  
  cluster.on('exit', (worker) => {
    console.log(`Worker ${worker.process.pid} died. Restarting...`);
    cluster.fork();
  });
} else {
  // 应用代码
  require('./app');
}

Go 优化

// 设置 GOMAXPROCS
runtime.GOMAXPROCS(runtime.NumCPU())

// 使用 goroutine 池
type WorkerPool struct {
    tasks chan Task
    wg    sync.WaitGroup
}

func NewWorkerPool(size int) *WorkerPool {
    pool := &WorkerPool{
        tasks: make(chan Task, 100),
    }
    
    pool.wg.Add(size)
    for i := 0; i < size; i++ {
        go pool.worker()
    }
    
    return pool
}

func (p *WorkerPool) worker() {
    defer p.wg.Done()
    for task := range p.tasks {
        task.Execute()
    }
}

func (p *WorkerPool) Submit(task Task) {
    p.tasks <- task
}

数据库优化

查询优化

-- 分析慢查询
EXPLAIN SELECT * FROM users WHERE email = '[email protected]';

-- 添加索引
CREATE INDEX idx_users_email ON users(email);

-- 优化查询
SELECT id, name FROM users WHERE email = '[email protected]';

索引优化

-- 查看索引使用情况
SELECT * FROM sys.schema_unused_indexes;

-- 删除未使用的索引
DROP INDEX idx_unused ON table_name;

-- 优化复合索引
-- 错误:索引顺序不对
CREATE INDEX idx_users_name_age ON users(name, age);

-- 正确:按照查询条件排序
CREATE INDEX idx_users_age_name ON users(age, name);

缓存优化

Redis 缓存

import redis
import json

# 连接池
pool = redis.ConnectionPool(
    host='localhost',
    port=6379,
    max_connections=100
)
r = redis.Redis(connection_pool=pool)

# 缓存策略
def get_with_cache(key, ttl=3600):
    # 尝试从缓存获取
    data = r.get(key)
    if data:
        return json.loads(data)
    
    # 缓存未命中,从数据库获取
    data = load_from_database(key)
    if data:
        r.setex(key, ttl, json.dumps(data))
    
    return data

# 预热缓存
def warm_up_cache():
    for user_id in all_user_ids:
        user = load_from_database(user_id)
        r.setex(f'user:{user_id}', 3600, json.dumps(user))

本地缓存

from functools import lru_cache
from cachetools import TTLCache

# 使用 LRU 缓存
@lru_cache(maxsize=1000)
def get_user(user_id):
    return load_from_database(user_id)

# 使用 TTL 缓存
cache = TTLCache(maxsize=1000, ttl=3600)

def get_user_with_ttl(user_id):
    if user_id in cache:
        return cache[user_id]
    
    user = load_from_database(user_id)
    cache[user_id] = user
    return user

网络优化

TCP 优化

# /etc/sysctl.conf
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_congestion_control = bbr
net.core.netdev_max_backlog = 5000
net.ipv4.tcp_fastopen = 3

CDN 加速

# 静态资源使用 CDN
location ~* \.(jpg|jpeg|png|gif|webp|css|js)$ {
    rewrite ^ https://cdn.example.com$uri permanent;
}

踩过的坑

坑一:过度优化

一开始追求极致性能,结果系统不稳定。

解决:先解决问题,再优化。

坑二:缓存失效

缓存策略设计不好,导致缓存雪崩。

解决

  • 随机过期时间
  • 多级缓存
  • 缓存预热
import random

def set_cache_with_random_ttl(key, value, base_ttl=3600):
    ttl = base_ttl + random.randint(0, 300)
    r.setex(key, ttl, value)

坑三:锁竞争

高并发场景,锁竞争严重。

解决

  • 减少锁的粒度
  • 使用乐观锁
  • 使用无锁数据结构
// 使用 sync.Map 替代 map + mutex
var cache sync.Map

func get(key string) (interface{}, bool) {
    return cache.Load(key)
}

func set(key string, value interface{}) {
    cache.Store(key, value)
}

性能测试

压力测试

# 使用 ab (Apache Bench)
ab -n 10000 -c 100 http://localhost:8080/api/users

# 使用 wrk
wrk -t12 -c400 -d30s http://localhost:8080/api/users

# 使用 locust
locust -f locustfile.py --host=http://localhost:8080

性能分析

# 使用 pprof
go tool pprof http://localhost:6060/debug/pprof/profile

# 使用 perf
perf top -p <pid>

# 使用 strace
strace -p <pid> -c

写在最后

服务器性能优化这东西,不是追求极致,而是追求合适。

优化顺序

  1. 先优化数据库查询
  2. 再优化缓存策略
  3. 最后优化系统配置

避免

  • 过早优化
  • 过度优化
  • 盲目优化

优化之前先评估:

  • 瓶颈在哪里
  • 优化成本
  • 预期收益

不是所有问题都需要优化,有时候加硬件更简单。


这次服务器性能优化花了三周,从配置调优到架构优化。优化完成后,QPS 从 1000 提升到 10000,响应时间从 500ms 降到 50ms。

版权声明: 本文首发于 指尖魔法屋-服务器性能优化:调优不够用了之后https://blog.thinkmoon.cn/post/68-server-performance-tuning-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!