最近因工做緣由開始瞭解Service Mesh與Envoy,爲系統性梳理所學內容,所以沉澱了此文檔,但因爲所知有限,如文檔中有描述不當之處,但願不吝賜教。html
提到Envoy就不得不提Service Mesh,說到Service Mesh就必定要談及微服務了,那麼咱們就先放下Envoy,簡單瞭解下微服務、Service Mesh以及Envoy在Service Mesh中處於一個什麼樣的角色。git
過去幾年間,架構領域最火的方向非微服務莫屬,那麼微服務架構到底爲咱們帶來了什麼樣的好處呢?下面經過一張圖說明架構的演進,以下:
github
伴隨着業務規模的變大,微服務的好處顯而易見,例如它自己所具有的可擴展性、易維護性、故障和資源隔離性等諸多特性使得產品的生產研發效率大大提升,同時,基於微服務架構設計,研發人員能夠構建出原生對於「雲」具有超高友好度的系統,讓產品的持續集成與發佈變得更爲便捷。編程
然而沒有所謂的銀彈,微服務帶來不少好處的同時也引入了不少問題。在雲原生模型裏,一個應用能夠由數百個服務組成,每一個服務可能有數千個實例,每一個實例的狀態可能持續的發生變化,此時,服務間的通訊不只異常複雜,並且都是運行時的行爲,管理好服務間通訊對於保證端到端的性能與可靠性來講無疑成爲重中之重。在Service Mesh沒有出現以前,微服務框架之間的通信大多采用SDK方案,但該方式短板也很是明顯,例如對業務有侵入性、沒法作到SDK升級對業務透明等。後端
基於以上種種複雜緣由催生了服務間通信層的出現,這個層即不該該與應用程序的代碼耦合,又能捕獲到底層環境的動態變化並做出適當的調整,避免業務出現單點故障;同時也可讓開發者只關注自身業務,將應用雲化後帶來的諸多問題以不侵入業務代碼的方式提供給開發者。安全
上述所說的這個服務間通信層就是Service Mesh(國內一般翻譯爲服務網格),它能夠提供安全、快速、可靠的服務間通信。若是用一句話來解釋什麼是Service Mesh,能夠將其比做微服務間的TCP/IP層,負責服務之間的調用、限流、熔斷和監控等。性能優化
讀到這裏你們必定仍然存在這樣的疑惑,Service Mesh究竟是什麼呢?這是一個全新的東西嗎?它的演進過程是什麼樣的呢?下面使用一張圖來講明其演進過程,以下:
從上圖能夠看到最初的Service Mesh始於一個網絡代理,在2016年1月業界第一個開源項目Linkerd發佈,同年9 月 29 日的 SF Microservices 大會上,「Service Mesh」這個詞彙第一次在公開場合被使用,隨後Envoy也發佈了本身的開源版本,但此時的Service Mesh更多停留在Sidecar層面,並無清晰的Sidecar管理面,所以屬於Service Mesh的第一代。此時雖然Service Mesh尚不成熟,但一個初具雛形的服務間通信層已然出現,以下圖:
隨後Google聯合IBM、Lyft發起了Istio項目,從架構層面明確了數據平面、控制平面,並經過集中式的控制平面概念進一步強化了Service Mesh的價值,再加上巨頭背書的緣故,所以Service Mesh、Istio概念迅速火爆起來。此時已然進入到了第二代的Service Mesh,控制平面的概念及做用被你們承認並接受,而更重要的一點是至此已經造成了一個完整意義上的SDN服務通信層。此時的Service Mesh架構以下圖:
至此Service Mesh的背景信息基本介紹完畢,接下來開始進入正題說說Envoy相關的內容。其在完整的Service Mesh體系中處於一個什麼位置呢?繼續看圖:服務器
Envoy是Istio中的Sidecar官方標配,是一個面向服務架構的高性能網絡代理,由C++語言實現,擁有強大的定製化能力,經過其提供的Filter機制基本能夠對請求轉發過程當中超過50%的流程作定製化,在性能方面因爲其實現參考了Nginx,也處於主流水平,固然還有不少特性,在這裏就不作一一介紹了。網絡
任何軟件架構設計,其核心都是圍繞數據展開的,基本上如何定義數據結構就決定了其流程的走向,剩下的不外乎加上一些設計手法,抽離出變與不變的部分,不變的部分最終會轉化爲程序的主流程,基本固化,變的部分儘可能保證擁有良好的擴展性、易維護性,最終會轉化爲主流程中各個抽象的流程節點。數據結構
對於Envoy也不例外,做爲一個網絡代理程序,其核心職責就是完成請求的轉發,在轉發的過程當中人們又但願能夠對其作必定程度的微處理,例如附加一個Header屬性等,不然就不必使用代理程序了。那麼Envoy是如何運做的呢?它是如何定義其數據結構,並圍繞該數據結構設計軟件架構、程序流程,又是如何抽象出變得部分,保證高擴展性呢?
帶着這些疑問,試想Envoy做爲一個高度可定製化的程序,其定製化的載體必然是配置信息,那麼咱們下面就試着從Envoy的一份配置來解讀其架構設計與程序流程。
在查看其配置前,咱們不妨先腦補一下網絡代理程序的流程,好比做爲一個代理,首先要能獲取請求流量,一般是採用監聽端口的方式實現,其次拿到請求數據後須要對其作些微處理,例如附加Header頭或校驗某個Header字段內容等,這裏針對來源數據的層次不一樣,就能夠分爲L3L4L7,而後將請求轉發出去,轉發這裏又能夠衍生出若是後端是一個集羣,須要從中挑選出一臺機器,如何挑選又涉及到負載均衡等。腦補下來大體流程應該就是這個樣子,接下來咱們看看Envoy是如何組織其配置信息的。
Envoy配置的簡單配置信息以下:
關鍵字段說明:
Listener: 服務(程序)監聽者。就是真正幹活的。 Envoy 會暴露一個或者多個listener監聽downstream的請求。
Filter: 過濾器。在 Envoy 中指的是一些「可插拔」和可組合的邏輯處理層。是 Envoy 核心邏輯處理單元。
Route_config: 路由規則配置,即請求路由到後端那個集羣(cluster)。
Cluster: 服務提供方集羣。Envoy 經過服務發現定位集羣成員並獲取服務。具體請求到哪一個集羣成員是由負載均衡策略決定。經過健康檢查服務來對集羣成員服務狀態進行檢查。
根據上面咱們腦補的流程,配合上這份配置的話,Envoy大體處理流程以下圖:
Envoy內部對請求的處理流程其實跟咱們上面腦補的流程大體相同,即對請求的處理流程基本是不變的,而對於變化的部分,即對請求數據的微處理,所有抽象爲Filter,例如對請求的讀寫是ReadFilter、WriteFilter,對HTTP請求數據的編解碼是StreamEncoderFilter、StreamDecoderFilter,對TCP的處理是TcpProxyFilter,其繼承自ReadFilter,對HTTP的處理是ConnectionManager,其也是繼承自ReadFilter等等,各個Filter最終會組織成一個FilterChain,在收到請求後首先走FilterChain,其次路由到指定集羣並作負載均衡獲取一個目標地址,而後轉發出去。
聊完了基本流程後,本節會試着分析其架構設計,但願從其架構設計中得到一些益處。
先賣個關子,在本節開始以前咱們不妨先思考一個有趣的問題:Envoy自己採用C++開發的,廣泛承認C++程序執行性能會更好,那麼延伸下來能夠想到Envoy的設計目標彷佛是在追求高性能,那麼真是如此嗎?
在探究Envoy架構設計以前,咱們先來看看Envoy自身是怎麼描述其設計目標的,以下:
Envoy並非很慢(咱們已經花了至關長的時間來優化關鍵路徑)。基於模塊化編碼,易於測試,而不是性能最優。咱們的觀點是,在其餘語言或者運行效率低不少的系統中,部署和使用Envoy可以帶來很好的運行效率。
很是有意思的表述,Envoy並無把追求極致性能做爲目標,那麼其架構設計會弱化性能這塊嗎?
目前業內公認代理程序性能最好的是Nginx,其採用了per thread one eventloop模型,這種架構被業內廣泛借鑑,那麼Envoy呢?咱們先看看下面的架構圖:
看到裏面Worker的工做方式是否是很熟悉,會不會有一點點困惑呢?呵呵,沒錯,Envoy也採用了類Nginx的架構,方式是:多線程 + 非阻塞 + 異步IO(Libevent),雖然Envoy沒有把極致性能做爲目標,但不等於沒有追求,只不過是相對於擴展性而言級別稍微低一點而已。
Envoy的另外一特色是支持配置信息的熱更新,其功能由XDS模塊完成,XDS是個統稱,具體包括ADS(Aggregated Discovery Service)、SDS(Service Discovery Service)、EDS(Endpoint Discovery Service)、CDS(Cluster Discovery Service)、RDS(Route Discovery Service)、LDS(Listener Discovery Service)。XDS模塊功能是向Istio的Pilot獲取動態配置信息,拉取配置方式分爲V1與V2版本,V1採用HTTP,V2採用gRPC。
Envoy還支持熱重啓,即重啓時能夠作到無縫銜接,其基本實現原理是:
Envoy一樣也支持Lua編寫的Filter,不過與Nginx同樣,都是工做在HTTP層,具體實現原理都同樣,不作贅述了。
到此爲止咱們看完了上面的架構圖,若是你對其內部實現也有興趣的話,能夠看看下面的內部實現類圖:
其內部實現爲了靈活性,作了不少抽象封裝,但基本上能夠拆分爲幾個大的功能模塊,具體如上圖,再也不贅述。
軟件的世界歷來就不存在什麼銀彈,雖然ServiceMesh優點很明顯,甚至被尊稱爲服務間的通信層,但不能否認的是ServiceMesh的到來確實對應用的性能帶來了損耗,能夠從兩個方面看待此問題:
本節主要談論Envoy在性能方面的努力及社區在性能方面呼聲較高的一些內容。
Envoy做爲Sidecar其提供的核心功能能夠簡單總結爲如下三點:
從上述三點中咱們試着分析下性能優化的關鍵點,其中第一、3點是與業務基本無關的,屬於通用型功能,而第2點的性能是與業務複雜度呈現相關性的,好比請求校驗規則的多與少、遙測數據的採集精細度、數據統計的維度多樣性等,所以最有可能提高Sidecar性能的點就是對請求的攔截與Sidecar之間通信協議的高效性。
針對請求的攔截,目前常規的作法是使用iptables,在部署Sidecar時配置好iptables的攔截規則,當請求來臨後iptables會從規則表中從上至下順序查找匹配規則,若是沒遇到匹配的規則,就一條一條往下執行,若是遇到匹配的規則,那就執行本規則並根據本規則的動做(accept, reject, log等),決定下一步執行的狀況。爲了更直觀的展現iptables的執行過程,請看下圖:
瞭解iptables的基本流程後,不難發現其性能瓶頸主要是兩點:
既然知道了iptables的缺陷,那麼優化手段不外乎從這兩點下手,而Linux社區與Envoy社區也正在計劃對此作優化,具體以下:
爲何規避Linux正常協議處理過程當中內核態與用戶態的轉換如此重要呢?就以對咱們最直觀的內存拷貝爲例,正常狀況下,一個網絡數據包從網卡到應用程序須要通過以下的過程:數據從網卡經過 DMA 等方式傳到內核開闢的緩衝區,而後從內核空間拷貝到用戶態空間,在 Linux 內核協議棧中,這個耗時操做甚至佔到了數據包整個處理流程的 57.1%。爲了更直觀的對內存拷貝消耗有所瞭解,畫了一張簡圖,以下:
DPDK全稱Intel Data Plane Development Kit,是Intel提供的數據平面開發工具集,爲Intel Architecture(IA)處理器架構下用戶空間高效的數據包處理提供庫函數和驅動的支持,它不一樣於Linux系統以通用性設計爲目的,而是專一於網絡應用中數據包的高性能處理,它將數據包處理、內存管理、處理器調度等任務轉移到用戶空間完成,而內核僅僅負責部分控制指令的處理。這樣就解決了處理數據包時的系統中斷、上下文切換、系統調用、系統調度等問題。
VPP是the vector packet processor的簡稱,是一套基於DPDK的網絡幀處理解決方案,是一個可擴展框架,提供開箱即用的交換機/路由器功能。是Linux基金會下開源項目FD.io的一個子項目,由思科貢獻的開源版本,目前是FD.io的最核心的項目。
整個DPDK仍是很是複雜的,經過一兩篇文章很難說清楚,且本文重點也不在DPDK,所以下面只簡單介紹下其基本原理,讓咱們大體清楚爲何Envoy引入VPP後能夠大幅提高請求處理轉發效率。
爲了說清楚DPDK是如何大幅提高了數據包的處理性能,咱們先看一下普通的數據包在Linux中的收發過程,以下圖:
經過上面兩張圖咱們能夠大體清楚數據包的一個完整的收發過程,能夠看到整個處理鏈路仍是比較長的,且須要在內核態與用戶態之間作內存拷貝、上下文切換、軟硬件中斷等。雖然Linux設計初衷是以通用性爲目的的,但隨着Linux在服務器市場的普遍應用,其原有的網絡數據包處理方式已很難跟上人們對高性能網絡數據處理能力的訴求。在這種背景下DPDK應運而生,其利用UIO技術,在Driver層直接將數據包導入到用戶態進程,繞過了Linux協議棧,接下來由用戶進程完成全部後續處理,再經過Driver將數據發送出去。原有內核態與用戶態之間的內存拷貝採用mmap將用戶內存映射到內核,如此就規避了內存拷貝、上下文切換、系統調用等問題,而後再利用大頁內存、CPU親和性、無鎖隊列、基於輪詢的驅動模式、多核調度充分壓榨機器性能,從而實現高效率的數據包處理。說了這麼多,接下來咱們看下在DPDK中數據包的收發過程,以下圖:
經過對比得知,DPDK攔截中斷,不觸發後續中斷流程,並繞過內核協議棧,經過UIO(Userspace I/O)技術將網卡收到的報文拷貝到應用層處理,報文再也不通過內核協議棧。減小了中斷,DPDK的包所有在用戶空間使用內存池管理,內核空間與用戶空間的內存交互不用進行拷貝,只作控制權轉移,減小報文拷貝過程,提升報文的轉發效率。
DPDK可以繞過內核協議棧,本質上是得益於 UIO 技術,UIO技術也不是DPDK創立的,是內核提供的一種運行在用戶空間的I/O技術,Linux系統中通常的驅動設備都是運行在內核空間,在用戶空間用的程序調用便可,UIO則是將驅動的不多一部分運行在內核空間,絕大多數功能在用戶空間實現,經過 UIO 可以攔截中斷,並重設中斷回調行爲,從而繞過內核協議棧後續的處理流程。
那麼UIO是如何攔截中斷的呢?咱們先看看做爲一個設備驅動的兩個主要職責:
UIO的實現機制實際上是對用戶空間暴露文件接口,好比當註冊一個 UIO 設備 uioX,就會出現文件 /dev/uioX,對該文件的讀寫就是對設備內存的讀寫。除此以外,對設備的控制還能夠經過 /sys/class/uio 下的各個文件的讀寫來完成。UIO架構及流程圖以下,再也不贅述。
說完了DPDK,那麼Cilium又是如何提升報文轉發效率呢?既然Cilium 是基於 eBPF 和 XDP 實現的,而XDP歸根結底也是利用eBPF爲Linux內核提供高性能、可編程的網絡數據路徑框架,既然核心是eBPF,那麼咱們先了解下eBPF是什麼。
eBPF(extended Berkeley Packet Filter)起源於BPF,它提供了內核的數據包過濾機制。Linux 3.15 開始引入 eBPF。其擴充了 BPF 的功能,豐富了指令集。它在內核提供了一個虛擬機,用戶態將過濾規則以虛擬機指令的形式傳遞到內核,由內核根據這些指令來過濾網絡數據包。直白地講就是咱們可讓內核按照咱們的規則來對數據包進行處理,包括未進入協議棧以前的處理哦,有沒有瞬間以爲eBPF很牛逼,既然都這麼強大了,有沒有什麼最佳實踐或者應用呢?請看下圖: