CDN 多服务商容灾建设
一、背景
CDN(内容分发网络)是互联网的核心基础设施,通过在全球部署的边缘节点为用户提供内容分发服务。这类似于在用户附近设立快递配送站,确保内容能够通过最短、最优的路径送达用户设备,从而提升访问体验。
在实际应用中,用户访问网站时需要加载 JavaScript、CSS、图片、视频等多种静态资源。这些静态资源往往托管于 CDN 服务,CDN 基于就近服务原则,可以显著提升资源的加载速度。然而,一旦 CDN 服务发生故障导致静态资源加载失败,可能会引发页面白屏等严重问题。瓴岳科技集团面向全球提供可靠的信贷服务,而 CDN 服务商在全球节点的稳定性并非对等,历史上各国 CDN 节点发生过多起故障,提醒我们必须建立完善的 CDN 多服务商容灾机制,以保障业务稳定。
二、现状
我们对集团当前面向用户提供的前端系统进行了梳理,盘点 CDN 建设现状如下:
- 单一服务商:各个国家仅接入了单一的 CDN 服务商,例如国内使用阿里云,印尼使用七牛云。
- 监控不及时:监控告警分布在各个服务商后台,缺乏统一管理,无法及时发现和感知 CDN 异常情况。
- 恢复时间长:当 CDN 发生故障时,需要运维团队与服务商反复沟通,问题定位和处理耗时较长。
- 可用性隐患:各国 CDN 服务商稳定性不对等,例如印尼七牛云服务可用性约 99.78%,低于 4 个 9 的基线要求。此外,当 CDN 发生故障时,可用性无法保证。
三、目标
基于现状分析,我们设定了以下目标:
- 建立多 CDN 服务商容灾机制。
- CDN 整体可用性达到 4 个 9。
四、整体方案
整体方案通过在端侧构建 SDK 实现 CDN 容灾机制,主要包含两个核心层面:
- SDK 层:管理端侧资源加载的完整生命周期,包括监测加载状态、执行重试策略以及数据上报,构建从资源加载到数据上报的闭环机制。
- 监控层:构建完整的 CDN 监控体系,集成数据上报与存储能力,通过实时数据大盘和故障监控告警,实现 CDN 服务质量的可观测性。
五、详细设计
在业务中我们通过注入 SDK,实现资源加载状态的全流程监控。在页面加载阶段识别资源类型(如 JavaScript、CSS、图片等),并根据不同资源的加载过程和特征,构建对加载状态的感知能力。对于加载失败的资源,我们实现重试机制,在达到最大失败次数后,记录失败状态写入数据队列;对于加载成功的资源,记录成功状态写入数据队列。最终,我们采用批量上报策略,将数据统一上报。
5.1 监测资源加载状态
前端应用的稳定强依赖于静态资源的成功加载,要构建 CDN 容灾方案,首先需要感知资源加载状态。这里我们重点关注如何监测 JavaScript、CSS 和图片这 3 类静态资源的加载状态。而这 3 类静态资源的加载方式归类为以下几种场景:
- 直接加载:通过
script
、link
、img
等 HTML 标签静态引入的资源,例如全局 CSS、umd 引入的公共 JS。 - 动态创建:动态
import
语法可以借助 webpack 等构建工具转化为document.createElement
动态创建标签来实现按需加载能力,而随着前端应用逐渐复杂化,动态创建资源的场景越来越多。 - CSS 图片:通过
background-image
、border-image
等属性引入的图片资源。
下面我们将通过具体代码示例,展示这几种场景下的核心监测实现细节。
5.1.1 直接加载
通过监听全局的 error
和 load
事件,我们可以捕获到直接通过 HTML 标签引入的静态资源(script
、link
、img
)的加载情况。当资源加载失败触发 error
事件时,获取目标元素信息并进行重试;当触发 load
事件时,则记录成功数据用于后续分析。这种全局事件监听方式实现简单且覆盖面广。
1 | function errorHandler() { |
5.1.2 动态创建
前端应用中的动态导入、路由懒加载等按需加载能力,最终会被构建工具转化为 document.createElement
来实现。通过代理这个函数并添加资源加载监听器,我们可以监测这类动态创建场景下的资源加载情况,在加载失败时及时触发重试策略。
1 | const originalCreateElement = document.createElement; |
5.1.3 CSS 图片
CSS 图片指的是通过样式表中的特定属性来引入图片资源的场景,这类场景包括了 background-image
、border-image
、list-style-image
等属性。针对这些场景,我们采用运行时多图拼接的方案。方案的原理是:以 background-image
为例,CSS 允许在一个属性中通过逗号分隔的方式指定多个 background-image
(例如:background-image: url("https://cdn1.com/img.jpg"), url("https://cdn2.com/img.jpg")
)。浏览器会按照顺序尝试加载这些图片,天然具备降级能力。利用这个特性,在原始 URL 后面拼接多个备用 CDN 的 URL,从而实现了在 CSS 层面的容灾降级。
1 | // 可能包含图片 URL 的 CSS 属性列表 |
5.2 重试机制
通过监测不同资源的加载状态,我们能够感知加载失败的资源,此时进入资源重试流程,下面展开介绍。
5.2.1 准备工作
在实施重试策略前,需要完成资源部署和 CDN 预热工作。具体来说,在 CI 阶段,我们会将资源统一上传至各服务商的 OSS(Object Storage Service),确保资源在所有 CDN 节点均可访问,例如同一个静态资源 a.js
可通过 https://cdn1.com/a.js
和 https://cdn2.com/a.js
等多个地址获取。同时,还需要提前对各 CDN 节点进行资源预热,将资源提前缓存至边缘节点,以减少 CDN 切换时的性能损耗。最后,我们也会部署一份到自有服务器并配置主域名可访问。
5.2.2 重试策略
我们在监测到资源加载状态后,对加载失败的资源实施自动重试策略。具体实现策略如下:
- 当检测到资源加载失败时(如
script
标签的error
事件触发),SDK 立即从等效域名池中选择下一个备选域名进行重试。 - 若当前重试仍然失败,则继续遍历尝试其他备选域名,直到资源加载成功或达到最大重试次数。
- 在极端情况下,若所有备选域名都无法完成资源加载,SDK 启动兜底策略,将请求切换至系统主域名(fallback 域名)。这么做的原因是我们认为当前页面可被访问时,主域名挂载的资源大概率也是可被访问的。
5.3 上报策略
上图展示了数据上报的大致流程,我们将从上报信息、批量上报、数据一致性展开介绍。
5.3.1 明确上报信息
为了全面评估 CDN 服务质量和容灾方案的有效性,我们需要关注两个核心指标:各 CDN 服务商可用性、整体可用性。为完成这两个指标的建设,资源加载生命周期中需要采集以下关键数据:
字段 | 说明 |
---|---|
appid | 系统标识 |
metricsType | 上报类型,例如资源加载成功/失败 |
assetsUrl | 资源完整路径 |
provider | CDN服务商,例如 qiniu、alioss、huawei、fallback |
retryTimes | 重试次数 |
url | 当前页面地址 |
5.3.2 批量上报
为了避免频繁的数据上报与业务接口竞争有限的网络资源,我们采用了批量上报机制。该机制通过设定阈值来控制上报频率,在满足以下任一条件时触发数据上报:
- 数据量阈值:当上报队列中的数据量达到预设值时,例如 20 条
- 时间阈值:距离上一次上报超过指定时间间隔时,例如 1s
这种批量上报策略既能保证数据的准实时性,又能最大程度地减少对业务性能的影响。
5.3.3 数据一致性
为了保证数据采集的准确性和完整性,我们基于用户维度而非资源维度进行采样。对于被采样的用户,SDK 会收集该用户在访问 session 期间内完整的资源请求链路,这对于准确计算各项指标(如可用性)至关重要。同时,为了避免数据重复或丢失,我们仅在单个资源的完整加载生命周期(包含重试过程)结束后,才将数据存入上报队列。
六、效果评估
为全面验证 CDN 容灾方案的有效性,我们从以下 4 个维度进行了评估:
- 故障模拟验证:通过模拟 CDN 服务不可用场景,验证了系统在容灾情况下能够被访问,保证业务连续性。
- 整体稳定性显著提升:监控数据显示,启用容灾机制后,资源加载成功率更加稳定且接近 100%。图中紫色线条表示单一 CDN 域名的资源加载成功率,绿色线条展示了启用容灾机制后的整体成功率。
- 真实故障场景下的效果验证:在 CDN 服务商出现故障期间(如图中紫色线条显示成功率降至 98.8%),容灾机制有效发挥作用,使得整体资源加载成功率仍然保持在接近 100% 的水平,体现了方案的可靠性。
- 历史数据分析:我们选取了某业务一周的历史日志数据进行分析,通过计算资源加载成功率和失败资源挽回率,进一步验证了容灾方案的长期效果。
七、总结与展望
通过建立 CDN 多服务商容灾机制,我们将静态资源加载的整体可用性从 99.7441% 提升至 99.9988%,达成了 4 个 9 的可用性目标。在 CDN 服务商出现故障时,失败请求通过重试得到了修复,有效保障了业务系统的稳定性。
然而,在业务全球化的背景下,我们发现了一个值得关注的问题:当首选 CDN 节点访问失败触发重试时,切换至备选节点的过程会带来一些延迟。特别是在不同地区不同时间下,由于网络条件的差异,各 CDN 服务商的可访问性也存在差异,固定的重试顺序并不能满足所有用户的最优体验。在下一阶段的建设中,我们将实现基于用户感知的 CDN 动态调度机制来优化此类问题。
我们会持续基于 CDN 可用性做更多的探索和尝试。如果您对 CDN 容灾也感兴趣,欢迎大家在文末评论区留言或者给出建议,非常感谢。