强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第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/HEADvarnishlog -q 'ReqMethod != "GET"'确保使用 GET 请求
后端返回 no-cache检查 Cache-Control 头部修改后端配置
响应包含 Set-Cookievarnishlog -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 注意事项

重要

  1. 故障排查前先备份当前配置
  2. 修改参数前记录原始值,便于回滚
  3. 生产环境调试时避免输出过多日志
  4. 使用 varnishadm 的在线修改是临时的,重启后失效
  5. 定期检查 panic 信息,及时发现潜在问题
  6. 建立监控告警,提前发现和预防问题

13.11 扩展阅读