缓存失效 #

一、缓存失效概述 #

1.1 为什么需要缓存失效 #

  • 内容更新后需要及时反映
  • 避免展示过期内容
  • 保持数据一致性
  • 支持即时发布

1.2 失效方法对比 #

方法 说明 适用场景
PURGE 立即删除特定URL缓存 精确失效
BAN 标记匹配的缓存失效 批量失效
TTL过期 自动过期 自动失效
Grace过期 宽限期后失效 优雅降级

二、PURGE方法 #

2.1 基本配置 #

vcl
acl purge {
    "localhost";
    "192.168.1.0"/24;
}

sub vcl_recv {
    if (req.method == "PURGE") {
        if (!client.ip ~ purge) {
            return (synth(405, "Not allowed."));
        }
        return (purge);
    }
}

2.2 使用PURGE #

bash
# PURGE特定URL
curl -X PURGE http://varnish:6081/images/logo.png

# PURGE带Host
curl -X PURGE -H "Host: example.com" http://varnish:6081/page.html

# PURGE带Cookie
curl -X PURGE -H "Cookie: session=abc" http://varnish:6081/page.html

2.3 PURGE响应处理 #

vcl
sub vcl_synth {
    if (resp.status == 200 && req.method == "PURGE") {
        set resp.http.Content-Type = "application/json";
        synthetic({"{"status":"ok","message":"Purged"}"});
        return (deliver);
    }
}

2.4 PURGE日志 #

bash
# 查看PURGE日志
varnishlog -q "ReqMethod eq PURGE"

# 统计PURGE次数
varnishstat -1 -f MAIN.n_purges

三、BAN方法 #

3.1 基本配置 #

vcl
acl purge {
    "localhost";
    "192.168.1.0"/24;
}

sub vcl_recv {
    if (req.method == "BAN") {
        if (!client.ip ~ purge) {
            return (synth(405, "Not allowed."));
        }
        ban("req.url ~ " + req.url);
        return (synth(200, "Banned."));
    }
}

3.2 BAN规则语法 #

vcl
# BAN特定URL
ban("req.url == /index.html");

# BAN URL模式
ban("req.url ~ ^/images/");

# BAN特定Host
ban("req.http.Host == example.com");

# BAN特定内容类型
ban("obj.http.Content-Type ~ image/");

# BAN组合条件
ban("req.http.Host == example.com && req.url ~ ^/news/");

# BAN特定标签
ban("obj.http.Surrogate-Key ~ article_123");

3.3 使用BAN #

bash
# BAN所有图片
curl -X BAN http://varnish:6081/ -H "X-Ban-Url: \.(jpg|png|gif)$"

# BAN特定路径
curl -X BAN http://varnish:6081/ -H "X-Ban-Url: ^/news/"

# BAN特定域名
curl -X BAN http://varnish:6081/ -H "X-Ban-Host: example.com"

3.4 高级BAN配置 #

vcl
sub vcl_recv {
    if (req.method == "BAN") {
        if (!client.ip ~ purge) {
            return (synth(405, "Not allowed."));
        }
        
        # 基于请求头BAN
        if (req.http.X-Ban-Url) {
            ban("req.url ~ " + req.http.X-Ban-Url);
        }
        
        if (req.http.X-Ban-Host) {
            ban("req.http.Host == " + req.http.X-Ban-Host);
        }
        
        if (req.http.X-Ban-Type) {
            ban("obj.http.Content-Type ~ " + req.http.X-Ban-Type);
        }
        
        if (req.http.X-Ban-Tag) {
            ban("obj.http.Surrogate-Key ~ " + req.http.X-Ban-Tag);
        }
        
        # 默认BAN请求URL
        if (!req.http.X-Ban-Url && 
            !req.http.X-Ban-Host && 
            !req.http.X-Ban-Type &&
            !req.http.X-Ban-Tag) {
            ban("req.url ~ " + req.url);
        }
        
        return (synth(200, "Banned."));
    }
}

3.5 BAN列表管理 #

bash
# 查看BAN列表
varnishadm ban.list

# 输出示例
Present bans:
1648123456.789012   C
1648123455.123456   C    req.url ~ ^/images/
1648123454.987654        req.url ~ \.css$

# C表示已完成(completed)

3.6 BAN清理 #

bash
# 设置BAN清理参数
varnishadm param.set ban_lurker_age 60
varnishadm param.set ban_lurker_batch 1000
varnishadm param.set ban_lurker_sleep 0.01

四、PURGE vs BAN #

4.1 工作原理对比 #

text
PURGE:
┌─────────────────────────────────────────┐
│  Request ──► 查找缓存 ──► 立即删除      │
└─────────────────────────────────────────┘

BAN:
┌─────────────────────────────────────────┐
│  Request ──► 添加BAN规则 ──► 标记失效   │
│                                         │
│  后续请求 ──► 检查BAN ──► 返回MISS      │
└─────────────────────────────────────────┘

4.2 特性对比 #

特性 PURGE BAN
作用范围 单个URL URL模式
执行方式 立即删除 标记失效
性能影响 可能累积
精确度 模式匹配
使用场景 精确失效 批量失效

4.3 选择建议 #

场景 推荐方法
单个页面更新 PURGE
批量图片更新 BAN
全站更新 BAN all
特定内容类型 BAN + Content-Type
标签关联内容 BAN + Surrogate-Key

五、Surrogate-Key失效 #

5.1 设置Surrogate-Key #

vcl
sub vcl_backend_response {
    # 基于URL设置标签
    if (bereq.url ~ "^/articles/") {
        set beresp.http.Surrogate-Key = "articles";
    }
    
    if (bereq.url ~ "^/products/") {
        set beresp.http.Surrogate-Key = "products";
    }
    
    # 设置多个标签
    if (bereq.url ~ "^/articles/([0-9]+)") {
        set beresp.http.Surrogate-Key = "articles article_" + 
            re.group.1;
    }
}

5.2 基于标签失效 #

vcl
sub vcl_recv {
    if (req.method == "BAN") {
        if (!client.ip ~ purge) {
            return (synth(405, "Not allowed."));
        }
        
        if (req.http.X-Ban-Tag) {
            ban("obj.http.Surrogate-Key ~ " + req.http.X-Ban-Tag);
            return (synth(200, "Banned by tag."));
        }
    }
}

5.3 使用标签失效 #

bash
# 失效所有文章
curl -X BAN -H "X-Ban-Tag: articles" http://varnish:6081/

# 失效特定文章
curl -X BAN -H "X-Ban-Tag: article_123" http://varnish:6081/

# 失效多个标签
curl -X BAN -H "X-Ban-Tag: articles|products" http://varnish:6081/

六、主动失效 #

6.1 Webhook触发失效 #

vcl
sub vcl_recv {
    if (req.url == "/webhook/purge" && req.method == "POST") {
        if (!client.ip ~ purge) {
            return (synth(405, "Not allowed."));
        }
        
        # 从请求体获取URL
        # 需要使用VMOD处理请求体
        return (synth(200, "Webhook received."));
    }
}

6.2 CMS集成失效 #

php
<?php
// WordPress/Drupal等CMS集成示例

function purge_varnish_cache($url) {
    $varnish_host = 'varnish.example.com';
    $varnish_port = 6081;
    
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "http://{$varnish_host}:{$varnish_port}{$url}");
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PURGE");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_exec($ch);
    curl_close($ch);
}

// 发布文章后失效缓存
purge_varnish_cache('/articles/new-article');

6.3 自动失效脚本 #

bash
#!/bin/bash
# auto_purge.sh

# 监控文件变化并触发失效
inotifywait -m -r -e modify,create,delete /var/www/html/ |
while read path action file; do
    echo "File $file $action, purging cache..."
    
    # 获取URL路径
    url_path="${path#/var/www/html}$file"
    
    # 发送PURGE请求
    curl -X PURGE "http://localhost:6081$url_path"
done

七、批量失效 #

7.1 批量PURGE脚本 #

bash
#!/bin/bash
# batch_purge.sh

URLS=(
    "/index.html"
    "/about.html"
    "/contact.html"
    "/products/"
)

for url in "${URLS[@]}"; do
    echo "Purging: $url"
    curl -X PURGE "http://localhost:6081$url"
done

echo "Batch purge completed."

7.2 从站点地图批量失效 #

bash
#!/bin/bash
# sitemap_purge.sh

SITEMAP_URL="https://example.com/sitemap.xml"

# 获取sitemap中的URL
urls=$(curl -s "$SITEMAP_URL" | grep -oP '(?<=<loc>)[^<]+')

# 批量PURGE
for url in $urls; do
    # 提取路径
    path=$(echo "$url" | sed 's|https\?://[^/]*/||')
    echo "Purging: /$path"
    curl -X PURGE "http://localhost:6081/$path"
done

echo "Sitemap purge completed."

7.3 正则批量失效 #

bash
#!/bin/bash
# regex_ban.sh

# BAN所有图片
curl -X BAN -H "X-Ban-Url: \.(jpg|png|gif)$" http://localhost:6081/

# BAN所有CSS/JS
curl -X BAN -H "X-Ban-Url: \.(css|js)$" http://localhost:6081/

# BAN特定路径
curl -X BAN -H "X-Ban-Url: ^/news/2023/" http://localhost:6081/

八、失效监控 #

8.1 失效统计 #

bash
# 查看PURGE次数
varnishstat -1 -f MAIN.n_purges

# 查看BAN数量
varnishstat -1 -f MAIN.bans

# 查看BAN相关统计
varnishstat -1 -f MAIN.bans_*

8.2 失效日志 #

bash
# 查看PURGE日志
varnishlog -q "ReqMethod eq PURGE"

# 查看BAN日志
varnishlog -q "ReqMethod eq BAN"

# 记录失效日志
varnishlog -w /var/log/varnish/purge.log -q "ReqMethod eq PURGE or ReqMethod eq BAN"

8.3 监控脚本 #

bash
#!/bin/bash
# purge_monitor.sh

echo "=== Cache Invalidation Stats ==="
echo ""

echo "PURGE Count:"
varnishstat -1 -f MAIN.n_purges

echo ""
echo "BAN Count:"
varnishstat -1 -f MAIN.bans

echo ""
echo "BAN List:"
varnishadm ban.list | head -20

echo ""
echo "Recent PURGE Requests:"
varnishlog -d -q "ReqMethod eq PURGE" | head -20

九、失效最佳实践 #

9.1 失效策略 #

内容类型 失效策略 TTL
静态资源 版本化URL 长TTL
动态页面 主动失效 短TTL
API响应 事件驱动 极短TTL
用户内容 按需失效 中等TTL

9.2 失效时机 #

vcl
sub vcl_backend_response {
    # 设置失效标记
    if (bereq.url ~ "^/articles/") {
        set beresp.http.Surrogate-Key = "articles";
        set beresp.http.X-Cache-Tags = "articles,homepage";
    }
}

9.3 避免过度失效 #

vcl
# 使用Grace避免频繁失效
sub vcl_backend_response {
    set beresp.grace = 1h;
}

sub vcl_hit {
    # 过期内容仍可服务
    if (obj.ttl + obj.grace > 0s) {
        return (deliver);
    }
    return (restart);
}

十、完整配置示例 #

vcl
vcl 4.1;

import std;

# ACL定义
acl purge {
    "localhost";
    "192.168.1.0"/24;
    "10.0.0.0"/8;
}

# 后端定义
backend default {
    .host = "127.0.0.1";
    .port = "8080";
}

# 请求处理
sub vcl_recv {
    # PURGE处理
    if (req.method == "PURGE") {
        if (!client.ip ~ purge) {
            return (synth(405, "Not allowed."));
        }
        return (purge);
    }
    
    # BAN处理
    if (req.method == "BAN") {
        if (!client.ip ~ purge) {
            return (synth(405, "Not allowed."));
        }
        
        # 基于标签失效
        if (req.http.X-Ban-Tag) {
            ban("obj.http.Surrogate-Key ~ " + req.http.X-Ban-Tag);
            return (synth(200, "Banned by tag: " + req.http.X-Ban-Tag));
        }
        
        # 基于URL失效
        if (req.http.X-Ban-Url) {
            ban("req.url ~ " + req.http.X-Ban-Url);
            return (synth(200, "Banned by URL: " + req.http.X-Ban-Url));
        }
        
        # 基于Host失效
        if (req.http.X-Ban-Host) {
            ban("req.http.Host == " + req.http.X-Ban-Host);
            return (synth(200, "Banned by Host: " + req.http.X-Ban-Host));
        }
        
        # 基于内容类型失效
        if (req.http.X-Ban-Type) {
            ban("obj.http.Content-Type ~ " + req.http.X-Ban-Type);
            return (synth(200, "Banned by Type: " + req.http.X-Ban-Type));
        }
        
        # 默认失效请求URL
        ban("req.url ~ " + req.url);
        return (synth(200, "Banned: " + req.url));
    }
    
    # 正常请求处理
    if (req.method != "GET" && req.method != "HEAD") {
        return (pass);
    }
    
    return (hash);
}

# 后端响应处理
sub vcl_backend_response {
    # 设置Surrogate-Key
    if (bereq.url ~ "^/articles/") {
        set beresp.http.Surrogate-Key = "articles";
    }
    
    if (bereq.url ~ "^/products/") {
        set beresp.http.Surrogate-Key = "products";
    }
    
    # 设置Grace
    set beresp.grace = 1h;
}

# 合成响应
sub vcl_synth {
    set resp.http.Content-Type = "application/json";
    
    if (resp.status == 200) {
        synthetic({"{"status":"ok","message":"} + resp.reason + {"}"});
    } else {
        synthetic({"{"status":"error","code":} + resp.status + {","message":"} + resp.reason + {"}"});
    }
    
    return (deliver);
}

十一、总结 #

本章我们学习了:

  1. 缓存失效概述:失效原因、方法对比
  2. PURGE方法:配置、使用、响应处理
  3. BAN方法:规则语法、高级配置、列表管理
  4. PURGE vs BAN:原理对比、选择建议
  5. Surrogate-Key:标签失效
  6. 主动失效:Webhook、CMS集成
  7. 批量失效:脚本、站点地图、正则
  8. 失效监控:统计、日志、监控脚本

掌握缓存失效后,让我们进入下一章,学习请求处理!

最后更新:2026-03-28