问题描述
有一些 api 资源在重负载下,其中响应是动态的,为了卸载我们使用 Varnish 作为前端缓存层的源服务器。 api 响应缓存控制标头,范围从 max-age=5 到 max-age=15。由于我们使用的是低缓存 ttl,因此许多请求仍以后端提取结束。从这个意义上说,我们不确定我们是否理解关于优雅的清漆请求正确合并。我们没有触及任何宽限设置,使用 VCL 中的宽限 og 从后端发送 stale-while-revalidate 标头。
所以问题是;资源从缓存中过期后,对该资源的所有请求都会在 varnish 中等待,直到资源再次在缓存中刷新,以防止出现雷鸣般的羊群问题?或者默认宽限设置会阻止“等待”请求,因为它们将在后端获取完成时提供“陈旧”的内容?从文档中我们不清楚默认值是如何工作的。
解决方法
Varnish 中对象生命周期的基础知识
一个对象的总生命周期是以下各项的总和:
TTL + grace + keep
让我们分解一下:
- TTL 定义了内容的新鲜度
- Grace 用于对过期内容进行异步重新验证
- Keep 用于对过期内容进行同步重新验证
执行顺序如下:
- 只要 TTL 未过期,就会从缓存中提供对象
- 当 TTL 等于或小于零时,需要重新验证
- 只要剩余 TTL(可能低于零)和宽限时间之和大于零,就可以提供过时的内容
- 如果有足够的宽限时间,Varnish 将在提供陈旧内容的同时异步重新验证内容
- 如果 TTL 和宽限期都已过期,则需要同步重新验证
- 同步重新验证使用等待列表并受请求合并的影响
- 剩余的保留时间将确保对象被保留,以便条件请求可以发生
默认值
- 根据http://varnish-cache.org/docs/trunk/reference/varnishd.html#default-ttl,默认TTL 设置为120 秒
- 根据http://varnish-cache.org/docs/trunk/reference/varnishd.html#default-grace,默认grace设置为10秒
- 根据http://varnish-cache.org/docs/trunk/reference/varnishd.html#default-keep,默认keep设置为0秒
请求合并怎么样?
Varnish 中用于请求合并的等待列表仅用于非缓存对象或已超过宽限期的过期对象。
以下场景不会触发请求合并:
TTL > 0
TTL + grace > 0
当对象是新鲜的或在范围内时,不需要使用等待列表,因为内容仍将从缓存中提供。在宽限内的对象的情况下,单个异步后端请求将发送到源进行重新验证。
当对象不在缓存中或不在宽限期时,需要同步重新验证,这是一个阻塞操作。为避免在多个客户端请求同一对象时出现问题,使用等待列表并将这些请求合并为单个后端请求。
最后,所有排队的请求都由同一个后端响应并行满足。
绕过等候名单
但这里有一个关于请求合并的重要说明:
请求合并仅适用于可缓存的内容。合并响应永远无法满足的有状态内容应该绕过等待列表。否则,将进行序列化。
序列化是一件坏事。这意味着排队的请求不能被响应满足,并被串行处理。这种线头阻塞可能会导致严重的延迟。
这就是为什么无状态/不可缓存的内容应该绕过等待列表。
绕过等待列表的决定是由命中未命中缓存做出的。这种机制缓存不缓存的决定。
以下代码用于:
set beresp.ttl = 120s;
set beresp.uncacheable = true;
这是您可以在 Varnish 的内置 VCL 中找到的那种 VCL 代码。它在找到 Set-Cookie
标头或出现 Cache-Control: private,no-cache,no-store
时触发。
这意味着在接下来的 2 分钟内,对象将从源提供服务,并且将绕过等待列表。当下一次缓存未命中将返回可缓存响应时,该对象仍存储在缓存中,并且 hit-for-miss 不再适用。
考虑到这一点,不要将 beresp.ttl
设置为零是至关重要的。因为这会使hit-for-miss信息过期,并且仍然会导致下一个请求最终出现在等待列表中,即使我们知道响应将无法缓存。