1. 背景程序員
隨着互聯網業務不斷髮展, 業務服務以及服務實例呈快速增加的趨勢,然而傳統微服務架構雖然能在一些場景知足服務高性能, 高可用, 可治理等需求,但同時也面臨着耦合性高,靈活性差,管理複雜,可運維性低,缺少多語言支持等問題。而現在雲原生場景下,Service Mesh則愈來愈成爲熱議的話題。後端
ESA Mesh是OPPO互聯網自研的Service Mesh組件, 隸屬於ESA Stack微服務體系的一部分。ESA Mesh致力於提供雲原生場景適合公司的Mesh方案,解決公司跨語言調用難,多語言服務治理生態匱乏,服務治理不統一等諸多問題。提供雲原生場景下彈性易用的微服務架構基礎組件,層層突破微服務落地難,Service Mesh落地難上難的困境。網絡
隨着近幾年來雲原生生態的不斷壯大,CNCF基金會中的會員以及容納的項目愈來愈多,CNCF爲雲原生進行了定位。架構
如下是CNCF對雲原生的從新定義(中英對照):app
Cloud native technologies empower organizations to build and run scalable applications in modern,dynamic environments such as public, private, and hybrid clouds. Containers, service meshes,microservices, immutable infrastructure, and declarative APIs exemplify this approach.
雲原生技術有利於各組織在公有云、私有云和混合雲等新型動態環境中,構建和運行可彈性擴展的應用。雲原生的表明技術包括容器、服務網格、微服務、不可變基礎設施和聲明式API。負載均衡
可見Service Mesh(服務網格)在CNCF的定義中已然成爲雲原生時代不可或缺的一部分, 而且同時與容器,微服務有着密不可分的關係。框架
最初人們想要讓不一樣計算機之間進行通信時, 最簡單的模型是這樣的。運維
雖然要真正完成計算機之間的交互須要很是多的網絡細節, 可是上圖依然是用戶最原始的需求:一個計算機上的服務調用另外一個計算機上的服務。tcp
可是實際上的交互須要更多的網絡細節上ide
上圖中網絡通信的細節是經過Networking Stack實現, 可是早年間這層網絡細節仍然是須要人們人爲的去管理網絡鏈接等細節,直到計算機開始變得不是那麼的昂貴,開始逐漸普及,計算機與計算機之間的鏈接需求開始了爆發式的增加,如何讓計算機能發現其餘的計算機, 如何有效控制計算機之間的流量, 特別是如何進行流量控制等成了廣泛性的問題。
因而爲了知足流量控制的功能需求, 人們在本身的應用中開發了流量控制的功能, 可是此功能邏輯的代碼與業務邏輯交織於一處。
直到TCP/IP的出現以及興起讓網絡細節問題以及流量控制等功能都獲得了統一且標準化的解決, 同時成爲計算機系統的一部分供用戶透明的使用。
直至今天互聯網大多都依賴着TCP/IP提供的基礎能力完成着上層複雜的功能。
微服務的出現能夠說掀起了互聯網服務實現與組織方式新的浪潮。同時也帶來了一些新的技術以及功能上的挑戰。
微服務強調着服務的細化(服務劃分或者說拆分)以及架構的輕量化,同時出現了一些新的需求:服務發現, 熔斷, 負載均衡等等。
初期面對這樣的需求聰明的程序員老是能很快的在業務中便實現相應的功能, 可是遭遇了與最初網絡計算機交互時代時一樣的問,這些功能與業務邏輯混雜在一塊兒, 難以管理與複用。
因而一些先驅者便將這些功能的實現打包成Library(或者說SDK)並公開給世界各地的程序員使用, 避免了重複造輪子,同時也讓不少沒有那麼多精力去研究此類技術的公司或者我的能快速的享受到前人的智慧結晶。
此間變出現了Spring Cloud, Dubbo等優秀的微服務框架, Spring生態更甚至能夠說當今Java生態中的「殺手鐗」,這些優秀的框架或是組件很大程度上推動了微服務的發展和標準化。
這個問題彷佛已經有了比較明確的答案。
微服務廣泛存在着落地困難的問題多語言支持困難
Library與業務耦合
Library升級地獄
陡峭的學習曲線
指數級增長的系統複雜度
...
與上面的Networking Stack同樣, 人們彷佛迫切的想要屏蔽掉一些通用基礎組件。
但是現在TCP/IP網絡棧已經足夠的穩定,彷佛不容許人們直接將微服務能力下沉至此, 因而便有了Sidecar的概念。Sidecar就是與應用程序一塊兒運行的獨立進程,爲應用程序提供額外的功能。
每一個服務都會有一個Sidecar與之配對。因而在錯綜複雜的服務部署結構下便會造成下圖。
全部的服務通信都經由Sidecar代理, 造成網狀, 所以稱之爲:服務網格。
Service Mesh的概念最初由Buoyant 的 CEO William Morgan在博客上的一篇文章What's a service mesh? And why do I need one?中提出。
其定義
A service mesh is a dedicated infrastructure layer for handling service-to-service communication. It’s responsible for the reliable delivery of requests through the complex topology of services that comprise a modern, cloud native application. In practice, the service mesh is typically implemented as an array of lightweight network proxies that are deployed alongside application code, without the application needing to be aware. (But there are variations to this idea, as we’ll see.)
服務網格是一個基礎設施層,用於處理服務間通訊。雲原生應用有着複雜的服務拓撲,服務網格保證請求能夠在這些拓撲中可靠地穿梭。在實際應用當中,服務網格一般是由一系列輕量級的網絡代理組成的,它們與應用程序部署在一塊兒,但應用程序不須要知道它們的存在。
這個定義最強有力的部分在於,它再也不把代理當作單獨的組件,並強調了這些代理所造成的網絡的重要性。
Cloud Native時代服務便以下圖所示
一個Cloud Native App部署時都將自動部署一個Sidecar與之對應, 服務期間全部的服務通信都經由Sidecar代理,同時Sidecar配置Control Plane(控制面板)完成諸如服務發現, 熔斷, 負載均衡, 限流, 鏈路追蹤等功能。而相對的業務服務僅僅須要關注本身的業務邏輯和一個僅僅用於通信的輕量級RPC便可。業務無需關注Service Mesh層面的邏輯,甚至沒法感知到它們的存在, 僅僅只須要像是咱們最初的網絡計算機交互時的模型同樣, 看成僅僅是服務調用了另一個服務便可。
業內已有許多優秀的Service Mesh開源組件
Linkerd(by: Buoyant)
Istio(by: Google, IBM)
Envoy(by: Lyft)
ServiceComb(by: 華爲)
SOFA Mesh(by: 螞蟻)
Nginmesh(by: Nginx)
TSF(by: Tencent)
隨着公司上「雲」步伐的層層邁進, 咱們已然具有了雲原生時代雄厚的基礎實力, 在此之上ESA Mesh致力於提供雲原生場景適合公司的彈性, 易用, 可靠,可觀察的Mesh方案。
跨語言支持
高性能,低延遲
業務無感知
服務發現
負載均衡
路由
熔斷/限流/隔離
多協議支持
故障注入
鏈路追蹤
..
咱們並非開發一套新的微服務生態, 而是用新的方式服務業務。
Service Mesh架構中首當其衝的問題即是如何攔截業務流量, 並引流到Sidecar的問題。
Istio中的流量攔截方式即採用的iptables實現,經過一系列的iptables 規則將業務Pod中的Inbound流量以及Outbound流量均Redirect到Sidecar中隨後由Sidecar處理。
此方式好處在於
用戶無感知
用戶無需感知Sidecar的存在, 像日常同樣進行RPC調用便可。
隨意接管任意流量
因爲iptables的規則很是的靈活, 全部Netfilter以後流量都可經過不一樣的規則實現流量接管。
老應用無縫遷移
甚至能夠在老應用徹底不用變動的狀況下, 接管全部的服務註冊/發現,服務調用的流量。
可同時iptables也存在着諸多問題
性能不理想
iptables的性能老是使人詬病的地方, 最初其存在的目標是用於網絡防火牆使用, 而且多年來Linux中的iptables並沒有太大改變(雖然隨後推出了nftables), 可是隨着iptables規則的增長,遍歷帶來的消耗劇增, 性能以及網絡延遲嚴重降低。
沒法增量更新
每次添加新規則時,必須更新整個規則列表。裝配2萬個Kubernetes服務產生16萬條的iptables規則須要耗時5個小時
應用流量複雜,複雜度高
Istio中是攔截全部的業務Pod流量, 而實際業務中除了RPC調用以外每每還存在着不少別的流量,錯綜複雜的流量對於Sidecar處理來講相對比較困難。
暫時沒法使用eBPF
現階段Linux內核版本大多爲3.x, 不太建議採用eBPF攔截方案(理論可行, 但一般須要4.x, 甚至4.8+使用XDP)。
eBFP爲比較理想的流量攔截方式, 具備性能高,靈活性強, 功能豐富等諸多特色。
在eBPF以前不得不聊聊BPF, BPF全稱Berkeley Packet Filter, 顧名思義這是一個用於過濾(filter)網絡報文(packet)的架構。
BPF的架構很是的簡潔, 途經網卡驅動層的報文在上報給協議棧的同時會多出一路來傳送給 BPF,再經後者過濾後最終拷貝給用戶態的應用。全部的過濾操做都在內核空間完成。單這麼看可能會有些許陌生, 可是若是提到大名鼎鼎的tcpdump以及wireshark想必便了然於心了, BPF即爲tcpdump以及wireshark的基礎, 乃至許多網絡監控領域的基石。最初在Linux中爲LSF(Linux Socket Filter),其實現幾乎與BPF無異。後改名爲cBPF(classical BPF)。
Linux 3.15版本伊始,eBPF便進入人們的視野, 並在隨後v3.17被添加到kernel/bpf 下(得到一等公民待遇),並以eBPF命名(extended BPF), 同時先前的LSF改名爲cBPF(classical BPF)。
相比於cBPF而言eBPF這次升級屬革命性改變
全新的開發接口
基於 map 的內核與用戶空間的交互方式
豐富了指令集
In-kernel verifier
C語言編寫程序
早期的cBPF所覆蓋的功能範圍很簡單而 eBPF 的利用範圍則要廣的多
XDP(eXpress Data Path)
流量控制
網絡包跟蹤
防火牆
應用性能調優/監控
cgroups
eBPF相較於cBFP帶來了大幅度的性能提高,同時在內核追蹤(Kernel Tracing)、應用性能調優/監控、流控(Traffic Control)等領域也帶來更多更豐富的特性和可能性。
ESA Mesh初期並未採用流量攔截的方式(雖然很想)來導入流量到Sidecar, 而是採用了輕量級Mesh SDK的方式直接從RPC客戶端定向打到Sidecar。
上圖能夠看到實際通信時,採用了Unix Domain Socket的方式進行業務與Sidecar的通信以求獲取更高的性能, 由於Sidecar始終會與業務Pod在同一個Node節點(物理機), 所以不必經過端口地址的方式, 直接進程間通信便可。
此種方式的好處
流量已知,可控
Sidecar全部接收到的流量都是本身指望的流量, 不會受到干擾。
服務治理參數傳遞方便
一般Sidecar進行服務治理時或多或少都須要一些特定的參數(好比AppId), 而使用SDK即可隨意傳遞想要的參數。
可規避協議探測邏輯
能夠將不一樣的協議分別在不一樣的端口上啓動, 避免籠統的綁定一個地址接受全部流量時頻繁的協議探測(有的協議理論上是沒法探測的, 好比Http協議)。
可是一樣也存在着問題
多語言問題
又回到了多語言須要提供SDK的問題。
業務SDK侵入
不可避免的形成了必定程度的SDK侵入
出於前期簡單化考慮, 咱們仍是選擇Mesh SDK的方式與Sidecar進行通信。
業內一般存在着兩種部署方案, 一種是Sidecar與業務在同一個Pod,分屬不一樣的Container(稱之爲Sidecar注入模式), Sidecar注入模式也是Istio採用的部署方案。而另外一種模式則是將Sidecar獨立使用DaemonSet部署, 讓每一個Node節點都啓動一個Sidecar實例爲當前節點的業務Pod服務
Sidecar注入的方式能夠說是Service Mesh中Sidecar部署模式的最終形態
它具備如下特色
隔離性強
全部諸如配置, 限流,鏈接等資源都是業務獨享, 不會和其餘業務互相影響。
擴展性強
Sidecar隨着業務Pod發佈自動注入, 業務擴縮容均不影響Sidecar提供服務。
可用性高
一個Sidecar僅服務於單個業務Pod, 即便一個Sidecar故障也僅會影響一個業務實例
資源佔用-按需
Sidecar隨着業務Pod發佈自動注入, 而且能夠根據業務需求分配不一樣的資源給Sidecar, 作到按需使用。
服務治理簡單
僅需對單個目標用戶(當前業務)進行服務治理, 簡單高效。
可持續發展性高
符合業內Sidecar趨勢, 方便吸取開源優秀的架構設計。
用戶可接受程度高
等優勢, 能夠說是比較理想的部署模型, 可是考慮前期投入,則存在一些須要考量的問題
不支持Sidecar 獨立升級
試想一下若是Sidecar作了版本升級(即便新增的特性並非一些業務所須要的)也要求業務去重啓一下本身的服務, 這彷佛違背用戶無感知的設計原則。
不支持 sidecar 監控(異常沒法告警)
這個幾乎能夠說是致命的了, Sidecar自身做爲基礎組件都沒法具有監控能力又拿什麼去像業務保證可用性呢。
不支持登陸 Sidecar Container進行故障排查
這個能夠說是致命的了(而不是」幾乎「), Sidecar出錯沒法登錄到對應的Container去進行問題排查, 應該沒有人敢發佈這樣的服務。
沒法控制業務Container和Sidecar啓動順序
一般咱們要求Sidecar要先於業務Container啓動。
DaemonSet模式屬於介於傳統網關與Sidecar之間的一種, 或者說一種折中。
藉助於DaemonSet, 在每一個Node節點上都會有一個Sidecar的實例, 用於服務當前Node中的全部業務(即便業務的Pod會常常的被調度)。
相較於Sidecar注入模式DaemonSet
隔離性較低
同時服務多個業務, 不免會有一些CPU/線程, 網絡, 甚至是內存資源上的共享。
擴展性較低
DaemonSet模式的部署模式已經相對比較固定, 沒法靈活的作擴展。
可用性較低
一旦Sidecar故障便會影響全部當前Node節點中的服務, 須要額外的高可用機制。
資源佔用高(前期較低)
由於K8s隨時都有可能調度不一樣的Pod到當前Node節點, 所以須要預先預分配能服務整個Node節點的資源(即便能夠超賣) 。理論上要真的能服務好全部的Pod就得佔用當前Node一半的資源(雖然實際這樣不太可能)。
服務治理難度較高
須要在Sidecar中同時維護多個業務的服務治理, 加劇Sidecar自己資源佔用的同時, 甚至比常規RPC更加複雜(由於RPC一般只須要在Client端作本身的服務治理就能夠了)。
可持續發展性通常
鮮有采用DaemonSet方案的用戶, 後期難以進行開源跟進。
用戶可接受程度通常
缺點雖多, 可是考慮實際狀況, DaemonSet仍舊有優勢
部署簡單
獨立部署, 不須要對業務部署作侵入。
支持監控與故障排查
因爲是獨立分配的容器, 支持使用CMDB登錄排查問題以及監控等。
可獨立升級
所以咱們初期選擇了DaemonSet做爲部署方案。
上面提到DaemonSet因爲是一對多的部署, 所以一旦Sidecar故障將會形成大面積的影響。
因而在DaemonSet以外咱們追加了一個Common集羣, 用於本地Sidecar故障的Failover。
當本地Sidecar請求故障後降級到Common集羣
本地Sidecar恢復後回退到本地Sidecar正常運行
這無疑又增長了SDK的複雜性。
ESA Shaft是ESA Mesh中的高性能sidecar實現, 至關於envoy, Linkerd的角色。
初期考慮開發效率以及Control Plane, 以及公司微服務生態等因素決定採用Java, Netty實現。
協議上支持
Http1/Http2
Dubbo
gRPC
HttpToDubbo
服務治理支持
Service Discovery
Loadbalance
Rate Limit
Circuit Breaker
Concurrent Limit
Tracing
架構設計
ESA Shaft架構上總體分爲
Listener
監聽本地地址並分發IO事件
L4 Filter
處理網絡事件及協議編解碼
L7 Filter
7層過濾器,負責服務治理及請求轉發
不一樣的協議由不一樣的Listener啓動(包含着不一樣的L4/L7 Filter), 經過不一樣的Filter組合完成協議解析, 服務治理等複雜的功能。
經過集成ESA Registry註冊中心SDK, 服務治理框架Service Keeper以及ESA Conf做爲配置中心下發動態配置完成動態化服務治理功能
ESA Shaft的線程模型很是簡單
Boss線程:負責監聽 & 處理鏈接
Worker:負責處理I/O, L4 Filter, L7 Filter,請求轉發等全部後續操做。
值得注意的是這裏的Worker線程數默認使用和CPU相同的數量, 意在儘可能減小線程切換帶來的開銷(雖然Java暫時沒法比較方便的作線程親和性)。
性能測試環境
直接在Sidecar層面返回Echo數據, 不作請求代理
最高TPS超過25W
正常負荷平均RT:avg(rt)<0.5ms
代理到3個後端節點, 負載均衡方式爲隨機。
最高TPS接近12W
正常負荷平均RT:avg(rt)<1ms
由圖中可看出性能上Shaft仍是比較高的, 可是在活躍鏈接較多的場景則表現稍差(線程數量設置偏小)。
總的來講初期實踐階段咱們採用適合公司環境的較爲折中的方案(Mesh SDK, DaemonSet, Java), 也踩了很多的坑, 將來ESA Mesh將着眼於行業領先的Mesh解決方案, 進行進一步的演進。
其中包括
採用流量攔截方式, 去除SDK
採用Sidecar注入方式部署
接入統一服務治理平臺(ESA Sailor)
自動化部署/運維
Rust重寫
Java語言確實不太適合作Sidecar, 即便有協程(如今尚未)也有着內存佔用高的問題, 再加上GC帶來的硬傷,所以很難在Sidecar這個領域施展拳腳。Rust是一門很是好的語言(除了學習曲線異常陡峭以外), 優秀的語言設計以及強大的編譯器讓應用能達到幾乎與C++媲美的性能和資源佔用, 也能保有必定的開發效率。目標內存佔用在10M級別完成Sidecar的重寫。
兼容XDS
與開源靠攏, 兼容XDS協議。
獨立升級 & 熱更新
自定義RPC協議(與ESA RPC保持一致)
採用更高效的RPC協議完成Sidecar之間的通信, 例如Coap, Quic, 基於UDP自定義協議等。
鑑權/加密
Back Pressure
目前咱們已經完成公司統一的服務治理平臺基本功能研發
兼容XDS標準, 採用XDS與客戶端通信
單元化的分級架構, 避免加載全量數據
ESA Mesh仍處於積極探索與實踐的時期, 期間可能會走彎路, 但隨着Mesh架構及技術的演進咱們但願提供給用戶一個開箱即用的Mesh解決方案, 在行業Service Mesh的演進之路上留下一個腳印甚至是一個里程碑。