介绍

本文是以下三部分系列中的第三篇。

(一)介绍背景和目的
② 使用 AWS Prototyping 程序进行开发
(3) 产品发布前解决的问题,引入的效果

本文涵盖以下主题:

  • 关于 ASL(亚马逊软件许可证)
  • GraphQL 缓存的工作原理
  • 在产品发布前解决的问题
  • 介绍效果

关于 ASL(亚马逊软件许可)

本文介绍的 GraphQL 缓存 Lambda 代码是使用 AWS Prototyping 程序开发的。本程序开发的可交付成果可根据 ASL(亚马逊软件许可证)使用。

它类似于 Apache License 2.0,但有一些区别,例如允许在 AWS 上使用。

在本文中,我们将发布实现 GraphQL 缓存的核心代码部分,但如果您想参考,请阅读上述许可。

GraphQL 缓存的工作原理

在产品发布之前存在许多挑战。后面为了介绍我是怎​​么清除问题的,先洛亚我将解释实际工作的 GraphQL 缓存机制。

建筑学

配置如下。

  • 云前
  • Lambda@Edge(缩写=L@E)
    • Python 3.9 运行时
  • CloudFront 功能(缩写 = CF2)

GraphQL caching ③プロダクションリリースまでに解決した課題、導入による効果

每个服务的角色

CloudFront

  1. 接受查看器向 API 端点发出的 POST 请求。
  2. 接受由 L@E 与 Origin Request 一起发出的 GET 请求。或者,绕过 L@E 并直接向源发送 POST 请求。 (用于后面描述的曝光措施)
  3. 如果缓存命中,则将缓存对象返回给 L@E。如果缓存对象不存在,则再次向源发送请求。
  4. 根据响应头策略向返回给查看器的响应添加静态响应头。

    Lambda@Edge

    1. 从 POST 到 GET 的方法转换 & 拆分请求正文,将其存储在将成为缓存键的标头中,然后再次向 CloudFront 请求。对于无法缓存的请求(响应),请直接从 CloudFront 向源发出请求。
    2. 当 CloudFront 发生缓存命中时,使用返回的缓存对象创建响应并将响应返回给 CloudFront。
    3. 如果没有缓存命中,则从CloudFront返回一个GET请求到L@E,所以方法由GET转换为POST,从缓存key的请求头中提取原始请求体,合并,请求被发送到 origin.issue。
    4. 接收从源返回的响应,创建响应以返回查看器,并将响应返回给 CloudFront。

      CloudFront 函数

      1. 使用查看器响应并将动态响应标头添加到返回给查看器的响应中。

        如何使 CloudFront 缓存 GraphQL 响应

        CloudFront 不会缓存 POST 请求的响应结果,因此您通常无法缓存 GraphQL 请求的响应。 CloudFront 只是将查看器发送的 POST 请求转发到您的源。

        因此,与 Origin Request 一起使用的 L@E 将方法从 POST 转换为 GET。它还将 POST 请求的主体拆分为 1783 个字符,并将其存储在自定义标头 Payload0~4 中以创建请求标头。这些标头用作 CloudFront 缓存策略中的缓存键。

        我们拆分为 1783 个字符的原因是 CloudFront 允许的自定义标头的最大长度为 1783 个字符。此外,还有 5 个缓存键 Payload0~4,因为 CloudFront 中组合的自定义标头值和名称的最大长度为 10240 个字符。
        1783 * 5 = 8915,所以有 1325 个字符可以备用,但是这个备用字符用于自定义标头而不是 Payload0~4,它是一个缓存键。

        创建一个 CloudFront 可以创建缓存对象的情况,方法是使用 L@E 将方法从 POST 转换为 GET,拆分 POST 请求的主体,将其作为缓存键存储在 header 中,然后向 CloudFront 发出 GET 请求。。

        CloudFront 将 L@E 发出的 GET 请求转发到源,因为第一个请求没有缓存对象。此时,L@E 再次与第二个 OriginRequest 一起工作。

        现在,将方法从 GET 转换为 POST,提取并组合 Payload0~4 标头值,并创建请求正文。换句话说,它重新创建了查看器发送的原始 GraphQL 请求。 L@E 然后向源发出 POST 请求并从源接收响应。

        L@E 将从源端收到的响应返回给 CloudFront,作为对 CloudFront 转发的 GET 请求(自身发出的 GET 请求)的响应。 CloudFront 从 GET 请求响应创建一个缓存对象。

        由于 L@E 发出了 GET 请求,因此从 CloudFront 返回响应。 L@E 使用从 CloudFront 返回的响应来创建对 Viewer 最初发送的 POST 请求的响应并将其返回给 CloudFront。

        CloudFront 将从 L@E 创建的响应返回给查看器。

        缓存启用/禁用控制

        有些请求是可缓存的 GraphQL 查询,有些则不是。满足以下任一条件的请求不会导致 CloudFront 缓存响应。

        • 请求缺少身份验证所需的标头
        • 使用用户私人信息作为响应的请求
        • 请求正文较大,无法存储在缓存键标头中

        这些请求必须绕过 L@E 并直接在 CloudFront 和源之间进行。 CloudFront 不会创建缓存对象,因为绕过 L@E 时不会发生从 POST 到 GET 的方法转换。

        下面是一个图像图。第一个 Origin Request 确定该请求是否可以缓存其响应。
        GraphQL caching ③プロダクションリリースまでに解決した課題、導入による効果

        第二个条件尤为重要,如果你缓存了包含用户个人信息的响应,就会发生用户个人信息泄露的缓存事故。1

        缓存命中时的行为

        如果有缓存命中,会进行如下处理。如果没有缓存命中,则会在第一个架构图中进行处理。
        GraphQL caching ③プロダクションリリースまでに解決した課題、導入による効果

        代码

        我将介绍 L@E 的代码,它是 GraphQL 缓存的核心。太长了,折叠一下。

        现在 RT-http-me tyod。 py
        # reference to https://github.com/aws-samples/amazon-cloudfront-cache-graphql
        # note to https://aws.amazon.com/jp/asl/
        # coding=utf-8
        import urllib.request
        import urllib.parse
        import urllib.error
        import json
        import base64
        import ssl
        # Lambda@EdgeからOriginへリクエストするときに "certificate verify failed: Hostname mismatch" のエラーが発生する事象の回避策
        ssl._create_default_https_context = ssl._create_unverified_context
        from urllib.parse import parse_qs
        from urllib.parse import quote
        
        # キャッシュできる Payload のサイズは Header 値の最大に設定している
        # 参考: https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html
        CACHE_PAYLOAD_SIZE_LIMIT = 1783
        
        # Payload 分割数の最大値
        # 「結合されるすべてのヘッダー値および名前の最大長 (10240)」を「ヘッダー値の最大長 (1783)」で割った数にしている
        # 参考: https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html
        NUM_SPLIT_MAX = 5
        
        # キャッシュ対象のエンドポイント
        # それ以外はバイパス処理
        GRAPHQL_ENDPOINT = '/graphql/'
        
        cacheable_operations = {
            'pc': [
            ],
            'sp': [
            ],
            'app-android': [
            ],
            'app-ios': [
            ]
        }
        
        # CloudFront にキャッシュさせないためのステータスコードをRFCの空き番号から決定する
        RESPONSE_STATUS_CODE_NO_CACHE = 
        # API から通常返却されるステータスコード
        RESPONSE_STATUS_CODE_DEFAULT = 200
        
        def device_type(is_desktop, user_agent):
            if is_desktop == 'true':
                return 'pc'
            else:
                if 'androidのuser-agent' in user_agent.lower():
                    return 'app-android'
                elif 'iosのuser-agent' in user_agent.lower():
                    return 'app-ios'
            return 'sp'
        
        def exists_required_header(req_headers):
            if 'example-auth-header' in req_headers:
                return True
            return False
        
        def is_cacheable_operation(operation_name, is_desktop, user_agent):
            """operationName からキャッシュ可能な Query かを判定する関数
        
            Args:
                operation_name: GraphQL Query の operationName
                is_desktop: ヘッダの cloudfront-is-desktop-viewer
                user_agent: ヘッダの user-agent
        
            Returns:
                True or False (True の場合はキャッシュ可能)
            """
            if operation_name.lower() in cacheable_operations[device_type(is_desktop, user_agent)]:
                return True
            return False
        
        def http_request(endpoint, method='GET', headers={}, data=None):
            """HTTP リクエストを実行して CloudFront のレスポンスの形式に変換する関数
        
            Args:
                endpoint: リクエスト先の Endpoint
                method  : HTTP メソッド           (デフォルト: GET)
                headers : ヘッダー                (デフォルト: {})
                data    : リクエスト Body         (デフォルト: None)
        
            Returns:
                CloudFront のレスポンスの形式に変換された Dict
            """
            req = urllib.request.Request(endpoint, method=method, headers=headers, data=data)
            res = urllib.request.urlopen(req)
            res_code = res.getcode()
            res_body = res.read().decode('utf-8')
            res_headers = response_headers(res)
        
            return {
                'status': res_code,
                'headers': res_headers,
                'body': res_body
            }
        
        def response_headers(res):
            """urllib のレスポンスの Header を CloudFront のレスポンスの形式に変換する関数
        
            参考: https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html#lambda-event-structure-response-origin
        
            Args:
                res: urllib のレスポンス
        
            Returns:
                CloudFront の形式に変換された Headers
            """
            headers_raw = dict(res.info())
            headers = list(map(lambda x: { x: { 'key': x, 'value': headers_raw[x] } }, headers_raw.keys()))
            return headers
        
        def split_payload(data):
            """Payload を CACHE_PAYLOAD_SIZE_LIMIT で分割する
        
            Args:
                data: base64 の Payload
        
            Returns:
                分割した Payload の配列
            """
            # Offset を示す cursor
            cursor = 0
        
            # 分割した Payload
            payloads = []
        
            while True:
                # cursor から Header に収まるサイズの data であれば終了
                if len(data[cursor:]) <= CACHE_PAYLOAD_SIZE_LIMIT:
                    payloads.append(data[cursor:])
                    break
                # CACHE_PAYLOAD_SIZE_LIMIT までデータを入れて、cursor を移動
                else:
                    payloads.append(data[cursor:cursor+CACHE_PAYLOAD_SIZE_LIMIT])
                    cursor += CACHE_PAYLOAD_SIZE_LIMIT
        
            # 分割数が NUM_SPLIT_MAX に満たない場合は、空文字列を挿入
            if len(payloads) < NUM_SPLIT_MAX:
                for _ in range(NUM_SPLIT_MAX - len(payloads)):
                    payloads.append('')
        
            return payloads
        
        def handler(event, context):
            """Lambda@Edge の Handler
        
            Args:
                event  : CloudFront の Event
                context: Lambda の Context
        
            Returns:
                CloudFront のレスポンス or バイパス処理
            """
            # CloudFront のイベントを取得し、ドメイン名とリクエストを取得
            # 参考: https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html#example-origin-request
            cloud_front_event = event['Records'][0]['cf']
            cloud_front_domain_name = cloud_front_event['config']['distributionDomainName']
            request = cloud_front_event['request']
            origin_domain_name = request['origin']['custom']['domainName']
        
            # GraphQL エンドポイントの場合は、GET <=> POST で変換する
            if request['uri'] == GRAPHQL_ENDPOINT:
                # POST を GET に変換する (Lambda@Edge => CloudFront)
                if request['method'] == 'POST':
                    print('POST')
                    # 認証に必要なヘッダーがないものはバイパスする
                    if not exists_required_header(request['headers']):
                        return request
        
                    # POST の Payload (base64) を取得
                    data = request['body']['data']
        
                    # 以下のような形式の Payload を想定し、異なる場合はバイパスする
                    # ```
                    # [
                    #   { "operationName": "xxx", "query": "yyy", "variables": {...} },
                    #   ...
                    # ]
                    # ```
                    try:
                        queries = json.loads(base64.b64decode(data).decode())
                        is_desktop = request['headers']['cloudfront-is-desktop-viewer'][0]['value']
                        user_agent = request['headers']['user-agent'][0]['value'] if 'user-agent' in request['headers'] else ''
        
                        if isinstance(queries, dict):
                            queries = [queries]
        
                        for query in queries:
                            # キャッシュ不可な operationName が含まれている場合はバイパス処理
                            if not is_cacheable_operation(query['operationName'], is_desktop, user_agent):
                                return request
                    except:
                        return request
        
                    # Payload を分割する
                    payloads = split_payload(data)
        
                    # 分割数が NUM_SPLIT_MAX (許容量) を越えている場合はバイパス処理
                    if len(payloads) > NUM_SPLIT_MAX:
                        return request
        
                    headers = {
                        # Payload は base64 のまま Header に格納
                        # Payload0~4は CloudFront の Cache Policy に指定する
                        'Payload0': payloads[0],
                        'Payload1': payloads[1],
                        'Payload2': payloads[2],
                        'Payload3': payloads[3],
                        'Payload4': payloads[4],
                        # 以下のヘッダーは CloudFront の Origin Request Policy に指定する
                        'User-Agent': user_agent,
                        'example-auth-header': request['headers']['example-auth-header'][0]['value'],
                        'X-Forwarded-For': request['headers']['x-forwarded-for'][0]['value'],
                        'Host': request['headers']['host'][0]['value'],
                        # Refererが欠けた場合のKeyError発生を回避
                        'Referer': request['headers']['referer'][0]['value'] if 'referer' in request['headers'] else '',
                        'true-client-ip': request['clientIp']
                    }
        
                    # CloudFront (自分自身) に GET リクエストをし直す
                    try:
                        response = http_request(f'https://{cloud_front_domain_name}{GRAPHQL_ENDPOINT}', headers=headers)
                    # ステータスコードが4xx, 5xxのレスポンスを受け取るとurllibがエラーを起こすので例外をキャッチする。
                    # CloudFront にキャッシュさせないステータスコードに400番台を使用している。
                    # GraphQLクライアント側は CloudFront にキャッシュさせないために書き換えたステータスコードを解釈できないため、200へ書き戻す。
                    except urllib.error.HTTPError as e:
                        if e.code == RESPONSE_STATUS_CODE_NO_CACHE:
                            response = {
                                'status': RESPONSE_STATUS_CODE_DEFAULT,
                                'headers': e.headers.as_string(),
                                'body': e.read()
                            }
        
                    return response
        
                # GET を POST に変換する (Lambda@Edge => Origin)
                elif request['method'] == 'GET':
                    print('GET')
                    # 認証に必要なヘッダーがないものはバイパスする
                    if not exists_required_header(request['headers']):
                        return request
        
                    # Payload0 ~ Payload4 という Header がない GET リクエストの場合はオリジンにそのまま流す
                    if 'payload0' not in request['headers'] or \
                       'payload1' not in request['headers'] or \
                       'payload2' not in request['headers'] or \
                       'payload3' not in request['headers'] or \
                       'payload4' not in request['headers']:
                        return request
        
                    # Header から Payload の値を取得 (中身は base64 の GraphQL Query)
                    payload = \
                        request['headers']['payload0'][0]['value'] + \
                        request['headers']['payload1'][0]['value'] + \
                        request['headers']['payload2'][0]['value'] + \
                        request['headers']['payload3'][0]['value'] + \
                        request['headers']['payload4'][0]['value']
        
                    # base64 の payload をデコードする
                    data = base64.b64decode(payload)
        
                    # リクエスト用のヘッダーを作成
                    headers = {
                        'Content-Type': 'application/json',
                        'Content-Length': len(data),
                        'example-auth-header': request['headers']['example-auth-header'][0]['value'],
                        # User-Agentが欠けた場合のKeyError発生を回避
                        'User-Agent': request['headers']['user-agent'][0]['value'] if 'user-agent' in request['headers'] else '',
                        'X-Forwarded-For': request['headers']['x-forwarded-for'][0]['value'],
                        'Host': request['headers']['host'][0]['value'],
                        # Refererが欠けた場合のKeyError発生を回避
                        'Referer': request['headers']['referer'][0]['value'] if 'referer' in request['headers'] else '',
                        'true-client-ip': request['headers']['true-client-ip'][0]['value']
                    }
        
                    # Origin に POST
                    response = http_request(f'https://{origin_domain_name}{GRAPHQL_ENDPOINT}', method='POST', data=data, headers=headers)
                    if response['status'] == RESPONSE_STATUS_CODE_DEFAULT:
                        try:
                            response_body = json.loads(response['body'])
                            if 'errors' in response_body and len(response_body['errors']) > 0:
                                raise Exception
                        # エラーレスポンスであってもステータスコードが200になるため、ステータスコードを書き換えないとCloudFrontにエラーレスポンスがキャッシュされるので、CloudFront がキャッシュしないステータスコードへ書き換える
                        except:
                            response['status'] = RESPONSE_STATUS_CODE_NO_CACHE
                    return response
        
            # リクエストをバイパスする
            return request
        

        生产发布前解决的问题

        我将写下在产品发布之前解决的问题,解释考虑因素。

        问题一:防止缓存事故(曝光对策)

        第一个问题是曝光控制。

        正如我在可缓存性/不可缓存性的控制中所写,CloudFront 不会缓存满足以下任何条件的请求。

        • 请求缺少身份验证所需的标头
        • 在响应中包含用户私人信息的请求
        • 请求正文较大,无法存储在缓存键标头中

        用流程图表示,可以表示如下。
        GraphQL caching ③プロダクションリリースまでに解決した課題、導入による効果

        大胆的条件是防止个人信息泄露的防线。图中的第二个条件include a non-cacheable operationName in payloads 对应于红色粗体条件。

        GraphQL 缓存检查请求正文。如果甚至包含一个不可缓存的 GraphQL operationName,CloudFront 将从查看器接收到的 POST 请求直接发送到源,以便 CloudFront 不会对其进行缓存。

        缓存启用/禁用管理以白名单格式完成。仅缓存响应为全局信息(不包含用户个人信息)的 GraphQL 操作。如果采用黑名单格式管理,可能会因为黑名单更新失败而发生缓存事故。

        问题 2:平衡与实时性能

        第二个问题是与实时性能的平衡。正如我在第二篇文章中所写,GraphQL 有一个 URL 端点。由于只能为 CloudFront 行为设置一种路径模式,因此 TTL 必须匹配最短的可缓存响应。

        在考虑 TTL 时,问题在于定时数据的处理。响应可能包含具有过期属性的数据。 (例:限时活动信息)

        可以将此类响应设置为不缓存,但我发现如果将它们排除在缓存之外,几乎没有响应可以缓存。

        所以在LOWYA中,需要在享受缓存带来好处的同时兼顾实时性能,所以我们将缓存对象的TTL设置为1分钟,看看效果如何。

        问题3:有必要设计获取客户端IP的方法

        第三个挑战是如何让后端获得正确的客户端 IP。

        随着请求源和后端之间跃点数的增加,跃点的 IP 会添加到 x-forwarded-for 标头中,因此我们需要付出额外的努力才能将客户端 IP 传递给后端。

        如下文所述,在 ELB 前面使用 CloudFront 的应用程序需要获取位于 x-forwarded-for 标头顶部的 IP 以安全地获取客户端 IP。

        但是,随着 2021 年 10 月的更新,CloudFront 现在支持 CloudFront-Viewer-Address 标头。2

        通过此更新,即使对于配置为 CloudFront → ELB → AppServer 的应用程序,现在也可以更轻松地获取客户端 IP。
        CloudFront-Viewer-Address 标头的格式为“ClientIPAddress:ClientPort”,因此您可以通过截去冒号后面的部分来获取客户端 IP。

        这一次,我尝试使用 CloudFront-Viewer-Address 标头在后端应用程序端获取客户端 IP。
        但是,在不可缓存的情况下,我能够将客户端 IP 传递给应用程序,但在可缓存的情况下却不行。

        在可缓存的情况下,Origin Request 的 L@E 发出一个新的 GET 方法的 HTTP 请求。由于是 L@E 向 CloudFront 发送 GET 请求,因此 CloudFront-Viewer-Address 标头中存储的值是 L@E 的 IP。

        ip-ranges.json AWS 发布应该属于 L@E 的 IP3当我检查时,它在下面命令提取的IP地址范围内,所以我认为它可能是区域边缘缓存的IP。

        jq -r '.prefixes[] | select(.region=="ap-northeast-1" and .service=="AMAZON" and .network_border_group=="ap-northeast-1") | .ip_prefix' < ip-ranges.json | sort -n
        

        为了即使在可缓存的情况下也能在后端获取客户端 IP,在 L@E 发送 GET 请求之前,获取记录在 Lambda@Edge 的请求事件中的客户端 IP 并使用自定义的true-client-ip 我存储了标头中的值。
        然后,修改后端,在以下条件下获取客户端IP。流程图如下所示:
        GraphQL caching ③プロダクションリリースまでに解決した課題、導入による効果

        在不可缓存的情况下,true-client-ip 头没有设置,所以CloudFront-Viewer-Address 头被转发到源,后端可以通过引用这个头的值来检索客户端的 IP。

        问题4:origin给出的header消失了

        第四个问题是origin给的header消失的现象。

        显然,L@E 调用了 http_request 函数并直接返回了响应,因此似乎将 origin 给出的响应标头作为 CloudFront 的行为删除(初始化)。

        结果,在可缓存情况下附加到响应的源的响应标头被初始化,从而阻止客户端正确处理响应。

        例如,content-type 标头丢失,导致客户端无法正确处理响应,以及 CORS 相关标头丢失,导致错误。

        为了解决此错误,我们使用 CloudFront 的响应标头策略为具有静态值的标头提供固定响应标头。

        此外,对于具有动态值的标头,已添加与 CloudFront 的查看器响应一起使用的 CF2。

        问题 5:无意缓存错误响应

        第五个问题是错误响应被无意缓存了。

        负缓存4有一种思维方式。负缓存意味着缓存负响应,例如在 CDN 上缓存 404 错误。

        缓存应该考虑不仅缓存像 200 这样的正面响应,还应该缓存负面响应。通过使用负缓存,您可以保护您的来源免受骚扰,因为骚扰会发送大量不存在 URL 的请求。

        实际上,在第一次发布时,CloudFront 缓存了源端返回的错误响应,即使在发送导致错误的请求的用户之外的环境中也会发生错误。

        原因是 GraphQL 的方式。即使将请求视为错误,GraphQL 也会返回状态码为 200 的响应。

        当错误被缓存时,情况是:

        1. 一个用户的请求导致 401 Unauthorized 错误
        2. 后端应用程序将错误消息存储在正文中并返回状态码为 200 的响应
        3. CloudFront 缓存对导致 401 Unauthorized 的请求的响应
        4. CloudFront 向 1 中发送请求的用户返回错误响应
        5. CloudFront 将 3 中创建的缓存对象返回给另一个用户,该用户发送的请求与 1 中的请求具有相同的正文内容

          在这个实现中,请求正文每 1783 个字符被分割并存储在 Payload0~4 标头值中,这些标头用作缓存键。
          会话令牌由请求标头发送,不包含在正文中。因此,即使是其他用户(会话),如果请求正文完全相同,也会返回CloudFront返回给其他用户的错误响应。

          为了避免这种现象,需要重写状态码。如果在可缓存情况下从源发送错误响应,请将状态代码从 200 重写为 CloudFront 不缓存的状态代码。然后,当向查看器返回响应时,状态码会再次写回 200。回写的原因是查看器(GraphQL 客户端)端不期望带有 CloudFront 不缓存的状态代码的响应。

                      # CloudFront (自分自身) に GET リクエストをし直す
                      try:
                          response = http_request(f'https://{cloud_front_domain_name}{GRAPHQL_ENDPOINT}', headers=headers)
                      # ステータスコードが4xx, 5xxのレスポンスを受け取るとurllibがエラーを起こすので例外をキャッチする。
                      # CloudFront にキャッシュさせないステータスコードに400番台を使用している。
                      # GraphQLクライアント側は CloudFront にキャッシュさせないために書き換えたステータスコードを解釈できないため、200へ書き戻す。
                      except urllib.error.HTTPError as e:
                          if e.code == RESPONSE_STATUS_CODE_NO_CACHE:
                              response = {
                                  'status': RESPONSE_STATUS_CODE_DEFAULT,
                                  'headers': e.headers.as_string(),
                                  'body': e.read()
                              }
          
                      return response
                      # Origin に POST
                      response = http_request(f'https://{origin_domain_name}{GRAPHQL_ENDPOINT}', method='POST', data=data, headers=headers)
                      if response['status'] == RESPONSE_STATUS_CODE_DEFAULT:
                          try:
                              response_body = json.loads(response['body'])
                              if 'errors' in response_body and len(response_body['errors']) > 0:
                                  raise Exception
                          # エラーレスポンスであってもステータスコードが200になるため、ステータスコードを書き換えないとCloudFrontにエラーレスポンスがキャッシュされるので、CloudFront がキャッシュしないステータスコードへ書き換える
                          except:
                              response['status'] = RESPONSE_STATUS_CODE_NO_CACHE
                      return response
          

          图像如下图所示。
          GraphQL caching ③プロダクションリリースまでに解決した課題、導入による効果
          这次我决定将CloudFront不缓存的状态码设置为471,而是RFC免费号5我刚刚从

          CloudFront 具有始终缓存的 HTTP 4xx、HTTP 5xx 状态代码6,这次的策略是不使用负缓存。

          引进效果

          引入时存在许多问题,但 GraphQL 缓存产生了预期的结果。

          更快的 Ajax 请求响应时间

          对比引入 GraphQL 缓存前后从浏览器向 API 发送的 Ajax 请求的响应时间,响应时间最多减少了 52%。
          GraphQL caching ③プロダクションリリースまでに解決した課題、導入による効果
          LOWYA 推出了名为 New Relic Browser 的 RUM(真实用户监控)工具,以及 timeToLoadEventStart7它是使用数据来衡量的。

          timeToLoadEventStart
          AJAX 请求开始与其加载事件开始之间的时间量(以秒为单位)。此值表示单页应用程序 (SPA) 监控的 AJAX 请求的持续时间。

          原产地成本降低

          在撰写本文时,CloudFront 的平均缓存命中率为 26%,这意味着 4 个请求中有 1 个是缓存命中。
          GraphQL caching ③プロダクションリリースまでに解決した課題、導入による効果
          缓存减轻了源站的负载,这使我们能够减少 ECS 任务的数量,从而为我们节省每月的 ECS 成本。

          综上所述

          引入 GraphQL 缓存的目的是减少站点(GraphQL 服务器)的负载,降低 AWS 成本,并在不久的将来实现可扩展性。

          自从我们引入 GraphQL 缓存以来,我们还没有经历过大规模的访问集中(spike),所以我们不能说它作为负载对策的效果如何。可以肯定的是,到达的请求数量正在减少,所以我愿意期待它。8

          关于AWS成本降低,我们已经实现了。

          目前尚不清楚可扩展性问题是否会在不久的将来实现,但我相信我们能够采取措施。

          已经很久了,但感谢您阅读到最后。如果您能帮助遇到类似问题的人,我将不胜感激。

          1. 过去,Mercari 的 CDN 切换工作导致个人信息泄露。
            https://engineering.mercari.com/blog/entry/2017-06-22-204500/
            缓存事故也作为 IPA 网站上的曝光对策引入。
            https://www.ipa.go.jp/security/awareness/vendor/programmingv2/contents/405.html
            我会写信阻止 Masakari,但我无意通过引用 Mercari 的案例来批评 Mercari 的反应。相反,我认为这是一个很好的回应,就像一个榜样。

          2. https://aws.amazon.com/jp/about-aws/whats-new/2021/10/amazon-cloudfront-client-ip-address-connection-port-header/

          3. https://docs.aws.amazon.com/ja_jp/general/latest/gr/aws-ip-ranges.html

          4. 我认为负缓存是一个 DNS 术语。
            https://jprs.jp/glossary/index.php?ID=0177#:~:text=DNS%E3%81%AB%E3%81%8A%E3%81%84%E3%81%A6%E3%80%81%E6%A4%9C%E7%B4%A2%E5%AF%BE%E8%B1%A1%E3%81%8C,%E3%83%8D%E3%82%AC%E3%83%86%E3%82%A3%E3%83%96%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5%E3%80%8D%E3%81%A8%E5%91%BC%E3%81%B3%E3%81%BE%E3%81%99%E3%80%82
            它还用于 CDN 上下文中以缓存不需要的响应。
            https://cloud.google.com/cdn/docs/using-negative-caching?hl=ja

          5. 在此处查看可用号码
            https://datatracker.ietf.org/doc/html/rfc7231#section-6.5

          6. https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/HTTPStatusCodes.html#HTTPStatusCodes-cached-errors

          7. https://docs.newrelic.com/jp/attribute-dictionary/?event=AjaxRequest&attribute=timeToLoadEventStart

          8. 随着 API 缓存层数的增加,我们必须注意一个事实,即在负载度量方面需要考虑的点更多。特别是 Lambda@Edge 不支持预置并发,必须对突发并发配额敏感。
            https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/invocation-scaling.html


原创声明:本文系作者授权爱码网发表,未经许可,不得转载;

原文地址:https://www.likecs.com/show-308628094.html

相关文章:

  • 2022-12-23
  • 2021-10-30
  • 2021-12-16
  • 2022-01-12
  • 2021-11-30
  • 2022-12-23
  • 2021-06-16
  • 2022-12-23
猜你喜欢
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-01-10
  • 2022-12-23
  • 2022-02-08
  • 2022-12-23
相关资源
相似解决方案