本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 [署名 4.0 国际 (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/deed.zh) 本文作者: 苏洋 创建时间: 2019年05月14日 统计字数: 5059字 阅读时间: 11分钟阅读 本文链接: https://soulteary.com/2019/05/14/simple-policies-make-front-end-resources-highly-available.html ----- # 简单策略让前端资源实现高可用 前几天有朋友问我,曾经在前公司里使用过的前端资源高可用方案是怎么做的。资源高可用听起来应该是后端、运维同学的“分内之事”。但是前端资源的高可用并没有那么简单,在当前复杂的网络环境下,你是指望用户多刷新几次、还是期望用户把Wi-Fi切换为4G,撞大运解决问题?获客成本如此之高的今天,放弃用户是不明智的。 想到许久没有写前端相关的文章了,决定在这里简单聊聊。希望能帮助到创业阶段的公司和团队。 在聊技术细节之前,我们先聊聊“什么是前端资源高可用”。 ## 资源高可用和前端有什么关系? 前端资源高可用这个需求,对于“大厂”的同学来说应该很陌生。 因为对于大公司来说,有大量冗余的云主机资源可以为业务团队提供,并且会配套一定规模的运维团队。当监控系统发现线上出现资源不可用的情况时,系统能够根据策略自动切换问题资源到备份资源,而有些不能自动切换的服务,则会有值守的运维同学,在第一时间手动进行切换,保障业务的高可用。 ![网络服务高可用基本玩法](https://attachment.soulteary.com/2019/05/14/basic.jpeg) 而小一些规模的创业公司就没那么幸运了,资源相对紧张,甚至没有完善的监控措施,更别提配一只相对完善的运维团队了。 或许会有人认为,将静态资源扔到 CDN 上后就一劳永逸了。然而现实世界中,网络环境十分复杂,相同主机在不同线路、不同地区、不同时间段的可用性和访问质量是不同的,所以使用 CDN 不是解决这个问题的银弹,但是同时使用多个 CDN 或许是当前阶段比较通用的方案。 比如默认不同地域的用户通过不同线路访问网站,如果其中一条线路出现问题,那么一部分用户就无法访问网站提供的服务。 ![常见用户访问情况](https://attachment.soulteary.com/2019/05/14/common-problems.jpeg) 这个时候,我们通常会使用切换请求资源服务器的方法来解决问题,比如下面这样。 ![如何解决用户可用性](https://attachment.soulteary.com/2019/05/14/switch-resource.jpeg) 当某条 CDN / 服务线路不正常的时候,我们可以通过切换域名来解决资源获取不到的问题,但是别忘记一件很重要的事情: **域名生效需要时间、多地域生效周期漫长**,在这个切换域名的时间窗口内,你的服务质量将会持续受到影响。 并且这个方案的资源切换动作通常会在后端进行,而此时页面已经推送到用户侧,资源已经不可用,用户需要刷新后才有可能请求到新的资源地址,并且是在 DNS 能够生效的前提下,我们知道很多流行的应用客户端为了性能优化,都为资源(甚至包含页面)设置了很长的有效期,可以说这个方案并不是一个很有效的方案。 所以,假设你采取类似这种方案,你必须确保下面四个条件都生效,才能达到你的目的: - 你的监控系统发现了问题,并自动进行了资源切换。 - 你的业务负责人,发现了问题,并手动进行资源切换。 - 你成功切换了资源,并且 DNS 快速生效(网络层、客户端层)。 - 你的用户在你切换资源、DNS 生效后,恰如其分的刷新了页面,而不是直接离开。 听起来是不是很魔幻。 那么有没有什么简单可靠的方案可以解决这个问题呢? 有,让资源在前端层面进行自动切换。 ## 方案简介 通过在前端环境监听资源加载错误信息,并根据一定策略自动加载其他位置的资源,实现前端依赖的资源在前端(用户侧)进行自动切换,达到前端资源高可用的目的,减少因前端资源加载失败而导致的服务不可用和用户流失。 ## 环境模拟 为了更直观的演示方案如何生效,我这里使用 Docker 做一个常见场景的模拟。 ### 模拟多个网络 我们先创建一个 `docker-compose.yml` ,里面包含下面的内容。 ```yaml version: '3' services: web: image: ${NGX_IMAGE} 4 expose: - 80 networks: - traefik labels: - "traefik.enable=true" - "traefik.frontend.rule=Host:${MAIN_HOST}" - "traefik.frontend.entryPoints=${SUPPORT_PROTOCOL}" volumes: - ./public/${MAIN_HOST}:/usr/share/nginx/html extra_hosts: - "${MAIN_HOST}:127.0.0.1" cdn1: image: ${NGX_IMAGE} expose: - 80 networks: - traefik labels: - "traefik.enable=true" - "traefik.frontend.rule=Host:${CDN_HOST1}" - "traefik.frontend.entryPoints=${SUPPORT_PROTOCOL}" - "traefik.frontend.headers.customResponseHeaders=Access-Control-Allow-Origin:*" volumes: - ./public/${CDN_HOST1}:/usr/share/nginx/html extra_hosts: - "${CDN_HOST1}:127.0.0.1" cdn2: image: ${NGX_IMAGE} expose: - 80 networks: - traefik labels: - "traefik.enable=true" - "traefik.frontend.rule=Host:${CDN_HOST2}" - "traefik.frontend.entryPoints=${SUPPORT_PROTOCOL}" - "traefik.frontend.headers.customResponseHeaders=Access-Control-Allow-Origin:*" volumes: - ./public/${CDN_HOST2}:/usr/share/nginx/html extra_hosts: - "${CDN_HOST2}:127.0.0.1" networks: traefik: external: true ``` 可以看到,编排文件里面定义了一个应用网站,和两个 CDN 服务,为了更接近真实场景。其中一个 CDN 和应用网站根域名相同、另外一个采取完全不同的域名,比如下面这样。 ```TeXT # 默认使用的镜像 NGX_IMAGE=nginx:1.15.8-alpine # 支持访问的协议 SUPPORT_PROTOCOL=https,http # 主站点的域名 MAIN_HOST=demo.lab.io # 模拟根域名相同的CDN CDN_HOST1=demo-cdn.lab.io # 模拟根域名不同的CDN CDN_HOST2=demo.cdn2.io ``` 将上面的内容保存为 `.env` ,并将上面内容中的域名绑定到本地之后,执行 `docker-compose up` ,就可以开始实战了。 ### 模拟常规场景 执行 `docker-compose up` 之后,我们会看到 Docker 自动帮我们创建了几个目录。 ```TeXT ./public ├── demo-cdn.lab.io ├── demo.cdn2.io └── demo.lab.io ``` 我们在 **demo.lab.io** 目录下创建 `index.html` 文件,作为应用入口。 ```html