Varnish Cache 运维教程 / 第05章:缓存清除与管理
第05章:缓存清除与管理
5.1 缓存清除概述
缓存清除(Cache Invalidation)是缓存管理中最关键的操作之一。当后端内容更新时,需要及时清除旧的缓存,确保用户获取最新内容。
缓存清除方式对比
| 方式 | 机制 | 精度 | 性能 | 适用场景 |
|---|---|---|---|---|
| TTL 过期 | 自然过期 | N/A | 无开销 | 通用策略 |
| Purge | 精确删除 | URL 级别 | 低 | 单个内容更新 |
| Ban | 正则匹配标记 | 模式匹配 | 中-高 | 批量清除 |
| ban lurker | 后台清理 | 模式匹配 | 低 | 异步清理 |
5.2 Purge 操作
Purge 用于精确删除指定 URL 的缓存对象。它立即生效,且只影响精确匹配的对象。
5.2.1 基本 Purge 配置
# /etc/varnish/default.vcl
vcl 4.1;
import std;
backend default {
.host = "127.0.0.1";
.port = "8080";
}
# ACL 定义:允许执行 Purge 的客户端
acl purge_allowed {
"localhost";
"127.0.0.1";
"192.168.0.0"/24;
"10.0.0.0"/8;
}
sub vcl_recv {
# 处理 Purge 请求
if (req.method == "PURGE") {
# 验证权限
if (!client.ip ~ purge_allowed) {
return (synth(405, "Not Allowed"));
}
# 执行 Purge
return (purge);
}
}
sub vcl_purge {
# Purge 完成后返回响应
return (synth(200, "Purged"));
}
5.2.2 发送 Purge 请求
# 使用 curl 发送 PURGE 请求
curl -X PURGE http://localhost:6081/
# 指定 Host 头部
curl -X PURGE -H "Host: www.example.com" http://localhost:6081/page/to/purge
# 使用 varnishadm 执行 purge
varnishadm "ban req.url == / && req.http.host == www.example.com"
# 验证清除结果
curl -I http://localhost:6081/page/to/purge
# 应该看到 X-Cache: MISS
5.2.3 高级 Purge:清除所有变体
当内容有多个变体(不同编码、语言等)时,需要清除所有变体:
sub vcl_recv {
if (req.method == "PURGE") {
if (!client.ip ~ purge_allowed) {
return (synth(405, "Not Allowed"));
}
# 清除所有变体
ban("req.url == " + req.url + " && req.http.host == " + req.http.host);
# 也可以只清除特定变体
# return (purge);
return (synth(200, "All variants purged"));
}
}
5.3 Ban 操作
Ban 是 Varnish 更强大的缓存清除机制。它使用正则表达式匹配缓存对象,并标记匹配的对象为"已禁止"。被标记的对象在下次请求时会被清除,而不是立即删除。
5.3.1 Ban 的工作原理
执行 Ban 命令
│
▼
┌─────────────────────────────┐
│ Ban 列表 │
│ (正则表达式规则) │
├─────────────────────────────┤
│ req.url ~ "^/products/" │
│ req.http.host == "example.com" │
└─────────────────────────────┘
│
▼
缓存查找时,检查对象是否匹配任何 Ban 规则
│
├── 匹配 → 清除对象,从后端重新获取
│
└── 不匹配 → 正常返回缓存对象
5.3.2 Ban 配置
vcl 4.1;
acl ban_allowed {
"localhost";
"127.0.0.1";
"192.168.0.0"/24;
}
sub vcl_recv {
# 处理 BAN 请求
if (req.method == "BAN") {
if (!client.ip ~ ban_allowed) {
return (synth(403, "Forbidden"));
}
# 根据自定义头部执行 Ban
if (req.http.X-Ban-URL) {
ban("req.url ~ " + req.http.X-Ban-URL);
return (synth(200, "Banned URL: " + req.http.X-Ban-URL));
}
if (req.http.X-Ban-Host) {
ban("req.http.host == " + req.http.X-Ban-Host);
return (synth(200, "Banned Host: " + req.http.X-Ban-Host));
}
# 默认:根据当前请求的 URL 和 Host
ban("req.url == " + req.url + " && req.http.host == " + req.http.host);
return (synth(200, "Banned"));
}
}
5.3.3 Ban 表达式语法
Ban 表达式由操作数、操作符和值组成:
ban <operand> <operator> <value>
操作数
| 操作数 | 说明 |
|---|---|
req.url | 请求 URL |
req.http.host | 请求 Host 头部 |
req.http.* | 任意请求头部 |
obj.status | 响应状态码 |
obj.http.* | 响应头部 |
操作符
| 操作符 | 说明 | 示例 |
|---|---|---|
== | 等于 | req.http.host == "example.com" |
!= | 不等于 | req.http.host != "example.com" |
~ | 正则匹配 | req.url ~ "^/products/" |
!~ | 正则不匹配 | `req.url !~ “.(css |
逻辑连接符
# AND 连接
ban "req.url ~ ^/products/ && req.http.host == example.com"
# OR 连接
ban "req.url ~ ^/products/ || req.url ~ ^/categories/"
# NOT 操作
ban "req.url !~ \.(css|js|jpg)$"
5.3.4 Ban 使用示例
# 禁止所有 /products/ 开头的 URL
curl -X BAN -H "X-Ban-URL: ^/products/" http://localhost:6081/
# 禁止特定主机的所有内容
curl -X BAN -H "X-Ban-Host: old.example.com" http://localhost:6081/
# 禁止特定文件类型
ban "req.url ~ \.(jpg|png|gif)$"
# 禁止特定状态码的缓存
ban "obj.status == 500"
# 禁止特定响应头的内容
ban "obj.http.X-Custom-Header == obsolete"
# 禁止所有内容(慎用!)
ban "req.url ~ .*"
# 通过 varnishadm 执行
varnishadm "ban req.url ~ ^/api/"
varnishadm "ban req.http.host == staging.example.com"
5.3.5 Ban 管理
# 查看当前的 Ban 列表
varnishadm ban.list
# 输出示例:
# Present bans:
# 1499349783.274919 10538182 - req.url ~ ^/products/
# 1499349780.123456 10538180 C req.http.host == old.example.com
# 字段说明:
# 时间戳 引用计数 -/C 表达式
# - 表示该 ban 仍在被检查
# C 表示该 ban 已被所有对象检查过(可以安全删除)
# 清除所有已完成的 ban
varnishadm "ban.url .*"
5.3.6 Ban Lurker
Ban Lurker 是 Varnish 的后台清理进程,用于异步清理被 ban 标记的对象,避免在请求时同步清理造成延迟。
# 查看 ban_lurker 配置
varnishadm param.show ban_lurker_batch_sleep
varnishadm param.show ban_lurker_sleep
# 启用 ban_lurker(默认启用)
varnishadm param.set ban_lurker_sleep 0.01
# 查看 ban_lurker 工作状态
varnishstat -1 | grep -i ban
5.4 管理接口(varnishadm)
5.4.1 连接管理接口
# 交互式连接
varnishadm
# 执行单条命令
varnishadm <command>
# 指定管理端口
varnishadm -T localhost:6082
# 指定密钥文件
varnishadm -T localhost:6082 -S /etc/varnish/secret
5.4.2 常用管理命令
# 查看帮助
help
# 查看运行状态
status
# 查看参数
param.show
param.show thread_pool_max
# 设置参数
param.set thread_pool_max 1000
# VCL 管理
vcl.list # 列出所有 VCL
vcl.load config1 /etc/varnish/new.vcl # 加载新 VCL
vcl.use config1 # 使用指定 VCL
vcl.discard config1 # 删除 VCL
vcl.show config1 # 显示 VCL 内容
# 缓存管理
ban.list # 查看 ban 列表
ban <expression> # 执行 ban
purge.url <url> # 精确清除 URL
purge <expression> # 精确清除
# 统计信息
stats # 显示统计
panic.show # 查看 panic 信息
5.4.3 VCL 热加载
# 1. 编写新的 VCL 配置
vim /etc/varnish/new_config.vcl
# 2. 加载新配置(不会中断服务)
varnishadm vcl.load new_config /etc/varnish/new_config.vcl
# 3. 激活新配置
varnishadm vcl.use new_config
# 4. 删除旧配置
varnishadm vcl.discard old_config
# 一步完成:加载并激活
varnishadm vcl.load new_config /etc/varnish/new_config.vcl && \
varnishadm vcl.use new_config
5.5 批量清除策略
5.5.1 基于 URL 模式的批量清除
# 批量清除 API
sub vcl_recv {
if (req.method == "BAN") {
if (!client.ip ~ ban_allowed) {
return (synth(403, "Forbidden"));
}
# 支持多种清除模式
if (req.http.X-Purge-Type) {
if (req.http.X-Purge-Type == "url-exact") {
# 精确 URL 清除
ban("req.url == " + req.http.X-Purge-URL +
" && req.http.host == " + req.http.host);
} elif (req.http.X-Purge-Type == "url-pattern") {
# URL 模式清除
ban("req.url ~ " + req.http.X-Purge-URL);
} elif (req.http.X-Purge-Type == "content-type") {
# 按内容类型清除
ban("obj.http.Content-Type ~ " + req.http.X-Purge-Content-Type);
} elif (req.http.X-Purge-Type == "host") {
# 按主机清除
ban("req.http.host == " + req.http.X-Purge-Host);
} elif (req.http.X-Purge-Type == "all") {
# 清除所有(慎用)
ban("req.url ~ .*");
}
return (synth(200, "Ban executed"));
}
return (synth(400, "Missing X-Purge-Type header"));
}
}
5.5.2 批量清除脚本
#!/bin/bash
# batch-purge.sh - 批量清除脚本
VARNISH_HOST="localhost"
VARNISH_PORT="6081"
# 函数:精确清除
purge_url() {
local url="$1"
local host="${2:-localhost}"
curl -s -o /dev/null -w "%{http_code}" \
-X PURGE \
-H "Host: ${host}" \
"http://${VARNISH_HOST}:${VARNISH_PORT}${url}"
echo " - Purged: ${url} (Host: ${host})"
}
# 函数:模式清除
ban_pattern() {
local pattern="$1"
curl -s -o /dev/null -w "%{http_code}" \
-X BAN \
-H "X-Purge-Type: url-pattern" \
-H "X-Purge-URL: ${pattern}" \
"http://${VARNISH_HOST}:${VARNISH_PORT}/"
echo " - Banned pattern: ${pattern}"
}
# 函数:清除特定主机的所有内容
purge_host() {
local host="$1"
curl -s -o /dev/null -w "%{http_code}" \
-X BAN \
-H "X-Purge-Type: host" \
-H "X-Purge-Host: ${host}" \
"http://${VARNISH_HOST}:${VARNISH_PORT}/"
echo " - Purged host: ${host}"
}
# 使用示例
echo "Starting batch purge..."
# 清除单个 URL
purge_url "/products/123" "www.example.com"
# 清除所有产品页
ban_pattern "^/products/"
# 清除所有 API 缓存
ban_pattern "^/api/"
# 清除所有图片
ban_pattern "\.(jpg|png|gif|webp)$"
# 清除特定主机
purge_host "staging.example.com"
echo "Batch purge completed."
5.5.3 基于文件变更的自动清除
#!/bin/bash
# auto-purge-on-deploy.sh - 部署时自动清除
# 部署目录
DEPLOY_DIR="/var/www/html"
# Varnish 配置
VARNISH_HOST="localhost"
VARNISH_PORT="6081"
# 获取变更的文件列表
CHANGED_FILES=$(git diff --name-only HEAD~1)
for file in $CHANGED_FILES; do
# 将文件路径转换为 URL
url=$(echo "$file" | sed "s|^${DEPLOY_DIR}||")
if [[ "$url" =~ \.(html|css|js|json)$ ]]; then
echo "Purging: ${url}"
curl -s -X PURGE \
-H "Host: www.example.com" \
"http://${VARNISH_HOST}:${VARNISH_PORT}${url}"
fi
done
# 如果有 CSS/JS 变更,清除所有 HTML 页面(因为可能引用了这些资源)
if echo "$CHANGED_FILES" | grep -qE "\.(css|js)$"; then
echo "CSS/JS changed, purging all HTML pages..."
curl -s -X BAN \
-H "X-Purge-Type: url-pattern" \
-H "X-Purge-URL: \.html$" \
"http://${VARNISH_HOST}:${VARNISH_PORT}/"
fi
5.6 清除 API 设计
5.6.1 RESTful 清除 API
# 设计一个 RESTful 风格的缓存清除 API
sub vcl_recv {
if (req.url ~ "^/cache-api/") {
# 验证 API 密钥
if (req.http.X-API-Key != "your-secret-key") {
return (synth(401, "Invalid API Key"));
}
# 解析请求
if (req.method == "DELETE" && req.url ~ "^/cache-api/purge/(.*)") {
# DELETE /cache-api/purge/products/123
set req.http.X-Purge-URL = regsub(req.url, "^/cache-api/purge/", "");
ban("req.url ~ " + req.http.X-Purge-URL);
return (synth(200, "Purged: " + req.http.X-Purge-URL));
}
if (req.method == "DELETE" && req.url == "/cache-api/purge-all") {
# DELETE /cache-api/purge-all - 清除所有
ban("req.url ~ .*");
return (synth(200, "All cache purged"));
}
if (req.method == "GET" && req.url == "/cache-api/bans") {
# GET /cache-api/bans - 查看 ban 列表
# 注意:需要通过其他方式获取 ban 列表
return (synth(200, "Use varnishadm ban.list"));
}
return (synth(404, "Not Found"));
}
}
5.6.2 基于标签的清除系统
# 标签化缓存管理
# 在后端响应中添加标签头部
sub vcl_backend_response {
# 从后端响应中提取标签
if (beresp.http.X-Cache-Tags) {
set beresp.http.X-Cache-Tags = beresp.http.X-Cache-Tags;
} else {
# 自动生成标签
set beresp.http.X-Cache-Tags = "url:" + bereq.url;
}
}
# 根据标签清除缓存
sub vcl_recv {
if (req.method == "BAN" && req.http.X-Cache-Tag) {
# 使用标签进行 ban
ban("obj.http.X-Cache-Tags ~ " + req.http.X-Cache-Tag);
return (synth(200, "Banned tag: " + req.http.X-Cache-Tag));
}
}
5.7 清除性能优化
5.7.1 Ban 性能问题
Ban 操作可能影响性能,特别是当 ban 列表过长时:
# 监控 ban 数量
varnishstat -1 | grep -i ban
# 查看 ban 处理效率
varnishstat -1 | grep -E "MAIN.bans|MAIN.bans_completed"
5.7.2 优化建议
# 1. 定期清理已完成的 ban
varnishadm "ban.purge" # 删除所有已完成的 ban
# 2. 调整 ban lurker 参数
varnishadm param.set ban_lurker_batch_sleep 0.01
varnishadm param.set ban_lurker_sleep 0.01
# 3. 限制 ban 数量告警
# 监控脚本
BAN_COUNT=$(varnishadm ban.list | wc -l)
if [ "$BAN_COUNT" -gt 1000 ]; then
echo "WARNING: Ban list too large: $BAN_COUNT"
fi
5.7.3 使用 Purge 替代 Ban
当可能时,优先使用 Purge 而非 Ban:
# 好的做法:精确清除
sub vcl_recv {
if (req.method == "PURGE") {
return (purge);
}
}
# 避免的做法:使用 ban 清除单个 URL
# ban "req.url == /specific/path" # 不如 purge 高效
5.8 自动化缓存管理
5.8.1 CMS 集成
<?php
// WordPress 示例:文章更新时清除缓存
function purge_varnish_cache($post_id) {
$post = get_post($post_id);
$url = get_permalink($post_id);
$host = parse_url($url, PHP_URL_HOST);
$path = parse_url($url, PHP_URL_PATH);
// 清除文章页面
$varnish_host = 'localhost';
$varnish_port = '6081';
// 精确清除
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://{$varnish_host}:{$varnish_port}{$path}");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PURGE');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Host: {$host}",
"X-Purge: true"
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);
// 清除首页和列表页
purge_pattern("^/$");
purge_pattern("^/page/");
purge_pattern("^/category/");
}
function purge_pattern($pattern) {
$varnish_host = 'localhost';
$varnish_port = '6081';
$host = $_SERVER['HTTP_HOST'];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://{$varnish_host}:{$varnish_port}/");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'BAN');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Host: {$host}",
"X-Ban-URL: {$pattern}"
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);
}
// WordPress 钩子
add_action('save_post', 'purge_varnish_cache');
add_action('delete_post', 'purge_varnish_cache');
?>
5.8.2 持续集成集成
# .github/workflows/deploy.yml 示例
name: Deploy and Purge Cache
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to server
run: |
rsync -avz ./dist/ user@server:/var/www/html/
- name: Purge Varnish cache
run: |
ssh user@server 'bash -s' << 'EOF'
# 清除所有 HTML 页面
varnishadm "ban req.url ~ \.html$"
# 清除 CSS/JS
varnishadm "ban req.url ~ \.(css|js)$"
# 或者清除所有
varnishadm "ban req.url ~ .*"
EOF
5.9 注意事项
重要
- Purge 需要精确的 URL 匹配,包括 Host 头部
- Ban 是异步操作,不会立即删除对象,但在下次访问时生效
- 过多的 ban 规则会影响性能,建议定期清理已完成的 ban
- 生产环境必须限制清除操作的权限(ACL 或 API Key)
- 批量清除可能导致后端服务器瞬间高负载(缓存雪崩)
- 清除操作前建议先在测试环境验证
- 使用
ban lurker进行异步清理可以减少请求延迟