服务器性能优化:调优不够用了之后
这篇记录的是怎么从监控数据倒推系统层调优,而不是堆更多机器。
接口 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
写在最后
服务器性能优化这东西,不是追求极致,而是追求合适。
优化顺序:
- 先优化数据库查询
- 再优化缓存策略
- 最后优化系统配置
避免:
- 过早优化
- 过度优化
- 盲目优化
优化之前先评估:
- 瓶颈在哪里
- 优化成本
- 预期收益
不是所有问题都需要优化,有时候加硬件更简单。
这次服务器性能优化花了三周,从配置调优到架构优化。优化完成后,QPS 从 1000 提升到 10000,响应时间从 500ms 降到 50ms。
版权声明: 本文首发于 指尖魔法屋-服务器性能优化:调优不够用了之后(https://blog.thinkmoon.cn/post/68-server-performance-tuning-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。