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