日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

上世紀八十年代末,不列顛的伯納斯-李爵士發明了萬維網,距今已有三十年。萬維網并非互聯網的全部,然而它的出現,為普羅大眾開啟了新世界的大門。從此之后,互聯網的風口輪動不休,靜態資源這棵門前的早樹卻安靜如故。許多年過去了,靜態資源服務的基礎構架沒有發生大的改變,改變的只是它所承載的內容和服務的對象。

作為萬維網的基石,HTTP 協議的版本號也曾停滯在 1.1 達十八年之久,期間經歷了兩次迭代。2015年除夕,HTTP/2 終于正式獲批。這一協議在繼承 HTTP/1.1 絕大部分語義的前提下,改革了傳輸方式,其效率接近 TCP 協議的能力極限。它將在未來幾年內,挑戰關于Web 性能優化的許多陳規俗制。然而我們圍繞著靜態資源基礎設施的工作,在那個春天才剛剛起步。幾年來所取得的進展很難說盡如人意,謹以此文作一注腳,一起來看“靜源深流”。

一、先有“靜”,還是先有“動”?

在萬維網面世的頭幾年,網頁就是電子化、通過互聯網傳輸的 html 頁面,除了支持超文本鏈接,可以天馬行空地從一個網頁跳轉到另一個網頁之外,它們和你翻開書本或雜志所見的頁面,以及商店櫥窗或影院門口張貼的海報,就瀏覽體驗而言,不見得有什么了不起的優勢。我的互聯網啟蒙,居然還是通過一份名為《計算機世界》的紙質報紙完成的——這種報紙價格便宜量又足,一塊錢就能買上沉沉的一疊,而且質地良好,很受同學們的歡迎。

那個時代,動態或靜態的說法還不怎么流行,大部分以 http:// 為前綴的 URL 是可以作為固定的參考文獻地址來引用的。

1996年,李爵士領銜的萬維網聯盟(W3C)從 IETF 手里贏回了 HTML 標準的主導權。也就是在這一年的12月,微軟公司推出一種名為 Active Server Pages 的腳本語言,便是日后大名鼎鼎的ASP 語言。在 php 這種“全世界最好的語言”大殺四方之前,ASP 一度是 Web 技術的代名詞。

?打開Notepad,寫下一行 <%Response.write “Hello world!” %>,?把這個文本文件命名為 index.asp,保存在 IIS 的工作目錄下,?然后打開 IE,輸入 http://localhost/index.asp,?「友好的世界」就此呈現。

簡單嗎?是的,就像把大象放進冰箱一樣簡單。Notepad、IIS、IE,所有的工具都是現成的,只需要一臺安裝了 windows NT 操作系統(即 Windows 操作系統的早期服務器版)的個人電腦,你就可以在一分鐘內創建自己的服務端動態網頁。與名字晦澀難懂、令人望而生畏的公共網關接口(CGI)相比,不得不說,這太酷了!

腳本語言的介入,使得服務端編程技術迅速走上了平民化的康莊大道。與此同時,瀏覽器端也在發生著天翻地覆的變化,DOM、JAVAScript/JScript、css 相繼被主流廠商接納,DHTML 技術初具雛形。之后數年間,動態網頁取代靜態網頁,Browser/Server 架構取代 Client/Server架構,分別成為萬維網和網絡應用的主流。

動態網頁大行其道之后,“靜態資源”的概念方才漸漸顯露輪廓。所謂動靜分離,起初只是 Web 程序員們的一種自發的、樸素的實踐。起手建好static 目錄,下設 css / imgs / js 子目錄,順帶斟酌下 img 和 imgs 哪個更妥,乃是一枚老鳥的自我修養的體現。隨著Web 站點或應用的結構日益龐雜,服務端程序和那些僅供瀏覽器下載、無須在服務端執行或運行的——也就是“靜態”的——文件之間顯然存在著重大的分歧。即使是涇渭分明的目錄,也不足以容納這種分歧。于是,架構師們開始將這些“靜態”文件部署在獨立的 Web 容器中,一則可以提升服務性能,二則方便開發和運維,“動靜分離”逐漸成為一種被普遍接受的頂層設計模式。

動靜分離的模式一直沿用至今,但是它的內涵已經發生了深刻的變化。今天,服務端中間件的性能優劣不再成為瓶頸,客戶端(不局限于通用瀏覽器)的用戶體驗才是亟需關注的重點。因此,我們也不再糾結于內容在服務端以什么樣的形態存在,而是站在客戶端的角度,去觀察 HTTP 請求和響應的特點,以此作為界定靜態資源的依據。

從客戶端看 HTTP 請求和響應,靜態資源通常擁有這樣的側寫:

(1) 其請求指向固定的地址(URL);

這是“靜態”的題中之義,但是,也不盡然。

(2) 其請求經由安全的 HTTP 方法執行;

所謂安全,是指這種請求不會嘗試寫入或變更服務端所存儲的數據,通常僅限于 GET / HEAD / OPTIONS 方法。

(3) 其響應具有特定的內容類型(Content-Type);

常見的靜態內容類型有Application/*、image/*、text/*,其中就包括了級聯樣式表、圖片和腳本。

(4) 其響應與上下文無關;

因此靜態資源通常部署在 Cookie-Free 的域名下,請求與響應中均不攜帶任何個性化的 cookie 信息。

然而這些是界定靜態資源的必要充分條件嗎?并不是!現實往往會超越我們最初的想象,以至于上述看似全面和嚴謹的描述,也不過是盲人摸象,想當然耳。比如,Polyfill 腳本的內容并不固定,依請求方的瀏覽器型號而變,顯然不合乎“上下文無關”的要求,但在實踐中,我們又的的確確是將它作為靜態資源來處理的。這并不是僅有的例外。

一文看懂靜態資源服務沉浮及其在攜程的演進

 

Polyfill.js 的內容依瀏覽器型號而定

那么,究竟什么是靜態資源?我們認為: 凡是固定的內容,如果擁有較長的生命周期、面向較多的用戶,即可視為廣義的靜態資源。 這樣的內容,必然可以、也應當按照 HTTP 協議的約定,在客戶端、代理和反向代理層面將其緩存。

正所謂,一動不如一靜。將可以靜態化的資源,盡可能實現靜態化,對于提升用戶訪問體驗大有裨益。但是,如果沒有下節將要講到的 CDN,這個結論的說服力恐怕就要大打折扣了。

二、不可或缺的 CDN

客戶端下載資源所消耗的流量(Traffic)和時間(Time)——我稱之為 T2 ——,是衡量靜態資源服務優劣的核心指標。幾十年來,許許多多的研究人員,孜孜不倦地撰寫了許許多多的論文,反反復復地論證以及量化了用戶等待時間對于體驗質量(QoE)的影響,結論是:越快越好!很好,至少他們的研究成果沒有顛覆我們的常識和經驗。

1994年,對 IETF 的效率深感失望的李爵士決定另起爐灶。他飛越大西洋,轉投有 Georgia Tech of the North 之稱的麻省理工學院,并在此創建了 W3C。次年,受到李爵士的啟發,應用數學教授Tom Leighton著手研究網絡擁塞問題的解決方案。1999年4月,一家名為 Akamai Technologies 的初創公司推出了一種分布式內容傳輸服務,并自豪地宣布當時如日中天的雅虎為其“特許客戶”。Leighton教授正是這家公司的聯合創始人。巧合的是,就在一個月后,畢業于佐治亞理工學院的梁建章先生在太平洋彼岸和他的伙伴共同創建了攜程,多年之后也成了 Akamai 的客戶。對于互聯網公司,1999年也許不是一個創業的好時機,能夠穿越世紀之交的 .com 泡沫,本身就是其價值的明證。

Leighton 教授領導的團隊通過在互聯網的“邊緣”——也就是靠近終端用戶的地方——部署服務器并緩存響應內容,從而減輕內容網站的負荷,同時提升終端用戶的訪問速度。一舉兩得!當然了,得花錢。這極有可能是云計算技術成功應用于商業領域的最早的案例。2002年,該公司首席架構師John Dilley 在其撰寫的Globally Distributed Content Delivery一文中披露,Akamai 已經在1,000個以上的網絡節點中部署了逾12,000臺服務器。[1]這在當時是一個非??捎^的數字,相比之下,截止到2002年年中,我國聯網計算機的總量也不過1,613萬臺,其中多數還是個人電腦。[2]

由 Akamai 公司首創的這一系統,后來被稱為CDN,其全稱是內容交付網絡(Content Delivery Network)或內容分發網絡(Content Distribution Network)。分發是手段,交付是目的,一個宗旨,兩種表述。這類系統迅速成為改善網站 QoE 的極為重要的工具,幾乎所有不甘于偏安一隅的互聯網企業在達到一定用戶規模之后,都會認真地思考 CDN 服務哪家強的問題。

雖然 Akamai 系統傳輸的內容很快就從“Web 對象”——也就是圖片和文本——,拓展到了動態內容和流媒體,但是時至今日,靜態資源仍然是體現 CDN 優勢的最佳標本,而大多數商業化 CDN 也依然因循邊緣緩存(Edge Caching)的思路來處理針對靜態資源的請求。

一文看懂靜態資源服務沉浮及其在攜程的演進

 

圖片引自 Globally Distributed Content Delivery

通常,網站租用 CDN 服務后,供應商會為其分配一個自己名下的專用子域名,由網站管理員自行將該專用子域名設置為網站域名的別名(CNAME)。這意味著由 CDN 供應商指定的——通常是其自有的——域名解析服務( DNS)將接管針對網站域名的解析請求,選擇距離較近的邊緣服務器,將其 IP 地址返回給發起解析請求的一方,也就是終端用戶所使用的本地 DNS 服務器,再由其反饋給終端用戶。經過這樣一番操作之后,用戶瀏覽器上發出的訪問請求,就將被發送到相應的邊緣服務器(Edge Server)并由其進行響應。這是通過 CDN 實現就近訪問的基本原理。

這種技術客觀上達到了在廣域網絡上由多個服務分擔流量的效果,因此術語也稱之為全球服務負載均衡(Global ServerLoad Balance),簡稱 GSLB。

邊緣服務器如果發現可用的緩存,則直接以緩存的內容響應,稱為“命中”(hit)緩存,或“卸載”(offload)流量;否則,請求將在 CDN 內部上行(upstream),直到命中合適的緩存或抵達內容網站自有的服務器,由其進行響應,稱為“回源”(back-to-origin)。源服務器(Origin,也稱源站)處理完回源請求后,其響應將循原路返回,途中,CDN 各級節點將按照約定的方式對其進行緩存,以備下次使用。這是 CDN 執行響應的基本流程。

在這個機制下,作為內容提供商的源站通常處在一個被動的位置。有時,我們也需要主動通知邊緣服務器,提前創建或者刪除緩存,這兩種操作,分別稱為“預取”(prefecth)及“清理”(purge)。

一文看懂靜態資源服務沉浮及其在攜程的演進

 

邊緣緩存的技術路線,決定了這是一個規模致勝、易守難攻的領域。現在,除了傳統的專業廠商之外,從國外的 AWS、Azure,到國內的阿里云、騰訊云,幾乎所有重量級的公有云服務供應商都提供 CDN 服務,這是它們在自身龐大的規?;A上順勢而為的選擇;而眾多的電訊運營商也紛紛憑借其基礎設施的便利,親自上陣,提供地區性乃至洲際的 CDN 服務,統稱為 Telco CDN。這些新興廠商的涌現,的確給志在八方的互聯網企業提供了更多的選擇。除了絕對的規模之外,地理分布的廣泛性是決定 CDN 服務質量的另一要素,而這正是傳統廠商長期深耕之處。已在全球擁有1,500+網絡節點的 Akamai 依然是行業的佼佼者,也是攜程在全球化進程中的技術合作伙伴。

在上一節的末尾曾提到反向代理。對于代理(Proxy),我們都不陌生。瀏覽器的學名是用戶代理(當然嚴格來說 Agent 和 Proxy 是有一定區別的),家用無線路由器也是代理,大家喜聞樂見的 Shadowsocks 則是一種基于 Socket 的代理,它們所代表的都是客戶端。那么反向代理,顧名思義,就是代表服務端的代理。Nginx 充當負載均衡設備時,扮演的就是反向代理的角色。

現在我們知道,CDN 也是一種反向代理。對于靜態資源服務來說,它的地位無可取代。

三、破“窗”之役

有了 CDN 的加持,靜態資源站點就不再是一般的網站,叫做“源站”(Origin)。這一節,說的是源站的事兒。

曾經,攜程的靜態資源服務是按1+n模式部署的,即一個負載均衡設備(Load Balancer),掛載若干臺服務器。負載均衡設備可能是NetScaler,也可能是 F5,而服務器是 Windows 操作系統的,上面運行著 IIS,所有進入的 HTTP 請求被直接映射到服務器的本地文件系統。

這是一個傳統意義上的靜態站點,簡單,純粹,一眼看得見底。NetScaler 和 F5 是業界知名的硬件,Windows 是天下聞名的軟件,這樣的強強聯合,自然沒有什么不好,只是稍貴罷了。之所以 SLB(軟件負載均衡)替代了NetScaler 和 F5,而服務端去 Windows 化也呈大勢所趨,顯現的軟硬件成本只是一個因素,更重要的原因是虛擬化技術和彈性計算解決方案日益成熟,顛覆了原有的基礎設施結構和運維模式。

一文看懂靜態資源服務沉浮及其在攜程的演進

 

實施遷移前的源服務架構

遷移勢在必行。

首節結尾已經明確,服務端采用什么樣的技術,不影響靜態資源服務的本質。略為遺憾的是,此時我們依然將源站應用定位成靜態 Web 應用,沿襲了中間件 + 本地文件系統的架構,只不過操作系統換成了 centos,而中間件換成了 Nginx,并按域名拆分成若干獨立的應用。從保證服務延續性和穩定性的角度來說無可厚非,但也就此錯過了一次重構的契機。

一文看懂靜態資源服務沉浮及其在攜程的演進

 

實施遷移后的源服務架構

遷移中遇到的棘手問題之一,是 URL 的大小寫兼容問題。按照 RFC3986的描述,URL 的路徑部分應當是大小寫敏感的。然而,Windows文件系統在缺省狀態下是大小寫不敏感的,結果就是,不管 HTTP 請求中的路徑怎樣顛倒大小,只要文件系統中存在不區分大小寫的同名文件,IIS 一概照單全收。但是 linux 系統恰恰相反,不僅嚴格區分文名稱中的大小寫,而且允許大小寫不同的同名文件并存。延續過去的邏輯,且不說技術上的實現難度——請注意這是一個依賴本地文件系統的靜態站點——,還會埋下引發歧義的隱患;若拒絕將錯就錯,又勢必將導致大量 404 錯誤。

進退兩難間,我們不得不采取以空間換時間的辦法。在搬遷時,所有的文件一式兩份,一份保持原有的大小寫名稱,放在常規目錄下;另一份則采用全小寫的名稱,放在保底目錄下。通過 Nginx 的內部重定向,將路徑大小寫混淆的請求匹配至保底目錄下的文件,以確?;诩扔?URL 的請求仍可得到預期的響應內容。保底目錄中的文件不允許修改,也不作增刪,希望隨著靜態資源的持續迭代,倒逼那些不規范的 URL 退出使用。

上述策略即使在后來的重構中依然保留了下來,但它真的我們所能采取的最佳策略嗎?也不盡然。盡管事前作了充分的解釋工作,依然有些開發人員不明就里,疑竇叢生。“靜態資源服務支持 URL 大小寫混淆嗎?”這個問題至今仍呆在我們的 FAQ 列表中。

一文看懂靜態資源服務沉浮及其在攜程的演進

 

實現 URL 大小寫向后兼容的偽代碼

大小寫的混淆,并非是 URL 與文件系統映射關系問題的全部。因為某種過于久遠而不可考的原因,相當一部分文件路徑中包含了特定的間綴,而這個間綴在 URL 中是不存在的。為此,IIS 中曾維護了大量的虛擬目錄配置。因為要與發布系統兼容的關系,服務遷移后,文件系統中必須繼續保留這個間綴。我們沒有在 Nginx 配置中逐條堆砌 location 指令,而是通過正則匹配、文件探測和內部重定向等一系列指令的協作,解決了這一問題。

一文看懂靜態資源服務沉浮及其在攜程的演進

 

文件路徑與資源路徑不一定完全匹配

因為是靜態 Web 應用,源服務端沒有一行代碼可供調遣,Nginx 配置文件幾乎是所能轉圜的全部空間,我們也把這種以指令為核心的配置語言,當作一門領域腳本語言來學習和運用。隨著需求的累積,整個配置文件的有效行數達五百行之多。

這一階段,在攜程的技術生態中,靜態資源服務是一組非標準的 HTTP 應用。它們在 Web 中間件之外沒有一行服務端代碼,同時又捆綁了大量資源文件,體型臃腫,根本沒辦法通過常規的應用發布系統對其進行全方位的管理。為此,我們不得不基于 Ansible 建立了一個微型的服務器間協同系統,協調位于多個數據中心的數十臺服務器,以應付諸如 Nginx 配置升級、資源文件批量管理、服務集群擴容等等任務。

Ansible 是一個基于 SSH 協議的中心化運維工具,只要擁有SSH 訪問權限,就可以通過腳本(Playbook)安排遠端服務器完成任何算力能及的工作,而不需要在服務器上額外安裝任何客戶端或代理軟件。我個人非常喜愛這款輕量化的DevOps工具,只是相見恨晚。后來源站應用標準化 + 容器化了,DockerContainer 取代了 Virtual machine,而 DockerFile 取代了 Playbook,真真是長江后浪推前浪,不許英雄見白頭啊。

四、分久必合

系統遷移這件事,說破了天也只是清理技術債。沒有哪個正常人類能夠分辨水電和火電對于 HiFi 音質的影響,同理,后臺架構和運維技術的改變也無法直接提升用戶體驗。因此,在消化沉疴的同時,我們也力爭有所輸出。靜態資源在線合并功能(res-concat),就是與系統遷移同時期的產物。

分久必合,合久必分。還是要從“分”說起。

HTTP Archive 的抽樣報告顯示,今天(2020年4月)一個網頁平均消耗的流量高達2MB,這在使用電話線上網的年代,簡直無法想象:56kbit/s的“貓”(MODEM)就算拿出吃奶的勁兒,也得花上5分鐘才能把這么多內容扒拉下來,擱現在短視頻都已經刷完一打了。

一文看懂靜態資源服務沉浮及其在攜程的演進

 

圖片截取自 httparchive.org

帶寬如此寶貴,發布在萬維網上的每個字節都值得掂量。圖片是首當其沖的節流對象。在次世代的 Web 論壇中,時常能看到標題中帶有「大圖殺貓」警示字樣的帖子,表示帖子里含有字節可觀的圖片,愛惜流量者應審慎點擊。那種欲拒還迎的氣質,和時下公眾號里出沒的「長文慎入」如出一轍。

使用模擬信號的電話線路帶寬有限,丟包率更是高得可怕。訪問大碼資源不僅極度考驗用戶的耐心,中途失敗、前功盡棄的可能性也不容忽視。所以嚴肅的商業網站在使用大幅背景圖片時,會選擇漸進式 JPEG 格式,或者將圖片切分后裝進表格布局,有點像當今微博或票圈的圖片九宮格。甚至在2014年,我們還研究過如何在瀏覽器端通過精益控制漸進格式圖片的下載,來提升移動設備跨網絡條件(2G / 3G / WIFI)下的訪問體驗。畢竟,過獨木橋的時候,小步快走是一種理性的姿態。

控制資源單體尺寸的考慮,對于文本格式的 JS / CSS 同樣適用。將代碼按照功能拆分成若干文件,有利于資源下載成功,有利于資源重復使用,有利于防止 CPU 在等待資源下載期間閑得發慌……就沒什么壞處嗎?當然有。

內容總量不變,文件粒度越小,請求次數越多。每一次 HTTP 請求背后,都有附帶的流量成本和時間成本。僅僅是請求首部中一條用來表明瀏覽器身份的 User-Agent 字段,往往就有一百多字節,請求和響應首部加起來超過 1KB 是稀松平常的事。而且,在 HTTP/2 之前,HTTP 首部是不支持壓縮的。(User-Agent 字段為什么這么長?它們你中有我,我中有你,根本就是一個無間道的故事。)

一文看懂靜態資源服務沉浮及其在攜程的演進

 

一個 HTTP 請求的時間瀑布圖(圖片截取自 Chrome 開發者工具)

重新建立 TCP 連接也是頗費周折的,如果是HTTPS 請求就更麻煩一些。啟用長連接會有幫助嗎?會,所以 HTTP/1.1 已經默認這么做了。但是,長連接中的序列化請求會遭遇隊首阻塞(HOL blocking),即使是管線化(pipeline)請求也要遵循先入先出的原則,不能從根本上解決這個問題。

一個長連接不夠,多建幾個可不可以?作為 HTTP/1.1 規范文本的 RFC 2068 和 RFC 2616 均規定:單個客戶端與任意服務器或代理之間的并發連接不得超過2個。不過瀏覽器廠商們對此不以為然,爭相放寬限制,以免在“全世界最快瀏覽器”的競爭中落入下風。允許瀏覽器最多和同一服務端同時建立6個長連接,幾乎成了事實上的標準。因此后來連 W3C 也不再堅持原來的說法,在RFC7230 中只是含糊其詞地表示,“客戶端應當(ought to)限制與特定服務器之間的并發連接”。同樣是“應當”,ought to 和 SHOULD 可不止是大小寫的區別。然而并發連接數終歸沒有徹底失控,惟恐觸發 DDoS 攻擊防御機制是一方面,另一方面也說明長連接并非是萬靈藥。

總之,雖然有佚失的風險,可要是每筆匯款不論多少都固定收取一分錢的手續費,那么一毛一毛地打錢也太不劃算了。買東西要湊單,靜態資源要合并,都是自然而然的選擇。

一文看懂靜態資源服務沉浮及其在攜程的演進

 

res-concat 示例

2015年,gulp / grunt / webpack 等等前端構建工具已如雨后春筍般嶄露頭角,JavaScript Bundle 漸漸深入人心。不同于這些大刀闊斧的構建工具,我們的靜態資源在線合并功能是一場潤物細無聲的毛毛雨,強調的是“在線”二字,針對的是存量資源。只需要調整一下 URL 的寫法,就可以實現資源合并的目的,不需要投入學習成本,也不涉及任何代碼重構。

在線合并看起來就像 1+1=2 那么簡單,事實上它就是那么簡單。至于加法背后的 Nginx 模塊開發(專司 /res/concat 路由)、ProtocolBuffers 協議應用(解決源站應用和提供合并功能的服務集群之間的通訊問題)、原始資源混淆壓縮結果的緩存結構設計……等等,不過是細節。

不要過分關注那些細節,因為,HTTP/2 已經來了。相比于 HTTP/1.1,前者雖然在語義上一脈相承,但從網絡傳輸的角度看,則是一個全新的協議:基于流 / 消息 / 幀的多路復用設計,徹底解決了隊首阻塞問題,同一域名下的并發 TCP 連接已經沒有必要;而 HPACK 的引入,不僅借助靜態字典大幅壓縮了首部尺寸(比如 Access-Control-Allow-Origin這么長的字段名,直接用編號 20 替代了),而且利用先入先出的動態字典節省了多個請求 / 響應之間重復首部所耗費的字節(簡而言之,如果下一個請求中不加說明,那就默認其首部“同上”)。這就意味著,合并資源的現實基礎不復存在了!

站在2015年的春風里向前眺望,HTTP/2 的大規模落地,也就只剩三、五年的光景??p縫補補的在線合并功能,并不能滿足大家對于靜態資源解決方案的預期。在線生意日益繁榮,背后的開發迭代速度越來越快,業務條線的同事于是也越來越多頻繁地發出直擊靈魂的質問:明明已經發布了,瀏覽器端的靜態資源為什么不更新?

五、對啊,為什么不更新?!

遇到疑難雜癥,前端寫手們有一句口頭禪:清一下緩存!似乎,沒有什么問題是 F5 解決不了的,如果有,那就Ctrl+F5。F5 敦促瀏覽器詢問服務器:自從我上次訪問這個URL 以來,它的內容有沒有變過?請求首部中通常包含 If-None-Match 或If-Modified-Since 字段,對應上次響應中的 ETag 和 Last-Modified。倘若有變,返回新的內容;否則返回 304 Not Modified。如果是 Ctrl+F5,便不啰嗦,就像一個從未謀面的陌生人那樣生硬地說:拿來!于是就拿來了。

一文看懂靜態資源服務沉浮及其在攜程的演進

 

圖片引自 Ilya Grigorik, HTTP 緩存

但是,被 CDN 橫插一杠之后,屢試不爽的一指禪和二指禪,好像都不管用了。靜態資源內容一旦離開源站,何時過期(Expires: <http-date>),又或者能活多久(Cache-Control: max-age=<seconds>),已是命中注定。雖然按照 HTTP 協議,作為客戶端,是允許對著代理或者反向代理作(zuō)的。

• 它可以在請求中委婉地說:

你幫我去問問源站有沒有更新?

Cache-Control: no-cache

• 它可以矯情:

快過期的緩存,我可不要!

Cache-Control:min-fresh=<seconds>

• 它可以年齡歧視:

太老的緩存,我也不要!

Cache-Control:max-age=<seconds>

• 它甚至還可以不管不顧:

別攔我,我就是要去源站!

Cache-Control: no-store

沒錯,協議上是這么說來著。只是我不知道按哪一個鍵,可以讓瀏覽器說出這樣的話?說了,CDN 服務器未必肯聽。上一節中也提到過,協議不是數學定理,也不是自然法則,它只是一份契約。就算代理肯聽,源站未必答應。因為 CDN 同時還擔負著保護源站、抵御 DDoS攻擊的責任,不大可能聽任請求穿透代理。

于是,如何更新同名資源的內容,成了一件讓人頭痛的事。CDN 廠商急人所急,通常都會提供“清理緩存”的接口。但是,想象一下整個內容交付網絡中數以千計的節點和數以萬計的服務器,就知道這件事不像按一下 Ctrl+F5 這么輕松,可以偶爾為之,卻不適合作為常規操作。何況,邊緣節點的緩存可以清理,可是終端用戶瀏覽器上的緩存,依然無解。

所以,必須盡力避免覆蓋同名靜態資源。這是共識!

很久很久以前,在靜態資源文件和虛擬目錄之間,還有一層特殊的目錄,稱為“槽位”。每個虛擬目錄下,默認設置 R0 至 R9 共計十個槽位。每次發布時,目標槽位依次向前遞進,用盡之后再從頭開始,如此更新周而復始,以求資源萬古長青。

一文看懂靜態資源服務沉浮及其在攜程的演進

 

缺點是顯而易見的,最大的問題是不支持增量更新,哪怕只是修改了某個腳本中的一行代碼甚至一個字符,客戶端和 CDN 都需要重新下載整個目錄下的所有資源。但是評價方案的優劣,不能脫離時代背景。畢竟,當下國內首屈一指的 CDN 廠商網宿公司直到2005年才推出自己的 CDN 系統,而攜程在兩年前就已經上市了。有些古老的辦法,因為簡單,因為可靠,往往能夠在新興技術的沖擊下頑強生存,保有一席之地。比如紙筆,比如槽位。

不過,全量更新實在太不經濟了。去 Windows 完成之后,我們就嘗試優化。Ilya Grigorik在《HTTP 緩存》中這樣寫道:“如何才能魚和熊掌兼得:客戶端緩存和快速更新?您可以在資源內容發生變化時更改其網址,強制用戶下載新響應。通常情況下,可以通過在文件名中嵌入文件的指紋或版本號來實現—例如style.x234dff.css。”

就這樣?就這樣。Ilya 的這篇文章最后更新于2019年初,可見這些年來,對于如何解決緩存沖突,依然沒有什么更好的辦法。Webpack 是這么干的,我們也是。

有時候,一行代碼所包含的邏輯,千言萬語也訴說不盡。有時候,一句話能說明白的方案,實施起來卻千頭萬緒。下面這張圖,粗略描述了我們的靜態資源增量更新流程。

一文看懂靜態資源服務沉浮及其在攜程的演進

 

攜程“靜態資源模塊”方案構成(第一版)

在文件名稱中嵌入“指紋”,對于圖片、文檔這類資源輕而易舉,只需用 MD5 或類似的數據摘要算法計算出內容摘要即可。但是對于 JS、CSS 這類可能涉及資源嵌套的前端代碼,就麻煩了。

比如,對于 CSS 文件,需要?解析代碼,找到其中涉及的圖片、字體文件等關聯資源的文件名稱,?獲取關聯資源嵌入指紋后的文件名稱,?替換 CSS 文件內容,然后計算內容摘要。?假如 CSS 文件中還依賴(include)其他的 CSS 文件,那么還要注意先后順序。對于JS 文件,情況就更復雜一些,不僅要?事前用特定的宏方法界定關聯資源名稱(因為我們無法通過語法分析來判斷哪些字面量代表資源名稱),如果提交的是已經混淆過的代碼,還需要?考慮 SourceMap 文件的重構問題。其中,第5點尤其令前端開發人員不滿。魚和熊掌兼得?不付出點代價怎么行呢。

在實施增量發布的過程中,“靜態資源模塊”的概念代替了原來的虛擬目錄。同時,引入語義化版本(Semantic Version)來控制模塊的更迭,希望借此推動跨團隊的靜態資源共享。但是這件事并不順利,直到2019年借國際化的東風,推出第二版的攜程靜態資源模塊化方案之后,情況才有所好轉。

六、上云,上云!

程序員們勤耕不輟,靜態資源的數量每天都在增長,服務器磁盤的剩余空間漸漸歸零。雖然是虛擬服務器,磁盤空間的擴容依然不易,而且受宿主機的約束,可以騰挪的余地有限。分拆應用雖然能解燃眉之急,實現難度也不大,卻勢必加劇日常運維的難度,也非一勞永逸之策。幸好此時,有了云存儲!

這一次我們放棄了靜態應用的執念,大膽采用了 Nginx + Node.js + CEPH 的應用架構。作為一種分布式對象存儲系統,CEPH 的讀寫性能、可用性和可擴展性,都合乎靜態資源內容存儲的需求。而選擇 Node.js,是因為它更靠近前端技術棧,并且攜程的技術團隊在利用 Node.js 開發 Web 應用方面也已經積累了不少經驗。為了將兩者相結合,我們事前作了認真的準備,開發了以下 NPM 模塊:

(1) ceph

云存儲訪問 SDK?;?CEPH Web API,提供對象讀寫和查詢功能,目前已兼容 AWS S3 和 Aliyun OSS。

(2) ceph-sync

云存儲同步 SDK 及命令行工具。可在本地文件系統和云存儲之間、以及不同廠商的云存儲之間實現容器級別的數據同步,支持斷點續傳。

(3) ceph-cli

云存儲管理工具。提供在命令行中完成容器創建和清理、對象查詢和刪除、文件(對象)上傳和下載等功能。

(4) ceph-agent

云存儲瀏覽器。該模塊可以啟動一個Web 服務,方便開發者在瀏覽器中瀏覽存儲中的內容。

上述模塊目前都已開源。

所謂欲善其事,先利其器。這些工具在我們的工作發揮了重要的作用。利用 ceph-sync,我們順利完成了千萬量級靜態資源文件的上云,差錯率接近于零。在攜程,ceph 模塊日均完成數以萬次計的對象寫入和數以億次計的對象讀取操作,沒有成為瓶頸,其可靠性也經受住了考驗。

使用攜程私有云存儲的過程中所積累的經驗,為過渡到公有云存儲鋪平了道路?,F在,我們已經實現了 Ceph(攜程私有云)+ AliyunOSS(境內)+ AWS S3(境外)三位一體的靜態資源同步存儲方案。

一文看懂靜態資源服務沉浮及其在攜程的演進

 

基于存儲的源服務架構

經過這次重構,靜態資源服務也完成了另一種意義上的動靜分離,即數據(資源文件)和應用的分離,從而實現了應用的標準化,可以借助攜程的 PaaS 系統輕松完成應用部署。換句話說,源站應用也真正地上云了。有了基于 Node.js 開發的源站應用,我們足以應對有關靜態資源服務的種種個性化需求,再也不需要在中間件配置這個逼仄的空間中苦苦掙扎。原先分拆的若干應用,也重新合兵一處,不僅節約了可觀的硬件資源,也終于消化了長期以來的運維壓力。大家都松了口氣。

七、國際化背景下的布局

隨著攜程業務日趨國際化,境外用戶的訪問體驗越來越受到重視。在新的背景下,用戶的地域分布越來越廣泛,而集中程度疏密不一,如何面向全球用戶提供更好的靜態資源服務,需要我們作出一些改變。

盡管 CDN 可以顯著提升終端用戶獲取靜態資源的速度,但是它不可能卸載所有的請求,仍然有一部分請求需要回源。同時,在新場景、新業務的需求驅動下,應用程序的迭代更快,相關的靜態資源也在不斷推陳出新,從而觸發更多的回源請求。針對回源請求的優化,是一個長尾問題。美洲、歐洲、非洲國家與我國地理上的遙遠距離,無論怎么優化路由,網絡響應都存在100~200毫秒的延時。慮及于此,我們決定將源站建在離CDN 邊緣節點更近——也就是離我們的終端用戶更近——的地方。

2019年中,依托于 AWS 公有云平臺,攜程的前端基礎設施團隊已在法蘭克福、蒙特利爾、新加坡城部署靜態資源源站,分別連接歐洲和非洲、美洲、東南亞地區的 CDN 邊緣節點,形成了一個微型的內容傳輸矩陣。上一節中提到過三位一體的靜態資源同步存儲方案,其中位于 AWS S3 的部分包括三個相應區域(Region)的存儲容器,就是作為上述源站應用的配套數據源而存在的。這些源站雖然不直接面向終端用戶,但確實改善了一部分人的訪問體驗,也減緩了發布期間的網頁性能波動。

一文看懂靜態資源服務沉浮及其在攜程的演進

 

源站矩陣,背景世界地圖引自 AWS 官方網站

國際化帶來的另一個改變,是互聯網企業需要尋求與不止一家 CDN 廠商合作,通過多個廠商在不同國家和地區的優勢互補,達到更好的全球覆蓋的效果。那么,如何達成區域與 CDN 的匹配呢?

我們知道,在整個 DNS 體系中,域名所有者指定的 DNS 是掌握域名最終解釋權的一方,被稱為權威服務器;而網絡中其他的 DNS 則會緩存權威服務器的解釋,它們所給出的答案,被稱為非權威應答。在“不可或缺的CDN”一節中提到過,DNS 是幫助用戶實現就近訪問的關鍵,其奧義就在于資源站點域名的權威 DNS 會根據本地 DNS 的位置,就近分配 IP 地址。“本地”,是此法行之有效的前提,也就是終端用戶與其所使用的 DNS 須同處一隅。

也許是疏于防范,也許是有意為之,域名劫持現象屢見不鮮,侵蝕了作為互聯網基石的 DNS 體系。主打安全的公共 DNS 越來越受歡迎,許多用戶開始放棄ISP 提供的本地 DNS,轉而使用公共 DNS。有名的如 IBM 的9.9.9.9,google 的8.8.8.8,以及中國電信的 114.114.114.114,這些 IP 地址遠比大多數域名更加令人過目難忘,用作公共 DNS 地址,再合適不過了。采用任播(AnyCast)技術的公共 DNS 面向全球用戶提供域名解析服務,其自身不具有地域特征,這就使得上游的權威 DNS——即 CDN 廠商的 DNS——難以判斷域名解析請求的真實來源,也就無法為其分配就近的邊緣節點地址。盡管隨著 EDNS0 協議的落地,有望緩解這一矛盾,但是要求公共DNS 為每一個客戶端單獨緩存解析結果,從而百分之百地忠實于權威 DNS,這在技術上是不太現實的。因此,當GSLB 遇到公共 DNS 時,DNS 污染的問題不可避免。

公共 DNS 所引發的邊緣節點錯配,對于 CDN 是一個嚴肅的問題。對于網站來說,如果堅持使用單一域名提供靜態資源服務,依靠 DNS 來選擇 CDN,則可能導致更為嚴重的供應商錯配的故障。因此,我們需要以“動”制“靜”,在服務端動態頁面程序中引入特定的 API,具體分析每一個用戶請求,?根據其來源 IP 地址判斷終端用戶所在區域或網絡情況,?根據預先確定的策略分配靜態資源域名,生成最終的靜態資源 URL 返回給瀏覽器,?再由相應的 CDN 提供資源訪問服務。該接口的時間開銷被限制在1毫秒以內(實際低于這個值),這個代價是可以承受的。

采用多域名策略,不僅可以規避 DNS 污染的風險,還有利于減少客戶端在域名解析(DNSLookup)環節的時間開銷。DNS 是一個層層遞進的系統,遇到一個全新的域名時,客戶端從發起解析請求到獲得 IP 地址,中間消耗的時間可能長達數百毫秒甚至更久,關鍵的因素在于權威服務器的距離遠近。單一域名在全球不同區域的解析時間,勢必有長有短。而使用區域相關域名,比如國別域名,可以顯著降低解析時間,這算是一個附帶的好處。

一文看懂靜態資源服務沉浮及其在攜程的演進

 

站在開發人員的角度,依賴服務端 API 動態生成 URL,當然不夠直觀。然而,服務端的一剎那,換來的不止于更好的靜態資源服務性能,也讓我們具備了靜態資源灰度發布能力,以及 CDN 級別的容災能力,可謂一舉多得。應國際化而生的“動靜結合”的理念,已孵化出一個集發布、部署、運維于一體的全新的靜態資源解決方案。

從動靜分離到動靜結合,始于動態,歸于動態,真是天道好輪回。

八、未來向何處去?

愛因斯坦說過:時間是種幻覺。然而,這種幻覺實實在在地驅動著我們的工作?;ヂ摼W讓信息的傳播速度出現了質的飛躍,卻刺激著受眾的靈魂,變得更加迫不及待。4K 解析度和 120FPS 幀率超高清電視的出現,似乎在宣揚人類對于感官極限的追求永無止境。

HTTP Archive 的報告顯示,當前 PC 端 Web 頁面的 onLoad 時間平均超過6秒,而移動端更是接近20秒。作為全球領先的 OTA 公司,攜程的數據要好看得多,這是自然的,也是應該的,但是秒級和百毫秒級的頁面加載時間,距離已知的極限還很遠。HTTP/2 和 HTTP/3 不斷挖掘現有傳輸網絡的潛力,而我們的靜態資源解決方案也會隨之修正??雌饋恚坪踹€可以沿著當前的道路走很久。

不過,比 HTTP 協議升級更值得關注的,是以超高帶寬和超低延遲為核心的 5G 網絡大規模商用已經箭在弦上,而骨干網絡必然也會升級換代。誠然,5G 不是為任何一種現有的成熟的互聯網業務模式準備的,它有著更重要的使命。對于傳統的 Web 而言,5G 網絡所代表的未來趨勢是帶寬近乎無限,而光速可能成為影響延遲的主要因素。那么,事情會變得怎樣?囿于地球尺度的限制,CDN 的邊緣節點依然有用,但是動靜分離的模式是否還有存在的必要,靜態資源的概念會不會就此湮沒在歷史的塵埃中,將是值得思考的問題。

九、尾聲:關于 ARES

ARES是靜態資源服務在攜程內部的代號。

有一段時間,部門內部鼓勵大家參照希臘神話中諸神的名字,統一項目命名,以求流傳有序。我們對 ARES 一見鐘情,因為看起來很像 AResource。剛開始,我興致勃勃地來了一個全稱演繹:an A-class WebREsource Solution,大張旗鼓地寫在文檔的首頁上。沒多久,就悄無聲息地地改成了遞歸演繹:ARES is a WebResource Elevating Solution。

什么一流?不存在的。能進一步,也就夠了!

參考資料

[1] John Dilley. GloballyDistributed Content Delivery[J]. IEEE InternetComputing.September/October 2002

[2] CNNIC. 第十次中國互聯網絡發展狀況統計報告. 2002年7月

【作者簡介】

蔣小魚和蔣小貓,攜程軟件技術專家,主要從事前端基礎設施方面的工作。

分享到:
標簽:靜態 資源 服務
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定