自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

前端遇上Go: 靜態(tài)資源增量更新的新實(shí)踐

開發(fā) 開發(fā)工具
美團(tuán)金融的業(yè)務(wù)在過去的一段時(shí)間里發(fā)展非??焖?。在業(yè)務(wù)增長(zhǎng)的同時(shí),我們也注意到,很多用戶的支付環(huán)境,其實(shí)是在弱網(wǎng)環(huán)境中的。

 為什么要做增量更新

美團(tuán)金融的業(yè)務(wù)在過去的一段時(shí)間里發(fā)展非??焖佟T跇I(yè)務(wù)增長(zhǎng)的同時(shí),我們也注意到,很多用戶的支付環(huán)境,其實(shí)是在弱網(wǎng)環(huán)境中的。

大家知道,前端能夠服務(wù)用戶的前提是 JavaScript 和 CSS 等靜態(tài)資源能夠正確加載。如果網(wǎng)絡(luò)環(huán)境惡劣,那么我們的靜態(tài)資源尺寸越大,用戶下載失敗的概率就越高。

根據(jù)我們的數(shù)據(jù)統(tǒng)計(jì),我們的業(yè)務(wù)中有2%的用戶流失與資源加載有關(guān)。因此每次更新的代價(jià)越小、加載成功率越高,用戶流失率也就會(huì)越低,從而就能夠變相提高訂單的轉(zhuǎn)化率。

作為一個(gè)發(fā)版頻繁的業(yè)務(wù),要降低發(fā)版的影響,可以做兩方面優(yōu)化:

更高效地使用緩存,減少靜態(tài)資源的重復(fù)下載。

使用增量更新,降低單次發(fā)版時(shí)下發(fā)的內(nèi)容尺寸。

針對(duì)***點(diǎn),我們有自己的模塊加載器來做,這里先按下不表,我們來重點(diǎn)聊聊增量更新的問題。

增量更新是怎么一個(gè)過程

看圖說話。

增量更新的客戶端流程圖

我們的增量更新通過在瀏覽器端部署一個(gè) SDK 來發(fā)起,這個(gè) SDK 我們稱之為 Thunder.js 。

Thunder.js 在頁(yè)面加載時(shí),會(huì)從頁(yè)面中讀取***靜態(tài)資源的版本號(hào)。同時(shí), Thunder.js 也會(huì)從瀏覽器的緩存(通常是 localStorage)中讀取我們已經(jīng)緩存的版本號(hào)。這兩個(gè)版本號(hào)進(jìn)行匹配,如果發(fā)現(xiàn)一致,那么我們可以直接使用緩存當(dāng)中的版本;反之,我們會(huì)向增量更新服務(wù)發(fā)起一個(gè)增量補(bǔ)丁的請(qǐng)求。

增量服務(wù)收到請(qǐng)求后,會(huì)調(diào)取新舊兩個(gè)版本的文件進(jìn)行對(duì)比,將差異作為補(bǔ)丁返回。Thunder.js 拿到請(qǐng)求后,即可將補(bǔ)丁打在老文件上,這樣就得到了新文件。

總之一句話:老文件 + 補(bǔ)丁 = 新文件。

增量補(bǔ)丁的生成,主要依賴于 Myers 的 diff 算法。生成增量補(bǔ)丁的過程,就是尋找兩個(gè)字符串最短編輯路徑的過程。算法本身比較復(fù)雜,大家可以在網(wǎng)上找一些比較詳細(xì)的算法描述,比如這篇 《The Myers diff algorithm》,這里就不詳細(xì)介紹了。

補(bǔ)丁本身是一個(gè)微型的 DSL(Domain Specific Language)。這個(gè) DSL 一共有三種微指令,分別對(duì)應(yīng)保留、插入、刪除三種字符串操作,每種指令都有自己的操作數(shù)。

例如,我們要生成從字符串“abcdefg”到“acdz”的增量補(bǔ)丁,那么一個(gè)補(bǔ)丁的全文就類似如下:

=1\t-1\t=2\t-3\t+z

這個(gè)補(bǔ)丁當(dāng)中,制表符\t是指令的分隔符,=表示保留,-表示刪除,+表示插入。整個(gè)補(bǔ)丁解析出來就是:

  1. 保留1個(gè)字符
  2. 刪除1個(gè)字符
  3. 保留2個(gè)字符
  4. 刪除3個(gè)字符
  5. 插入1個(gè)字符:z

具體的 JavaScript 代碼就不在這里粘貼了,流程比較簡(jiǎn)單,相信大家都可以自己寫出來,只需要注意轉(zhuǎn)義和字符串下標(biāo)的維護(hù)即可。

增量更新其實(shí)不是前端的新鮮技術(shù),在客戶端領(lǐng)域,增量更新早已經(jīng)應(yīng)用多年??催^我們《美團(tuán)金融掃碼付靜態(tài)資源加載優(yōu)化實(shí)踐》的朋友,應(yīng)該知道我們其實(shí)之前已有實(shí)踐,在當(dāng)時(shí)僅僅靠增量更新,日均節(jié)省流量達(dá)30多GB。而現(xiàn)在這個(gè)數(shù)字已經(jīng)隨著業(yè)務(wù)量變得更高了。

那么我們是不是就已經(jīng)做到萬事無憂了呢?

我們之前的增量更新實(shí)踐遇到了什么問題

我們最主要的問題是增量計(jì)算的速度不夠快。

之前的優(yōu)化實(shí)踐中,我們絕大部分的優(yōu)化其實(shí)都是為了優(yōu)化增量計(jì)算的速度。文本增量計(jì)算的速度確實(shí)慢,慢到什么程度呢?以前端比較常見的JS資源尺寸——200KB——來進(jìn)行增量計(jì)算,進(jìn)行一次增量計(jì)算的時(shí)間依據(jù)文本不同的數(shù)量,從數(shù)十毫秒到十幾秒甚至幾十秒都有可能。

對(duì)于小流量業(yè)務(wù)來說,計(jì)算一次增量補(bǔ)丁然后緩存起來,即使***次計(jì)算耗時(shí)一些也不會(huì)有太大影響。但用戶側(cè)的業(yè)務(wù)流量都較大,每月的增量計(jì)算次數(shù)超過 10 萬次,并發(fā)計(jì)算峰值超過 100 QPS 。

那么不夠快的影響是什么呢?

我們之前的設(shè)計(jì)大致思想是用一個(gè)服務(wù)來承接流量,再用另一個(gè)服務(wù)來進(jìn)行增量計(jì)算。這兩個(gè)服務(wù)均由 Node.js 來實(shí)現(xiàn)。對(duì)于前者, Node.js 的事件循環(huán)模型本就適合進(jìn)行 I/O 密集型業(yè)務(wù);然而對(duì)于后者,則實(shí)際為 Node.js 的軟肋。 Node.js 的事件循環(huán)模型,要求 Node.js 的使用必須時(shí)刻保證 Node.js 的循環(huán)能夠運(yùn)轉(zhuǎn),如果出現(xiàn)非常耗時(shí)的函數(shù),那么事件循環(huán)就會(huì)陷入進(jìn)去,無法及時(shí)處理其他的任務(wù)。常見的手法是在機(jī)器上多開幾個(gè) Node.js 進(jìn)程。然而一臺(tái)普通的服務(wù)器也就8個(gè)邏輯CPU而已,對(duì)于增量計(jì)算來說,當(dāng)我們遇到大計(jì)算量的任務(wù)時(shí),8個(gè)并發(fā)可能就會(huì)讓 Node.js 服務(wù)很難繼續(xù)響應(yīng)了。如果進(jìn)一步增加進(jìn)程數(shù)量,則會(huì)帶來額外的進(jìn)程切換成本,這并不是我們的***選擇。

更高性能的可能方案

“讓 JavaScript 跑的更快”這個(gè)問題,很多前輩已經(jīng)有所研究。在我們思考這個(gè)問題時(shí),考慮過三種方案。

Node.js Addon

Node.js Addon 是 Node.js 官方的插件方案,這個(gè)方案允許開發(fā)者使用 C/C++ 編寫代碼,而后再由 Node.js 來加載調(diào)用。由于原生代碼的性能本身就比較不錯(cuò),這是一種非常直接的優(yōu)化方案。

ASM.js / WebAssembly

后兩種方案是瀏覽器側(cè)的方案。

其中 ASM.js 由 Mozilla 提出,使用的是 JavaScript 的一個(gè)易于優(yōu)化的子集。這個(gè)方案目前已經(jīng)被廢棄了。

取而代之的 WebAssembly ,由 W3C 來領(lǐng)導(dǎo),采用的是更加緊湊、接近匯編的字節(jié)碼來提速。目前在市面上剛剛嶄露頭角,相關(guān)的工具鏈還在完善中。 Mozilla 自己已經(jīng)有一些嘗試案例了,例如將 Rust 代碼編譯到 WebAssembly 來提速 sourcemap 的解析。

然而在考慮了這三種方案之后,我們并沒有得到一個(gè)很好的結(jié)論。這三個(gè)方案的都可以提升 JavaScript 的運(yùn)行性能,但是無論采取哪一種,都無法將單個(gè)補(bǔ)丁的計(jì)算耗時(shí)從數(shù)十秒降到毫秒級(jí)。況且,這三種方案如果不加以復(fù)雜的改造,依然會(huì)運(yùn)行在 JavaScript 的主線程之中,這對(duì) Node.js 來說,依然會(huì)發(fā)生嚴(yán)重的阻塞。

于是我們開始考慮 Node.js 之外的方案。換語(yǔ)言這一想法應(yīng)運(yùn)而生。

換語(yǔ)言

更換編程語(yǔ)言,是一個(gè)很慎重的事情,要考慮的點(diǎn)很多。在增量計(jì)算這件事上,我們主要考慮新語(yǔ)言以下方面:

  • 運(yùn)行速度
  • 并發(fā)處理
  • 類型系統(tǒng)
  • 依賴管理
  • 社區(qū)

當(dāng)然,除了這些點(diǎn)之外,我們還考慮了調(diào)優(yōu)、部署的難易程度,以及語(yǔ)言本身是否能夠快速駕馭等因素。

最終,我們決定使用 Go 語(yǔ)言進(jìn)行增量計(jì)算服務(wù)的新實(shí)踐。

選擇Go帶來了什么

高性能

增量補(bǔ)丁的生成算法,在 Node.js 的實(shí)現(xiàn)中,對(duì)應(yīng) diff 包;而在 Go 的實(shí)現(xiàn)中,對(duì)應(yīng) go-diff 包。

在動(dòng)手之前,我們首先用實(shí)際的兩組文件,對(duì) Go 和 Node.js 的增量模塊進(jìn)行了性能評(píng)測(cè),以確定我們的方向是對(duì)的。

相同算法、相同文件的計(jì)算時(shí)間對(duì)比

結(jié)果顯示,盡管針對(duì)不同的文件會(huì)出現(xiàn)不同的情況,Go 的高性能依然在計(jì)算性能上碾壓了 Node.js 。這里需要注意,文件長(zhǎng)度并不是影響計(jì)算耗時(shí)的唯一因素,另一個(gè)很重要的因素是文件差異的大小。

不一樣的并發(fā)模型

Go 語(yǔ)言是 Google 推出的一門系統(tǒng)編程語(yǔ)言。它語(yǔ)法簡(jiǎn)單,易于調(diào)試,性能優(yōu)異,有良好的社區(qū)生態(tài)環(huán)境。和 Node.js 進(jìn)行并發(fā)的方式不同, Go 語(yǔ)言使用的是輕量級(jí)線程,或者叫協(xié)程,來進(jìn)行并發(fā)的。

專注于瀏覽器端的前端同學(xué),可能對(duì)這種并發(fā)模型不太了解。這里我根據(jù)我自己的理解來簡(jiǎn)要介紹一下它和 Node.js 事件驅(qū)動(dòng)并發(fā)的區(qū)別。

如上文所說, Node.js 的主線程如果陷入在某個(gè)大計(jì)算量的函數(shù)中,那么整個(gè)事件循環(huán)就會(huì)阻塞。協(xié)程則與此不同,每個(gè)協(xié)程中都有計(jì)算任務(wù),這些計(jì)算任務(wù)隨著協(xié)程的調(diào)度而調(diào)度。一般來說,調(diào)度系統(tǒng)不會(huì)把所有的 CPU 資源都給同一個(gè)協(xié)程,而是會(huì)協(xié)調(diào)各個(gè)協(xié)程的資源占用,盡可能平分 CPU 資源。

Go 的 goroutine

相比 Node.js ,這種方式更加適合計(jì)算密集與 I/O 密集兼有的服務(wù)。

當(dāng)然這種方式也有它的缺點(diǎn),那就是由于每個(gè)協(xié)程隨時(shí)會(huì)被暫停,因此協(xié)程之間會(huì)和傳統(tǒng)的線程一樣,有發(fā)生競(jìng)態(tài)的風(fēng)險(xiǎn)。所幸我們的業(yè)務(wù)并沒有多少需要共享數(shù)據(jù)的場(chǎng)景,競(jìng)態(tài)的情況非常少。

實(shí)際上 Web 服務(wù)類型的應(yīng)用,通常以請(qǐng)求 -> 返回為模型運(yùn)行,每個(gè)請(qǐng)求很少會(huì)和其他請(qǐng)求發(fā)生聯(lián)系,因此使用鎖的場(chǎng)景很少。一些“計(jì)數(shù)器”類的需求,靠原子變量也可以很容易地完成。

不一樣的模塊機(jī)制

Go 語(yǔ)言的模塊依賴管理并不像 Node.js 那么成熟。盡管吐槽 node_modules 的人很多,但卻不得不承認(rèn),Node.js 的 CMD 機(jī)制對(duì)于我們來說不僅易于學(xué)習(xí),同時(shí)每個(gè)模塊的職責(zé)和邊界也是非常清晰的。

具體來說,一個(gè) Node.js 模塊,它只需關(guān)心它自己依賴的模塊是什么、在哪里,而不關(guān)心自己是如何被別人依賴的。這一點(diǎn),可以從 require 調(diào)用看出:

  1. const util = require('./util'); 
  2. const http = require('http'); 
  3. module.exports = {}; 

這是一個(gè)非常簡(jiǎn)單的模塊,它依賴兩個(gè)其他模塊,其中 util 來自我們本地的目錄,而 http 則來自于 Node.js 內(nèi)置。在這種情形下,只要你有良好的模塊依賴關(guān)系,一個(gè)自己寫好的模塊想要給別人復(fù)用,只需要把整個(gè)目錄獨(dú)立上傳到 npm 上即可。

簡(jiǎn)單來說, Node.js 的模塊體系是一棵樹,最終本地模塊就是這樣:

  1. |- src 
  2.     |- module-a 
  3.         |- submodule-aa 
  4.         |- submodule-ab 
  5.     |- module-b 
  6.     |- module-c 
  7.         |- submodule-ca 
  8.             |- subsubmodule-caa 
  9. |- bin 
  10. |- docs 

但 Go 語(yǔ)言就不同了。在 Go 語(yǔ)言中,每個(gè)模塊不僅有一個(gè)短的模塊名,同時(shí)還有一個(gè)項(xiàng)目中的“唯一路徑”。如果你需要引用一個(gè)模塊,那么你需要使用這個(gè)“唯一路徑”來進(jìn)行引用。比如:

  1. package main 
  2.  
  3. import ( 
  4.     "fmt" 
  5.     "github.com/valyala/fasthttp" 
  6.     "path/to/another/local/module" 

***個(gè)依賴的 fmt 是 Go 自帶的模塊,簡(jiǎn)單明了。第二個(gè)模塊是一個(gè)位于 Github 的開源第三方模塊,看路徑形式就能夠大致推斷出來它是第三方的。而第三個(gè),則是我們項(xiàng)目中一個(gè)可復(fù)用模塊,這就有點(diǎn)不太合適了。其實(shí)如果 Go 支持嵌套的模塊關(guān)系的話,相當(dāng)于每個(gè)依賴從根目錄算起就可以了,能夠避免出現(xiàn) ../../../../root/something 這種尷尬的向上查找。但是, Go 是不支持本地依賴之間的文件夾嵌套的。這樣一來,所有的本地模塊,都會(huì)平鋪在同一個(gè)目錄里,最終會(huì)變成這樣:

  1. |- src 
  2.     |- module-a 
  3.     |- submodule-aa 
  4.     |- submodule-ab 
  5.     |- module-b 
  6.     |- module-c 
  7.     |- submodule-ca 
  8.     |- subsubmodule-caa 
  9. |- bin 
  10. |- docs 

現(xiàn)在你不太可能直接把某個(gè)模塊按目錄拆出去了,因?yàn)樗鼈冎g的關(guān)系完全無法靠目錄來斷定了。

較新版本的 Go 推薦將第三方模塊放在 vendor 目錄下,和 src 是平級(jí)關(guān)系。而之前,這些第三方依賴也是放在 src 下面,非常令人困惑。

目前我們項(xiàng)目的代碼規(guī)模還不算很大,可以通過命名來進(jìn)行區(qū)分,但當(dāng)項(xiàng)目繼續(xù)增長(zhǎng)下去,就需要更好的方案了。

過于簡(jiǎn)單的去中心化第三方包管理

和有 npm 的 Node.js 另一個(gè)不一樣是: Go 語(yǔ)言沒有自己的包管理平臺(tái)。對(duì)于 Go 的工具鏈來說,它并不關(guān)心你的第三方包到底是誰來托管的。 社區(qū)里 Go 的第三方包遍布各個(gè) Git 托管平臺(tái),這不僅讓我們?cè)谒阉靼鼤r(shí)花費(fèi)更多時(shí)間,更麻煩的是,我們無法通過在企業(yè)內(nèi)部搭建一個(gè)類似 npm 鏡像的平臺(tái),來降低大家每次下載第三方包的耗時(shí),同時(shí)也難以在不依賴外網(wǎng)的情況下,進(jìn)行包的自由安裝。

Go 有一個(gè)命令行工具,專門負(fù)責(zé)下載第三方包,叫做“ go-get ”。和大家想的不一樣,這個(gè)工具沒有版本描述文件。在 Go 的世界里并沒有 package.json 這種文件。這給我們帶來的直接影響就是我們的依賴不僅在外網(wǎng)放著,同時(shí)還無法有效地約束版本。同一個(gè)go-get命令,這個(gè)月下載的版本,可能到下個(gè)月就已經(jīng)悄悄地變了。

目前 Go 社區(qū)有很多種不同的第三方工具來做,我們最終選擇了 glide 。這是我們能找到的最接近 npm 的工具了。目前官方也在孕育一個(gè)新的方案來進(jìn)行統(tǒng)一,我們拭目以待吧。

Go 社區(qū)的各種第三方包管理工具

對(duì)于鏡像,目前也沒有太好的方案,我們參考了 moby (就是 docker )的做法,將第三方包直接存入我們自己項(xiàng)目的 Git 。這樣雖然項(xiàng)目的源代碼尺寸變得更大了,但無論是新人參與項(xiàng)目,還是上線發(fā)版,都不需要去外網(wǎng)拉取依賴了。

匱乏的內(nèi)部基礎(chǔ)設(shè)施支持

Go 語(yǔ)言在美團(tuán)內(nèi)部的應(yīng)用較少,直接結(jié)果就是,美團(tuán)內(nèi)部相當(dāng)一部分基礎(chǔ)設(shè)施,是缺少 Go 語(yǔ)言 SDK 支持的。例如公司自建的 Redis Cluster ,由于根據(jù)公司業(yè)務(wù)需求進(jìn)行了一些改動(dòng),導(dǎo)致開源的 Redis Cluster SDK ,是無法直接使用的。再例如公司使用了淘寶開源出 KV 數(shù)據(jù)庫(kù)—— Tair ,大概由于開源較早,也是沒有 Go 的 SDK 的。

由于我們的架構(gòu)設(shè)計(jì)中,需要依賴 KV 數(shù)據(jù)庫(kù)進(jìn)行存儲(chǔ),最終我們還是選擇用 Go 語(yǔ)言實(shí)現(xiàn)了 Tair 的 SDK。所謂“工欲善其事,必先利其器”,在 SDK 的編寫過程中,我們逐漸熟悉了 Go 的一些編程范式,這對(duì)之后我們系統(tǒng)的實(shí)現(xiàn),起到了非常有益的作用。所以有時(shí)候手頭可用的設(shè)施少,并不一定是壞事,但也不能盲目去制造輪子,而是要思考自己造輪子的意義是什么,以結(jié)果來評(píng)判。

語(yǔ)言之外

要經(jīng)受生產(chǎn)環(huán)境的考驗(yàn),只靠更換語(yǔ)言是不夠的。對(duì)于我們來說,語(yǔ)言其實(shí)只是一個(gè)工具,它幫我們解決的是一個(gè)局部問題,而增量更新服務(wù)有很多語(yǔ)言之外的考量。

如何面對(duì)海量突發(fā)流量

因?yàn)橛星败囍b,我們很清楚自己面對(duì)的流量是什么級(jí)別的。因此這一次從系統(tǒng)的架構(gòu)設(shè)計(jì)上,就優(yōu)先考慮了如何面對(duì)突發(fā)的海量流量。

首先我們來聊聊為什么我們會(huì)有突發(fā)流量。

對(duì)于前端來說,網(wǎng)頁(yè)每次更新發(fā)版,其實(shí)就是發(fā)布了新的靜態(tài)資源,和與之對(duì)應(yīng)的 HTML 文件。而對(duì)于增量更新服務(wù)來說,新的靜態(tài)資源也就意味著需要進(jìn)行新的計(jì)算。

有經(jīng)驗(yàn)的前端同學(xué)可能會(huì)說,雖然新版上線會(huì)創(chuàng)造新的計(jì)算,但只要前面放一層 CDN ,緩存住計(jì)算結(jié)果,就可以輕松緩解壓力了不是嗎?

這是有一定道理的,但并不是這么簡(jiǎn)單。面向普通消費(fèi)者的 C 端產(chǎn)品,有一個(gè)特點(diǎn),那就是用戶的訪問頻度千差萬別。具體到增量更新上來說,就是會(huì)出現(xiàn)大量不同的增量請(qǐng)求。因此我們做了更多的設(shè)計(jì),來緩解這種情況。

增量服務(wù)架構(gòu)設(shè)計(jì)

這是我們對(duì)增量更新系統(tǒng)的設(shè)計(jì)。

放在首位的自然是 CDN 。面對(duì)海量請(qǐng)求,除了幫助我們削峰之外,也可以幫助不同地域的用戶更快地獲取資源。

增量服務(wù) API 層

在 CDN 之后,我們將增量更新系統(tǒng)劃分成了兩個(gè)獨(dú)立的層,稱作 API 層和計(jì)算層。為什么要?jiǎng)澐珠_呢?在過往的實(shí)踐當(dāng)中,我們發(fā)現(xiàn)即使我們?cè)傩⌒脑僦?jǐn)慎,仍然還是會(huì)有犯錯(cuò)誤的時(shí)候,這就需要我們?cè)诓渴鸷蜕暇€上足夠靈活;另一方面,對(duì)于海量的計(jì)算任務(wù),如果實(shí)在扛不住,我們需要保有最基本的響應(yīng)能力?;谶@樣的考慮,我們把 CDN 的回源服務(wù)獨(dú)立成一個(gè)服務(wù)。這層服務(wù)有三個(gè)作用:

  1. 通過對(duì)存儲(chǔ)系統(tǒng)的訪問,如果有已經(jīng)計(jì)算好的增量補(bǔ)丁,那么可以直接返回,只把最需要計(jì)算的任務(wù)傳遞給計(jì)算層。
  2. 如果計(jì)算層出現(xiàn)問題,API 層保有響應(yīng)能力,能夠進(jìn)行服務(wù)降級(jí),返回全量文件內(nèi)容。
  3. 將對(duì)外的接口管理起來,避免接口變更對(duì)核心服務(wù)的影響。在這個(gè)基礎(chǔ)上可以進(jìn)行一些簡(jiǎn)單的聚合服務(wù),提供諸如請(qǐng)求合并之類的服務(wù)。

那如果 API 層沒能將流量攔截下來,進(jìn)一步傳遞到了計(jì)算層呢?

增量服務(wù)計(jì)算層

為了防止過量的計(jì)算請(qǐng)求進(jìn)入到計(jì)算環(huán)節(jié),我們還針對(duì)性地進(jìn)行了流量控制。通過壓測(cè),我們找到了單機(jī)計(jì)算量的瓶頸,然后將這個(gè)限制配置到了系統(tǒng)中。一旦計(jì)算量逼近這個(gè)數(shù)字,系統(tǒng)就會(huì)對(duì)超量的計(jì)算請(qǐng)求進(jìn)行降級(jí),不再進(jìn)行增量計(jì)算,直接返回全量文件。

預(yù)熱的設(shè)計(jì)

另一方面,我們也有相應(yīng)的線下預(yù)熱機(jī)制。我們?yōu)闃I(yè)務(wù)方提供了一個(gè)預(yù)熱工具,業(yè)務(wù)方在上線前調(diào)用我們的預(yù)熱工具,就可以在上線前預(yù)先得到增量補(bǔ)丁并將其緩存起來。我們的預(yù)熱集群和線上計(jì)算集群是分離的,只共享分布式存儲(chǔ),因此雙方在實(shí)際應(yīng)用中互不影響。

如何容災(zāi)

  • 有關(guān)容災(zāi),我們總結(jié)了以往見到的一些常見故障,分了四個(gè)門類來處理。
  • 線路故障。我們?cè)诿恳粚臃?wù)中都內(nèi)置了單機(jī)緩存,這個(gè)緩存的作用一方面是可以泄洪,另一方面,如果線路出現(xiàn)故障,單機(jī)緩存也能在一定程度上降低對(duì)線路的依賴。
  • 存儲(chǔ)故障。對(duì)于存儲(chǔ),我們直接采用了兩種公司內(nèi)非常成熟的分布式存儲(chǔ)系統(tǒng),它們互為備份。

CDN 故障。做前端的同學(xué)或多或少都遇到過 CDN 出故障的時(shí)候,我們也不例外。因此我們準(zhǔn)備了兩個(gè)不同的 CDN ,有效隔離了來自 CDN 故障的風(fēng)險(xiǎn)。

***,在這套服務(wù)之外,我們?yōu)g覽器端的 SDK 也有自己的容災(zāi)機(jī)制。我們?cè)谠隽扛孪到y(tǒng)之外,單獨(dú)部署了一套 CDN ,這套 CDN 只存儲(chǔ)全量文件。一旦增量更新系統(tǒng)無法工作, SDK 就會(huì)去這套 CDN 上拉取全量文件,保障前端的可用性。

回顧與總結(jié)

服務(wù)上線運(yùn)轉(zhuǎn)一段時(shí)間后,我們總結(jié)了新實(shí)踐所帶來的效果:

考慮到每個(gè)業(yè)務(wù)實(shí)際的靜態(tài)文件總量不同,在這份數(shù)據(jù)里我們刻意包含了總量和人均節(jié)省流量?jī)蓚€(gè)不同的值。在實(shí)際業(yè)務(wù)當(dāng)中,業(yè)務(wù)方自己也會(huì)將靜態(tài)文件根據(jù)頁(yè)面進(jìn)行拆分(例如通過 webpack 中的 chunk 來分),每次更新實(shí)際不會(huì)需要全部更新。

由于一些邊界情況,增量計(jì)算的成功率受到了影響,但隨著問題的一一修正,未來增量計(jì)算的成功率會(huì)越來越高。

現(xiàn)在來回顧一下,在我們的新實(shí)踐中,都有哪些大家可以真正借鑒的點(diǎn):

  1. 不同的語(yǔ)言和工具有不同的用武之地,不要試圖用錘子去鋸木頭。該換語(yǔ)言就換,不要想著一個(gè)語(yǔ)言或工具解決一切。
  2. 更換語(yǔ)言是一個(gè)重要的決定,在決定之前首先需要思考是否應(yīng)當(dāng)這么做。
  3. 語(yǔ)言解決更多的是局部問題,架構(gòu)解決更多的是系統(tǒng)問題。換了語(yǔ)言也不代表就萬事大吉了。
  4. 構(gòu)建一個(gè)系統(tǒng)時(shí),首先思考它是如何垮的。想清楚你的系統(tǒng)潛在瓶頸會(huì)出現(xiàn)在哪,如何加強(qiáng)它,如何考慮它的備用方案。

對(duì)于 Go 語(yǔ)言,我們也是摸著石頭過河,希望我們這點(diǎn)經(jīng)驗(yàn)?zāi)軌驅(qū)Υ蠹矣兴鶐椭?/p>

***,如果大家對(duì)我們所做的事情也有興趣,想要和我們一起共建大前端團(tuán)隊(duì)的話,歡迎發(fā)送簡(jiǎn)歷至 liuyanghe02@meituan.com 。

作者簡(jiǎn)介

洋河,2013年加入攜程UED實(shí)習(xí),參與研發(fā)了人生中***個(gè)星數(shù)超過100的 Github 開源項(xiàng)目。2014年加入小米云平臺(tái),同時(shí)負(fù)責(zé)網(wǎng)頁(yè)前端開發(fā)、客戶端開發(fā)及路由器固件開發(fā),積累了豐富的端開發(fā)經(jīng)驗(yàn)。2017年加入美團(tuán),現(xiàn)負(fù)責(zé)金服平臺(tái)基礎(chǔ)組件的開發(fā)工作。

【本文為51CTO專欄機(jī)構(gòu)“美團(tuán)點(diǎn)評(píng)技術(shù)團(tuán)隊(duì)”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過微信公眾號(hào)聯(lián)系機(jī)構(gòu)獲取授權(quán)】

戳這里,看該作者更多好文

責(zé)任編輯:武曉燕 來源: 51CTO專欄
相關(guān)推薦

2017-08-24 15:02:01

前端增量式更新

2022-01-24 12:38:58

Vite插件開發(fā)

2018-04-10 16:01:09

前端緩存靜態(tài)資源

2024-05-14 00:15:42

JSONWeb 應(yīng)用程

2019-03-14 11:00:40

GoLua語(yǔ)言

2021-10-23 09:20:39

AI

2024-01-31 18:02:47

OpenAISpringAI

2021-04-15 08:08:48

微前端Web開發(fā)

2023-10-04 18:24:54

wpf動(dòng)態(tài)資源

2025-03-21 00:05:00

2025-03-31 00:00:00

?增量靜態(tài)再生Next.jsISR

2018-07-19 07:17:48

Windows 10Windows更新模式

2022-12-29 15:01:48

SpringBoot增量部署

2023-10-29 16:18:26

Go接口

2016-10-28 15:01:35

Cookie前端實(shí)踐

2021-09-05 18:25:30

Go命令倉(cāng)庫(kù)

2022-04-18 09:41:14

Go架構(gòu)設(shè)計(jì)

2022-10-30 23:13:30

contextGo語(yǔ)言

2023-09-07 20:04:06

前后端趨勢(shì)Node.js

2019-06-20 10:23:23

架構(gòu)代碼前端
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)