亚马逊AWS官方博客

Amazon CloudFront 部署小指南(十一)- 实现指定请求特征绕行缓存(Bypass Cache)

1. 背景信息

在使用 CloudFront 时,我们可能会遇到特定请求需要绕行缓存的情况(Bypass Cache),比如:

  1. 通过 Cookie 判断,为已登陆用户展示个性化页面,非登陆态用户展示默认页面。
  2. 通过 User-Agent 判断,对指定特征的请求(比如机器人流量),直接命中 CloudFront 缓存,非指定特征的请求则须回源获取内容。

CloudFront 可在缓存策略(Cache Policy)中自定义缓存键值,通过缓存键值的不同或实现随机性,我们可以实现让需要命中缓存的内容使用相同的缓存键值,对于需要回源的请求,进行缓存键值的随机化。以请求头作为缓存键值举例:

  1. 用户侧请求携带自定义 x-use-cache
  2. CloudFront 设置 x-use-cache 为缓存键值
  3. 对于需要使用缓存的请求,携带请求头 x-use-cache: true
  4. 对于需要强制回源的请求,携带请求头 x-use-cache: ${random}

但这种方式并不是非常严谨,实际上是利用随机化缓存键值。对于需要命中缓存的请求,使用相同的缓存键值,对于需要回源的请求,则通过随机缓存键值从而大幅度的加大缓存未命中的情况。这不是真正绕行 CloudFront 缓存,如果随机化后的结果重复,则存在一定的可能性会复用缓存,在源服务提供个性化响应的场景,复用缓存将可能导致问题。另外,客户端还需要自行实现携带随机化 参数/请求头/cookie,供 CloudFront 缓存策略设置缓存键值时使用。

为了使用户可以更方便灵活的在亚马逊云科技平台通过 WAF 或边缘计算服务,实现缓存绕行的目的,以取代修改源站以及客户端逻辑的实现方式,本文将提供两种思路,从真正意义上让 CloudFront 绕行缓存(Bypass Cache)。

2. 方案架构及思路概述

CloudFront 缓存原理解释

在下面的思路 1 和思路 2 中,都利用到了识别指定特征,并针对不缓存的请求在 Lambda@Edge 响应 Cache-Control: no-store,这是因为在 CloudFront 开发者文档中 — 管理内容保留在缓存中的时间长度(过期),我们可以看到如果最小 TTL= 0 且 CloudFront 收到 Cache-Control: no-store 时,CloudFront 将不会对该响应内容进行缓存,本文所提及的绕行缓存,核心正是利用了该原理实现。

请求逻辑图

为了让读者更好地理解本文提及的处理逻辑,以下为两种思路的请求逻辑图:

思路 1

涉及服务:

  • Amazon WAF
  • Amazon CloudFront
  • Amazon Lambda@Edge
  1. 用户发起请求;
  2. Amazon WAF 使用自定义规则,为匹配中特征的请求添加自定义请求头;
  3. Amazon CloudFront 根据自定义请求头添加缓存键值,决策是否命中缓存,命中缓存键值则直接响应已有缓存,未命中则转发请求回源;
  4. Lambda@Edge 针对特定请求头判断是否响应 Cache-Control: no-store;
  5. Amazon CloudFront 收到 Cache-Control: no-store,不复用已有缓存;
  6. 响应内容给最终用户。

方案优势:

  1. 对当前 Amazon CloudFront Distribution 已经挂载了 Amazon WAF 的业务友好;
  2. 如果需要识别的流量为机器人流量,也可考虑结合 Amazon WAF 的Bot Control 托管规则组,无需手工维护机器人流量特征。

思路 2

涉及服务:

  • Amazon CloudFront Function
  • Amazon CloudFront
  • Amazon Lambda@Edge
  1. 用户发起请求;
  2. Amazon CloudFront Function 通过代码逻辑,为匹配中特征的请求添加自定义请求头;
  3. Amazon CloudFront 根据自定义请求头添加缓存键值,决策是否命中缓存,命中缓存则直接响应,未命中则转发请求回源;
  4. Lambda@Edge 针对特定请求头判断是否响应 Cache-Control: no-store;
  5. Amazon CloudFront 收到 Cache-Control: no-store,不复用已有缓存;
  6. 响应内容给最终用户。

方案优势:

  1. 对于用户自维护的请求特征,且特征具备一定体量,用户可考虑进一步使用 CloudFront Function KeyValueStrore,以 Key/Value 形式存储请求特征,以符合更佳的特征库扩展性。

3. 功能实现步骤拆解

功能实现模拟场景

对于互联网上已识别的机器人流量,我们须从缓存中提供静态内容,识别方式为:从 User-Agent 中检测已识别出来的值,具体值包含 identified-bot 关键字。

思路 1. 部署步骤拆解

  1. 在 Amazon WAF 中添加一条自定义规则,配置指定的请求特征、Count 模式,并设置自定义头部信息。

注:Amazon WAF 在添加自定义请求头时,将会自动在用户指定的 Key 值前添加“x-amzn-waf-”字段,以便更好地标识。在这个例子中,添加的自定义请求头最终的完整 Key 值结果是 “x-amzn-waf-identified-bot”。

  1. 在 Amazon CloudFront 中创建自定义缓存策略(Cache Policies),将“x-amzn-waf-identified-bot”作为缓存键值,并确保最小 TTL = 0,TTL = 0 才可以让 Cache-Control: no-store 正常工作。
  1. 在需要应用该策略的路径中,应用刚才设置好的缓存策略,且回源策略(Origin request policy)需要将自定的请求头加白,供 Lambda@Edge 使用,为方便演示,此处使用托管的策略 – AllViewer。
  1. 创建并部署 Lambda@Edge 代码逻辑,并与需要该策略的路径关联,event 类型为 Origin response,此处以 python 作为示例:
import json

def lambda_handler(event, context):
    # 获取CloudFront请求和响应对象
    cf_request = event['Records'][0]['cf']['request']
    cf_response = event['Records'][0]['cf']['response']
    
    # 获取User-Agent
    user_agent = cf_request['headers'].get('user-agent', [{'value': ''}])[0]['value']
    
    # 检查User-Agent是否不包含'identified-bot'(不区分大小写)
    if 'identified-bot' not in user_agent.lower():
        # 如果不包含'identified-bot',添加或修改Cache-Control头
        cf_response['headers']['cache-control'] = [{'key': 'Cache-Control', 'value': 'no-store'}]
    
    return cf_response
Python

注:对于 Lambda@Edge 的使用入门,可参考 “Amazon CloudFront 部署小指南(六)- Lambda@Edge 基础与诊断

思路 2. 部署步骤拆解

  1. 创建并部署 CloudFront Function 逻辑,event 类型为 Viewer request,为携带指定特征的请求添加“x-cfcache-identified-bot”请求头,代码示例:
function handler(event) {
    var request = event.request;
    var headers = request.headers;
    var userAgent = headers['user-agent'];
    
    if (userAgent && userAgent.value.toLowerCase().includes('identified-bot')) {
        if (!headers['x-cfcache-identified-bot']) {
            headers['x-cfcache-identified-bot'] = {value: 'true'};
        }
    }
    
    return request;
}
PowerShell
  1. 同思路 1 的步骤 2,不过由于该请求头的值发生了变化,此时我们需要设置的请求头缓存键值为“x-cfcache-identified-bot”,并确保最小 TTL 等于 0。
  1. 同思路 1 的步骤 3。
  1. 同思路 1 的步骤 4。

4. 功能测试

由于思路 1 和思路 2 最终达成的结果相同,此处我们以思路 1 来进行部署,并测试结果进行展示:

  1. 使用 Amazon CloudFront 清除缓存功能(Invalidation),下发清除指定路径的缓存,避免测试请求命中旧缓存,无法成功触发 Lambda@Edge。确定缓存清除任务完成后,即可开始下一步测试。
  1. 测试用户 User-Agent 携带了 identified-bot 特征,请求多次观察缓存命中情况。
% for i in `seq 1 10`;do curl -vo /dev/null http://www.example.com/test.file -H 'user-agent: identified-bot' 2>&1 | grep 'X-Cache';done
< X-Cache: Miss from cloudfront
< X-Cache: Hit from cloudfront   <-- 第二次请求开始命中缓存
< X-Cache: Hit from cloudfront
< X-Cache: Hit from cloudfront
< X-Cache: Hit from cloudfront
< X-Cache: Hit from cloudfront
< X-Cache: Hit from cloudfront
< X-Cache: Hit from cloudfront
< X-Cache: Hit from cloudfront
< X-Cache: Hit from cloudfront
PowerShell
  1. 测试用户 User-Agent 没有携带 identified-bot 特征。
% for i in `seq 1 10`;do curl -vo /dev/null http://www.example.com/test.file 2>&1 | grep 'X-Cache';done 
< X-Cache: Miss from cloudfront
< X-Cache: Miss from cloudfront
< X-Cache: Miss from cloudfront
< X-Cache: Miss from cloudfront
< X-Cache: Miss from cloudfront
< X-Cache: Miss from cloudfront
< X-Cache: Miss from cloudfront
< X-Cache: Miss from cloudfront
< X-Cache: Miss from cloudfront
< X-Cache: Miss from cloudfront   <-- 持续N次请求均无命中缓存
PowerShell

总结

您可以根据目前使用亚马逊云科技服务的情况来选择具体的方案,通过本文的两种思路,我们可以灵活地利用 Amazon WAF 和亚马逊云科技的边缘计算服务,实现 Amazon CloudFront 绕行缓存(Bypass Cache)的目的,从而实现具体的业务需求。

本篇作者

王骏兴

亚马逊云科技边缘产品架构师,负责亚马逊云科技 Edge 服务领域在中国的技术推广。在 CDN 内容分发以及 WAF 领域拥有多年实战经验,专注于边缘服务设计以及体验优化。

赵克鸣

亚马逊云科技解决方案架构师,负责云计算解决方案的咨询和设计。热爱 AI/ML 领域的技术研究,并通过可实施的解决方案,帮助客户取得业务价值。