hj24.life

聊聊OSS和CDN架构

2021.07.25

这片文章会讲讲 OSS & CDN 在扇贝的应用,并介绍一下业内常用的 OSS & CDN 架构,所有内容均已脱敏处理。 照例放一下大纲:

  1. OSS/CDN 是什么?
  2. 为什么需要它们?
  3. 扇贝 OSS 梳理
  4. 扇贝 CDN 梳理

是什么和为什么

OSS

OSS 全名叫对象存储服务,顾名思义也是存储资源的服务,那么有了数据库我们还需要这么一个专门用来存储资源的服务吗?

某种程度上 OSS 承担的角色确实和数据库有一部分重叠,但它又和数据库不同,我们可以通过下面这个场景来体会:

某天我们接到了需求要存下用户上传在论坛话题区里的图片,作为一个合格的开发,我们第一反应就是那简单,用 BLOB 类型的字段存下来就可以了,没错,用数据库完全可以做到,但这样做真的好吗?

考虑一下以下的问题我们就会发现,这样做其实有不少缺陷:

  1. 图片的上传是一个耗时操作,如果直接由我们的业务后端来做,假设接口是同步上传的,一个图片上传耗时 1s,那这样系统的吞吐量就会大大降低,服务端扛不起高并发那就无法应对与日俱增的用户量
  2. 如果把接口做成异步上传,那么第一个问题解决了,但压力就全落在了数据库上,上传图片这种比较重的任务在业务请求量高的情况下必定会导致 mysql 的 iops 飙升,iops 提高就会导致 sql 执行排队(哪怕是存 blob 无关的 sql)从而进一步影响其它业务
  3. 即使真的要用 blob 存,要知道 mysql 单行的数据是有上限的(65535 字节),一个用户在 cms 的一条发帖回帖记录算一行的话,能存储的图片非常有限(当然理论上是可以通过表结构的拆分去解决的,但是这一方面提高了业务查询的复杂性,一方面占用的数据库资源没有任何减少)

这时候 oss 的出现就彻底解放了数据库,我们来改进一下上面那个用数据库存储的方案,去掉万恶的 blob 字段,把所有用 blob 存的图片都改成存到 oss,这样做的话:

  1. 资源的上传处理全部交给了 oss 承担,专业的人做专业的事,对于我们自己同时又减轻了数据库压力,避免了对其他业务的影响
  2. 一个用户的发帖记录可以上传的图片理论上大大得到了提升,我们在数据库可以只存下图片指向的 oss 的 url,比起存一个 blob 可是减少了不少资源,更进一步的话,因为 oss 的 bucket 和域名都是固定的,我们只需要存下一个 oss 的 key 就可以在服务端拼接好最终的 url,能节省的空间就更多了

从上面这个例子我们就可以看到,术业有专攻,像图片视频这种相对静态的资源更适合用 oss 存储,它的定位也是和数据库完全不一样的,总结一下使用场景如下:

  1. 海量图片音视频的存储,如同上面的例子一样,海量、高并发场景下直接把这些数据用二进制的形式存在数据库是不可取的,服务器资源可能就直接耗在上传下载这些内容上,从而影响到正常的服务了
  2. 网页、app 的静态资源:静态脚本、音频视频图片等,可以配合 CDN 加速用户对这些资源的访问(相对的动态资源指的是订单,用户信息这种 crud 评率较高的关系型数据,需要更高的一致性,更适合用数据库去做)
  3. 低频访问的数据,OSS 的存储成本很低,低频访问的数据如果长期占用数据库资源,可以考虑归档到 OSS

CDN

CDN 全名叫内容分发网络,它其实相当于一个网络资源的缓存,把我们源网站的资源缓存到全球各地的节点上,让不同地区的用户能就近快速的访问到我们服务的内容,同时减轻我们服务自身的压力,正确配置的 CDN 还可帮助保护网站免受某些常见的恶意攻击,例如 ddos(简单讲就是 cdn 可以智能的将来自 ddos 的流量分流,同时作为缓存,它大大降低了源站承受攻击的压力)。

简单讲它主要做了以下三件事:

  1. 全球到处堆节点
  2. 把源网站内容缓存到全球各节点上
  3. 根据网络流量和各节点的连接、负载情况以及到用户的距离和响应时间等综合因素将用户的请求重定向到离用户最近的服务节点上

为什么需要 cdn,其实说完它是什么之后就很明显了,一能提升用户体验二能减轻我们自己的压力,三是合理的配置能保护我们避免一些常见的恶意的攻击,何乐而不为呢?

不过或许我们可以用最近 b 站挂了这件事的一个可能原因来更直观的讲讲为什么。

b 站挂了之后有很多大佬出来分析是 cdn 挂了,有理有据,令人信服,总结一下可能的时间线:

  1. 云服务供应商出现意外,大量请求绕过CDN直接打到应用网关,多家互联网公司的主站发生无法访问的现象。
  2. 豆瓣,Acfun等公司的运维收到告警,紧急切换了CDN,或启动了容灾策略进行降流,系统正常对外提供服务。
  3. Bilibili同样收到告警,启动了容灾策略,但是当时正是Bilibili流量高峰期,有两个可能:
  4. 自研的LB没办法处理这么多请求,直接崩了,但是如果LB崩了,只需要切换CDN并且重启LB就可以成功继续对外服务了,对于Bilibili来说这个概率要小一些。
  5. 自研的降流有问题,导致大量请求发送到后续底层服务,服务雪崩,整个环境炸了。

作者:余歌 链接:https://www.zhihu.com/question/472065470/answer/1996564735

来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

可以看到,cdn 一挂,访问压力就直接由源站网关抗下来了,对于服务来讲这种风险是难以承受的,这也是为什么各大厂的 cdn 回源策略都是一层套一层的,扇贝也不例外(下面梳理 cdn 的时候会看)。

术语

  • 这里暂时看不懂没关系,后面详细介绍 CDN 的时候可以回来看

一、回源

和 redis 缓存一样,在我们发现 redis 里没有缓存下需要的数据时我们通常会去数据库把数据捞出来再存到 redis 里,这样下次访问就会直接走缓存,回源也是一样,当前 cdn 节点没有想要的数据时就会根据配置的回源路径一层一层的往源站查资源再缓存下来。

二、参数跟随

并不是所有服务商的这个功能都叫参数跟随,比如阿里云就直接叫配置过滤参数,但是几乎所有服务商都有类似的概念,这里我们以又拍云对这个功能的命名进行讲解

通常我们访问 cdn 链接时会带上各种各样的参数,所谓参数跟随就是指 cdn 回源时需不需要带上这些参数,常见的参数跟随策略有下面三种:

不跟随

所有访问 cdn 的请求都不会带上参数,这可能会导致我们配置的诸如 x-oss-process 这类访问控制参数失效

全程跟随

所有参数都会原封不动的传递给源站,这样做会有一些隐患,比如一些带随机参数的接口,会导致 cdn 缓存几乎不起任何作用,因为每一次都命中不了缓存

回源跟随

指只有在回源阶段才会带上这些参数,和 1 一样,会导致我们配置的诸如 x-oss-process 这类访问控制参数失效

更具体的可以看又拍云关于参数跟随的解释:

扇贝 OSS 梳理

目前 OSS 的工作流程可以看下面这张时序图:

  • 假设扇贝有一个专门用来管理资源上传的微服务叫 media-go(实际上并不是)
  • oss 服务商,比如七牛云、阿里云、又拍云之类的,这里我们简称其 oss

  1. 业务方先到这个服务注册一个业务 code
  2. 前端需要上传的时候带着这个 code 请求 media-go,读取对应的配置,media-go 根据业务方注册的配置为其生成上传到 oss 的临时凭证
  3. 前端上传到 oss 时,oss 会根据请求里带上的回调地址(如果有的话)请求 media-go 的回调接口,告诉 media-go 有业务上传了数据,此时 media-go 会根据对应的 code 去看这个业务有没有注册一些任务(比如加水印之类的),有的话会把这个任务放进消息队列去异步的排队执行
  4. 接收完回调并将业务注册的任务发送到消息队列之后,media-go 会告诉 oss 它已经处理完了,oss 侧完成上传流程,将最终资源存储的地址返回给前端
  5. 最后 media-go 借助 RabbitMQ 订阅通知机制告诉业务任务已经完成了

整体上都遵循了业内通用的一套最佳实践,具体可以看下面这张阿里云官网的结构图,细节可以参考阿里云关于 oss 最佳实践的这一节(数据直传 oss):

使用现状

这里的使用现状可以分两块:

大前端直传的模式

也就是上面我们介绍的那种架构,是目前的主流使用场景

后端直接操作 oss 的模式

某些服务也会在后端直接操作 oss,比如会把数据库里的冷数据定期归档到 oss,类似的还有我们也会把网关日志备份到 oss

bucket 使用情况

公共读(public-read) 权限可以不通过身份验证直接读取您 Bucket 中的数据,安全风险高,且有可能产生预期外的公网流量费用,为确保您的数据安全,不推荐此配置,建议您选择私有(private)。

如上面的引用所示,扇贝绝大部分 bucket 的权限都会设置为私有,需要公有访问的资源通过 CDN 鉴权然后回源到私有 bucket,关于 CDN 后面会讲,这么做主要是出于下面两种原因:

  1. 避免不必要的公网流量,oss 的计费大头都在公网访问的流量费,存储本身比较便宜
  2. 公有 bucket 不需要身份验证,安全风险较高

扇贝 CDN 梳理

架构

一个典型的流程:

  1. 用户请求加速域名后请求首先经过 DNS 解析重定向到 CDN 配置的 CNAME 记录
  2. 解析 CNAME 得到最终的最佳 IP 地址(某个 CDN 节点)
  3. 如过缓存命中则返回,如果未命中则回源
  4. 缓存下回源到的内容,下次访问直接返回

可能需要关注的问题:

为什么需要 CNAME?

其实正常 CDN 服务商在你配好加速域名之后都会分配一个 CNAME,假设没有这个 CNAME,直接访问加速域名通过 DNS 解析得到 A 记录也就是一个具体的 IP,这时候 CDN 应该这么返回距你最近的节点?

答案是没办法做,如果这样 CDN 就失去了它的意义

使用现状

除了少量 v1 的老 CDN 资源会走到七牛云(并且七牛云直接回源到老扇贝)目前最主要的回源链路:

又拍云 cdn -> 阿里云 cdn -> 阿里云 oss

其实在 21 年之前我们是没有又拍云 CDN 的,加入又拍云主要出于以下两点考虑:

  1. 更便宜,走又拍云 CDN 访问流量费用上能节省一笔开销
  2. 分散阿里云 CDN 的单点风险,其实现在很多公司都是多云混合,CDN 服务商接入不止一家,这样哪怕阿里云挂了,又拍云还是能顶一段时间的

其中有这么几条需要重点关注的事:

为什么又拍云要回源到阿里云 CDN 而不是直接到阿里云 OSS?

如上图所示,因为目前阿里云 OSS 有很多私有 bucket 需要访问控制,在阿里云 CDN 那边是可以配置为允许回源私有权限 bucket 的(且权限为只读),但又拍云是受到限制的,所以这里并不可以直接回源到阿里云 OSS

为什么又拍云现在只回源到阿里云公有访问的 cdn?

https://media-image1.baydn.com/fake_bucket/vwpqcw/8f389a2316ff5464c96e288866af1dd1.f850de401210982cd16464dc092bb97e.png?x-oss-process=image/quality,Q_90

https://media-image1.baydn.com/fake_bucket/kzmrpf/ff45d5f6dbcb2ee4d5f3ad59bd270757.3e4eb643140e4895785ceeaa625245fa.jpeg?auth_key=1626687791-0-0-723c445739eb8101b39c1728f51150dc

可以看上面这两个例子,其实我们在访问阿里云 CDN 时会经常带着上面的 x-oss-process、auth_key 等控制参数用来修改图片大小类型、鉴权等,这就会导致下面两个问题:

  1. 如果不开启参数跟随,那么我们配置的所有这些访问控制参数(用于修改图片大小之类的如 x-oss-process)就会失效
  2. 如果开启了,那些需要鉴权的 url(带 auth_key 之类的参数)会因为鉴权签名参数的随机性导致又拍云缓存在大多数时候都无法命中,需要频繁回源,此时作为 CDN 而言我们就失去了启用它的意义

所以目前我们只会把公有访问的 CDN 域名切换到又拍云,并且在又拍云开启参数全程跟随,而需要鉴权访问的 CDN 域名保持在阿里云不动

粗略统计了一下,目前扇贝的 cdn 命中率基本都在 97 以上,最低点也在 87 以上,这里就不放监控图了

参考

  1. 知乎:CDN存在的必要性及工作原理详解
  2. 阿里云 OSS 介绍
  3. 阿里云 CDN 介绍
  4. 关于 cdn 与回源的概念
  5. cloudflare:cdn 工作原理