You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用Varnish缓存带分页的博客搜索结果页的最佳实践

Varnish缓存带分页的博客搜索结果页的最佳实践

作为常年和Varnish打交道、踩过无数缓存坑的老司机,我来给你拆解下带分页的博客搜索结果页该怎么高效缓存——毕竟这种场景搞不好要么缓存命中率低到感人,要么用户刷到旧内容骂娘,得平衡好缓存效率和内容新鲜度。

一、先搞定最核心的:精准设计缓存键

搜索结果的唯一性完全由query(搜索词)和page(页码)两个参数决定,所以第一步要确保Varnish把这俩参数纳入缓存键的计算,同时避免重复缓存相同内容的请求。

1. 标准化参数顺序,避免冗余缓存

用户有时候会传不同顺序的参数(比如?query=varnish&page=2?page=2&query=varnish),默认Varnish会把它们当成不同的请求缓存两次,纯纯浪费资源。可以在vcl_recv里标准化参数顺序:

sub vcl_recv {
    # 针对搜索接口,强制把query参数放在page前面,统一缓存键
    if (req.url ~ "^/search\?") {
        set req.url = regsuball(req.url, "(page=[^&]+)&(query=[^&]+)", "\2&\1");
        # 去除重复的参数(比如用户不小心传了两个page参数)
        set req.url = regsuball(req.url, "&page=[^&]+(?=&page=)", "");
    }
}

2. 确保缓存键包含必要维度

默认的vcl_hash已经包含req.url,但如果你有多域名或者多语言的场景,记得把req.http.hostreq.http.Accept-Language也加进去,避免跨域名/跨语言的缓存混乱:

sub vcl_hash {
    hash_data(req.http.host);
    hash_data(req.url);
    # 如果有语言区分,加上Accept-Language
    if (req.http.Accept-Language) {
        hash_data(req.http.Accept-Language);
    }
    return (lookup);
}

二、后端该返回的缓存控制头

后端要给Varnish明确的缓存指令,别让它猜!推荐返回这样的响应头:

  • Cache-Control: public, s-maxage=3600, max-age=60
    • public:告诉Varnish这是公共可缓存的内容,不用因为Cookie之类的跳过缓存
    • s-maxage=3600:给Varnish的缓存时间(比如1小时,根据你的内容更新频率调整,更新勤就设短点)
    • max-age=60:给浏览器的缓存时间(比如1分钟,避免浏览器存太久导致用户看不到新内容)
  • 额外加分项:返回ETagLast-Modified头,这样Varnish可以在缓存过期后用If-None-Match/If-Modified-Since去后端验证,如果内容没变化,直接返回304,省带宽又快。

三、Varnish端的针对性配置

光靠后端头还不够,Varnish里还要做一些适配,让分页缓存更丝滑:

1. 缓存空搜索结果

很多人会忽略空结果的缓存——比如用户搜了一个完全没匹配的词,每次都回源太浪费。可以在vcl_backend_response里给空结果设缓存:

sub vcl_backend_response {
    if (req.url ~ "^/search\?" && beresp.status == 200 && beresp.http.Content-Length == "0") {
        set beresp.ttl = 1h;
        set beresp.http.Cache-Control = "public, s-maxage=3600";
    }
}

2. 给搜索缓存打标签,方便批量失效

当你发布了新博客,可能需要让相关的搜索结果缓存失效。给所有搜索请求的缓存加个标签,后面要刷新的时候一键搞定:

sub vcl_backend_response {
    if (req.url ~ "^/search\?") {
        set beresp.http.X-Cache-Tag = "search_results";
        # 更精准的话,可以给特定搜索词打标签,比如query=varnish的加tag: search_varnish
        if (req.url ~ "query=([^&]+)") {
            set beresp.http.X-Cache-Tag = beresp.http.X-Cache-Tag + ", search_" + regsub(req.url, "^.*query=([^&]+).*$", "\1");
        }
    }
}

然后要刷新时,用Varnish admin命令:

# 刷新所有搜索结果缓存
varnishadm ban obj.http.X-Cache-Tag ~ search_results
# 只刷新和varnish相关的搜索缓存
varnishadm ban obj.http.X-Cache-Tag ~ search_varnish

3. 过滤无关的Cookie

如果你的搜索结果和用户登录状态无关(比如不管登不登录,搜索结果都一样),那就在vcl_recv里去掉Cookie,避免Varnish因为Cookie跳过缓存:

sub vcl_recv {
    if (req.url ~ "^/search\?" && req.http.Cookie !~ "user_preference") {
        # 这里的user_preference是指如果有影响搜索结果的Cookie才保留,否则都清掉
        unset req.http.Cookie;
    }
}

四、分页缓存的最佳实践

  • 预缓存热门搜索词的前几页:如果某些搜索词(比如你的博客高频话题)的前3页访问量特别大,可以在内容更新后主动预热缓存——比如用curl循环请求这些页面,让Varnish提前把缓存建好,用户访问时直接命中。
  • 处理非法分页请求:比如用户传了page=0或者超过最大页数的page=1000,后端返回404或者重定向到page=1,这些响应也要缓存,避免每次都回源处理无效请求。
  • 分离个性化内容:如果搜索结果里有个性化模块(比如“你可能喜欢”),别把这部分和公共搜索结果混在一起!最好的做法是:公共的搜索列表缓存,个性化部分用前端AJAX单独请求,这样既不影响缓存命中率,又能保留个性化体验。

五、避坑提醒

  • 别乱加privateCache-Control:加了这个Varnish会直接跳过缓存,白忙活一场。
  • 别设置过长的s-maxage:如果你的博客一天更好几次,设1小时就够了,不然用户会看到旧内容;如果是静态博客,设个1天甚至1周都没问题,但要配好失效机制。
  • 别忘了Vary:如果搜索结果根据语言(Accept-Language)或者设备类型(User-Agent)返回不同内容,后端要返回Vary: Accept-Language, User-Agent,Varnish会根据这些头区分缓存键,避免内容混乱。

备注:内容来源于stack exchange,提问作者ahmedbhs

火山引擎 最新活动