我使用API管理和外部Azure Cache for Redis来缓存指向外部服务的响应。我需要提供一种机制,通过从服务器重新提取数据来使缓存的响应无效,然后用新的响应更新缓存。
当缓存的响应到期或收到带有Cache-Control: must-revalidate
标头的新请求时,我的当前策略(如下所示(从服务器接收最新数据。同样,当收到头时,我想用新的响应更新缓存的响应。我是在概念上还是在政策上遗漏了什么?
<policies>
<inbound>
<base />
<set-backend-service id="apim-generated-policy" backend-id="func-myapp-dev-001" />
<set-variable name="mustRevalidate" value="@(context.Request.Headers.GetValueOrDefault("Cache-Control","").Contains("must-revalidate"))" />
<choose>
<when condition="@(context.Variables.GetValueOrDefault<bool>("mustRevalidate") == false)">
<cache-lookup vary-by-developer="false" vary-by-developer-groups="false" allow-private-response-caching="true" />
</when>
</choose>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
<set-header name="Cached-At" exists-action="override">
<value>@(System.DateTime.Now.ToString())</value>
</set-header>
<cache-store duration="360" />
</outbound>
<on-error>
<base />
</on-error>
</policies>
问题是<cache-store>
离不开<cache-lookup>
。<cache-lookup>
不仅从缓存中获取缓存值,而且也是<cache-store>
的一种配置。你可以在痕迹中看到这一点。当您使用Control-Cache: must-revalidate
头调用API时,由于<when>
策略的原因,<cache-lookup>
不会被触发,但不幸的是,<cache-store>
也不会被触发。在日志中,您将看到消息:
缓存存储(0.019毫秒("未应用相应的缓存查找策略。正在跳过缓存存储">
就这样了。跳过<cache-lookup>
会从后端获得新的响应,但之后不会缓存它以备将来请求。
关于如何实现你想要的。
我已经检查了两次,但不幸的是,<cache-lookup>
并没有基于参数跳过查找的选项。
也在考虑不同的方法。我认为这可以通过使用<cache-lookup-value>
策略来实现。一开始,基于must-revalidate
参数,如果它设置为true,则可以删除缓存的值。然后正常进行。最困难的部分是考虑可以根据请求生成缓存密钥的机制。APIM在cache-store
中也在做同样的事情。密钥在不同的请求之间必须是唯一的,但对于相同的请求必须是相同的——这与缓存的工作原理完全相同。APIM应用于缓存密钥的模式如下(您可以在跟踪中进行检查(:
"cacheKey": "{apimName}.{apiId};{revision};{backendurl with all query parameters}"
我的API示例:
"cacheKey": "my-apim.azure-api.net.1_weatherbit-io;rev=1.2432_get-current-city_id-city_id-key-key_4_https_api.weatherbit.io_443_/v2.0/current?city_id=4487042"
一旦你有了一些智能生成的密钥,我认为你就可以使用了,最终的解决方案可能是这样的:
<policies>
<inbound>
<set-variable name="cacheKey" value="@{
return ""; // here implement some smart key generation based on the request parameters from context.
}" />
<set-variable name="mustRevalidate" value="@(context.Request.Headers.GetValueOrDefault("Cache-Control","").Contains("must-revalidate"))" />
<choose>
<when condition="@(context.Variables.GetValueOrDefault<bool>("mustRevalidate") == true)">
<cache-remove-value key="@(context.Variables.GetValueOrDefault<string>("cacheKey"))" />
</when>
</choose>
<cache-lookup-value key="@(context.Variables.GetValueOrDefault<string>("cacheKey"))" variable-name="cachedResponse" />
<choose>
<when condition="@(context.Variables.GetValueOrDefault<JObject>("cachedResponse") != (JObject)null)">
<return-response>
<set-header name="Content-Type" exists-action="override">
<value>application/json; charset=utf-8</value>
</set-header>
<set-body>@{
return context.Variables.GetValueOrDefault<JObject>("cachedResponse").ToString();
}</set-body>
</return-response>
</when>
</choose>
<base />
</inbound>
<backend>
<base />
</backend>
<outbound>
<cache-store-value key="@(context.Variables.GetValueOrDefault<string>("cacheKey"))" value="@(context.Response.Body.As<JObject>(preserveContent: true))" duration="30" />
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
当然,这只缓存响应,如果你也想缓存头,你就必须实现如何存储它的机制,然后在什么时候将它返回到<return-response>
中,如何将它解析为头和正文。我希望你能看到这个解决方案的缺点,你需要自己实现一切。您不再可以使用<cache-lookup>
和<cache-store>
的内置功能。但与此同时,你有更多的自由,可以实现你想要的。不管怎样,祝你好运。
PS。<cache-store-value>
策略具有caching-type
属性。您可以使用它将缓存更改为外部缓存。