低成本打造一个带宽无限的网站 —— No.2 缓存防御攻击
网站攻击
有次在和朋友讨论网站防护时,提到一个信息发布的站点 —— 它的结构很简单,只有几个页面而已,正常情况下打开是非常快的。然而一到关键时刻,流量如同洪水般涌来。网站无法访问,那些付费发布的信息就错过最佳展现时间了。
对于网站攻击,现成的解决方案有很多,例如用上 WAF、CDN 等服务,多少能分担一些。不过,通用的防御方案,自然就有通用的攻击方案。
例如通过 DNS 实现的负载均衡,攻击者使用现成的工具,就能轻易遍历出对应的 IP。更糟的是,有时域名会缓存很久,使得攻击都快结束了解析还没生效。
对于前端爱好者来说,这种传统的方案一点都不 Geek —— 理想的防护,显然应该从前后端同时入手。
提到前端,也许你会觉得奇怪,网站都被打垮了,还哪来的前端?别急。我们先来思考个本质问题:为什么网站容易垮。
相比网页,传统的应用程序在网络不好的情况下,表现的更为强劲。例如一些网络视频播放器,即使没连网也能启动,只是不能观看在线视频而已,但仍可作为普通播放器使用;而网页版的视频站点,显然就没这么强大了,如果没连网,连基本界面都看不到。
这个道理大家都懂。因为应用程序是事先下载到本地的,所以后期运行时,界面、程序可以直接启动,只有信息才依赖网络;而网页的界面、程序、信息,很多时候是混在一起的,每次都得实时传输。所以极端情况下,网页表现得更为脆弱。
改进
因此,我们需要对网页做一些改造,将「界面、程序」和「信息」彻底分离。
-
前者通常较少变动,因此可对相应的资源设置
强缓存
。强缓存是不走流量、直接从本地读取的,所以用户只要访问过一次,之后界面就可以瞬间展现、程序可以立即运行 —— 无论网站是否繁忙! -
后者是动态加载的数据,存放位置并没有限制,因此可放在多个后备站点上 —— 无论自己的站点,还是免费空间。
当基本界面展现后,程序通过 AJAX 从外部站点获取信息,然后填充到页面里。如果获取失败,则尝试后备列表 —— 除非所有站点都垮了,否则只要有一个活着,信息仍能展示!
有了这样的机制,就能降低网站故障的影响了。除了没有缓存的「新用户」无法打开网站,那些曾经访问过的回头客,仍可正常浏览!
再改进
更进一步,只要不是服务崩溃、流量被封那种硬故障,我们还可继续优化,使新用户也有机会访问。
在带宽吃紧的情况下,我们需要对「界面和程序」进行精简,使其只需极小的传输流量,从而能在夹缝中求生。
那么,这究竟能精简到多小?事实上,只需一行 HTML 就够了:
<script src="//free-host-n/boot.js"></script>
我们可让网站所有的界面和功能,都由一个外部脚本来创建。这样,整个站点只需一个几十字节的页面,仅仅作为启动器而已!
尽管最终 99.99% 的流量都来自其他站点,但浏览器的地址栏,显示的仍是当前站点:)
现在,只要带宽还有一丝残喘的余地,新用户就有机会获取到这个迷你启动页,进而从互联网上各个节点加载出完整内容!
由于整个站点只承载一个极小的文件,因此防御策略可以简单很多。此外,外部脚本的路径可通过后端工具随时改变,用以避开速度缓慢的节点 —— 毕竟 HTTP 控制缓存的能力,比 DNS 丰富多了!
缺陷
当然,这个方案似乎过于激进 —— 不仅需要对业务做大量改造,而且对搜索引擎也不利,因此最终并没有实际应用。
此外,还有一个大问题也未能解决:用户刷新页面,会导致强缓存失效,从而产生网络请求。如果此时网站挂了,那么用户刷新后,不是长时间等待,就是直接显示错误。
如此美妙的防御方案,最终却防不住 F5 按钮。。。这个问题,直到 HTML5 时代的一项新科技才能解决 —— 应用缓存。
应用缓存
关于应用缓存,熟悉前端的小伙伴们都不陌生,它正是为提高 WebApp 的体验而设计。
应用缓存的用法很简单,通过一个列表清单,告诉浏览器预先缓存哪些资源:
<html manifest="list.appcache">
之后访问列表中的资源时,浏览器就直接从本地读取。相比强缓存,应用缓存的离线程度更高 —— 不仅没连网也能访问,甚至还可以刷新!
缓存不耐刷的问题,总算是能解决了。只是此时兴致已过,并没有去尝试改进。对于浏览器缓存,当时觉得更好玩的还是攻击方面 —— 中间人和前端脚本相互结合,批量污染缓存。
也许正是因为易受中间人污染,以及 不够灵活 等原因,应用缓存始终存在一些争议,以至于后来被 Web 标准废弃了。取而代之的,则是一个更逆天的标准。。。