1. 程式人生 > >網易容器雲平臺的微服務化實踐(一)

網易容器雲平臺的微服務化實踐(一)

社區 重建 tps 導致 編寫 高可用 均衡 深度 很大的

此文已由作者馮常健授權網易雲社區發布。

歡迎訪問網易雲社區,了解更多網易技術產品運營經驗。



摘要:網易雲容器平臺期望能給實施了微服務架構的團隊提供完整的解決方案和閉環的用戶體驗,為此從 2016 年開始,我們容器服務團隊內部率先開始進行 dogfooding 實踐,看看容器雲平臺能不能支撐得起容器服務本身的微服務架構,這是一次很有趣的嘗試。

一旦決定做微服務架構,有很多現實問題擺在面前,比如技術選型、業務拆分問題、高可用、服務通信、服務發現和治理、集群容錯、配置管理、數據一致性問題、康威定律、分布式調用跟蹤、CI/CD、微服務測試,以及調度和部署等等,這並非一些簡單招數能夠化解。實踐微服務架構的方式有千萬種,我們探索並實踐了其中的一種可能性,希望可以給大家一個參考。本文是《網易容器雲平臺的微服務化實踐》系列文章的第一篇。

Docker 容器技術已經過了最早的喧囂期,逐漸在各大公司和技術團隊中應用。盡管以今天來看,大家從觀念上已經逐漸認可 “將鏡像定義為應用交付標準,將容器作為應用運行的標準環境” 的觀點,但還是有相當一部分人在迷惑容器技術作為一個標準,應該怎麽落地,怎樣才能大規模線上應用,怎麽玩才能真正解放生產力,促進軟件交付效率和質量?答案其實在應用的架構當中。

微服務架構不是因 Docker 容器技術而生,但確實是因容器技術而火。容器技術提供了一致性的分發手段和運行環境,使得只有微服務化後的應用架構,才能配合容器發揮其最大價值。而微服務化架構引入了很大的復雜性,只有應用容器化以及規模化的容器編排與調度才能避免運維效率下降。容器技術和微服務化架構之間本是一種相輔相成的互補關系。

網易容器雲平臺的前身是網易應用自動部署平臺 (OMAD),它能夠利用 IaaS 雲提供的基礎設施,實現包括構建和部署一體化在內的整個應用生命周期管理。2014 年,以 Docker 為代表的容器技術進入大眾視野,我們驚喜地發現,容器技術是自動部署平臺從工具型應用進化為平臺型應用過程中最重要的一塊拼圖。原本用戶需要初始化主機,然後借助自動部署平臺完成應用的構建和部署。引入容器技術之後,用戶從功能開發到測試到一鍵部署上線,整個應用交付過程中不用關心主機初始化、主機間通信、實例調度等一系列應用之外的問題。這簡直是信仰 DevOps 的人的福音。

我們從 2015 年開始探索容器技術的最佳實踐方式,從當初 “胖容器” 與容器集群的產品形態,到後來關於有狀態和無狀態服務的定義,以及如今的新計算與高性能計算,我們一直在思考並豐富著容器技術的應用場景。無論產品形態如何調整,容器雲平臺的核心概念一直是 “微服務”,通過微服務這一抽象提供高性能的容器集群管理方案,支持彈性伸縮、垂直擴容、灰度升級、服務發現、服務編排、錯誤恢復、性能監測等功能,滿足用戶提升應用交付效率和快速響應業務變化的需求。網易雲容器平臺期望能給實施了微服務架構的團隊提供完整的解決方案和閉環的用戶體驗,為此從 2016 年開始,我們容器服務團隊內部率先開始進行 dogfooding 實踐,一方面檢驗容器雲平臺能不能支撐得起容器服務本身的微服務架構,另一方面通過微服務化實踐經驗反哺容器雲平臺產品設計,這是一次很有趣的嘗試,也是我們分享容器雲平臺微服務化架構實踐的初衷。

在談容器服務的微服務架構實踐之前,有必要先把網易雲容器服務大致做個介紹。目前網易雲容器服務團隊以 DevOps 的方式管理著30+微服務,每周構建部署次數 400+。網易雲容器服務架構從邏輯上看由 4 個層次組成,從下到上分別是基礎設施層、Docker 容器引擎層、Kubernetes (以下簡稱 K8S)容器編排層、DevOps 和自動化工具層:

技術分享圖片



容器雲平臺整體業務架構如下:

技術分享圖片

拋開容器服務具體業務不談,僅從業務特征來說,可以分成以下多種類型(括號內為舉例的微服務):

○ 面向終端用戶 (OpenAPI 服務網關)、面向服務(裸機服務)
○ 同步通信(用戶中心)、異步通信(構建服務)
○ 數據強一致需求(etcd 同步服務)、最終一致需求(資源回收服務)
○ 吞吐量敏感型(日誌服務)、延時敏感型(實時服務)
○ CPU 計算密集型(簽名認證中心)、網絡 IO 密集型(鏡像倉庫)
○ 在線業務(Web 服務)、離線業務(鏡像檢查)
○ 批處理任務(計費日誌推送)、定時任務(分布式定時任務)
○ 長連接(WebSocket 服務網關)、短連接(Hook 服務)
○ ……

一旦決定做微服務架構,有很多現實問題擺在面前,比如技術選型、業務拆分問題、高可用、服務通信、服務發現和治理、集群容錯、配置管理、數據一致性問題、康威定律、分布式調用跟蹤、CI/CD、微服務測試,以及調度和部署等等……這並非一些簡單招數能夠化解。

作為主要編程語言是 Java 的容器服務來說,選擇 Spring Cloud 去搭配 K8S 是一個很自然的事情。Spring Cloud 和 K8S 都是很好的微服務開發和運行框架。從應用的生命周期角度來看,K8S 覆蓋了更廣的範圍,特別像資源管理,應用編排、部署與調度等,Spring Cloud 則對此無能為力。從功能上看,兩者存在一定程度的重疊,比如服務發現、負載均衡、配置管理、集群容錯等方面,但兩者解決問題的思路完全不同,Spring Cloud 面向的純粹是開發者,開發者需要從代碼級別考慮微服務架構的方方面面,而 K8S 面向的是 DevOps 人員,提供的是通用解決方案,它試圖將微服務相關的問題都在平臺層解決,對開發者屏蔽復雜性。舉個簡單的例子,關於服務發現,Spring Cloud 給出的是傳統的帶註冊中心 Eureka 的解決方案,需要開發者維護 Eureka 服務器的同時,改造服務調用方與服務提供方代碼以接入服務註冊中心,開發者需關心基於 Eureka 實現服務發現的所有細節。而 K8S 提供的是一種去中心化方案,抽象了服務 (Service),通過 DNS+ClusterIP+iptables 解決服務暴露和發現問題,對服務提供方和服務調用方而言完全沒有侵入。

對於技術選型,我們有自己的考量,優先選擇更穩定的方案,畢竟穩定性是雲計算的生命線。我們並不是 “K8S 原教旨主義者”,對於前面提到的微服務架構的各要點,我們有選擇基於 K8S 實現,比如服務發現、負載均衡、高可用、集群容錯、調度與部署等。有選擇使用 Spring Cloud 提供的方案,比如同步的服務間通信;也有結合兩者的優勢共同實現,比如服務的故障隔離和熔斷;當然,也有基於一些成熟的第三方方案和自研系統實現,比如配置管理、日誌采集、分布式調用跟蹤、流控系統等。

我們利用 K8S 管理微服務帶來的最大改善體現在調度和部署效率上。以我們當前的情況來看,不同的服務要求部署在不同的機房和集群(聯調環境、測試環境、預發布環境、生產環境等),有著不同需求的軟硬件配置(內存、SSD、安全、海外訪問加速等),這些需求已經較難通過傳統的自動化工具實現。K8S 通過對 Node 主機進行 Label 化管理,我們只要指定服務屬性 (Pod label),K8S 調度器根據 Pod 和 Node Label 的匹配關系,自動將服務部署到滿足需求的 Node 主機上,簡單而高效。內置滾動升級策略,配合健康檢查 (liveness 和 readiness 探針)和 lifecycle hook 可以完成服務的不停服更新和回滾。此外,通過配置相關參數還可以實現服務的藍綠部署和金絲雀部署。集群容錯方面,K8S 通過副本控制器維持服務副本數 (replica),無論是服務實例故障(進程異常退出、oom-killed 等)還是 Node 主機故障(系統故障、硬件故障、網絡故障等),服務副本數能夠始終保持在固定數量。

技術分享圖片


Docker 通過分層鏡像創造性地解決了應用和運行環境的一致性問題,但是通常來講,不同環境下的服務的配置是不一樣的。配置的不同使得開發環境構建的鏡像無法直接在測試環境使用,QA 在測試環境驗證過的鏡像無法直接部署到線上……導致每個環境的 Docker 鏡像都要重新構建。解決這個問題的思路無非是將配置信息提取出來,以環境變量的方式在 Docker 容器啟動時註入,K8S 也給出了 ConfigMap 這樣的解決方案,但這種方式有一個問題,配置信息變更後無法實時生效。我們采用的是使用 Disconf 統一配置中心解決。配置統一托管後,從開發環境構建的容器鏡像,可以直接提交到測試環境測試,QA 驗證通過後,上到演練環境、預發布環境和生產環境。一方面避免了重復的應用打包和 Docker 鏡像構建,另一方面真正實現了線上線下應用的一致性。

Spring Cloud Hystrix 在我們的微服務治理中扮演了重要角色,我們對它做了二次開發,提供更靈活的故障隔離、降級和熔斷策略,滿足 API 網關等服務的特殊業務需求。進程內的故障隔離僅是服務治理的一方面,另一方面,在一個應用混部的主機上,應用間應該互相隔離,避免進程間互搶資源,影響業務 SLA。比如絕對要避免一個離線應用失控占用了大量 CPU,使得同主機的在線應用受影響。我們通過 K8S 限制了容器運行時的資源配額(以 CPU 和內存限制為主),實現了進程間的故障和異常隔離。K8S 提供的集群容錯、高可用、進程隔離,配合 Spring Cloud Hystrix 提供的故障隔離和熔斷,能夠很好地實踐 “Design for Failure” 設計哲學。

服務拆分的好壞直接影響了實施微服務架構的收益大小。服務拆分的難點往往在於業務邊界不清晰、歷史遺留系統改造難、數據一致性問題、康威定律等。從我們經驗來看,對於前兩個問題解決思路是一樣的:1)只拆有確定邊界能獨立的業務。2)服務拆分本質上是數據模型的拆分,上層應用經得起倒騰,底層數據模型經不起倒騰。對於邊界模糊的業務,即使要拆,只拆應用不拆數據庫。

以下是我們從主工程裏平滑拆出用戶服務的示例步驟:

技術分享圖片


1.將用戶相關的 UserService、UserDAO 分離出主工程,加上 UserController、UserDTO 等,形成用戶服務,對外暴露 HTTP RESTful API。
2.將主工程用戶相關的 UserService 類替換成 UserFa?ade 類,采用 Spring Cloud Feign 的註解,調用用戶服務 API。
3.主工程所有依賴 UserServce 接口的地方,改為依賴 UserFa?ade 接口,平滑過渡。

經過以上三個步驟, 用戶服務獨立成一個微服務,而整個系統代碼的復雜性幾乎沒有增加。

數據一致性問題在分布式系統中普遍存在,微服務架構下會將問題放大,這也從另一個角度說明合理拆分業務的重要性。我們碰到的大部分數據一致性場景都是可以接受最終一致的。“定時任務重試+冪等” 是解決這類問題的一把瑞士軍刀,為此我們開發了一套獨立於具體業務的 “分布式定時任務+可靠事件” 處理框架,將任何需保證數據最終一致的操作定義為一種事件,比如用戶初始化、實例重建、資源回收、日誌索引等業務場景。以用戶初始化為例,註冊一個用戶後,必須對其進行初始化,初始化過程是一個耗時的異步操作,包含租戶初始化、網絡初始化、配額初始化等等,這需要協調不同的系統來完成。我們將初始化定義為一種 initTenant 事件,將 initTenant 事件及上下文存入可靠事件表,由分布式定時任務觸發事件執行,執行成功後,清除該事件記錄;如果執行失敗,則定時任務系統會再次觸發執行。對於某些實時性要求較高的場景,則可以先觸發一次事件處理,再將事件存入可靠事件表。對於每個事件處理器來說,要在實現上確保支持冪等執行,實現冪等執行有多種方式,我們有用到布爾型狀態位,有用到 UUID 做去重處理,也有用到基於版本號做 CAS。這裏不展開說了。

技術分享圖片

當業務邊界與組織架構沖突時,從我們的實踐經驗來看,寧願選擇更加符合組織架構的服務拆分邊界。這也是一種符合康威定律的做法。康威定律說,系統架構等同於組織的溝通結構。組織架構會在潛移默化中約束軟件系統架構的形態。違背康威定律,非常容易出現系統設計盲區,出現 “兩不管” 互相推脫的局面,我們在團隊間、團隊內都碰到過這種情況。
nbsp;
本文是《網易容器雲平臺的微服務化實踐》系列文章的第一篇,介紹了容器技術和微服務架構的關系,我們做容器雲平臺的目的,以及簡單介紹了網易雲容器服務基於 Kubernetes 和 Spring Cloud 的微服務化實踐經驗。限於篇幅,有些微服務架構要點並未展開,比如服務通信、服務發現和治理、配置管理等話題;有些未提及,比如分布式調用跟蹤、CI/CD、微服務測試等話題,這些方面的實踐經驗會在後續的系列文章中再做分享。實踐微服務架構的方式有千萬種,我們探索並實踐了其中的一種可能性,希望可以給大家一個參考。


網易雲計算基礎服務深度整合了 IaaS、PaaS 及容器技術,提供彈性計算、DevOps 工具鏈及微服務基礎設施等服務,幫助企業解決 IT、架構及運維等問題,使企業更聚焦於業務,是新一代的雲計算平臺,點擊可免費試用。

相關文章:
【推薦】 Vue 全家桶單元測試簡要指南
【推薦】 用Go編寫的本地文件服務器

網易容器雲平臺的微服務化實踐(一)