C10K 非阻塞 Web 服务器
????本文由作為 Going Concurrency in Go 的作者 Nathan Kozyra 撰寫, 解決了互聯(lián)網(wǎng)上最著名,最受尊敬的挑戰(zhàn)之一, 并試圖通過核心 Go 包來解決它.
原文地址: https://hub.packtpub.com/c10k-non-blocking-web-server-go/
我們已經(jīng)構(gòu)建了一些可用的應(yīng)用程序,并且可以在日常使用的真實系統(tǒng)中使用這些技術(shù). 通過這些, 我們已經(jīng)能夠演示Go的并發(fā)語法和方法中涉及的基本和中級模式. 然而, 現(xiàn)在是時候去面對一個現(xiàn)實世界的問題 - 一個讓開發(fā)人員(以及他們的經(jīng)理和副總裁)在網(wǎng)絡(luò)的早期歷史中煩惱的問題.
在解決這個問題時, 我們希望能夠開發(fā)出一種能夠處理大量實時, 活躍流量的高性能Web服務(wù)器.
多年來, 解決這個問題的方法完全是為了解決這個問題的硬件或侵入式緩存系統(tǒng);所以, 換句話說, 用編程方法解決它應(yīng)該激發(fā)任何程序員.
很多年以來, 這個問題的解決方案是調(diào)整底層硬件和使用侵入式緩存系統(tǒng). 因此, 用編程方法解決這個問題將會是非常令人激動的.
我們將使用到目前為止我們學到的所有技術(shù)和語言結(jié)構(gòu), 但我們將以比迄今為止更加結(jié)構(gòu)化和深思熟慮的方式進行. 到目前為止我們探索的所有內(nèi)容都將發(fā)揮作用, 包括以下幾點:
- 創(chuàng)建并發(fā)應(yīng)用程序的可視化表示
- 利用 goroutine 以可擴展的方式處理請求
- 構(gòu)建強大的渠道來管理 goroutine 和管理它們的循環(huán)之間的通信
- 分析和基準測試工具(JMeter, ab)來檢查我們的事件循環(huán)實際工作的方式
- 超時和并發(fā)控制確保數(shù)據(jù)和請求的一致性
C10K 問題
C10K問題的根源在于串行, 阻塞的編程方式, 因此,我們嘗試使用 Go 的并發(fā)編程方法解決這個問題將會非常適合.
當在1999年被問到這個問題時, 對于許多服務(wù)器管理員和工程師來說, 服務(wù) 10,000 個并發(fā)訪問是可以通過硬件解決的問題. 在普通硬件上來處理這種問題, 而CPU和網(wǎng)絡(luò)帶寬不會崩潰, 這對大多數(shù)人來說是很陌生.
這個問題的解決方案的關(guān)鍵在于產(chǎn)生非阻塞代碼. 當然, 在1999年, 并發(fā)模式和庫并不普遍. C++ 通過一些第三方庫和多線程語法的最早前身提供了一些輪詢和排隊選項, 后來可以通過 Boost 和 C++ 11 獲得.
在接下來的幾年中, 問題的解決方案開始涌入各種語言, 編程設(shè)計和一些通用方法.
任何性能和可擴展性問題最終都將涉及到底層硬件, 因此, 不同硬件的結(jié)果往往不同. 在一個 500MB RAM 的 486 處理器上處理 10,000 個并發(fā)連接肯定比在擁有大內(nèi)存多核處理器的 Linux 服務(wù)器上處理這個問題更具挑戰(zhàn)性.
同樣值得注意的是, 顯然, 簡單的回顯服務(wù)器能夠承擔比返回大量數(shù)據(jù)的功能性 Web 服務(wù)器更多的鏈接. 而在這里, 我們將處理具有復雜的請求會話的情況.
10,000個并發(fā)連接的服務(wù)器失敗
當網(wǎng)絡(luò)誕生并且互聯(lián)網(wǎng)商業(yè)化時, 交互水平非常低. 如果你是一個 graybeard, 你可能會想起從 NNTP/IRC 的過渡, 以及網(wǎng)絡(luò)如何簡陋.
為了解決[頁面請求]→[HTTP響應(yīng)]的基本問題, 20 世紀 90 年代早期對 Web 服務(wù)器的要求非常寬松. 它會忽略所有錯誤響應(yīng), 標題讀取和設(shè)置, 以及其他必要(但與in→out機制無關(guān))功能, 與現(xiàn)代 Web 服務(wù)器相比, 早期服務(wù)器本質(zhì)上非常簡單.
第一個 Web 服務(wù)器是由 Web 之父 Tim Berners-Lee 開發(fā)的.
ERN httpd 是在 CERN(如WWW/HTTP本身)開發(fā)的, 它能處理許多你今天在網(wǎng)絡(luò)服務(wù)器中所期望的東西 - 通過閱讀代碼, 你會發(fā)現(xiàn)核心的HTTP協(xié)議基本沒有變化. 與大多數(shù)技術(shù)不同, HTTP的保質(zhì)期非常長.
在 1990 年用C編寫, 它無法使用類似 Erlang 的許多語言中的并發(fā)策略. 實話說, 這樣做可能是不必要的 - 大多數(shù)網(wǎng)絡(luò)流量的主要問題是基本文件檢索和協(xié)議的問題. Web服務(wù)器的的主要功能不是處理流量, 而是處理協(xié)議本身的規(guī)則.
您仍然可以訪問原始的 CERN httpd 站點并從 http://www.w3.org/Daemon/ 下載源代碼. 我強烈建議您這樣做, 這樣做您可以學習最早的 Web 服務(wù)器解決某些最早期問題的方法.
然而, 1990 年的 Web 和首次提出 C10K 問題的 Web 是兩個截然不同的問題.
到 1999 年, 大多數(shù)網(wǎng)站都有一定程度的二級或三級延遲, 由第三方軟件, CGI, 數(shù)據(jù)庫等軟件導致, 所有這些都使問題更加復雜. 同時提供 10,000 個文件的服務(wù)的概念本身就是一個挑戰(zhàn).
到 20 世紀 90 年代中期, Apache Web 服務(wù)器已經(jīng)在很大程度上控制了市場(到 2009 年, 它已成為第一個服務(wù)超過1億個網(wǎng)站的服務(wù)器軟件).
Apache 的方法在互聯(lián)網(wǎng)最早期就扎根了. 在開始時, 連接被按照先進先出的順序處理, 然后給每個連接分配一個來自連接線程池的線程. Apache 服務(wù)器有如下兩個問題:
- 阻塞連接可能導致多米諾骨牌效應(yīng), 其中一個或多個緩慢的連接可能會導致服務(wù)不可訪問. 無論是在那種硬件上, Apache 都會對您可以使用的線程/工作線程數(shù)進行嚴格限制. 在這里, 如果使用 actor(Erlang), 代理(Clojure)或 goroutines(Go)的并發(fā)服務(wù)器似乎完全符合要求. 并發(fā)本身并不能解決C10k問題, 但它絕對是解決這個問題的一個方法.
- 目前解決 C10K 問題的方法最值得注意和有效的例子是使用 Nginx, 它是使用并發(fā)模式開發(fā)的, 在 2002 年 C 中提供, 以解決 C10k 問題. 今天, Nginx 代表了世界上的#2 或 #3 Web 服務(wù)器.
使用并發(fā)挑戰(zhàn) C10K 問題
處理大量并發(fā)請求主要有兩種方法. 第一個涉及為每個連接分配線程. 這就是 Apache(以及其他一些人)所做的事情.
一方面, 給每個連接分配一個線程很有意義 - 它可以通過應(yīng)用程序和內(nèi)核的上下文切換來隔離, 控制這些連接. 而且在增加硬件時也非常方便進行擴展.
但是大多數(shù) Linux Web 服務(wù)器所面臨的一個問題是, 每個線程默認為其堆棧保留 8MB 內(nèi)存. 雖然這個內(nèi)存大小可以(并且應(yīng)該)被自定義, 但是這會給單個服務(wù)器帶來很大程度上無法達到的內(nèi)存量. 即使您將默認堆棧大小設(shè)置為 1MB, 我們也只處理 10GB 內(nèi)存以處理連接的內(nèi)存開銷.
這里一個極端的例子, 在顯示應(yīng)用中不太可能遇到. 原因如下:首先, 因為你可以決定每個線程可用的最大資源量, 其次, 因為你可以通過在幾個服務(wù)器之間實現(xiàn)負載平衡, 而不是添加 10GB 到 80GB 的 RAM.
即使在線程服務(wù)器環(huán)境中, 我們也會遇到導致性能下降(到崩潰點)的問題.
首先, 讓我們看一下綁定到線程的連接的服務(wù)器(如下圖所示), 這可能導致非常糟糕的情形并最終導致崩潰:
這顯然是我們想要避免的. 任何可能導致速度減慢的 I/O, 網(wǎng)絡(luò)或外部進程都會帶來我們所討論的雪崩效應(yīng), 這樣我們的可用線程就會被積壓并且傳入的請求開始堆疊.
我們可以在這個模型中使用更多的線程, 但如前所述, 那里也存在潛在的風險, 即使這樣也無法緩解潛在的問題.
另一種方法
為了創(chuàng)建可以處理 10,000 個并發(fā)連接的 Web 服務(wù)器, 我們顯然會利用我們的 goroutine/channel 機制在一個循環(huán)中處理請求.
在這個例子中, 我們假設(shè)我們正在為一家快速增長的公司建立一個公司網(wǎng)站.為此, 我們的網(wǎng)站需要能夠同時提供靜態(tài)和動態(tài)網(wǎng)頁內(nèi)容.
我們想要引入動態(tài)內(nèi)容的原因不僅僅是為了演示目的 - 我們希望挑戰(zhàn)自己, 即使在其他進程阻塞時也能處理 10,000 個真正的并發(fā)連接.
我們將嘗試將并發(fā)策略直接映射到 goroutines 和 channel. 在許多其他語言和應(yīng)用程序中, 這直接類似于事件循環(huán), 我們將維護可用的 goroutine, 過期或重用已完成的 goroutine, 并在必要時生成新的 goroutine.
在下圖中, 我們展示了事件循環(huán)(和相應(yīng)的goroutine)如何允許我們擴展連接而不使用太多的硬件資源, 如 CPU 線程或 RAM:
這里最重要的是管理該事件循環(huán). 我們創(chuàng)建一個無限循環(huán)來管理我們的 goroutine 和各個 channel 的創(chuàng)建和到期.
作為其中的一部分, 我們還會打印一些內(nèi)部程序執(zhí)行信息的 log, 以便對我們的應(yīng)用程序進行基準測試和調(diào)試.
原本測試對比忽略, 想看的讀者可以直接點擊文章開始的連接進行閱讀.
總結(jié)
以上是生活随笔為你收集整理的C10K 非阻塞 Web 服务器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小心使用tf.image.resize_
- 下一篇: 区块链读书笔记一