项目背景
我们这边有一个云文档的项目,为了让用户在编辑过程中的数据能够实时保存在云端,目前采用的是定时全文同步的方式对数据进行保存,在DB保存的是文档多个版本快照的内容。
项目分析
- 文档的文本内容大部分是kb级别大小的,目前直接保存到DB,不管是数据读取还是数据写入,对DB的I/O性能以及网络传输方面,都会产生较大的压力,能否通过一些小成本的改造进行优化呢?
- 每个文档用户只要发生编辑,都会产生多个快照版本,且版本的数据是新增的,而不是更新,具备内容静态特性
文本存储占用情况
对线上数据进行抽样统计,得出大致的存储分布情况,可以看出目前大部分文档内容数据都小于64kb
| 大小范围 | count | 比例 |
|---|---|---|
| (0, 16kb] | 333172 | 34.34% |
| (16kb, 64kb] | 620820 | 64% |
| (64kb, +∞) | 16069 | 1.66% |
优化方案
目前针对这个业务场景,我们需要优化的问题有两个,一个是数据的存储,另一个是数据读取。由于篇幅原因,本文先针对数据读取进行优化,存储优化放到下一篇文章哦~
对于数据读取的优化,我们很容易想到能否通过一些缓存机制来进行优化,且缓存的内容有两个很重要的特性:大文本 & 静态数据
- 由于文档快照的数据记录并不会存在更新操作,更新内容会产生新的版本,可以认为一份文档的某一个版本的内容属于一个静态文件
CDN(内容分发网络)介绍
内容分发网络(CDN):是指一组分布在不同地理位置的服务器,协同工作以提供互联网内容的快速交付。
- 缩短网站加载时间: 通过边缘节点提供的就近访问,可以让访问者体验到更快的页面加载时间,一般来说,网站速度越快,用户停留的时间越长
- 减少带宽成本: 网站托管的带宽一般是有限的且成本较高,而CDN提供的带宽一般是没有上限且成本低廉的,而且通过就近访问,网络传输的距离也大大缩短,这也是减少带宽成本的原因之一
- 增加内容可用性和冗余: 大流量或硬件故障可能会扰乱正常的网站功能。由于 CDN 具有分布式特性,因此与许多源服务器相比,CDN 可以处理更多流量并更好地承受硬件故障
- 改善网络安全性: 增加了CDN之后,用户请求不会直接到业务后端,相当于多了一层代理,在CDN层面可以配置很多安全策略(如 DDoS 缓解、IP、referer 限制等)
方案一:OSS + CDN
优缺点分析
- 优点:
- 资源存储和数据库分离,释放db压力
- 资源存储和资源响应放到oss,减轻后端服务的压力
- 缺点:
- 后端改造成本大,需要关注哪些版本需要存储到oss(文档只有当前版本是可读的,其他版本只是用于文档修复,大部分时候用不上),做好新旧两种方式的兼容(数据落库、数据返回)
- oss并不适合存储海量小文件
- 引入新的依赖,服务的可用性会有所降低(存储服务、OSS),也需要做好重试机制
OSS存储小文件的弊端
根据线上已有数据分析,98%的文本内容都是小于64kb的,这个范围对于OSS来说来说算是小文件,成本上没有很大的优势
- OSS不适合存储海量小文件,存储利用率不高(zhuanlan.zhihu.com/p/434780987)
- 小于64kb的文件对于OSS来说很难进行成本上的优化
- 如对文件进行生命周期管理,上传时间超过半年的由标准存储转为低频存储,而低频存储中不足64kb的文件按64kb进行收费
- 如对文件进行生命周期管理,上传时间超过半年的由标准存储转为低频存储,而低频存储中不足64kb的文件按64kb进行收费
方案二:DB + CDN ✅
优缺点分析
- 优点:
- 改造成本很低,前后端逻辑上不会发生较大的变更,后端也不用关注引用版本的问题(只要文档版本被访问就自动缓存到CDN)
- 通过CDN缓存内容,可以提高资源的响应速度,也可以大大降低后台服务和db的压力
- 缺点:
- 现阶段大文本数据还是存储在MySQL,对数据库存储和后端服务有一定的压力
- 数据库存储压力
- 可以将文档快照表进行分库存储,避免查询和写入影响其他表
- 字段压缩技术
- 后端服务:在CDN没有命中缓存的情况下,会触发回源操作,此时后端服务需要读取大文本内容到缓存中,QPS大的情况下可能会导致服务异常
- 进行CDN缓存策略调优,提高缓存命中率
- 服务拆分部署
- 数据库存储压力
- 现阶段大文本数据还是存储在MySQL,对数据库存储和后端服务有一定的压力
结论
优先考虑方案二,在返回文档内容前加一层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 参数不参与缓存即可。
一般为了减少回源,我们会在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访问源站进行鉴权,中间有一定的额外耗时