關於envoy 代碼的底層文檔至關稀少。爲了解決這個問題我計劃編寫一系列文檔來描述各個子系統的工做。因爲是第一篇, 請讓我知道你但願其餘主題覆蓋哪些內容。html
一個我所瞭解到最共同的技術問題是關於envoy使用的線程模型的底層描述,這篇文章將會描述envoy如何使用線程來處理鏈接的(how envoy maps connections to threads), 同事也會描述Thread Local Storage(TLS)系統是如何使內部代碼平行且高效的。linux
Figure 1: Threading overviewgit
如同圖一所示,envoy使用了三中不一樣類型的線程。github
Main: 這個線程管理了服務器自己的啓動和終止, 全部xDS API的處理(包括DNS, 健康檢查(health checking) 和一般的集羣管理), running, 監控刷新(stat flushing), 管理界面 還有 通用的進程管理(signals, hot restart。在這個線程中發生的全部事情都是異步和非阻塞的(non blocking)。一般,主線程用來協調全部不須要大量cpu完成的關鍵功能。這容許大部分代碼編寫的如同單線程同樣。編程
Worker 在envoy系統中,默認爲每一個硬件(every harware)生成一個worker線程(能夠經過--concurrency參數控制),每一個工做線程運行一個無阻塞的事件循環(event loop),for listening on every listener (there is currently no listener sharding), accept 新的鏈接,爲每一個鏈接實例化過濾棧,以及處理這個鏈接生命週期的全部io。同時,也容許全部鏈接代碼編寫的如同單線程同樣。api
File flusher Envoy 寫入的每一個文件如今都有一個單獨的block的刷新線程。這是由於使用O_NONBLOCK在寫入文件系統有時也會阻塞。當Worker線程須要寫入文件時,這個數據實際上被移動至in-memory buffer中,最終會被File Flusher線程刷新至文件中。envoy的代碼會在一個worker試圖寫入memory buffer時鎖住全部worker。還有一些其餘的會在下面討論。緩存
如上所述, 全部worker線程都會監聽端口而沒有任何分片,所以,內核智能的分配accept的套接字給worker進程。現代內核通常都很擅長這一點,在開始使用其餘監聽同一套接字的其餘線程以前,內核使用io 優先級提高等特性來分配線程的工做,一樣的還有不適用spin-lock來如理全部請求。服務器
一旦woker accept了一個鏈接,那麼這個鏈接永遠不會離開這個worker(一個鏈接只由同一worker進行處理), 全部關於鏈接的處理都將會在worker線程內進一步處理,包括任何轉發行爲。這有一些重要的含義:網絡
Envoy鏈接池中都是worker線程,所以經過http/2 鏈接池每次只與上游主機創建一個鏈接,若是有4個worker,那在穩定狀態將只有四個http/2鏈接與上游主機進行鏈接。數據結構
Envoy以這種方式工做的緣由在於這樣幾乎全部的代碼均可以如同單線程同樣以沒有鎖的方式進行編寫。這個設計使得大部分代碼易於編寫和擴展。
從內存和鏈接池效率的角度來看,調整 - concurrency選項實際上很是重要。擁有比所須要的worker更多的worker將會浪費大量的內存,形成大量空置的鏈接,並致使較低的鏈接池命中率。在lyft,envoy以較低的concurerency運行,性能大體與他們旁邊的服務相匹配。
到目前爲止,在討論主線程和worker線程時咱們已經屢次使用術語'non-blocking'.幾乎全部代碼都是假設沒有阻塞的狀況下進行編寫。可是,這並不是徹底正確(哪裏有徹底正確的東西),Envoy確實使用了一些process wide locks.
因爲envoy將主線程的職責和worker線程的職責徹底分開,須要在主線程完成複雜的處理同時使每一個worker線程高度可用。本節將介紹Envoy的高級線程本地存儲(TLS)系統。在下一節中,我將描述如何使用它來處理集羣管理。
如已經描述的那樣,主線程基本上處理Envoy過程當中的全部管理/控制平面功能。(控制平面在這裏有點過載,但在特使程序自己考慮並與工人作的轉發進行比較時,彷佛是合適的)。主線程進程執行某些操做是一種常見模式,而後須要使用該工做的結果更新每一個工做線程,而且工做線程不須要在每次訪問時獲取鎖定
Envoy的TLS系統工做以下:
雖然很是簡單,但它很是強大與只讀副本更新鎖相似.(實質上,worker線程在工做時從不會看到插槽的數據發生任何改變, 變化只發生在event切換的時候5), Envoy用兩種不一樣的方式來使用它:
在本節中,我將描述TLS如何用於集羣管理。羣集管理包括xDS API處理和DNS以及運行情況檢查。
圖3顯示了涉及如下組件和步驟的整體流程:
經過這些過程,Envoy可以處理每一個請求而不使用任何鎖。除了TLS代碼自己的複雜度以外,大部分代碼無需理解線程是如何具體工做的,使用單線程的方式便可。這使得大部分代碼易於修改且性能較好。
TLS和RCU(Read-Copy Update6)在Envoy內普遍使用。
其餘一些例子包括:
還有其餘狀況,但前面的例子應該已經足夠闡釋TLS在envoy中的做用。
雖然Envoy總體表現至關不錯,可是當它以很是高的併發和高吞吐使用時,有一些已知的點須要注意:
Envoy的線程模型旨在簡化編程和提升性能,但若是設置不當,可能會浪費內存和鏈接使用。Envoy的線程模型容許它在很是高的worker數量和高吞吐量下表現良好。
正如我在Twitter上簡略提到的那樣,該設計也適合在DPDK之類的完整用戶模式網絡堆棧上運行,這使得商用服務器在進行完整的L7處理時每秒處理數百萬個請求。觀察envoy再接下來幾年如何進展是一件很是有趣的事情
。
最後一個評論:我屢次被問到爲何咱們爲Envoy選擇C ++?
緣由是它仍然是惟一普遍部署的生產語言,在該語言中能夠構建本文所述的體系結構。C ++固然不適合全部項目,甚至許多項目,但對於某些用例,它仍然是完成工做的惟一工具。