如何为相似缓存键的多请求一次性缓存相同合成响应
解决Varnish中同路径不同参数的重复空响应缓存问题
问题核心
你面临的矛盾很清晰:
- 正常用户的
/user/{id}/config响应会随查询参数变化,因此缓存键必须包含完整路径+所有查询参数 - 但当目标用户不存在时,后端返回固定空JSON
{},此时每个带不同参数的请求都会生成独立缓存条目,完全没必要,还会浪费缓存资源、增加后端请求量 - 同时xkey必须保持为资源路径(比如
user/1/config),用于按用户ID触发缓存清理
下面是两种可落地的解决方案,分别对应能修改应用端和无法修改应用端的场景:
方案一:应用端配合添加标识,Varnish统一复用缓存
这是最简洁的方案,只需要应用端加个响应头,VCL做对应调整:
应用端改造:当检测到用户不存在时,返回状态码
200的同时,在响应头里加个自定义标识,比如X-User-Exists: false,响应体还是{}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的二次查询逻辑来实现:
首次请求:按原规则发往后端,拿到空响应后,额外存一个以资源路径为键的缓存条目
后续请求:先查这个无参数的缓存,如果存在且是空响应,直接返回;否则再按原规则发往后端
示例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
相关产品推荐
相关产品推荐

