第13章:故障排查
第13章:故障排查
13.1 故障排查流程
13.1.1 通用排查步骤
1. 确认问题现象
│
2. 检查日志(varnishlog)
│
3. 检查状态(varnishstat)
│
4. 验证配置(varnishadm)
│
5. 测试后端(curl)
│
6. 分析根因
│
7. 实施修复
│
8. 验证修复
13.1.2 快速诊断命令
# 检查 Varnish 进程状态
sudo systemctl status varnish
# 检查端口监听
ss -tlnp | grep 6081
# 检查配置语法
varnishd -C -f /etc/varnish/default.vcl
# 查看运行状态
varnishadm status
# 查看关键统计
varnishstat -1 | grep -E "MAIN.cache_hit|MAIN.cache_miss|MAIN.sess"
# 查看后端状态
varnishadm backend.list
# 查看 VCL 列表
varnishadm vcl.list
# 查看参数
varnishadm param.show
# 查看 panic 信息
varnishadm panic.show
# 检查日志
varnishlog -g request -q 'RespStatus >= 500' | head -20
13.2 缓存未命中问题
13.2.1 缓存未命中原因
| 原因 | 检查方法 | 解决方案 |
|---|
| 请求方法不是 GET/HEAD | varnishlog -q 'ReqMethod != "GET"' | 确保使用 GET 请求 |
| 后端返回 no-cache | 检查 Cache-Control 头部 | 修改后端配置 |
| 响应包含 Set-Cookie | varnishlog -q 'RespHeader:Set-Cookie' | 在 VCL 中剥离 Cookie |
| Vary 头部导致变体过多 | 检查 Vary 头部 | 标准化 Vary |
| URL 有追踪参数 | 检查请求 URL | 在 VCL 中清理 URL |
| 后端返回错误状态码 | 检查响应状态码 | 修复后端问题 |
13.2.2 诊断脚本
#!/bin/bash
# diagnose-miss.sh - 缓存未命中诊断
URL="$1"
echo "=== Cache Miss Diagnosis ==="
echo "URL: $URL"
echo ""
# 测试请求
echo "--- Response Headers ---"
curl -sI "http://localhost:6081${URL}"
echo ""
# 检查缓存状态
echo "--- Cache Status ---"
CACHE=$(curl -sI "http://localhost:6081${URL}" | grep -i "x-cache")
echo "Cache: $CACHE"
# 检查 Vary
echo "--- Vary Header ---"
VARY=$(curl -sI "http://localhost:6081${URL}" | grep -i "vary")
echo "Vary: $VARY"
# 检查 Set-Cookie
echo "--- Set-Cookie ---"
COOKIE=$(curl -sI "http://localhost:6081${URL}" | grep -i "set-cookie")
echo "Set-Cookie: $COOKIE"
# 检查 Cache-Control
echo "--- Cache-Control ---"
CC=$(curl -sI "http://localhost:6081${URL}" | grep -i "cache-control")
echo "Cache-Control: $CC"
# 使用 varnishlog 分析
echo "--- Varnish Log ---"
varnishlog -g request -q "ReqURL == '${URL}'" -i ReqMethod,ReqURL,VCL_call,RespStatus 2>/dev/null | head -20
13.2.3 常见缓存未命中修复
# 问题 1:Cookie 导致未命中
sub vcl_recv {
# 移除静态资源的 Cookie
if (req.url ~ "\.(css|js|jpg|png|gif|webp|svg|ico|woff2)$") {
unset req.http.Cookie;
}
}
sub vcl_backend_response {
# 移除后端的 Set-Cookie(对于可缓存内容)
if (bereq.url ~ "\.(css|js|jpg|png|gif|webp|svg|ico|woff2)$") {
unset beresp.http.Set-Cookie;
}
}
# 问题 2:Vary 头部导致变体过多
sub vcl_recv {
# 标准化 Accept-Encoding
if (req.http.Accept-Encoding ~ "gzip") {
set req.http.Accept-Encoding = "gzip";
} else {
unset req.http.Accept-Encoding;
}
}
# 问题 3:后端返回 no-cache
sub vcl_backend_response {
# 覆盖后端的缓存策略
if (bereq.url ~ "^/static/") {
unset beresp.http.Cache-Control;
set beresp.http.Cache-Control = "public, max-age=3600";
set beresp.ttl = 1h;
}
}
# 问题 4:URL 追踪参数
sub vcl_recv {
# 移除追踪参数
set req.url = regsub(req.url, "[?&](utm_[^&]*|fbclid|gclid|_ga)=[^&]*", "");
set req.url = regsub(req.url, "\?&?$", "");
}
13.3 内存问题
13.3.1 内存使用监控
# 查看内存使用
varnishstat -1 | grep -E "SMA.g_bytes|SMA.g_space|SMA.n_object"
# 查看系统内存
free -h
# 查看 Varnish 进程内存
ps aux | grep varnishd
# 查看共享内存
ls -la /var/lib/varnish/
13.3.2 内存问题诊断
# 内存不足症状
# 1. SMA.g_space 接近 0
# 2. n_lru_nuked 持续增加
# 3. 后端请求数增加(缓存命中率下降)
# 查看 LRU 淘汰统计
varnishstat -1 | grep -E "MAIN.n_lru_nuked|MAIN.n_lru_moved"
# 查看对象大小分布
varnishstat -1 | grep -E "SMA.n_object|SMA.bereq_bodybytes|SMA.bereq_hdrbytes"
13.3.3 内存优化
# 增加缓存大小
# 修改启动参数
varnishd -s malloc,2G # 增加到 2GB
# 或修改 systemd 服务文件
# /etc/systemd/system/varnish.service
# ExecStart=/usr/bin/varnishd ... -s malloc,2G
# 重启服务
sudo systemctl daemon-reload
sudo systemctl restart varnish
# 优化 VCL,减少缓存不必要的内容
sub vcl_backend_response {
# 不缓存大文件
if (beresp.http.Content-Length ~ "[0-9]{7,}") {
set beresp.uncacheable = true;
set beresp.ttl = 0s;
return (deliver);
}
# 不缓存错误响应
if (beresp.status >= 400) {
set beresp.uncacheable = true;
set beresp.ttl = 0s;
return (deliver);
}
}
13.3.4 内存泄漏排查
# 监控内存增长
while true; do
echo "$(date): $(ps -o rss= -p $(pgrep varnishd)) KB"
sleep 60
done >> /var/log/varnish-memory.log
# 使用 valgrind 检测内存泄漏(调试模式)
# 注意:会影响性能,仅用于调试
valgrind --leak-check=full varnishd -F -f /etc/varnish/default.vcl
13.4 连接问题
13.4.1 连接问题症状
# 连接被拒绝
curl: (7) Failed to connect to localhost port 6081: Connection refused
# 连接超时
curl: (28) Operation timed out after 10000 milliseconds
# 连接重置
curl: (56) Recv failure: Connection reset by peer
13.4.2 连接问题诊断
# 检查端口监听
ss -tlnp | grep 6081
# 检查防火墙
sudo iptables -L -n | grep 6081
sudo ufw status
# 检查 Varnish 进程
ps aux | grep varnishd
# 检查系统日志
sudo journalctl -u varnish -f
# 检查连接数
varnishstat -1 | grep -E "MAIN.sess_conn|MAIN.sess_drop|MAIN.sess_fail"
# 检查文件描述符
cat /proc/$(pgrep varnishd)/limits | grep "Max open files"
ls /proc/$(pgrep varnishd)/fd | wc -l
13.4.3 连接问题修复
# 1. 增加文件描述符限制
# /etc/security/limits.d/varnish.conf
varnish soft nofile 131072
varnish hard nofile 131072
# 2. 增加系统最大连接数
# /etc/sysctl.d/99-varnish.conf
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
# 应用
sudo sysctl -p /etc/sysctl.d/99-varnish.conf
# 3. 增加 Varnish 连接参数
varnishadm param.set sess_timeout 10s
varnishadm param.set timeout_idle 10s
13.4.4 连接数耗尽
# 监控连接数
varnishstat -1 | grep -E "MAIN.sess_queued|MAIN.sess_dropped"
# 如果 sess_dropped 持续增加
# 增加线程池大小
varnishadm param.set thread_pool_max 1000
# 增加队列限制
varnishadm param.set thread_queue_limit 100
13.5 后端故障
13.5.1 后端故障症状
# 后端不可用
varnishadm backend.list
# 显示 Sick 状态
# 后端超时
varnishlog -g request -q 'VCL_Error ~ "backend fetch"'
# 后端错误响应
varnishlog -g request -q 'RespStatus >= 500 && BackendName != ""'
13.5.2 后端健康检查
# 查看后端健康状态
varnishadm backend.list -p
# 手动测试后端
curl -I http://backend-server/health
# 检查后端连接
telnet backend-server 80
13.5.3 后端故障处理
# 启用 Grace 模式
sub vcl_recv {
if (!std.healthy(req.backend_hint)) {
# 后端不健康时使用过期缓存
set req.grace = 1h;
} else {
set req.grace = 30s;
}
}
sub vcl_backend_response {
# 设置 Grace 时间
set beresp.grace = 1h;
}
# 后端故障转移
sub vcl_backend_response {
if (beresp.status >= 500) {
# 尝试其他后端
return (retry);
}
}
sub vcl_backend_fetch {
# 重试时使用不同的后端
if (req.restarts > 0) {
set bereq.backend = fallback_director.backend();
}
}
13.5.4 后端超时优化
sub vcl_backend_fetch {
# 根据请求类型设置不同的超时
if (bereq.url ~ "^/api/") {
# API 请求设置较长超时
set bereq.first_byte_timeout = 30s;
} else {
# 静态资源设置较短超时
set bereq.first_byte_timeout = 10s;
}
}
13.6 配置问题
13.6.1 VCL 语法错误
# 验证 VCL 语法
varnishd -C -f /etc/varnish/default.vcl
# 常见错误:
# - 缺少分号
# - 括号不匹配
# - 未定义的变量
# - 错误的返回动作
13.6.2 VCL 加载失败
# 通过管理接口加载
varnishadm vcl.load test /etc/varnish/default.vcl
# 查看错误信息
# 如果加载失败,会显示具体的错误行号和原因
# 常见问题:
# 1. VCL 版本不匹配
# 2. 缺少必要的子程序
# 3. 未定义的后端
13.6.3 参数配置问题
# 查看当前参数
varnishadm param.show
# 重置参数为默认值
varnishadm param.reset <param_name>
# 重置所有参数
varnishadm param.reset
13.7 性能问题
13.7.1 响应延迟高
# 查看慢请求
varnishlog -g request -q 'Timestamp:Process[2] > 1.0' | head -20
# 查看后端响应时间
varnishlog -g request -q 'VCL_call == "MISS"' -i Timestamp
# 查看线程使用
varnishstat -1 | grep -E "MAIN.threads|MAIN.thread_queue_len"
# 如果 thread_queue_len > 0,说明请求在排队
# 增加线程数
varnishadm param.set thread_pool_max 1000
13.7.2 缓存命中率低
# 查看命中率
varnishstat -1 | grep -E "MAIN.cache_hit|MAIN.cache_miss"
# 计算命中率
HIT=$(varnishstat -1 -f MAIN.cache_hit | awk '{print $2}')
MISS=$(varnishstat -1 -f MAIN.cache_miss | awk '{print $2}')
echo "Hit Rate: $(echo "scale=2; $HIT * 100 / ($HIT + $MISS)" | bc)%"
# 分析未命中的原因
varnishlog -g request -q 'VCL_call == "MISS"' -i ReqURL | head -20
13.7.3 CPU 使用率高
# 查看 CPU 使用
top -p $(pgrep varnishd)
# 可能原因:
# 1. 正则表达式过于复杂
# 2. VCL 逻辑复杂
# 3. 大量后端请求
# 优化建议:
# 1. 简化正则表达式
# 2. 减少不必要的 VCL 处理
# 3. 提高缓存命中率
13.8 调试技巧
13.8.1 VCL 调试
# 添加调试头部
sub vcl_recv {
# 记录请求信息
set req.http.X-Debug-URL = req.url;
set req.http.X-Debug-Host = req.http.Host;
set req.http.X-Debug-Method = req.method;
}
sub vcl_deliver {
# 添加缓存信息
if (obj.hits > 0) {
set resp.http.X-Debug-Cache = "HIT (" + obj.hits + ")";
} else {
set resp.http.X-Debug-Cache = "MISS";
}
set resp.http.X-Debug-TTL = obj.ttl;
set resp.http.X-Debug-Grace = obj.grace;
set resp.http.X-Debug-Backend = req.backend_hint;
set resp.http.X-Debug-VCL = "active";
}
13.8.2 实时调试
# 实时查看请求处理
varnishlog -g request -q 'ReqURL ~ "/api/"'
# 实时查看后端请求
varnishlog -g request -q 'VCL_call == "MISS"' -i ReqURL,BackendName
# 实时查看错误
varnishlog -g request -q 'RespStatus >= 400'
# 实时查看缓存状态
varnishlog -g request -i VCL_call,ReqURL
13.8.3 使用 varnishtest
# test.vtc - VCL 测试
varnishtest "Basic cache test"
server s1 {
rxreq
txresp -body "Hello World"
} -start
varnish v1 -vcl+backend {
sub vcl_recv {
return (hash);
}
sub vcl_backend_response {
set beresp.ttl = 1m;
}
} -start
client c1 {
# 第一次请求(MISS)
txreq
rxresp
expect resp.http.X-Cache == "MISS"
# 第二次请求(HIT)
txreq
rxresp
expect resp.http.X-Cache == "HIT"
} -run
# 运行测试
varnishtest test.vtc
13.8.4 日志分析
# 分析请求分布
varnishtop -i ReqURL -n 20
# 分析后端负载
varnishtop -i BackendName
# 分析错误分布
varnishtop -q 'RespStatus >= 400' -i RespStatus
# 分析缓存命中
varnishtop -q 'VCL_call == "HIT"' -i ReqURL
13.9 常见问题解答
13.9.1 FAQ
| 问题 | 原因 | 解决方案 |
|---|
| Varnish 启动失败 | 端口被占用 | 更换端口或停止占用进程 |
| 缓存不生效 | Cache-Control 头部 | 检查并修改后端配置 |
| 响应延迟高 | 后端慢或缓存未命中 | 优化后端或调整缓存策略 |
| 内存使用过高 | 缓存对象过多 | 增加内存或减少 TTL |
| 后端连接失败 | 后端不可用或网络问题 | 检查后端状态和网络 |
| VCL 加载失败 | 语法错误 | 使用 varnishd -C 验证 |
| 503 错误 | 后端超时或 VCL 错误 | 检查后端和 VCL 配置 |
| 日志过大 | 未配置轮转 | 配置 logrotate |
13.9.2 紧急处理
# 1. 清除所有缓存(慎用)
varnishadm "ban req.url ~ .*"
# 2. 禁用后端
varnishadm backend.set_health web01 sick
# 3. 启用后端
varnishadm backend.set_health web01 healthy
# 4. 重新加载 VCL
varnishadm vcl.load emergency /etc/varnish/emergency.vcl
varnishadm vcl.use emergency
# 5. 增加线程(应对突发流量)
varnishadm param.set thread_pool_max 2000
# 6. 减少缓存 TTL(快速更新)
# 需要在 VCL 中修改并重新加载
13.10 注意事项
重要
- 故障排查前先备份当前配置
- 修改参数前记录原始值,便于回滚
- 生产环境调试时避免输出过多日志
- 使用
varnishadm 的在线修改是临时的,重启后失效 - 定期检查 panic 信息,及时发现潜在问题
- 建立监控告警,提前发现和预防问题
13.11 扩展阅读