Skip to main content

搭建自己的 CDN 的乐趣和好处

如你所见,我也喜欢页面能快速加载,越快越好.但在我们开始讨论之前,先要有一个清楚的认知:CDN 并不是万能的.如果因为糟糕的前端工作导致你的网站变慢,CDN 并不能帮到你太多,你需要先做好前端工作.但一旦你已经做好了所有的优化,就需要来研究一下内容传输这块了.

我碰到的主要问题是即使能通过一次 HTTP 请求来完成初始网站的加载,但因为我的服务器托管在法兰克福,在澳大利亚的人仍需要等待2-3秒后才能访问到它.超过300ms的往返延时和中间大量的服务提供商使得页面加载就跟 Wordpress 网站一样慢.

那如何解决这个问题呢? 一种解决方案是使用传统的 CDN.然而大多数商业 CDN 在从服务器请求到数据之后,都会缓存一段时间.

由于 CDN 的存在,在内容获取上稍微绕行了一下,导致在使用传统 CDN 之后,初始页面的加载反而变慢了.如果你的网站流量高,内容一直缓存着,这没什么大问题.但反过来说,如果和我一样仅仅是运行一个小博客,内容并不会常驻缓存,传统的 pull-CDN 反而会让网站变得更慢.当然,我也可以通过 push-CDN 直接上传内容,但跟我要搭建的 CDN 相比,这种成本要昂贵许多.

CDN 是如何工作的?

我们的方案很明确:为了扩大世界影响力,我们应该保证内容在任何位置都能被快速访问.这意味着内容所在位置应该尽量靠近访问者.方便的是,很多云服务商在多个地区都提供了廉价的虚拟服务器.我们是否仅需要把内容放到比如6台服务器上,就万事大吉了呢?

好吧,没那么快.那如何把用户路由到正确的服务器上呢?我们看一下实际访问网站的过程.首先,浏览器通过 DNS 查询网站的 IP 地址,获取到 IP 之后,就可以连接网站并下载请求的页面.

上层的解决方案其实很简单:需要一台智能 DNS 服务器,对请求的IP 做 GeoIP 查询并返回离它最近的 IP 地址.事实上,几乎所有的商业 CDN 都是这么做的.虽然还牵扯到许多工程学领域的内容,比如延迟测量等,但基本原理还是这个.

让 DNS 服务器快起来

接下来新的问题出现了:如何让 DNS 服务器快起来?从最近节点上访问网站仅仅解决了一半问题,如果 DNS 查找不得不绕行地球一周,还是会有极大的延迟.

事实证明,支撑互联网的基础设施非常适合解决这个问题.网络提供商使用边界网关协议来相互告知可连接的网络和跃点的多少.多数情况下,最终互联网提供商会采用最短路线来到达目标地址.

如果在多个位置使用一个 IP 地址,DNS 总是会路由到最近的节点,这就是 BGP Anycast.

为何网站下载不使用 BGP Anycast?

如果能做到,我们为何不简单使用 BGP 来路由网络流量呢?主要有三个原因.
首先,使用 BGP Anycast 需要在网络硬件上做控制,并且需要一个至少包含256个 IP 地址的池子,这超出了我们的预算.

其次,BGP 路由并不那么稳定.不同于 DNS 请求仅仅只需要向两个方向发送单个数据包,HTTP web 请求需要创建一个连接来下载内容.期间如果路由改变或者连接不稳定,HTTP 连接就会断开.对这种规模的工程来说反而增加了很多复杂性.
最后,跃点作为 BGP 路由计算基础,它数目的减小并不能保证往返延迟的减小.一个跨大洋的跃点可能仅仅是一个跃点,但却是时间最长的一个.

扩展阅读: 关于这个话题,Linkedin Engineering上有一篇非常棒的博文

创建 DNS

既然已经确认不能运行我们自己的 BGP Anycast,也意味着同样不能运行我们自己的 DNS 服务器.让我们去找找收费的!...事实证明,同时提供 BGP Anycast 和延迟路由的 DNS 提供商很难找到.我只搜索到了2个:相当昂贵的 Dyn 和非常便宜的 Amazon Route53.(更新:后来发现, DNS Made Easy 也能实现延迟路由)既然想合算,就选 Route53 了.添加域名之后开始为机器设置 IP. 我们需要设置跟我们遍布世界各地的(边缘节点)服务器同样多的 DNS 记录,每条记录设置如下:


提示:最好对每个边缘节点都创建一个健康检查以便在他们失效后进行移除.

分发内容

我们需要解决的下一个问题是分发内容.每个边缘节点都需要有相同的内容.如果你使用的是 Jekyll 这样的静态网站生成器,工作很简单:只需要将生成的 HTML 文件复制到所有的服务器上即可.一个简单的 rsync 就能搞定.

如果想使用 Wordpress 这样的内容编辑系统,工作会困难的多,因为它并不能在 CDN 上运行.当然也可以做,但免不了有缺陷,静态内容的分发仍然是一个问题.你可能必须要创建一个分布式存储才能正常工作.

使用 SSL/TLS 证书

下一个痛点是使用 SSL/TLS 证书.实际上,可以统称他们为:x509证书.每个边缘节点都需要为域名设置有效的证书.当然,最简单的解决方案是使用 LetsEncrypt 来生成不同的证书.但要注意, 我在其中一个边缘节点上碰上了 LE 有效期的问题,导致在每周限额到期之前我不得不暂时把伦敦节点下掉.

我使用 Traefik 作为我的选择代理, 它支持分布式键值存储,甚至支持 Apache Zookeeper 后端同步.虽然这需要一些程序设计,可从长远来看会更稳定一些.

结果

是时候检验一下结果了,我的 CDN 表现如何呢?使用这个工具,看一下总体数据:

如你所见,相当不错的结果.我可能还需要在亚洲和南美加两个节点来优化加载时间.

更新:在把它提交到 Hacker News 首页之后,我可以使用 Google Analytics 来收集一些实际使用数据:

结论:我确实需要一个新加坡节点.在印度的加载时间超过了预期的1秒.

常见问题

我在做这项工作的时候,人们经常问我:"你为什么要做这个呢?你这是自寻烦恼啊 ". 确实,在某种程度上,我喜欢做不同的事情来探索新方向和新技术,建立自己的 CDN 可能具有很大的意义.让我们来谈谈关于设置的一些问题.

首先申明:如果商业提供商能推出价格合理的 push CDN,让我能处理 nice URLs, SSL 和自定义头信息,我绝对乐意花钱解决问题并停掉我自己的基础设施.搭建它很有趣,但我还有很多服务器没有运行它.

为何不用 CloudFlare?
对很多人来说 CloudFlare 是一个很棒的工具,但如上所述, CDN 会从缓存中移除未使用的内容.我管理的其他网站,在正确配置的情况下缓存命中率大概能达到75%.拥有自己的 CDN 则意味着内容会常驻缓存,不会有访问远程服务器而导致的往返延时.

为何不用 S3 或 CloudFront?
Amazon S3有托管静态网站的选项,并且可以与 CloudFront 结合着使用.然而,它不允许你自定义缓存头信息,设置 nice URLs等.为此,你需要使用 Lambda@Edge, 这是一个可以让你在 CloudFront 边缘节点上运行代码的工具.但Lambda@Edge 跟 CDN 有同样的问题:如果它在一段时间内没有接收到请求,运行它的容器就会关闭,再次重启则需要一秒钟的时间.

为何不用 Google AMP?
Google AMP 只会在用户通过 Google 搜索引擎访问你的网站时才会带来效果. 而我的大部分流量并非来自 Google, 所以它并不能解决问题. 它仅仅对 Google 有效果,不管其他. 而且我完全有能力在不使用他们提供的简化 HTML 的情况下搭建一个快速网站.

谁在意? 3秒的加载时间已经很棒了!
我是一个擅长内容传输的 DevOps 工程师. 不管其他人如何,我都应该有一个全球都能快速访问的网站,不是吗?我一般都会将 Google AMP关闭,因为跟他们所期望的不同,这是一项糟糕的技术.

搭建
现在看你的了:是否要搭建你自己的 CDN? 源代码在 GitHub 上,拿去吧!

Comments

Popular posts from this blog

CKA Simulator Kubernetes 1.22

  https://killer.sh Pre Setup Once you've gained access to your terminal it might be wise to spend ~1 minute to setup your environment. You could set these: alias k = kubectl                         # will already be pre-configured export do = "--dry-run=client -o yaml"     # k get pod x $do export now = "--force --grace-period 0"   # k delete pod x $now Vim To make vim use 2 spaces for a tab edit ~/.vimrc to contain: set tabstop=2 set expandtab set shiftwidth=2 More setup suggestions are in the tips section .     Question 1 | Contexts Task weight: 1%   You have access to multiple clusters from your main terminal through kubectl contexts. Write all those context names into /opt/course/1/contexts . Next write a command to display the current context into /opt/course/1/context_default_kubectl.sh , the command should use kubectl . Finally write a second command doing the same thing into ...

OWASP Top 10 Threats and Mitigations Exam - Single Select

Last updated 4 Aug 11 Course Title: OWASP Top 10 Threats and Mitigation Exam Questions - Single Select 1) Which of the following consequences is most likely to occur due to an injection attack? Spoofing Cross-site request forgery Denial of service   Correct Insecure direct object references 2) Your application is created using a language that does not support a clear distinction between code and data. Which vulnerability is most likely to occur in your application? Injection   Correct Insecure direct object references Failure to restrict URL access Insufficient transport layer protection 3) Which of the following scenarios is most likely to cause an injection attack? Unvalidated input is embedded in an instruction stream.   Correct Unvalidated input can be distinguished from valid instructions. A Web application does not validate a client’s access to a resource. A Web action performs an operation on behalf of the user without checkin...