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

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 注意事项

重要

  1. Purge 需要精确的 URL 匹配,包括 Host 头部
  2. Ban 是异步操作,不会立即删除对象,但在下次访问时生效
  3. 过多的 ban 规则会影响性能,建议定期清理已完成的 ban
  4. 生产环境必须限制清除操作的权限(ACL 或 API Key)
  5. 批量清除可能导致后端服务器瞬间高负载(缓存雪崩)
  6. 清除操作前建议先在测试环境验证
  7. 使用 ban lurker 进行异步清理可以减少请求延迟

5.10 扩展阅读