静态资源缓存利器——CDN

2,411 阅读8分钟

项目背景

我们这边有一个云文档的项目,为了让用户在编辑过程中的数据能够实时保存在云端,目前采用的是定时全文同步的方式对数据进行保存,在DB保存的是文档多个版本快照的内容。

项目分析

  • 文档的文本内容大部分是kb级别大小的,目前直接保存到DB,不管是数据读取还是数据写入,对DB的I/O性能以及网络传输方面,都会产生较大的压力,能否通过一些小成本的改造进行优化呢?
  • 每个文档用户只要发生编辑,都会产生多个快照版本,且版本的数据是新增的,而不是更新,具备内容静态特性

文本存储占用情况

对线上数据进行抽样统计,得出大致的存储分布情况,可以看出目前大部分文档内容数据都小于64kb

大小范围count比例
(0, 16kb]33317234.34%
(16kb, 64kb]62082064%
(64kb, +∞)160691.66%

优化方案

目前针对这个业务场景,我们需要优化的问题有两个,一个是数据的存储,另一个是数据读取。由于篇幅原因,本文先针对数据读取进行优化,存储优化放到下一篇文章哦~

对于数据读取的优化,我们很容易想到能否通过一些缓存机制来进行优化,且缓存的内容有两个很重要的特性:大文本 & 静态数据

  • 由于文档快照的数据记录并不会存在更新操作,更新内容会产生新的版本,可以认为一份文档的某一个版本的内容属于一个静态文件

CDN(内容分发网络)介绍

image.png

内容分发网络(CDN):是指一组分布在不同地理位置的服务器,协同工作以提供互联网内容的快速交付。

  • 缩短网站加载时间: 通过边缘节点提供的就近访问,可以让访问者体验到更快的页面加载时间,一般来说,网站速度越快,用户停留的时间越长
  • 减少带宽成本: 网站托管的带宽一般是有限的且成本较高,而CDN提供的带宽一般是没有上限且成本低廉的,而且通过就近访问,网络传输的距离也大大缩短,这也是减少带宽成本的原因之一
  • 增加内容可用性和冗余: 大流量或硬件故障可能会扰乱正常的网站功能。由于 CDN 具有分布式特性,因此与许多源服务器相比,CDN 可以处理更多流量并更好地承受硬件故障
  • 改善网络安全性: 增加了CDN之后,用户请求不会直接到业务后端,相当于多了一层代理,在CDN层面可以配置很多安全策略(如 DDoS 缓解、IP、referer 限制等)

方案一:OSS + CDN

image.png

优缺点分析

  • 优点:
    • 资源存储和数据库分离,释放db压力
    • 资源存储和资源响应放到oss,减轻后端服务的压力
  • 缺点:
    • 后端改造成本大,需要关注哪些版本需要存储到oss(文档只有当前版本是可读的,其他版本只是用于文档修复,大部分时候用不上),做好新旧两种方式的兼容(数据落库、数据返回)
    • oss并不适合存储海量小文件
    • 引入新的依赖,服务的可用性会有所降低(存储服务、OSS),也需要做好重试机制

OSS存储小文件的弊端

根据线上已有数据分析,98%的文本内容都是小于64kb的,这个范围对于OSS来说来说算是小文件,成本上没有很大的优势

  • OSS不适合存储海量小文件,存储利用率不高(zhuanlan.zhihu.com/p/434780987
  • 小于64kb的文件对于OSS来说很难进行成本上的优化
    • 如对文件进行生命周期管理,上传时间超过半年的由标准存储转为低频存储,而低频存储中不足64kb的文件按64kb进行收费
      • image.png

方案二:DB + CDN ✅

image.png

优缺点分析

  • 优点:
    • 改造成本很低,前后端逻辑上不会发生较大的变更,后端也不用关注引用版本的问题(只要文档版本被访问就自动缓存到CDN)
    • 通过CDN缓存内容,可以提高资源的响应速度,也可以大大降低后台服务和db的压力
  • 缺点:
    • 现阶段大文本数据还是存储在MySQL,对数据库存储和后端服务有一定的压力
      • 数据库存储压力
        • 可以将文档快照表进行分库存储,避免查询和写入影响其他表
        • 字段压缩技术
      • 后端服务:在CDN没有命中缓存的情况下,会触发回源操作,此时后端服务需要读取大文本内容到缓存中,QPS大的情况下可能会导致服务异常
        • 进行CDN缓存策略调优,提高缓存命中率
        • 服务拆分部署

结论

优先考虑方案二,在返回文档内容前加一层CDN缓存即可

  • 按照目前线上小于64kb的文本内容分布(98%),以及正常文档合理的存储占用来看,这部分存储在数据库不会有很大的问题
  • 目前该服务还处于初步阶段,后期肯定会有很多需求是围绕文档内容展开的,后期的需求可能还需要进行二次重构,CDN的方式可以用最小成本达到可观的优化效果
  • CDN这种形式不一定是这个功能的最终形态,但这是可以在很长一段时间降低服务和db压力的最低成本的实现

CDN缓存优化

增加缓存最主要的目的无非就是加快访问速度、降低源站压力,所以缓存命中率是我们比较关心的指标

示例:

  • 假设content-url为:https://{host}/doc-server/{docId}/{versionId}?expires={expires}&signature={sign}
    • /doc-server/{docId}/{versionId}:资源的path,可以唯一定位一个资源
    • expires:过期时间戳(资源为临时授权访问,有时效性,起放盗链效果)
    • signature:签名,防止篡改资源路径以及过期时间

如何进行缓存优化?

CDN 的缓存命中,我们简单理解就是一个类似 K-V 的映射关系,key为请求的 path+参数,value为对应的资源内容

以上面示例举例:key为/doc-server/{docId}/{versionId}?expires={expires}&signature={sign} 

  • /doc-server/{docId}/{versionId}:这部分为不变的内容,唯一确定一个资源,作为缓存的key没问题
  • expires & signature:过期时间戳以及签名参数并不影响资源内容本身,但却是经常变化的,会大大降低缓存命中

进行分析之后,可以发现其实优化很简单,我们只要在CDN上配置 expires & signature 参数不参与缓存即可。 image.png

一般为了减少回源,我们会在CDN侧配置缓存时间为 7天+,但这又带来了一个问题,expires参数我们本意是临时授权(如2小时),但一旦内容被CDN缓存后,那这个校验就失效了,该链接就可以在CDN缓存失效前无限次访问到资源。

如何解决CDN中链接过期但仍能访问资源的问题?

造成该问题的本质原因就是CDN侧没有我们的 URL鉴权算法,它无法识别链接的有效性,在CDN第一次回源拉取资源的时候,由源站(即我们的后台服务)进行鉴权,之后资源缓存在CDN侧就失去了鉴权能力了

  • 方案一: 使用CDN厂商提供的签名规则,不使用自定义鉴权算法
    • 腾讯云:cloud.tencent.com/document/pr…
    • 优点:
      • CDN厂商会提供签名sdk,开发简单
      • CDN侧就可以完成鉴权,处理性能高
    • 缺点:
      • 后端服务需要对接CDN的API,有一定的业务侵入
      • 无法利用CDN无状态和易切换的天然优势,如当前CDN厂商出现异常,无法快速切换到其他CDN厂商(不利于降本和高可用)
  • 方案二: 使用自定义鉴权算法,结合CDN的回源鉴权功能 ✅ 
    • 回源鉴权:在cdn层面配置鉴权服务器,用户请求到达cdn节点时,cdn节点会将用户的请求原封不动的转发给鉴权服务器,鉴权服务器可以根据请求参数给出鉴权结果 
    • 优点:
      • 算法自定义,后期容易扩展一些业务特性的字段
      • CDN接入规则统一,不会与单一CDN服务商耦合,可以快速进行CDN的切换 ✅
    • 缺点:
      • 回源鉴权需要从CDN通过http访问源站进行鉴权,中间有一定的额外耗时