陌陌的 Service Mesh 探索与实践
Service Mesh Virtual Meetup 是 ServiceMesher 社區和 CNCF 聯合主辦的線上系列直播。本期為 Service Mesh Virtual Meetup#1 ,邀請了四位來自不同公司的嘉賓,從不同角度展開了 Service Mesh 的應用實踐分享,分享涵蓋來自陌陌和百度的 Service Mesh 生產實踐,Service Mesh 的可觀察性和生產實踐以及與傳統微服務中可觀察性的區別,還有如何使用 SkyWalking 來觀測 Service Mesh。
本文根據5月6日晚,陌陌中間件架構師高飛航的主題分享《陌陌的 Service Mesh 探索與實踐》整理。文末包含本次分享的視頻回顧鏈接以及 PPT 下載地址。
前言
本次演講為大家分享的是陌陌目前正在進行的 Service Mesh 實踐的相關內容。共分為三個部分:
第一部分是原有微服務架構的相關背景;
第二部分是原有架構遇到的問題以及決定采用 Service Mesh 方案的思考過程;
最后的部分對Service Mesh落地實踐的方案進行介紹;
陌陌微服務體系演進歷程
單體應用到微服務
各個應用的發展過程,都會經歷從單體應用、到應用拆分再到微服務架構這樣一個過程。陌陌的這個演進過程,有一點比較特別的是,在應用拆分時加入了由 PHP 開發、與客戶端 App 進行對接的 API 層,并采用 Java 開發底層具有復雜運算的業務邏輯,這樣能夠兼得 PHP 的開發效率與 Java 高性能的優勢。
但這個選擇的影響也是十分深遠的,由于 PHP 在業務中的比重很高,我們在后續進行微服務改造和服務治理時,需要不斷地去應對多語言架構帶來的挑戰。
微服務體系演進
陌陌的微服務架構改造從 2013 年就開始了,在當時還沒有較為完善的服務框架產品的情況下,我們自研了服務框架產品 MOA,支撐了陌陌 IM、附近動態、直播、短視頻等核心業務的高速發展歷程。
在多年的迭代發展中,我們逐步完善了服務框架產品功能,同時也擴充了其他基礎架構產品,最終形成了一個完善的微服務體系。其他基礎架構產品也都是采用了自研的方案,因此整體是一個非常定制化的架構。這一點也成為后續 Service Mesh 落地選型時重點要考量的因素。
微服務體系整體架構
下面對微服務體系的整體架構進行介紹。我們采用了一個 Redis 作為底層存儲的注冊中心。服務實例的存活檢測主要依賴一個中心化的檢測應用 MOA Watcher,能夠將無法連通的實例從注冊中心的在線列表中移除、摘除實例的業務流量。
在多語言支持方面,我們除了支持最核心的 Java 與 PHP 應用之外,后續還支持了 Python、C++、Go、NodeJS 等非常多的語言接入微服務體系。由于陌陌的中間件團隊是以 Java 工程師為主導的,服務框架組件的核心產品也是一個 Java 的 SDK。在沒有足夠的資源投入到其他語言 SDK 開發的情況下,我們采用了很多能夠簡化多語言開發工作的方案。
例如跨語言調用和 Java 應用內部調用會采用不同的協議,Java 內部是較為傳統的自定義二進制傳輸協議與 Hessian 序列化,跨語言則采用了 Redis 傳輸協議與 JSON 序列化。Redis 協議分別利用 GET 命令的 key 和 value 的位置傳遞 Request 和 Response,這樣每種語言都可以基于成熟的 Redis 客戶端開發 SDK,避免重復編寫復雜的網絡通信邏輯。此外還增加了一個地址發現服務 Lookup,使其他語言能夠像調用普通服務的方式,輪詢獲取目標服務地址。跨語言場景的這些方案雖然簡化了開發工作,但卻不是最優方案。這也為整體架構的長期發展埋下了隱患。
微服務體系的其他產品還包括統一的配置中心、監控平臺、日志采集平臺、分布式跟蹤系統等,這些都是為微服務體系保駕護航的重要基礎架構能力。
流量代理機制
在多語言支持的場景中,我們很早就采用了兩個和 Service Mesh 非常相近的方案。一個是為了支持多語言發布服務的入流量代理方案,使用 Java 開發的 Proxy 復用了 Java SDK 注冊發現與監控等諸多服務治理能力,使得其他語言僅簡單處理本地請求后就能實現發布服務。這些 Java Proxy 與多語言的業務進程是 1:1 部署的,但當時的方案是和業務進程放在一個容器里,升級時需要和業務進程一起重新發布。
另外一個方案是為了解決 PHP 并行調用下游服務而實現的出流量代理,但這個方案中代理層的進程是運行在獨立的服務器上,沒有部署在與調用端相同的服務器。
我們將流量代理機制應用于多語言服務治理的經歷,在某種程度上突顯了 Service Mesh 的價值,我們可能想到類似的方案去解決問題,但都沒有像 Service Mesh 一樣系統地給出一種最佳方案。不過這些相近方案的經驗是有助于我們后續去推進 Service Mesh 落地的。
微服務體系規模
隨著業務的發展,整個微服務體系也達到了一個很具有挑戰的量級。特別是在服務數量大幅增長后,Java 應用的服務治理問題也逐步暴露出來,其中最難以解決的是 SDK 升級的問題,這一點也是進一步推動我們轉向 Service Mesh 架構的原因。
借助 Service Mesh 解決現有架構痛點
架構痛點分析
前面我們提到的各種問題,其實都可以歸結為微服務體系中服務治理能力滯后的問題。對于非 Java 的應用,由于沒有足夠的開發資源,會導致服務框架的 SDK 迭代進度非常緩慢。對于 Java 應用,雖然 SDK 具備最完善的功能,但使全量應用完成升級需要耗費大量人力和時間。根據以往的經驗來看,一次推廣至少需要一個季度的時間,并且為業務團隊帶來很多不必要的負擔。
兩種場景最終的危害是一致的,都會導致架構能力上無法實現統一,先進的功能無法覆蓋到全部應用,使應用穩定性受到損失,甚至引發故障。我們在多語言方案中依賴的中心化的 Lookup 服務,曾經就因為一次服務異常導致整個 API 層不可用,原因就是 PHP 的 SDK 采用了一種有缺陷的機制沒有升級。在我們設計新方案時,也會因為架構能力無法統一而無法采用最佳的方案。例如我們在設計多機房架構時,由于流量調度機制無法快速覆蓋到全部應用,因此只能采用從應用入口整體調度流量的一種粗粒度的方案。
引入 Service Mesh
Service Mesh 將基礎架構邏輯與業務邏輯解耦、并支持獨立升級的方式,能夠很好地解決前面描述的架構痛點。但引入 Service Mesh 是一項非常重大的架構變更,并且需要多方面的成本投入。因此在實際落地實施前,我們必須思考以下幾個問題,并在不同階段完成對應的工作。
第一個是方案是否足夠成熟。這里的方案不局限于 Service Mesh 本身,也依賴公司內其他基礎設施的演進積累。例如我們在觀察階段實現了應用容化的推廣覆蓋、日志 Agent 方案的經驗積累等。
第二個是遇到的問題是否有其他替代方案。例如我們之前急需在多語言場景覆蓋的一個能力是流量調度機制,嘗試過一個只提供地址路由機制,不代理流量的 Agent 方案。但發現很多邏輯還是要保留在 SDK 中,最終放棄了這個方案。
第三點是能否接受方案帶來的成本,包括性能損耗、服務器消耗、研發投入等。評估階段我們在性能損耗方面做了非常詳盡的分析,給出采用 Service Mesh 方案后對頂層 API 接口耗時增長的影響。
第四點是這項方案帶來的價值,是否是當前最迫切需要的?公司或部門在不同階段會存在不同的目標。只有契合團隊的整體目標,相關工作才能得到最大的支持、順利啟動與開展。大家在推廣 Service Mesh 方案時,也不妨等待一個最佳的時機。
Service Mesh 架構在陌陌的落地實踐
方案選型
在 Service Mesh 領域當前最熱門的開源方案是 Istio,但也有很多像螞蟻金服、美團等公司采用了自研的方案。在這兩個方向的選擇上,我覺得最重要的還是要結合公司的實際情況。在陌陌的場景下,我們重點考慮了三方面的問題。
第一點是與現有架構的兼容性。我們的首要目標是使存量服務接入 Service Mesh 方案,而不是構建一個全新的應用,并且我們需要對接大量自研的內部系統。這一點上我們傾向使用自研的方案。
第二點要考慮現階段的關鍵需求。當前最迫切需要解決的是多語言服務治理與 SDK 升級的問題,這兩個關鍵的收益都是由數據平面產出的,暫時并不急需完善的控制平面功能。在這一點上我們傾向于優先實現數據平面方案的落地,并通過自研的方式逐步建設控制平面功能。
第三點是技術儲備與原則類的因素。目前我們整體架構中沒有核心的業務或組件是用 Go 語言來開發的,也無法快速補充人才儲備來支持 Go 語言方案快速開發與長期維護。Java 是我們積累最多的服務端開發語言,原有的服務框架產品也能夠提供很多直接的實踐經驗。
綜合上述原因,我們最終選擇了數據平面與控制平面均自研的方案,并采用 Java 開發數據平面的 Proxy Agent。
MOA Mesh 整體架構
在我們的 MOA Mesh 整體架構中,數據平面是我們現階段的重點目標。代理流量的 Agent 需要支持現有服務調用請求的轉發,并具備平滑升級機制以實現獨立的迭代升級。
控制平面會作為一項長期的規劃逐步進行建設,當前的方案中計劃增加一層輕量的 Pilot Proxy,以實現數據平面與其他內部系統的解耦。Pilot Proxy 與其他組件、系統的交互協議,將優先采用 Istio 的標準協議,為長期向社區方案靠攏提供可能。
數據平面實現細節
在數據平面的實踐細節部分,會重點向大家介紹三個關鍵環節的設計方案,與三個需要重點關注的問題。
部署方式
第一項關鍵的方案是部署方式。Agent 會采用 Sidecar 方式,與業務進程 1:1 部署。在容器化場景,Agent 會運行在與業務進程相同的 Pod、不同的 Container 中。由于陌陌當前微服務的容器化部署比例很高,這種容器化方案已能覆蓋大多數的業務場景。少數物理機或虛擬機部署的場景,可以通過一些定制化的方案解決。
業務在接入 Mesh 方案時,目前沒有依賴 iptables 機制,而是通過一次 SDK 升級讓新版 SDK 將流量發送給本地的 Agent。這一點上我們覺得如此重大的架構升級,讓業務升級一次 SDK 是可以接受的。升級步驟中還加入了一個發布項配置,使運維同學能夠更靈活地開啟或關閉 Service Mesh 運行模式。
升級方式 - 平滑升級機制
第二項關鍵的方案是平滑升級,這項方案是能否實現數據平面獨立迭代升級的關鍵機制。我們需要實現升級過程不需要業務團隊參與,具體需要做到業務進程不重啟、并且使業務進程的流量保持不變。
在實現方案上,我們有過一次比較深入的討論,是采用類似于 MOSN 的 FD 遷移方案,還是采用將流量臨時切換到公共 Agent 集群的方案。最終我們選擇了對業務感知更少的 FD 遷移方案,這一點上 MOSN 為我們做了很好的示范、取得了很好的效果。
FD 遷移的原理方面,社區中介紹的文章很多,這里不詳細展開講解了。對我們的一個新挑戰是,如何用 Java 來實現 FD 遷移?因為 Java 并不像 Go 語言一樣,可以直接調用 send 和 recv 以及其他操作系統底層的接口。對 Java 來說,至少要通過 JNI 調用這些底層接口的方式來實現。幸運的是 Java 的網絡庫 Netty 已經實現了相關 JNI 調用的封裝并提供了 SO 庫,我們可以直接基于 Netty 的 Java API 實現 FD 遷移機制。
云原生網絡代理 MOSN:https://github.com/mosn/mosn
升級方式 - 發布流程
FD 遷移只解決了新舊 Agent 平滑替換的問題,Agent 升級還需要一套完整的發布流程來支持。由于 FD 遷移要求升級過程中同時存在新舊兩個 Agent,針對這個場景我們考慮了三個備選方案。
第一個是像 MOSN 一樣,通過注入一個新的 Container 來啟動新 Agent 進程,但注入 Container 的機制原生的 K8s 是不支持的,需要通過修改源碼才能實現。目前我們認為修改 K8s 源碼后期維護成本太高,是無法接受的方案。
第二個方案是預留一個占位的 Container,Pod 啟動時 Agent 運行在1號 Container,需要升級時在2號 Container 啟動新 Agent。雖然這種方案能實現平滑升級,但 Agent 實際運行在哪個 Container 的狀態是難以維護并且和其他發布流程沖突的,因此這個方案也無法被接受。
最后我們采用了第三種方案,讓 Agent 在同一個 Container 內部完成升級。在原來的 Container 里面啟動新 Agent,完成升級后舊 Agent 進程退出。但這種方案無法再通過 Agent 容器鏡像來發布 Agent,而是需要在 Container 中拉取新 Agent 的部署文件并啟動進程。這項運維操作,我們通過增加一個新的運維 Agent 進程來實現。
運維 Agent 隨 Container 啟動,后續 Proxy Agent 的部署工作,均由運維 Agent 管理。運維 Agen t提供 HTTP 接口,接收運維 Agent 管理服務發送的部署操作指令,完成單個 Pod 的 Proxy Agent 升級。而一個應用全部 Pod 的升級流程,由發布系統編排并調用運維 Agent 管理服務來執行。
容災方式
第三個關鍵方案是數據平面的容災方式。這個方案中我們遵循的原則是盡可能簡單,復用原有服務治理的能力。對于大多數應用,Agent 代理的出流量都是由入流量產生的,當 Agent 發生故障時,只要將入流量摘除,業務流量就不再受異常 Agent 的影響。由于代理入流量后注冊服務的端口會改為由 Agent 監聽,原有微服務體系的健康檢測機制通常能直接滿足容災要求。
但也有一些特殊類型的應用,如流式計算節點、定時任務等,Agent 代理的出流量無法通過摘除入流量來容災,此時需要單獨設計出流量的容災機制。在這種場景下將流量切換至同應用的其他 Agent 是最佳的選擇,因為同應用內的 Agent 具有相同的配置、資源和鑒權要求。切換至本應用其他 Agent 的方案,具有最小的切換代價和最高的穩定性。
性能問題
其他經常被關注的問題,首先是性能問題。在微服務體系中應用間的調用鏈路是非常復雜的,看似微小的耗時增長,疊加以后對頂層的接口與業務的影響也會是非常巨大的。因此 Agent 代理流量之后縮減服務調用的耗時增幅,是 Service Mesh 落地過程中非常重要的一個目標。
在整體方案上,我們需要考慮是否新增通信協議。MOA 原有的跨語言協議不包含 header 字段,因此 Agent 在轉發請求時,必須 decode 整個請求體才能獲得服務名、方法等請求路由信息。新協議加入可擴展的 header 字段后可消除這個步驟的開銷。此外 header 字段不涉及業務請求數據的序列化,可以使用 ProtoBuf 代替 JSON 提升性能。Redis 協議不支持單連接并行處理請求,這一點可以加入 RequestId 進行連接復用優化,通過設置合理的連接數進一步優化性能。
Agent 內部我們也進行了一些優化工作,如針對 Java 語言減少 GC 壓力的對象池機制,以及響應等待的非阻塞機制等。另外在縮減耗時增幅絕對值這個目標下,服務器性能是一個非常關鍵的因素。在衡量耗時增幅時需要使用和生產環境一致的服務器,而不能使用低配的測試服務器。
截止目前的測試結果來看,在 1K QPS 和 1K 消息體這個標準場景下,一次服務調用、Agent 兩次請求轉發的累計耗時增幅可以優化至小于 0.2ms。基于我們對整個服務端調用鏈路的分析,預計在所有服務都接入 Service Mesh 方案后,頂層 API 接口的耗時增幅將小于6%,這是一個我們能夠接受的性能損耗成本。
資源問題
第二個重要的問題是資源消耗。首先在資源分配上,我們采用了 Agent 容器與業務容器共享資源配額的方案。由于業務進程與 Agent 進程二者是缺一不可的,這是一種比較合理的的分配方式。
在標準場景下,我們會為 Agent 分配 256M 的內存,算上可能使用到的堆外內存,運行時大約需要占用 300M 內存。由于平滑升級過程中,容器內會同時運行2個 Agent,所以實際需要預留 600M 的內存空間。
在這樣的內存資源消耗下,我們評估全部服務接入 Service Mesh 方案后新增的服務器消耗約為 10%。但這項數據是維持現有內存使用百分比,以及服務器內存配置不變的最壞情況。實際在挖掘業務容器內存空間、采購大內存服務器后,這項數據可以進一步優化。
對于采用 Java 開發 Agent 的方案,資源消耗的劣勢會非常明顯,這一點是無法回避的問題。選擇這個方向,是綜合整體的情況考慮,通過犧牲服務器資源換取方案長期穩定性的一種選擇。
兼容問題
最后一個問題是關于如何兼容原有架構。一方面存在數據平面先上線,控制平面尚未就緒的狀態。此時要求數據平面 Agent 能夠與其他系統通過原有的 SDK 與接入方式進行對接,不會導致原有功能無法使用。另一方面一定會出現一部分服務先升級,另外一部分服務尚未升級的場景。此時為了支持兩類服務間能夠互相調用,Agent 必須支持以原有通信協議處理請求,以及根據服務端的升級情況選擇適當的協議發起調用。
總結與展望
通過實踐經歷我們發現,Service Mesh 確實能夠解決微服務領域的關鍵架構痛點。并且是以一種全新的理念,直接給出這些問題的最優解。但何時引入 Service Mesh 架構,需要結合實際情況、選擇一個適當的時機,以便能快速達成目標。
陌陌當前的進展是完成了數據平面的研發與線上業務的小流量驗證。未來還有很多的工作要做,業務大規模推廣落地之后才能使 Service Mesh 的價值真正發揮出來,我們計劃在年內實現這個目標。
長期規劃中我們的方案會逐步向社區靠攏,因為通過社區的力量能夠建設出更完善的產品。對于我們當前采用的基于 Java 的方案,我們也計劃在整體方案完善之后通過開源等方式貢獻給社區,提供給同樣需要 Java 方案的公司和團隊。
以上就是此次分享的全部內容,感謝大家的關注與支持!
嘉賓介紹
高飛航,陌陌中間件架構師,在微服務、多機房架構及中間件產品領域有較為深入的研究,當前關注 Service Mesh、云原生等技術方向。
回顧視頻以及 PPT 下載地址
視頻回顧:https://www.bilibili.com/video/BV1xQ4y1N7PR/
PPT 下載:https://github.com/servicemesher/meetup-slides/tree/master/2020/05/virtual
總結
以上是生活随笔為你收集整理的陌陌的 Service Mesh 探索与实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 抱歉,请不要把 “业务逻辑层” 理解为
- 下一篇: 教你配置windows上的windbg,