低成本打造一個(gè)帶寬無(wú)限的網(wǎng)站(上)
時(shí)間:2022-08-11 02:42:01 | 來(lái)源:網(wǎng)站運(yùn)營(yíng)
時(shí)間:2022-08-11 02:42:01 來(lái)源:網(wǎng)站運(yùn)營(yíng)
前言
前些年斷斷續(xù)續(xù)寫(xiě)的,最近突然想起來(lái),于是翻出來(lái)又補(bǔ)充了一些,目前整理了一下,分享給大家。此文是上篇。
配圖是 SVG 格式的,由于無(wú)法上傳,目前放在 GitHub Pages 上。如果速度很慢或者被墻,可以開(kāi)代理試試。
一、免費(fèi)空間的遐想
免費(fèi)空間
自從學(xué)習(xí)網(wǎng)頁(yè)制作那天起,就開(kāi)始期待有朝一日能有個(gè)自己的網(wǎng)站。
盡管當(dāng)時(shí)有不少免費(fèi)空間,對(duì)于簡(jiǎn)單的個(gè)人網(wǎng)站也夠用了,然而像我這樣挑剔的,試用后幾乎都不怎么滿(mǎn)意 —— 要么會(huì)偷偷插些廣告,這對(duì)于有潔癖的我是無(wú)法容忍的;要么奇慢無(wú)比,而我那些「炫酷」的頁(yè)面充斥了大量圖片和特效,也不懂得優(yōu)化,所以每次傳到空間后,效果總是慘不忍睹。
也許你會(huì)說(shuō),為什么非要用免費(fèi)的,花錢(qián)買(mǎi)個(gè)好點(diǎn)的配置不就得了。不過(guò)那時(shí)零花錢(qián)十分有限,每天幾塊錢(qián)除了早飯偶爾買(mǎi)些書(shū)之外,所剩無(wú)幾。用在網(wǎng)站空間上?壓根就沒(méi)有過(guò)這樣的念頭!好在有大把的時(shí)光,于是每當(dāng)閑暇時(shí),便開(kāi)始鼓搗一些極(diao)客(si)的方案,嘗試將免費(fèi)空間變廢為寶。
有次耐下心來(lái)仔細(xì)分析,發(fā)現(xiàn)一些空間并沒(méi)有想象中那么慢 —— 如果網(wǎng)頁(yè)只有幾個(gè)字符的話,還是很快就能出現(xiàn)的。只是我的網(wǎng)頁(yè)里圖片太多了,光背景就是一組高清大圖。。。加上各種限速,所以才會(huì)顯得十分緩慢。
客觀地說(shuō),這些空間不算太差,至少延時(shí)并不高,只是帶寬稍小而已。
既然找到痛點(diǎn),那就能對(duì)癥下藥了。當(dāng)然,前提還是不!能!花!錢(qián)!于是被迫開(kāi)啟腦洞,激發(fā)各種猥瑣思路:)
改進(jìn)
免費(fèi)空間 —— 既然是免費(fèi)的嘛,一個(gè)費(fèi)用是 0,一百個(gè)也是 0,為何不多注冊(cè)幾個(gè)呢?
然后,從中選一家「延時(shí)最低」的專(zhuān)門(mén)放網(wǎng)頁(yè),其他的則用來(lái)放圖片 —— 也許你也猜到了,只要對(duì)網(wǎng)頁(yè)做些調(diào)整,把所有的圖片都改成「絕對(duì)路徑」,從不同的站點(diǎn)分別加載。這樣,就能享受好幾倍的免費(fèi)帶寬了~
事實(shí)上有些插廣告的免費(fèi)空間,只會(huì)篡改網(wǎng)頁(yè)或腳本文件,圖片倒不會(huì)變化。于是這些空間就能充分利用起來(lái)~
要是臉皮厚的話,甚至還可以打起論壇、相冊(cè)、網(wǎng)盤(pán)、圖床的主意,尋找那些附件可外鏈、下載速度快的網(wǎng)站,進(jìn)一步擴(kuò)充免費(fèi)資源的節(jié)點(diǎn)~
只要節(jié)點(diǎn)充足,帶寬顯然是管夠的!
不過(guò),要同時(shí)維護(hù)這么多資源,顯然是很麻煩的。因此需要一套自動(dòng)化工具,用于各個(gè)節(jié)點(diǎn)的數(shù)據(jù)同步;若要利用論壇附件,還得實(shí)現(xiàn)更多功能,例如自動(dòng)上傳、外鏈檢測(cè)、文件名記錄、列表管理、定期維護(hù)。。。
此外,前端網(wǎng)頁(yè)也需進(jìn)行改造。為了方便使用,還得開(kāi)發(fā)一個(gè) JS 腳本,對(duì)頁(yè)面中的圖片路徑自動(dòng)調(diào)整。這其中涉及不少細(xì)節(jié),例如站點(diǎn)選擇的算法、無(wú)效資源的切換、本地緩存的命中。。。
看起來(lái)很有趣吧,似乎是一個(gè)前端版的負(fù)載均衡:) 要是算法夠好、節(jié)點(diǎn)夠多的話,估計(jì) CDN 都可以省了~
缺陷
當(dāng)然想象總是美好的,但真要放在現(xiàn)實(shí)中,估計(jì)沒(méi)一個(gè)網(wǎng)站會(huì)這么做 —— 誰(shuí)會(huì)為了省一點(diǎn)帶寬費(fèi)用,把原本很簡(jiǎn)單的東西搞得這么復(fù)雜呢。
除了復(fù)雜之外,風(fēng)險(xiǎn)也會(huì)大幅增加。某些節(jié)點(diǎn)要是往圖片里加些水印、廣告之類(lèi)的倒還好,要是加入些非法反動(dòng)內(nèi)容,那簡(jiǎn)直就得不償失了!
況且這樣濫用免費(fèi)資源,感覺(jué)也不太好意思。于是簡(jiǎn)單嘗試了一段時(shí)間后,覺(jué)得意義不大又麻煩,便不再折騰。
直到多年后的一天,又回想起這個(gè)方案。。。
二、通過(guò)緩存防御網(wǎng)站
網(wǎng)站攻擊
有次在討論網(wǎng)站防護(hù)時(shí),提到一個(gè)信息發(fā)布的站點(diǎn) —— 它的結(jié)構(gòu)很簡(jiǎn)單,只有幾個(gè)頁(yè)面而已,正常情況下打開(kāi)是非??斓?。然而一到關(guān)鍵時(shí)刻,流量如同洪水般涌來(lái)。網(wǎng)站無(wú)法訪問(wèn),那些付費(fèi)發(fā)布的信息就錯(cuò)過(guò)最佳展現(xiàn)時(shí)間了。
對(duì)于網(wǎng)站攻擊,現(xiàn)成的解決方案有很多,例如用上 WAF、CDN 等服務(wù),多少能分擔(dān)一些。不過(guò),通用的防御方案,自然就有通用的攻擊方案。
例如通過(guò) DNS 實(shí)現(xiàn)的負(fù)載均衡,攻擊者使用現(xiàn)成的工具,就能輕易遍歷出對(duì)應(yīng)的 IP。更糟的是,有時(shí)域名會(huì)緩存很久,使得攻擊都快結(jié)束了解析還沒(méi)生效。
對(duì)于前端愛(ài)好者來(lái)說(shuō),這種傳統(tǒng)的方案一點(diǎn)都不 Geek —— 理想的防護(hù),顯然應(yīng)該從前后端同時(shí)入手。
提到前端,也許你會(huì)覺(jué)得奇怪,網(wǎng)站都被打垮了,還哪來(lái)的前端?別急。我們先來(lái)思考個(gè)本質(zhì)問(wèn)題:為什么網(wǎng)站容易垮。
相比網(wǎng)頁(yè),傳統(tǒng)的應(yīng)用程序在網(wǎng)絡(luò)不好的情況下,表現(xiàn)的更為強(qiáng)勁。例如一些網(wǎng)絡(luò)視頻播放器,即使沒(méi)連網(wǎng)也能啟動(dòng),只是不能觀看在線視頻而已,但仍可作為普通播放器使用;而網(wǎng)頁(yè)版的視頻站點(diǎn),顯然就沒(méi)這么強(qiáng)大了,如果沒(méi)連網(wǎng),連基本界面都看不到。
這個(gè)道理大家都懂。因?yàn)閼?yīng)用程序是事先下載到本地的,所以后期運(yùn)行時(shí),界面、程序可以直接啟動(dòng),只有信息才依賴(lài)網(wǎng)絡(luò);而網(wǎng)頁(yè)的界面、程序、信息,很多時(shí)候是混在一起的,每次都得實(shí)時(shí)傳輸。所以極端情況下,網(wǎng)頁(yè)表現(xiàn)得更為脆弱。
改進(jìn)
因此,我們需要對(duì)網(wǎng)頁(yè)做一些改造,將「界面、程序」和「信息」徹底分離。
- 前者通常較少變動(dòng),因此可對(duì)相應(yīng)的資源設(shè)置強(qiáng)緩存。強(qiáng)緩存是不走流量、直接從本地讀取的,所以用戶(hù)只要訪問(wèn)過(guò)一次,之后界面就可以瞬間展現(xiàn)、程序可以立即運(yùn)行 —— 無(wú)論網(wǎng)站是否繁忙!
- 后者是動(dòng)態(tài)加載的數(shù)據(jù),存放位置并沒(méi)有限制,因此可放在多個(gè)后備站點(diǎn)上 —— 無(wú)論自己的站點(diǎn),還是免費(fèi)空間。
當(dāng)基本界面展現(xiàn)后,程序通過(guò) AJAX 從外部站點(diǎn)獲取信息,然后填充到頁(yè)面里。如果獲取失敗,則嘗試后備列表 —— 除非所有站點(diǎn)都垮了,否則只要有一個(gè)活著,信息仍能展示!
有了這樣的機(jī)制,就能降低網(wǎng)站故障的影響了。除了沒(méi)有緩存的「新用戶(hù)」無(wú)法打開(kāi)網(wǎng)站,那些曾經(jīng)訪問(wèn)過(guò)的回頭客,仍可正常瀏覽!
再改進(jìn)
更進(jìn)一步,只要不是服務(wù)崩潰、流量被封那種硬故障,我們還可繼續(xù)優(yōu)化,使新用戶(hù)也有機(jī)會(huì)訪問(wèn)。
在帶寬吃緊的情況下,我們需要對(duì)「界面和程序」進(jìn)行精簡(jiǎn),使其只需極小的傳輸流量,從而能在夾縫中求生。
那么,這究竟能精簡(jiǎn)到多???事實(shí)上,只需一行 HTML 就夠了:
我們可讓網(wǎng)站所有的界面和功能,都由一個(gè)外部腳本來(lái)創(chuàng)建。這樣,
整個(gè)站點(diǎn)只需一個(gè)幾十字節(jié)的頁(yè)面,僅僅作為啟動(dòng)器而已!盡管最終 99.99% 的流量都來(lái)自其他站點(diǎn),但瀏覽器的地址欄,顯示的仍是當(dāng)前站點(diǎn):)
現(xiàn)在,只要帶寬還有一絲殘喘的余地,新用戶(hù)就有機(jī)會(huì)獲取到這個(gè)迷你啟動(dòng)頁(yè),進(jìn)而從互聯(lián)網(wǎng)上各個(gè)節(jié)點(diǎn)加載出完整內(nèi)容!
由于整個(gè)站點(diǎn)只承載一個(gè)極小的文件,因此防御策略可以簡(jiǎn)單很多。此外,外部腳本的路徑可通過(guò)后端工具隨時(shí)改變,用以避開(kāi)速度緩慢的節(jié)點(diǎn) —— 畢竟 HTTP 控制緩存的能力,比 DNS 豐富多了!
缺陷
當(dāng)然,這個(gè)方案似乎過(guò)于激進(jìn) —— 不僅需要對(duì)業(yè)務(wù)做大量改造,而且對(duì)搜索引擎也不利,因此最終并沒(méi)有實(shí)際應(yīng)用。
此外,還有一個(gè)大問(wèn)題也未能解決:
用戶(hù)刷新頁(yè)面,會(huì)導(dǎo)致強(qiáng)緩存失效,從而產(chǎn)生網(wǎng)絡(luò)請(qǐng)求。如果此時(shí)網(wǎng)站掛了,那么用戶(hù)刷新后,不是長(zhǎng)時(shí)間等待,就是直接顯示錯(cuò)誤。
如此美妙的防御方案,最終卻防不住 F5 按鈕。。。這個(gè)問(wèn)題,直到 HTML5 時(shí)代的一項(xiàng)新科技才能解決 —— 應(yīng)用緩存。
應(yīng)用緩存
關(guān)于應(yīng)用緩存,熟悉前端的小伙伴們都不陌生,它正是為提高 WebApp 的體驗(yàn)而設(shè)計(jì)。
應(yīng)用緩存的用法很簡(jiǎn)單,通過(guò)一個(gè)列表清單,告訴瀏覽器預(yù)先緩存哪些資源:
之后訪問(wèn)列表中的資源時(shí),瀏覽器就直接從本地讀取。相比強(qiáng)緩存,應(yīng)用緩存的離線程度更高 —— 不僅沒(méi)連網(wǎng)也能訪問(wèn),甚至還可以刷新!
緩存不耐刷的問(wèn)題,總算是能解決了。只是此時(shí)興致已過(guò),并沒(méi)有去嘗試改進(jìn)。對(duì)于瀏覽器緩存,當(dāng)時(shí)覺(jué)得更好玩的還是攻擊方面 —— 中間人和前端腳本相互結(jié)合,批量污染緩存。有興趣的可以看之前寫(xiě)的流量劫持相關(guān)文章。
也許正是因?yàn)橐资苤虚g人污染,以及 不夠靈活 等原因,應(yīng)用緩存始終存在一些爭(zhēng)議,以至于后來(lái)被 Web 標(biāo)準(zhǔn)廢棄了。取而代之的,則是一個(gè)更逆天的標(biāo)準(zhǔn)。。。
三、前端代理服務(wù)
前端代理
HTML5 時(shí)代的黑科技層出不窮,但最具創(chuàng)新的也許要數(shù) Service Worker,它甚至可以顛覆傳統(tǒng)的 B/S 網(wǎng)絡(luò)架構(gòu)。
顧名思義,Service 是服務(wù)程序,而 Worker 常用于多線程。因此 Service Worker(以下簡(jiǎn)稱(chēng) SW)是一種獨(dú)立于頁(yè)面、可持續(xù)運(yùn)行的瀏覽器后臺(tái)程序。
SW 提供了一組 API,可讓網(wǎng)站開(kāi)發(fā)者攔截自己站點(diǎn)下 所有頁(yè)面 產(chǎn)生的 所有請(qǐng)求,并且能自定義響應(yīng)結(jié)果。(除了一些特殊請(qǐng)求無(wú)法攔截)
這,如同在本地開(kāi)啟一個(gè)反向代理服務(wù)!
有了這么逆天的功能,在前端做負(fù)載均衡就非常容易了,甚至還能實(shí)現(xiàn)過(guò)去不敢想象的效果 —— 實(shí)時(shí)無(wú)縫的切換。
實(shí)時(shí)切換
作為代理,當(dāng) SW 加載上游資源失敗時(shí),可選擇不返回錯(cuò)誤結(jié)果,而是嘗試后備站點(diǎn)再次加載,直到返回正確結(jié)果,才響應(yīng)給下游網(wǎng)頁(yè):
在網(wǎng)頁(yè)看來(lái),這只是一次普通的請(qǐng)求與響應(yīng) —— 也許用時(shí)更長(zhǎng)一些,但結(jié)果仍是正常的。SW 中的重試細(xì)節(jié),對(duì)于業(yè)務(wù)是完全透明的!
相比 DNS 最少也有數(shù)秒的緩存時(shí)間,這種通過(guò)程序控制的方案,能在極短的時(shí)間內(nèi)切換源站點(diǎn)。這樣即使某些節(jié)點(diǎn)出現(xiàn)故障,頁(yè)面甚至都毫無(wú)感知!
校驗(yàn)加密
除了能改變 URL 之外,SW 當(dāng)然還能操作返回的數(shù)據(jù)。
這意味著,我們可以增加一個(gè)校驗(yàn)機(jī)制,用以檢測(cè)資源是否遭到篡改。于是那些插廣告、加水印之類(lèi)的問(wèn)題,就能很好解決了!
此外,我們還可以對(duì)原始數(shù)據(jù)進(jìn)行加密,再由 SW 解密。這對(duì)于私密性不高的節(jié)點(diǎn),很是有意義。
例如用 Raw Git 作為免費(fèi)空間,我們所有的文件都能在 GitHub 倉(cāng)庫(kù)里找到,任何人都可以輕易查看。但如果對(duì)文件進(jìn)行加密,同時(shí)對(duì) SW 中的解密算法進(jìn)行混淆保護(hù),就能增加查看難度了 —— 至少 GitHub 的搜索功能、以及普通的蜘蛛,是不會(huì)抓到明文內(nèi)容了。
更進(jìn)一步,我們甚至還可以對(duì)文件名進(jìn)行 Hash 再存儲(chǔ)。這樣,暴露的只是一堆亂七八糟、沒(méi)有目錄層次的文件!
離線啟動(dòng)
前面我們提到,SW 能攔截頁(yè)面里的請(qǐng)求。事實(shí)上 SW 開(kāi)啟之后,訪問(wèn)頁(yè)面本身也會(huì)經(jīng)過(guò) SW。
這意味著:用戶(hù)只要裝上 SW,之后所有的請(qǐng)求都可代理到外部節(jié)點(diǎn)上,于是可大幅減少自己網(wǎng)站的流量消耗!
這樣就算我們的網(wǎng)站掛了,但只要有一個(gè)節(jié)點(diǎn)可用,用戶(hù)仍能正常訪問(wèn)!
精簡(jiǎn)啟動(dòng)
為了能在帶寬吃緊的情況下迎接新用戶(hù),我們參照之前「迷你啟動(dòng)器」的方案,把安裝 SW 所需的資源,精簡(jiǎn)到最小 —— 最終只需兩個(gè)極小的文件:html 和 js 文件。(SW 的腳本必須在當(dāng)前站點(diǎn)下)
用戶(hù)首次訪問(wèn)時(shí),無(wú)論訪問(wèn)哪個(gè) URL,我們都返回這個(gè) html 文件,用以安裝 SW 服務(wù);安裝完成后,頁(yè)面自動(dòng)刷新,這時(shí)所有請(qǐng)求都走 SW 代理了!
關(guān)于 html 的內(nèi)容,和之前探討的一樣,所有功能都由外部腳本實(shí)現(xiàn):
而 SW 腳本的內(nèi)容,同樣也可以放置在外部:
于是,我們的站點(diǎn)只需承載兩個(gè)極小的文件,就能獲得無(wú)盡的帶寬!
改造成本
相比之前強(qiáng)緩存的方案,如今使用 SW 無(wú)需對(duì)前端做任何改造,頁(yè)面里的資源仍保持原始路徑即可。如同使用 VPN 一樣,無(wú)需對(duì)應(yīng)用程序?qū)θ魏涡薷?,開(kāi)啟后流量就能自動(dòng)轉(zhuǎn)發(fā)到代理上,用起來(lái)非常簡(jiǎn)單。
這樣,任何一個(gè)網(wǎng)站都能輕松接入使用!
事實(shí)上 SW 可實(shí)現(xiàn)的效果遠(yuǎn)不止這些,我們繼續(xù)深入挖掘吧。
作者:etherdream,阿里巴巴高級(jí)安全工程師
原文鏈接
更多技術(shù)干貨敬請(qǐng)關(guān)注云棲社區(qū)本站機(jī)構(gòu)號(hào):阿里云云棲社區(qū) - 本站