久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

如何在Node.js中優(yōu)化服務(wù)器端渲染,?

 就這樣了__ 2018-07-22
作者|Ben Hughes
譯者|無明

在 Airbnb,我們花了數(shù)年時間將所有前端代碼遷移到 React 架構(gòu),,Ruby on Rails 在 Web 應(yīng)用中所占的比例每天都在減少,。實(shí)際上,,我們很快會轉(zhuǎn)向另一個新的服務(wù),,即通過 Node.js 提供完整的服務(wù)器端渲染頁面。這個服務(wù)將為 Airbnb 的所有產(chǎn)品渲染大部分 HTML,。這個渲染引擎不同于其他后端服務(wù),,因?yàn)樗皇怯?Ruby 或 Java 開發(fā)的,但它也不同于常見的 I/O 密集型 Node.js 服務(wù),。

一說起 Node.js,,你可能就開始暢想著高度異步化的應(yīng)用程序,可以同時處理成千上萬個連接,。你的服務(wù)從各處拉取數(shù)據(jù),,以迅雷不及掩耳之勢處理好它們,然后返回給客戶端,。你可能正在處理一大堆 WebSocket 連接,,你對自己的輕量級并發(fā)模型充滿自信,認(rèn)為它非常適合完成這些任務(wù),。

但服務(wù)器端渲染(SSR)卻打破了你對這種美好愿景的假設(shè),,因?yàn)樗怯?jì)算密集型的。Node.js 中的用戶代碼運(yùn)行在單個線程上,,因此可以并發(fā)執(zhí)行計(jì)算操作(與 I/O 操作相反),,但不能并行執(zhí)行它們。Node.js 可以并行處理大量的異步 I/O,,但在計(jì)算方面卻受到了限制,。隨著計(jì)算部分所占比例的增加,,開始出現(xiàn) CPU 爭用,并發(fā)請求將對延遲產(chǎn)生越來越大的影響,。

以 Promise.all([fn1,,fn2]) 為例,如果 fn1 或 fn2 是屬于 I/O 密集型的 promise,,就可以實(shí)現(xiàn)這樣的并行執(zhí)行:

如果 fn1 和 fn2 是計(jì)算密集型的,,它們將像這樣執(zhí)行:

一個操作必須等待另一個操作完成后才能運(yùn)行,因?yàn)橹挥幸粋€執(zhí)行線程,。

在進(jìn)行服務(wù)器端渲染時,,當(dāng)服務(wù)器進(jìn)程需要處理多個并發(fā)請求,就會出現(xiàn)這種情況,。正在處理中的請求將導(dǎo)致其他請求延遲:

在實(shí)際當(dāng)中,,請求通常由許多不同的異步階段組成,盡管仍然以計(jì)算為主,。這可能導(dǎo)致更糟糕的交叉,。如果我們的請求包含一個像 renderPromise().then(out => formatResponsePromise(out)).then(body => res.send(body)) 這樣的鏈,那么請求的交叉可能是這樣的:

在這種情況下,,兩個請求都需要兩倍的時間才能處理完成,。隨著并發(fā)的增加,這個問題將變得更加嚴(yán)重,。

SSR 的一個目標(biāo)是能夠在客戶端和服務(wù)器上使用相同或類似的代碼,。這兩種環(huán)境之間存在一個巨大的差異,客戶端上下文本質(zhì)上是單租戶的,,而服務(wù)器上下文卻是多租戶的,。在客戶端可以正常運(yùn)行的東西,比如單例或全局狀態(tài),,到了服務(wù)器端就會導(dǎo)致 bug,、數(shù)據(jù)泄漏和各種混亂。

這兩個問題都與并發(fā)有關(guān),。在負(fù)載水平較低時,,或在開發(fā)環(huán)境當(dāng)中,一切都正常,。

這與 Node 應(yīng)用程序的情況完全不同,。我們之所以使用 JavaScript 運(yùn)行時,是因?yàn)樗峁┑膸熘С趾蛯g覽器的支持,,而不是因?yàn)樗牟l(fā)模型,。上述的示例表明,異步并發(fā)模型所帶來的成本已經(jīng)超出了它所能帶來的好處。

從 Hypernova 中學(xué)到的教訓(xùn)

我們的新渲染服務(wù) Hyperloop 將成為 Airbnb 用戶的主要交互服務(wù),。因此,,它的可靠性和性能對用戶體驗(yàn)來說至關(guān)重要。隨著逐漸在生產(chǎn)環(huán)境中使用新服務(wù),,我們將參考從早期 SSR 服務(wù) Hypernova 中吸取到的教訓(xùn),。

Hypernova 的工作方式與新服務(wù)不同。它是一個純粹的渲染器,,Rails 單體應(yīng)用 Monorail 會調(diào)用它,,它返回渲染組件的 HTML 片段。在大多數(shù)情況下,,“片段”是整個頁面的一部分,,Rails 只提供外部布局。頁面上的各個部分可以使用 ERB 拼接在一起,。但是,,不管是哪一種情況,Hypernova 都不獲取數(shù)據(jù),,數(shù)據(jù)由 Rails 提供,。

也就是說,在計(jì)算方面,,Hyperloop 和 Hypernova 具有類似的操作特性,,而 Hypernova 提供了良好的測試基礎(chǔ),可以幫助我們理解生產(chǎn)環(huán)境中的頁面內(nèi)容是如何進(jìn)行替換的,。

用戶請求進(jìn)入我們的 Rails 主應(yīng)用程序 Monorail,,它為需要進(jìn)行渲染的 React 組件組裝 props,,并向 Hypernova 發(fā)送帶有這些 props 和組件名稱的請求,。Hypernova 使用收到的 props 來渲染組件,生成 HTML 并返回給 Monorail,,Monorail 將 HTML 片段嵌入到頁面模板中,,并將所有內(nèi)容發(fā)送給客戶端。

如果 Hypernova 渲染失?。ㄓ捎阱e誤或超時),,就將組件及 props 嵌入頁面,或許它們可以成功地在客戶端渲染,。因此,,我們認(rèn)為 Hypernova 是一個可選的依賴項(xiàng),我們能夠容忍一些超時和失敗,。我根據(jù) SLA p95 來設(shè)置超時時間,,不出所料,我們的超時基線略低于 5%,。

在高峰流量負(fù)載期間進(jìn)行部署時,,我們可以看到從 Monorail 到 Hypernova 最多有 40%的請求超時,。我們可以從 Hypernova 中看到 BadRequestError:aborted 的錯誤率峰值。

部署超時峰值示例(紅線)

我們把這些超時和錯誤歸因于緩慢的啟動時間,,如 GC 啟動初始化,、缺少 JIT、填充緩存等等,。新發(fā)布的 React 或 Node 有望提供足夠的性能改進(jìn),,以緩解啟動緩慢的問題。

我懷疑這可能是由于不良的負(fù)載均衡或部署期間的容量問題造成的,。當(dāng)我們在同一個進(jìn)程上同時運(yùn)行多個計(jì)算請求時,,我們看到了延遲的增加。我添加了一個中間件來記錄進(jìn)程同時處理的請求數(shù),。

我們將啟動延遲歸咎于并發(fā)請求等待 CPU,。從我們的性能指標(biāo)來看,我們無法區(qū)分用于等待執(zhí)行的時間與用于實(shí)際處理請求的時間,。這也意味著并發(fā)性帶來的延遲與新代碼或新特性帶來的延遲是相同的——這些實(shí)際上都會增加單個請求的處理成本,。

很明顯,我們不能將 BadRequestError:Request aborted 錯誤歸咎于啟動延遲,。這個錯誤來自消息解析器,,特別在服務(wù)器完全讀取請求消息體之前,客戶端中止了請求,??蛻舳岁P(guān)閉了連接,我們無法拿到處理請求所需的寶貴數(shù)據(jù),。發(fā)生這種情況的可能性更大,,比如:我們開始處理請求,然后事件循環(huán)被另一個請求渲染阻塞,,當(dāng)回到之前被中斷的地方繼續(xù)處理時,,發(fā)現(xiàn)客戶端已經(jīng)消失了。Hypernova 的請求消息體也很大,,平均有幾百千字節(jié),,這樣只會讓事情變得更糟。

我們決定使用兩個現(xiàn)有的組件來解決這個問題:反向代理(Nginx)和負(fù)載均衡器(HAProxy),。

反向代理和負(fù)載均衡

為了充分利用 Hypernova 實(shí)例上的多核 CPU,,我們在單個實(shí)例上運(yùn)行多個 Hypernova 進(jìn)程。因?yàn)檫@些是獨(dú)立的進(jìn)程,,所以能夠并行處理并發(fā)請求,。

問題是每個 Node 進(jìn)程將在整個請求時間內(nèi)被占用,包括從客戶端讀取請求消息體。雖然我們可以在單個進(jìn)程中并行讀取多個請求,,但在渲染時,,這會導(dǎo)致計(jì)算操作交叉。因此,,Node 進(jìn)程的使用情況取決于客戶端和網(wǎng)絡(luò)的速度,。

解決辦法是使用緩沖反向代理來處理與客戶端的通信。為此,,我們使用了 Nginx,。Nginx 將客戶端的請求讀入緩沖區(qū),并在完全讀取后將完整請求傳給 Node 服務(wù)器,。這個傳輸過程是在本地機(jī)器上進(jìn)行的,,使用了回送或 unix 域套接字,這比機(jī)器之間的通信更快,、更可靠,。

通過使用 Nginx 來處理讀取請求,我們能夠?qū)崿F(xiàn)更高的 Node 進(jìn)程利用率,。

我們還使用 Nginx 來處理一部分請求,,不需要將它們發(fā)送給 Node.js 進(jìn)程。我們的服務(wù)發(fā)現(xiàn)和路由層通過 /ping 低成本請求來檢查主機(jī)之間的連接性,。在 Nginx 中處理這些可以降低 Node.js 進(jìn)程的吞吐量,。

接下來是負(fù)載均衡。我們需要明智地決定哪些 Node.js 進(jìn)程應(yīng)該接收哪些請求,。cluster 模塊通過 round-robin 算法來分配請求,,當(dāng)請求延遲的變化很小時,這種方式是很好的,,例如:

但是當(dāng)有不同類型的請求需要花費(fèi)不同的處理時間時,,它就不那么好用了。后面的請求必須等待前面的請求全部完成,,即使有另一個進(jìn)程可以處理它們,。

更好的分發(fā)模型應(yīng)該像這樣:

因?yàn)檫@可以最大限度地減少等待時間,并可以更快地返回響應(yīng),。

這可以通過將請求放進(jìn)隊(duì)列中并只將請求分配給空閑的進(jìn)程來實(shí)現(xiàn)。為此,,我們使用了 HAProxy,。

當(dāng)我們在 Hypernova 中實(shí)現(xiàn)了這些,就完全消除了部署時的超時峰值以及 BadRequestError 錯誤,。并發(fā)請求也是造成延遲的主要因素,,隨著新方案的實(shí)施,延遲也降低了。在使用相同的超時配置的情況下,,超時率基線從 5%變?yōu)?2%,。部署期間的 40%失敗也降低到了 2%,這是一個重大的勝利?,F(xiàn)在,,用戶看到空白頁的幾率已經(jīng)很低了。未來,,部署穩(wěn)定性對于我們的新渲染器來說至關(guān)重要,,因?yàn)樾落秩酒鳑]有 Hypernova 的回滾機(jī)制。

細(xì)節(jié)和配置

要搭建起整個環(huán)境,,需要對 Nginx,、HAProxy 和 Node 應(yīng)用程序進(jìn)行配置。我們提供了一個帶有 Nginx 和 HAProxy 配置的 Node 示例應(yīng)用程序(https://github.com/schleyfox/example-Node-ops),。

Nginx 遵循標(biāo)準(zhǔn)的配置,,服務(wù)器監(jiān)聽 9000 端口,負(fù)責(zé)將請求轉(zhuǎn)發(fā)給監(jiān)聽 9001 端口的 HAProxy(我們使用了 Unix 域套接字),。它還攔截 /ping 端點(diǎn),,用于檢查機(jī)器間的連接性。與標(biāo)準(zhǔn)的 Nginx 配置不同的是,,我們將 worker_processes 減少為 1,,因?yàn)閱蝹€ Nginx 進(jìn)程足以使我們的單個 HAProxy 進(jìn)程和 Node 應(yīng)用程序飽和。我們使用了大型的請求和響應(yīng)緩沖區(qū),,因?yàn)?Hypernova 組件的 props 可能非常大(數(shù)百千字節(jié)),。你應(yīng)該根據(jù)自己的請求 / 響應(yīng)大小調(diào)整緩沖區(qū)大小。

Node 的 cluster 模塊負(fù)責(zé)處理負(fù)載均衡和進(jìn)程生成,。要使用 HAProxy 作為負(fù)載均衡,,我們必須為 cluster 的流程管理部分創(chuàng)建替代品。這就像 pool-hall(https://github.com/airbnb/pool-hall)一樣,,它比 cluster 更擅長于維護(hù)工作進(jìn)程池,,但它與負(fù)載均衡沒有關(guān)系。這個示例應(yīng)用程序(https://github.com/schleyfox/example-node-ops/blob/master/index.js#L18)演示了如何使用 pool-hall 啟動四個 worker 進(jìn)程,,每個進(jìn)程監(jiān)聽不同的端口,。

HAProxy 配置了一個監(jiān)聽 9001 端口的代理,該代理將流量路由給監(jiān)聽 9002 到 9005 端口的四個 worker 進(jìn)程,。最重要的是將每個 worker 的 maxconn 設(shè)置為 1,,也就是將 worker 每次處理的請求限定為 1。

HAProxy 會跟蹤它與每個 worker 之間的連接數(shù),,最大只能達(dá)到 maxconn 配置的值,。路由被設(shè)置為 static-rr(固定的 round robin),,因此每個 worker 會依次獲得請求?;谶@些配置,,路由會跳過當(dāng)前達(dá)到請求限制的 worker。如果沒有可用的 worker,,請求就被放入隊(duì)列,,并被分派給最先可用的 worker。這就是我們想要的行為,。

深入 HAProxy

HAProxy 上有很多配置正如我們預(yù)期的那樣,,如果它們沒有按照我們預(yù)期的方式處理并發(fā)請求限制或隊(duì)列,那對我們就沒有太大用處,。了解如何處理(或不處理)各種類型的故障對我們來說也很重要的,。我們需要對使用這個方案作為 cluster 模塊的替代品有足夠的信心。為此,,我們進(jìn)行了一系列測試,。

在進(jìn)行測試時,我們一般會使用 ab(Apache Benchmark)在各種并發(fā)級別上發(fā)送 10,000 個請求,。

ab -l -c <CONCURRENCY> -n 10000 http://<HOSTNAME>:9000/render

我們在示例應(yīng)用程序中使用了 15 個 worker 而不是 4 個,,為了避免基準(zhǔn)測試與被測系統(tǒng)之間相互干擾,我們在單獨(dú)的實(shí)例上運(yùn)行 ab,。我們分別在低負(fù)載(并發(fā)為 5),、高負(fù)載(并發(fā)為 13)和排隊(duì)負(fù)載(并發(fā)為 20)的情況下運(yùn)行測試。

第一組測試都是正常的操作,,而第二組測試是在重啟所有進(jìn)程的情況下進(jìn)行的,。在進(jìn)行最后一組測試時,我隨機(jī)停止了一部分進(jìn)程,。

另外,,應(yīng)用程序代碼中的無限循環(huán)一直是個問題,因此,,在測試中我們通過一個無限循環(huán)向端點(diǎn)發(fā)送請求,。

在正常情況下,maxconn 1 可以完全按照預(yù)期工作,,也就是說每個進(jìn)程一次只處理一個請求,。我們沒有在后端配置 HTTP 或 TCP 健康檢查,因?yàn)檫@樣會導(dǎo)致更多的混亂,。健康檢查似乎不受 maxconn 的限制,,盡管我還沒有在代碼中證實(shí)這一點(diǎn)。我們預(yù)期的行為是一個進(jìn)程要么能夠提供服務(wù),,要么無法監(jiān)聽端口并立即拋出連接異常,。我們發(fā)現(xiàn)健康檢查對于我們的場景來說不是很有用。

我們需要處理連接錯誤,,為此,,我們設(shè)置了 redispatch,并把 retries 設(shè)置為 3,,這樣收到連接錯誤的請求可以重新連接到后端的另一個實(shí)例,。

由于我們使用的是本地網(wǎng)絡(luò),所以連接超時對我們來說不是特別有用,。我們最初期望通過設(shè)置一個較低的連接超時來防止 worker 陷入無限循環(huán),。我們將超時設(shè)置為 100 毫秒,但我們驚訝地發(fā)現(xiàn),,請求在 10 秒后才超時(這是客戶端與服務(wù)器之間的超時時間),。

我們在測試過程中發(fā)現(xiàn)了另一個有趣的結(jié)果,客戶端 / 服務(wù)器超時會導(dǎo)致一些非預(yù)期的行為,。當(dāng)請求被發(fā)送給一個進(jìn)程并導(dǎo)致進(jìn)程進(jìn)入無限循環(huán)時,,后端的連接計(jì)數(shù)被設(shè)置為 1。因?yàn)?maxconn 為 1,,所以其他請求就無法被分配給這個進(jìn)程,。在客戶端 / 服務(wù)器超時后,連接計(jì)數(shù)會減少到 0,。而當(dāng)客戶端由于超時或其他原因關(guān)閉連接時,,連接計(jì)數(shù)不會生效,但路由仍然可以繼續(xù)工作,。打開 abortonclose 可以讓連接計(jì)數(shù)在客戶端關(guān)閉后立即減少,。因此,最好的做法是為這些超時設(shè)置一個較高的值并關(guān)閉 abortonclose,。我們可以在客戶端或 Nginx 端設(shè)置更嚴(yán)格的超時時間,。

我們還發(fā)現(xiàn)在高負(fù)載時會出現(xiàn)一個非常詭異的情況。如果 worker 在服務(wù)器具有穩(wěn)定隊(duì)列的情況下崩潰(這應(yīng)該是非常罕見的),,后端會嘗試處理請求,,但由于沒有進(jìn)程在監(jiān)聽,將無法建立連接,。然后,,HAProxy 會將請求發(fā)給另一個后端,這樣很快就會把重試次數(shù)用完并導(dǎo)致請求失敗,,因?yàn)檫B接錯誤遠(yuǎn)比渲染 HTML 發(fā)生得更快,。該進(jìn)程將重復(fù)嘗試處理其余的請求,直到請求隊(duì)列變空為止,。這種情況很糟糕,,不過也很罕見,。在我們的案例中,我們的服務(wù)發(fā)現(xiàn)健康檢查程序會發(fā)現(xiàn)這些問題,,并快速將整個實(shí)例標(biāo)記為不健康,,不用于處理新的請求。雖然這樣不是很好,,但可以最大限度地降低風(fēng)險,。在未來,我們將通過更深入的 HAProxy 集成來處理這個問題,,讓管理進(jìn)程監(jiān)控其他進(jìn)程的退出,,并通過 HAProxy 數(shù)據(jù)套接字將其標(biāo)記為 MAINT。

另外有一個值得注意的變化,,在設(shè)置了 Node 的 server.close 后,,服務(wù)器將等待當(dāng)前請求完成,但 HAProxy 隊(duì)列中的任何請求都將失敗,,因?yàn)榉?wù)器不會等待尚未收到的請求,。在大多數(shù)情況下,確保實(shí)例停止接收請求與啟動服務(wù)器重啟進(jìn)程之間有足夠的銜接時間就可以解決這個問題,。

我們還發(fā)現(xiàn),,如果設(shè)置了 balance first,也就是將大多數(shù)流量按順序重定向給第一個可用的 worker,,相比使用 balance static-rr,,延遲將減少 15%。這種效果在部署之后會持續(xù)數(shù)小時,,應(yīng)該不僅僅是因?yàn)榉?wù)器預(yù)熱的關(guān)系,。在較長時間(12 小時)后,性能開始下降,,可能是熱進(jìn)程的內(nèi)存泄漏導(dǎo)致,。由于冷進(jìn)程極冷,因此應(yīng)對流量高峰的彈性能力也較差,。對此,,我們還不知道該作何解釋。

最后,,Node 的 server.maxConnections 配置似乎會有所幫助,,但我們發(fā)現(xiàn)它并沒有真正提供太多好處,并且有時還會導(dǎo)致錯誤,。這個配置可以防止服務(wù)器在達(dá)到限制后關(guān)閉新句柄來接收超過 maxConnections 數(shù)量的連接,。這個檢查被用在 JavaScript 中,所以它無法應(yīng)對無限循環(huán)的情況,。我們還看到在正常操作下它會造成連接錯誤,。我們懷疑這只是一個時間問題或 HAProxy 和 Node 之間關(guān)于連接何時開始和結(jié)束存在分歧,。

 英文原文

https:///airbnb-engineering/operationalizing-node-js-for-server-side-rendering-c5ba718acfc9

課程推薦




    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn),。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多