首页 文章详情

Free Arch:给 GraphQL 增加 CDN 缓存

哈德韦 | 272 2021-10-25 10:54 0 0 0
UniSMS (合一短信)

FBI 警告:这是一篇极具参考价值的文章,它利用免费架构,将 GraphQL 请求响应加快到令人发指的程度,而且具有一个反常的特性,那就是访问越频繁,流量分布越宽广,其整体性能越好。





极具参考价值


因为网上的文档极少或者完全没有,而仅存的少量文档在关键部分却一笔带过,含糊其辞,对于我这样的小白极不友好!


免费架构的反常特性

一般的应用服务,访问量一大,其性能急剧下降。这是因为一般的应用,使用了服务器,不仅昂贵,而且扛不住大流量。免费架构其实就是利用了 CDN,而这个反常特性,其实是 CDN 的正常特性。


问题背景


前面几篇文章都是针对万能 BFF,其中的一能,就是利用 gatsby-source-yuque 插件,将语雀文章 GraphQL 服务化。但是这个插件的实现是非常简单粗暴的,即一篇一篇下载语雀文章,并保存为一个 json 文件。由于它的本来目的是服务于静态站点的生成,即只在站点构建阶段运行,实际运行时是很快的。当新增语雀文章时,通过 webhook 触发站点重新构建,生成全新的站点,所以简单粗暴的实现并没有问题。


但是万能 BFF 把它的使用场景扩大了,比如给小程序提供服务。由于小程序需要审核,不像 Web 站点发布那般自由,于是需要将这个服务动态化,从而在不需要发布新的小程序的前提下,也能在小程序里看到最新的文章。



免费架构薅了 AWS Lambda 的羊毛,将万能 BFF 部署在 AWS lambda 上,于是让原本就慢的服务雪上加霜,因为 lambda 的冷启动本身就很耗时。另外因为小程序需要连接备案域名的问题,采用了代理服务绕过,而这个代理服务也是免费的 Heroku 服务,也存在冷启动问题,因此是三慢合一


免费架构的问题解决思路


如果说优化 gatsby-source-yuque 插件,让其性能提升,是有很多办法的,比如存数据库,增量拉取更新、用 Redis 做一层缓存等等。


但这都不是免费架构的问题解决思路。



FBI 警告:什么是架构?如果通过代码优化提升系统性能,可能会遮盖架构的光辉。好的架构,就是在烂代码的前提下,提升系统的整体表现。



再说,数据库、Redis 等等资源成本是我等穷困程序员所不能接受的,更别提开发成本了。


免费架构准备绕过所有后端优化,直接将 GraphQL 响应放在 CDN 边缘节点上,不仅节省了后端资源成本,而且其性能也是秒杀所有后端优化方案。


免费架构的实现细节


利用 Cloudflare 的全球 CDN 网络,加上其页面规则,在 GraphQL 服务被第一次请求时被缓存在离用户最近的边缘服务器,从而使该用户周围的用户收益,在发起请求时直接从边缘节点获取到响应,实现页面秒开效果。当有新的语雀文章更新时,可以利用 Cloudflare 的 API 删除缓存。



免费架构的缺点


仍需少量开发


后面的详细步骤会讲解


不解决第一次请求速度问题


第一次请求会很慢,这只能通过后端代码优化解决。


FBI 警告:如果你打开“哈德韦”小程序,碰到了十几秒才看到文章的话,那么我感谢你,因为你的宝贵时间没有浪费,提高了你周围很多小伙伴的用户体验以及你后续再次访问的速度。



给 GraphQL 增加 CDN 缓存的具体步骤


一、少量开发:启用 APQ

因为 GraphQL 本质上是一个 HTTP POST 请求,通过启用 APQ,能够将缓存过的请求,转为 GET 请求。从而为后面利用 Cloudflare 设置页面规则(GET 请求)埋下了伏笔。


服务器端:

https://github.com/Jeff-Tian/serverless-space/blob/c566d9ca16913952142d6c9caae07e2a130319b3/src/app.module.ts?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L15

import {Module} from '@nestjs/common'import {GraphQLModule} from '@nestjs/graphql'import {ApolloServerPluginCacheControl, ApolloServerPluginLandingPageLocalDefault} from 'apollo-server-core'import {CatsModule} from "./cats/cats.module"import {RecipesModule} from "./recipes/recipes.module"import {YuqueModule} from './yuque/yuque.module'

const ONE_DAY_IN_SECONDS = 60 * 60 * 24

@Module({ imports: [CatsModule, RecipesModule, YuqueModule, GraphQLModule.forRoot({ autoSchemaFile: true, sortSchema: true, playground: false, // 这里! persistedQueries: { ttl: ONE_DAY_IN_SECONDS }, plugins: [ApolloServerPluginLandingPageLocalDefault(), ApolloServerPluginCacheControl({defaultMaxAge: ONE_DAY_IN_SECONDS})] })],})export class AppModule {}


小程序端

https://github.com/Jeff-Tian/weapp/blob/94c0a22ab54b579ec6991e33c3a8d327bd0f31d8/src/apollo-client.ts?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L27

import {ApolloClient, ApolloLink, createHttpLink, InMemoryCache} from "@apollo/client"import Taro from "@tarojs/taro"import crypto from 'crypto'

import {createPersistedQueryLink} from "@apollo/client/link/persisted-queries"

const graphQLServerUrl = 'https://sls.pa-ca.me/nest/graphql'

const httpLink = createHttpLink({ uri: graphQLServerUrl, async fetch(url, options) { console.log('url = ', url, options) const res = await Taro.request({ url: url.toString(), method: (options?.method || 'POST') as 'POST' | 'GET', header: { 'content-type': 'application/json' }, data: options?.body, success: console.log })

return {text: async () => JSON.stringify(res.data)} as any }})

const queryLink = createPersistedQueryLink({ useGETForHashedQueries: true, sha256: async (document: string) => crypto.createHash('sha256').update(document).digest('hex')})

export const client = new ApolloClient({ link: ApolloLink.from([queryLink, httpLink]), cache: new InMemoryCache()})


二、去掉代理、启用 AWS API 网关的自定义域名功能(极具参考价值!)


看过前面几篇文章的同学会了解,免费架构为了求快,在解决小程序的安全域名要求备案的限制时,使用了代理方案。而且代理的代码很粗糙,存在被滥用的风险,因此这里去掉它,这是为了安全。


在小程序里接入 GraphQL



但是,更重要的是,我们需要加快响应速度,要知道这个代理也是一个免费服务,当冷启动时,是非常慢的,再加上 AWS lambda 的冷启动,以及这个插件本身的慢,所以是三慢合一


要去掉它,就需要将自定义域名指向 AWS lambda 自动生成的长长的域名。

极具参考价值,主要指这里。因为要利用 Cloudflare 的 CDN 网络和页面规则,自然,这个自定义域名需要交给 Cloudflare 托管。但是如何将 Cloudflare 托管的域名,指向 AWS lambda,文档少,关键步骤缺失。这让小白我,通过长时间的试错,才最终成功。


开通自定义域名


在使用 serverless 部署好 lambda 后,会自动生成相关的 API 网关。但是并未开通自定义域名,需要另外去开通:



这里的难点是申请证书。


验证域名


申请证书前,需要验证域名。



选择 DNS 验证



然后根据提示,在 Cloudflare 的控制面板添加相应的 CNAME 以及 CAA 记录完成验证。



注意!这里的 CAA 记录,请参考如上截图全部加上,而不要相信 AWS 文档里说的只需要添加一种!更要注意 issuewild 和 issue 记录各添加 4 个!


在域名验证验证通过后,才可以进行下一步。


证书导入


AWS 控制面板提供了两种证书申请方式,即 AWS 颁发,或者自行导入。这里选择自行导入证书。然后就进入了非常迷惑的面板:



我们准备导入的是 Cloudflare 的证书,证书正文和私钥,都可以轻易地从 Cloudflare 控制面板获取。



点击创建证书后,通过下载即可以获取到证书正文和私钥,分别贴入 AWS ACM 控制面板。让人傻眼的是这个证书链,没有任何文档说明如何获取这个证书链。


通过各种信息的拼凑和反复试错,以下是正确的获取姿势:


证书链


虽然 AWS 控制面板上说这是可选项,但是没有的话,根本导入不了。


证书链需要将上一步生成的源服务器证书内容,和 Cloudflare 根证书文本内容,拼在一起,并且要注意顺序!


从这个链接获取 Cloudflare 根证书的内容(RSA 格式):https://developers.cloudflare.com/ssl/e2b9968022bf23b071d95229b5678452/origin_ca_rsa_root.pem

-----BEGIN CERTIFICATE-----MIIEADCCAuigAwIBAgIID+rOSdTGfGcwDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTQwMgYDVQQLEytDbG91ZEZsYXJlIE9yaWdpbiBTU0wgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMB4XDTE5MDgyMzIxMDgwMFoXDTI5MDgxNTE3MDAwMFowgYsxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTQwMgYDVQQLEytDbG91ZEZsYXJlIE9yaWdpbiBTU0wgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwEiVZ/UoQpHmFsHvk5isBxRehukP8DG9JhFev3WZtG76WoTthvLJFRKFCHXmV6Z5/66Z4S09mgsUuFwvJzMnE6Ej6yIsYNCb9r9QORa8BdhrkNn6kdTly3mdnykbOomnwbUfLlExVgNdlP0XoRoeMwbQ4598foiHblO2B/LKuNfJzAMfS7oZe34b+vLByrP/1bgCSLdc1AxQc1AC0EsQQhgcyTJNgnG4va1c7ogPlwKyhbDyZ4e59N5lbYPJSmXI/cAe3jXj1FBLJZkwnoDKe0v13xeF+nF32smSH0qB7aJX2tBMW4TWtFPmzs5IlwrFSySWAdwYdgxw180yKU0dvwIDAQABo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUJOhTV118NECHqeuU27rhFnj8KaQwHwYDVR0jBBgwFoAUJOhTV118NECHqeuU27rhFnj8KaQwDQYJKoZIhvcNAQELBQADggEBAHwOf9Ur1l0Ar5vFE6PNrZWrDfQIMyEfdgSKofCdTckbqXNTiXdgbHs+TWoQwAB0pfJDAHJDXOTCWRyTeXOseeOi5Btj5CnEuw3P0oXqdqevM1/+uWp0CM35zgZ8VD4aITxity0djzE6Qnx3Syzz+ZkoBgTnNum7d9A66/V636x4vTeqbZFBr9erJzgzhhurjcoacvRNhnjtDRM0dPeiCJ50CP3wEYuvUzDHUaowOsnLCjQIkWbR7Ni6KEIkMOz2U0OBSif3FTkhCgZWQKOOLo1P42jHC3ssUZAtVNXrCk3fw9/E15k8NPkBazZ60iykLhH1trywrKRMVw67F44IE8Y=-----END CERTIFICATE-----


然后和自己生成的源服务器证书内容拼在一起粘贴到 AWS ACM 控制面板,注意上下顺序!


-----BEGIN CERTIFICATE-----Cloudflare 根证书内容-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----源服务器证书内容-----END CERTIFICATE-----


导入成功后,你就能在自定义域名开通面板选择到它了!


三、设置域名 CNAME 记录指向 API 网关的自定义域名


注意是 CNAME 到终端节点配置显示的 API Gateway 域名:




四、Cloudflare 页面 SSL 规则


注意需要设置这个新的 CNAME 域名所有的路径(*)的 SSL 规则为“完全,否则,直接访问会报错。



五、Cloudflare 页面缓存规则

经历了以上九九八十一难,你的自定义域名终于通了,也就是说,可以通过你的已备案域名访问到 AWS lambda 的服务了!


这个时候,不要忘记了我们的初衷,我们费了这么大劲,是要给 GraphQL 响应加上 CDN 缓存!


这很简单,再加上两个规则即可,对于缓存级别,选择缓存所有内容;对于 TTL,选最长的。因为对于这个使用场景,是不需要它过期的,但是最长只能选到一个月。当有语雀文章更新时,是可以通过 Cloudflare API 主动清除缓存的。



大功告成!


总结

少量开发(大量配置,好在一劳永逸)结合少量费用(基本免费),将极其缓慢的接口响应,变得快得不能再快,这就是免费架构!


通过少量开发使得原本的 GraphQL HTTP Post 请求转变为 GET 请求,从而可以利用 Cloudflare 的页面规则来实现 CDN 缓存;又通过 API Gateway 的自定义域名,去掉了代理服务,最终将三慢合一中的两慢解决掉了,实现了小程序页面的秒开效果!



good-icon 0
favorite-icon 0
收藏
回复数量: 0
    暂无评论~~
    Ctrl+Enter