You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为相似缓存键的多请求一次性缓存相同合成响应

解决Varnish中同路径不同参数的重复空响应缓存问题

问题核心

你面临的矛盾很清晰:

  • 正常用户的/user/{id}/config响应会随查询参数变化,因此缓存键必须包含完整路径+所有查询参数
  • 但当目标用户不存在时,后端返回固定空JSON{},此时每个带不同参数的请求都会生成独立缓存条目,完全没必要,还会浪费缓存资源、增加后端请求量
  • 同时xkey必须保持为资源路径(比如user/1/config),用于按用户ID触发缓存清理

下面是两种可落地的解决方案,分别对应能修改应用端和无法修改应用端的场景:

方案一:应用端配合添加标识,Varnish统一复用缓存

这是最简洁的方案,只需要应用端加个响应头,VCL做对应调整:

  1. 应用端改造:当检测到用户不存在时,返回状态码200的同时,在响应头里加个自定义标识,比如X-User-Exists: false,响应体还是{}

  2. VCL逻辑调整:

    • 在vcl_backend_response中判断这个标识,如果存在,就把缓存键改成仅资源路径(去掉所有查询参数);正常用户的请求还是保留原缓存键规则
    • xkey保持原逻辑,只设为资源路径

    示例VCL片段:

    sub vcl_backend_response {
        # 提取纯资源路径(去掉查询参数)
        set req.url_clean = regsub(req.url, "\?.*$", "");
        # 设置统一的xkey
        set beresp.http.xkey = req.url_clean;
    
        # 如果是用户不存在的空响应,重写缓存键
        if (beresp.http.X-User-Exists == "false") {
            set beresp.cache_key = req.url_clean;
            # 可选:给这类缓存设个较长TTL,因为用户不存在的状态不会频繁变
            set beresp.ttl = 1h;
        } else {
            # 正常用户响应,用原路径+参数当缓存键
            set beresp.cache_key = req.url;
            # 按业务需求设置正常TTL
            set beresp.ttl = 10m;
        }
    }
    

    效果:第一个带任意参数的请求会触发后端请求,缓存键设为无参数的路径;后续同路径的所有参数请求都会命中这个缓存,不用再发后端请求

方案二:纯VCL逻辑实现,无需修改应用端

如果没法改应用端,可以通过VCL的二次查询逻辑来实现:

  1. 首次请求:按原规则发往后端,拿到空响应后,额外存一个以资源路径为键的缓存条目

  2. 后续请求:先查这个无参数的缓存,如果存在且是空响应,直接返回;否则再按原规则发往后端

    示例VCL片段:

    sub vcl_recv {
        # 提取纯资源路径
        set req.url_clean = regsub(req.url, "\?.*$", "");
        
        # 先尝试查询无参数的缓存条目
        set req.hash = req.url_clean;
        return(lookup);
    }
    
    sub vcl_hit {
        # 如果命中无参数缓存,且响应体是空JSON,直接返回
        if (req.url_clean != req.url && beresp.body == "{}") {
            return(deliver);
        }
        # 否则,按原请求的完整路径+参数重新查询
        set req.hash = req.url;
        return(lookup);
    }
    
    sub vcl_miss {
        # 如果是无参数缓存未命中,切换回原请求的缓存键继续查询
        if (req.url_clean != req.url) {
            set req.hash = req.url;
            return(lookup);
        }
    }
    
    sub vcl_backend_response {
        set req.url_clean = regsub(req.url, "\?.*$", "");
        set beresp.http.xkey = req.url_clean;
    
        # 如果后端返回空响应,同时存原参数缓存和无参数缓存
        if (beresp.body == "{}") {
            # 存无参数的缓存条目,供后续同路径请求复用
            set beresp.cache_key = req.url_clean;
            # 同时保留原参数的缓存(可选,避免同参数重复请求后端)
            set beresp.http.x-cache-original-key = req.url;
            set beresp.ttl = 1h;
        } else {
            # 正常响应只存原参数的缓存
            set beresp.cache_key = req.url;
            set beresp.ttl = 10m;
        }
    }
    

    注意:如果后端返回的空响应可能有细微差异(比如带空格、换行),要在VCL里先统一处理(比如用regsub去掉多余字符)再判断

关键注意点

  • 缓存TTL区分:用户不存在的空响应可以设较长TTL,但要确保xkey能正常触发清理(当用户被创建后,通过xkey删除对应的缓存条目)
  • 空响应判断要准确:避免误把正常的空响应(比如用户存在但确实没有配置)当成用户不存在的情况,最好还是用方案一的自定义头标识更可靠

内容的提问来源于stack exchange,提问作者Abhishek Surve

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 22:24:31