Pingora有多屌!超受歡迎 Web 伺服器超越Nginx

WBOY
發布: 2024-02-19 11:12:17
轉載
1342 人瀏覽過

很高興介紹 Pingora,這是我們基於 Rust 建立的新 HTTP 代理。每天處理超過 1 兆個請求,提升了效能,為 Cloudflare 客戶帶來新功能,且僅需原代理基礎架構三分之一的 CPU 和記憶體資源。

隨著 Cloudflare 的規模不斷擴大,我們發現 NGINX 的處理能力已經無法滿足我們的需求。儘管多年來它一直表現良好,但隨著時間的推移,我們意識到它在應對我們規模上的挑戰時存在局限性。因此,我們認為有必要建立一些新的解決方案來滿足我們對效能和功能的需求。

Cloudflare客戶和使用者使用Cloudflare全球網路作為HTTP客戶端和伺服器之間的代理。我們已進行多次討論,開發了許多技術並實施了新協議,如QUIC和HTTP/2優化,以提高瀏覽器和其他用戶代理連接到我們網路的效率。

今天,我們將聚焦於另一個與此等式相關的面向:代理服務,它負責管理我們網路和網際網路伺服器之間的流量。這個代理服務為我們的CDN、Workers fetch、Tunnel、Stream、R2等多項功能和產品提供支援和動力。

讓我們深入研究為何我們決定升級我們的舊版服務,並探討Pingora系統的開發過程。這個系統是專門為Cloudflare的客戶用例和規模而設計的。

為什麼要再建一個代理程式

#近年來,我們在使用NGINX時遇到了一些限制。針對一些限制,我們做了最佳化或採取了繞過的方式。然而,還有一些限制更具挑戰性。

架構的限制損害了效能

#NGINX worker(進程)架構 [4] 對於我們的用例有操作缺陷,這會損害我們的效能和效率。

首先,在 NGINX 中,每個請求只能由單一 worker 處理。這會導致所有 CPU 核心之間的負載不平衡 [5],進而 導致速度變慢 [6]。

由於這種請求進程鎖定效應,執行 CPU 密集型或阻止IO任務的請求可能會減緩其他請求的速度。正如這些部落格文章所指出的,我們已經投入了大量時間來解決這些問題。

我們的用例最關鍵的問題是連線重複使用,當我們的機器與原始伺服器建立 TCP 連線時,代理了 HTTP 請求。透過重複使用連接池中的連接,可以跳過新連接所需的 TCP 和 TLS 握手,從而加快請求的 TTFB(首字節時間)。

但是,NGINX 連接池 [9] 與單一 worker 相對應。當請求到達某個 worker 時,它只能重複使用該 worker 內的連線。當我們添加更多 NGINX worker 以進行擴展時,我們的連接重用率會變得更差,因為連接分散在所有進程的更孤立的池中。這導致更慢的 TTFB 以及需要維護更多連接,進而消耗我們和客戶的資源(和金錢)。 Pingora有多牛逼!超受欢迎 Web 服务器超越Nginx

正如在過去的部落格文章中所提到的,我們為其中一些問題提供了解決方法。但如果我們能夠解決根本問題:worker / 進程模型,我們將自然而然地解決所有這些問題。

有些類型的功能難以加入

NGINX 是一個非常好的 Web 伺服器、負載平衡器或簡單的閘道。但 Cloudflare 的作用遠不止於此。我們過去常常在 NGINX 周圍建立我們需要的所有功能,但要盡量避免與 NGINX 上游程式碼庫有太多分歧,這並不容易。

例如,當重試請求 / 請求失敗 [10] 時,有時我們希望將請求傳送到具有不同請求標頭集的不同來源伺服器。但 NGINX 並不允許執行此操作。在這種情況下,我們需要花費時間和精力來解決 NGINX 的限制。

同時,我們被迫使用的程式語言並沒有幫助緩解這些困難。 NGINX 純粹是用 C 語言編寫的,這在設計上不是記憶體安全的。使用這樣的第 3 方程式碼庫非常容易出錯。即使對於經驗豐富的工程師來說,也很容易陷入記憶體安全問題 [11],我們希望盡可能避免這些問題。

我們用來補充 C 語言的另一種語言是 Lua。它的風險較小,但性能也較差。此外,在處理複雜的 Lua 程式碼和業務邏輯時,我們經常發現自己缺少靜態類型 [12]。

而且 NGINX 社群也不是很活躍,開發往往是 「閉門造車」[13]。

選擇建立我們自己的

在過去的幾年裡,隨著我們的客戶群和功能集的持續成長,我們持續評估了三種選擇:

  • 1. 繼續投資 NGINX,向其付款進行定制,使其 100% 滿足我們的需求。我們擁有所需的專業知識,但鑑於上述架構限制,需要付出大量努力才能以完全支持我們需求的方式重建它。
  • 2. 遷移到另一個第三方代理程式碼庫。一定有好的項目,例如 envoy[14] 和其他一些 [15]。但這條路意味著在幾年內可能會重複同樣的循環。
  • 3. 從頭開始建立一個內部平台和框架。這項選擇需要在工程方面進行最大的前期投資。
  • 在過去的幾年中,我們每季都會對這些選項進行評估。沒有明顯的公式來判斷哪個選擇是最好的。在幾年的時間裡,我們繼續走阻力最小的道路,繼續增強 NGINX。然而,在某些情況下,建立自有代理的投資報酬率似乎更值得。我們呼籲從頭開始建立一個代理,並開始設計我們夢想中的代理應用程式。

    Pingora 專案

    #設計決定

    為了打造一個每秒提供數百萬次請求且快速、有效率且安全的代理,我們必須先做出一些重要的設計決定。

    我們選擇 Rust[16] 作為專案的語言,因為它可以在不影響效能的情況下以記憶體安全的方式完成 C 語言可以做的事情。

    儘管有一些很棒的現成第3 方HTTP 庫,例如 hyper[17],我們選擇建立自己的庫是因為我們希望最大限度地提高處理HTTP 流量的靈活性,並確保我們可以按照自己的節奏進行創新。

    在 Cloudflare,我們處理整個網路的流量。我們必須支援許多奇怪且不符合 RFC 的 HTTP 流量案例。這是 HTTP 社群和 Web 中的一個常見困境,在嚴格遵循 HTTP 規範,和適應潛在遺留客戶端或伺服器的廣泛生態系統的細微差別之間存在矛盾和衝突,需要在其中作出艱難抉擇。

    HTTP 狀態碼在 RFC 9110 中定義為一個三位整數 [18],通常預期在 100 到 599 的範圍內。 Hyper 就是這樣一種實作。但是,許多伺服器支援使用 599 到 999 之間的狀態代碼。我們為此功能創造了一個 問題 [19],探討了爭論的各個面向。雖然 hyper 團隊最終確實接受了這項更改,但他們有充分的理由拒絕這樣的要求,而這只是我們需要支持的眾多不合規行為案例之一。

    為了滿足Cloudflare 在HTTP 生態系統中的地位要求,我們需要一個穩健、寬容、可自訂的HTTP 庫,該庫可以在互聯網的各種風險環境中生存,並支援各種不合規的用例。保證這一點的最佳方法就是實施我們自己的架構。

    下一個設計決策關於我們的工作負載調度系統。我們選擇多執行緒而不是多處理 [20],以便輕鬆共享資源,尤其是連線池。我們認為還需要實施 工作竊取 [21] 來避免上述的某些類別的效能問題。 Tokio 非同步運作時結果 非常適合 [22] 我們的需求。

    最後,我們希望我們的專案直觀且對開發人員友好。我們建構的不是最終產品,而是應該可以作為一個平台進行擴展,因為在它之上建立了更多的功能。我們決定實作一個類似 NGINX/OpenResty[23] 的基於 「請求生命週期」 事件的可程式介面。例如,「請求過濾器」 階段允許開發人員在收到請求標頭時執行程式碼來修改或拒絕請求。透過這種設計,我們可以清楚地分離我們的業務邏輯和通用代理邏輯。先前從事 NGINX 工作的開發人員可以輕鬆切換到 Pingora 並迅速提高工作效率。

    Pingora 在生產中更快

    #讓我們快轉到現在。 Pingora 處理幾乎所有需要與來源伺服器互動的 HTTP 請求(例如快取未命中),我們在此過程中收集了許多效能資料。

    首先,讓我們看看 Pingora 如何加快我們客戶的流量。 Pingora 上的整體流量顯示,TTFB 中位數減少了 5 毫秒,第 95 個百分位數減少了 80 毫秒。這不是因為我們運行程式碼更快。甚至我們的舊服務也可以處理亞毫秒範圍內的請求。

    時間節省來自我們的新架構,它可以跨所有執行緒共享連線。這意味著更好的連線重用率,在 TCP 和 TLS 握手上花費的時間更少。 Pingora有多牛逼!超受欢迎 Web 服务器超越Nginx

    在所有客戶中,與舊服務相比,Pingora 每秒的新連線數只有三分之一。對於一個主要客戶,它將連線重用率從 87.1% 提高到 99.92%,這將新連線減少了 160 倍。更直觀地說,透過切換到 Pingora,我們每天為客戶和用戶節省了 434 年的握手時間。

    更多功能

    擁有工程師熟悉的開發人員友善介面,同時消除先前的限制,讓我們能夠更快地開發更多功能。像新協議這樣的核心功能可作為我們為客戶提供更多產品的基石。

    例如,我們能夠在沒有重大障礙的情況下在 Pingora 上新增 HTTP/2 上游支援。這使我們能夠在不久之後向我們的客戶提供 gRPC[24]。將相同的功能加入 NGINX 將需要 更多的工程工作,並且可能無法實現 [25]。

    最近,我們宣布推出了 Cache Reserve[26],其中 Pingora 使用 R2 儲存作為快取層。隨著我們為 Pingora 添加更多功能,我們能夠提供以前不可行的新產品。

    更有效率

    在生產環境中,與我們的舊服務相比,Pingora 在相同流量負載的情況下,消耗的 CPU 和記憶體減少了約 70% 和 67%。節省來自幾個因素。

    與舊的 Lua 程式碼 [27] 相比,我們的 Rust 程式碼運作 效率更高 [28]。最重要的是,它們的架構也存在效率差異。例如,在 NGINX/OpenResty 中,當 Lua 程式碼想要存取 HTTP 頭時,它必須從 NGINX C 結構中讀取它,分配一個 Lua 字串,然後將其複製到 Lua 字串中。之後,Lua 也對其新字串進行垃圾回收。在 Pingora 中,它只是一個直接的字串存取。

    多執行緒模型也使得跨請求共享資料更有效率。 NGINX 也有共享內存,但由於實施限制,每次共享內存訪問都必須使用互斥鎖,並且只能將字串和數字放入共享內存。在 Pingora 中,大多數共享項目可以透過原子引用計數器 [29] 後面的共享引用直接存取。

    如上所述,CPU 節省的另一個重要部分是減少了新的連線。與僅透過已建立的連線傳送和接收資料相比,TLS 握手成本顯然更為高昂。

    更安全

    在我們這樣的規模下,快速安全地發布功能十分困難。很難預測在每秒處理數百萬個請求的分散式環境中可能發生的每個邊緣情況。模糊測試和靜態分析只能緩解這麼多。 Rust 的記憶體安全語意保護我們免受未定義行為的影響,並讓我們相信我們的服務將正確運作。

    有了這些保證,我們可以更專注於我們的服務變更將如何與其他服務或客戶來源互動。我們能夠以更高的節奏開發功能,而不用背負記憶體安全和難以診斷崩潰的問題。

    當崩潰確實發生時,工程師需要花時間來診斷它是如何發生的以及是什麼原因造成的。自 Pingora 創立以來,我們已經處理了數百萬億個請求,至今尚未因為我們的服務代碼而崩潰。

    事實上,Pingora 崩潰是如此罕見,當我們遇到一個問題時,我們通常會發現不相關的問題。最近,我們的服務開始崩潰後不久,我們發現了一個核心錯誤 [30]。我們還在一些機器上發現了硬體問題,過去排除了由我們的軟體引起的罕見記憶體錯誤,即使在幾乎不可能進行重大調試之後也是如此。

    總結

    總而言之,我們已經建立了一個更快、更有效率、更通用的內部代理,作為我們目前和未來產品的平台。

    我們之後將介紹有關我們面臨的問題和應用優化的更多技術細節,以及我們從構建 Pingora 並將其推出以支持互聯網的重要部分的經驗教訓。同時也將介紹我們的開源計劃。

    本文轉載自 CloudFlare 博客,作者Yuchen Wu & Andrew Hauck

    連結:https://blog.cloudflare.com/zh-cn/how-we-built-pingora-the-proxy-that-c​​onnects-cloudflare-to-the-internet-zh-cn/

    以上是Pingora有多屌!超受歡迎 Web 伺服器超越Nginx的詳細內容。更多資訊請關注PHP中文網其他相關文章!

    相關標籤:
    來源:mryunwei.com
    本網站聲明
    本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
    熱門教學
    更多>
    最新下載
    更多>
    網站特效
    網站源碼
    網站素材
    前端模板
    關於我們 免責聲明 Sitemap
    PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!