當前位置:
首頁 > 科技 > Nginx動態路由的新姿勢:使用Go取代lua

Nginx動態路由的新姿勢:使用Go取代lua

導語: 在Nitro 中, 我們需要一款專業的負載均衡器。 經過一番研究之後,Mihai Todor和我使用Go構建了基於Nginx、Redis 協議的路由器解決方案,其中nginx負責所有繁重工作,路由器本身並不承載流量。 這個解決方案過去一年在生產環境中運行順暢。 以下是我們所做的工作以及我們為什麼那樣做。

為什麼

我們正在構建的新服務將位於負載均衡池之後,負責執行代價很高的計算任務,正因如此,我們需要做本地緩存。 為了緩存優化, 我們想嘗試將相同資源的請求發送到同一主機上(如果這台主機是可用的)。

解決這個問題有很多現有方案,以下是一個不完全的清單列表:

  • 利用cookie維護黏性session

  • 利用Header

  • 基於源IP的黏性

  • HTTP重定向到正確實例

這個服務在每個頁面載入時將會被觸發多次, 因此出於性能的考慮, HTTP重定向方式並不可行。 如果所有的入站請求都通過同樣的負載均衡器,那麼剩下的幾種解決方案都可以正常工作。 另一方面, 如果你的前端是一個負載均衡器池, 你需要能夠在它們之間共享狀態或實現複雜的路由邏輯。 我們對當前需要在負載均衡器之間共享狀態變更的設計並沒有興趣,因此我們為這個服務選擇了更複雜的路由邏輯。

我們的架構

了解一下我們的設計架構也許能夠幫你更好的理解我們的意圖。

我們擁有一組前端負載均衡器,這些服務的實例被部署在Mesos, 以便根據服務規模和資源可用性進行進出控制。 將主機和埠號列表放入負載均衡器中不是問題,這已經成為我們平台的核心。

因為一切都在Mesos上運行, 並且我們擁一種簡單的方式定義和部署服務,所以添加任何新服務都很簡單。

在Mesos之上, 我們在每處都運行著基於gossip的Sidecar來管理服務發現。 我們的前端負載均衡器是由Lyft的Envoy組成 , 它背後由Sidecar的Envoy集成支持。 這能滿足大部分服務的需求。 Envoy主機運行在專用實例上, 但所有的服務都根據需要, 在主機之間遷移,由Mesos和Sigualarity調度器執行。

仍在考慮中的Mesos服務節點將擁有基於磁碟的本地緩存。

設計

看著這個問題我們下了決定,我們著實想要一種一致性哈稀環。 我們可以讓節點根據需要控制進出,只有那些節點所服務的請求才會被重新路由。 剩下的所有節點將繼續服務於任何公開的會話。 我們可以很簡單地通過Sidecar數據來支持一致性哈稀環 (你可以用Mesos 或k8s代替) 。 Sidecar健康檢查節點, 我們可以靠這些健康檢查節點判斷它們在Sidecar中是否工作正常。

然後,我們需要某種一致性哈稀方法將流量導入到正確的節點中。它需要接收每一個請求, 識別問題資源, 然後將請求傳遞給其他已經準備處理該資源的服務實例。

當然, 資源識別可以簡單的通過URL處理,並且任何負載均衡器能夠將他們分開來處理簡單的路由。 所以我們只需要將他們與一致性哈稀關聯起來,對此我們已經有一種解決方案。

你可以在nginx用lua那樣做, 也可在HAproxy中用lua 。 在Nitro里, 我們沒有一個人是Lua 專家,並且顯然沒有庫能夠實現我們的需要。 理想情況下, 路由邏輯將在Go中實現, Go在我們的技術棧中是一門關鍵語言並且得到了很好的支持。

Nginx有著豐富的生態環境, 跳脫常規的思路還引發了一些很有趣的nginx插件。 這些插件中首選插件Valery Kholodko的nginx-eval-module。 這個插件允許你從nginx到一個端點生成一個調用,並且將返回的結果評估為nginx的變數。 在其他可能的作用中, 這個插件的意義在於它允許您動態地決定哪個端點應該接收代理傳遞。 這就是我們想要做的。 你從Ngnix到某個地方生成一個調用, 獲取一個結果後, 你可以根據返回的結果值生成路由決策。 你可以使用HTTP服務實現該請求的接收方。 該服務僅返回目標伺服器端點的主機名和埠號的字元串。 這個服務始終保持一致性哈希,並且告知Nginx 每個請求流量路由的位置 , 但是生成一個單獨的HTTP請求,仍然有些笨重。 整個預期的回復內容將會是字元串10.10.10.5:23453。 通過HTTP,我們會在兩個方向傳遞頭部信息,這將大大超出響應正文的大小。

於是我開始研究Nginx支持的其他協議, 發現memcache協議和redis協議它都支持。其中,對Go服務最友好的支持是Redis協議。所以那就是我們改進的方向。Nginx 中有兩個Redis模塊,有一個適合通過nginx-eval-module 使用。 實現Redis Go語言最好的庫是Redeo。Rodeo實現了一個極其簡單的處理機制,非常類似於go標準庫中的http包。 任何redis協議命令將會包含一個handler函數,並且它的寫法非常簡單。 相比Nginx插件,它能夠處理更新版本的redis協議。 於是, 我摒棄了我的C技能,並補充了Nginx插件以使用最新的Redis協議編碼。

於是, 我們最新的解決方案是:

Nginx動態路由的新姿勢:使用Go取代lua

這個調用從公網進入, 觸發一個Envoy 節點, 然後到一個Nginx節點.Nginx 節點(1) 詢問路由器將請求送至何處。 然後Nginx節點(2)將請求送至指定的服務端點。

實現

我們在Go中建立了一個庫來管理由Sidecar或Hashicorp的Memberlist庫支持的一致性哈希。我們稱之為Ringman庫。然後,我們將該庫強制接入Redeo庫支持的Redis協議請求的服務中。

這種方案只需要兩個Redis命令:GET和SELECT。我們選擇實現一些用於調試的的命令,其中包括INFO,可以用您想要的任何伺服器狀態進行回復。在兩個必需的命令中,我們可以放心地忽略SELECT,這是用由於選擇Redis DB以用於任何後續調用。我們只接受它,什麼也不做。GET讓所有的工作都很容易實現。以下是通過Redis和Redeo為Ringman端點提供服務的完整功能。 Nginx會傳遞它接收到的URL,然後從哈希環中返回端點。

Nginx動態路由的新姿勢:使用Go取代lua

這是Nginx使用以下配置調用:

Nginx動態路由的新姿勢:使用Go取代lua

Nginx動態路由的新姿勢:使用Go取代lua

Nginx動態路由的新姿勢:使用Go取代lua

我們調用Nginx和容器里的路由,讓他們在同樣的host上運行,這樣我們就可以在其中實現較低成本的調用。

以下是我們建立的Nginx:

Nginx動態路由的新姿勢:使用Go取代lua

性能

我們在自有環境中進行了細緻的性能測試, 我們看到,通過Redis協議從Nginx到Go路由器的平均響應時間大約為0.2-0.3ms。由於來自上游服務的響應時間的中值大約為70毫秒,所以這是可以忽略的延遲。

一個更複雜的Nginx配置大概能夠做更複雜的錯誤處理。服務運行了一年多可靠性非常好,性能一直很穩定。

結束語

如果您有類似需求,則可以復用大部分組件。只需按照上面的鏈接到實際的源代碼。如果您有興趣直接向Ringman添加對K8或Mesos的支持,我們會非常歡迎。

這個解決方案聽起來有點黑客,不過它最終成為我們基礎設施的重要補充。希望它能幫助別人解決類似的問題。

本文作者Karl Mathias,由王賀翻譯。轉載譯文請註明出處,技術原創及架構實踐文章,歡迎通過公眾號菜單「聯繫我們」進行投稿。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 高可用架構 的精彩文章:

程序員如何跑得比別人更快?微博研發總監李慶豐訪談

TAG:高可用架構 |