01.21 微服務:微服務化之無狀態化與容器化架構實踐

本系列包括作者有關微服務架構實踐與思考,共六篇,此為第四篇:

微服務化之無狀態化與容器化架構實踐

原址:https://mp.weixin.qq.com/s/Y6JeeC9NPaFvyhpQ0hwWug前三篇為:

微服務:微服務的接入層設計與動靜資源隔離架構設計

微服務:微服務化的數據庫設計與讀寫分離架構

一、為什麼要做無狀態化和容器化

很多應用拆分成微服務,是為了承載高併發,往往一個進程扛不住這麼大的量,因而需要拆分成多組進程,每組進程承載特定的工作,根據併發的壓力用多個副本公共承擔流量。

將一個進程變成多組進程,每組進程多個副本,需要程序的修改支撐這種分佈式的架構,如果架構不支持,僅僅在資源層創建多個副本是解決不了問題的。

很多人說,支撐雙十一是靠堆機器,誰不會?真正經歷過的會覺得,能夠靠堆機器堆出來的,都不是問題,怕的是機器堆上去了,因為架構的問題,併發量仍然上不去。

阻礙單體架構變為分佈式架構的關鍵點就在於狀態的處理。如果狀態全部保存在本地,無論是本地的內存,還是本地的硬盤,都會給架構的橫向擴展帶來瓶頸。

狀態分為分發,處理,存儲幾個過程,如果對於一個用戶的所有的信息都保存在一個進程中,則從分發階段,就必須將這個用戶分發到這個進程,否則無法對這個用戶進行處理,然而當一個進程壓力很大的時候,根本無法擴容,新啟動的進程根本無法處理那些保存在原來進程的用戶的數據,不能分擔壓力。

所以要講整個架構分成兩個部分,無狀態部分和有狀態部分,而業務邏輯的部分往往作為無狀態的部分,而將狀態保存在有狀態的中間件中,如緩存,數據庫,對象存儲,大數據平臺,消息隊列等。

這樣無狀態的部分可以很容易的橫向擴展,在用戶分發的時候,可以很容易分發到新的進程進行處理,而狀態保存到後端。而後端的中間件是有狀態的,這些中間件設計之初,就考慮了擴容的時候,狀態的遷移,複製,同步等機制,不用業務層關心。

微服務:微服務化之無狀態化與容器化架構實踐


如圖所示,將架構分為兩層,無狀態和有狀態。

容器和微服務是雙胞胎,因為微服務會將單體應用拆分成很多小的應用,因而運維和持續集成會工作量變大,而容器技術能很好的解決這個問題。然而在微服務化之前,建議先進行容器化,在容器化之前,建議先無狀態化,當整個流程容器化了,以後的微服務拆分才會水到渠成。

二、無狀態化架構的幾個要點

前面說對於任何狀態,需要考慮它的分發,處理,存儲。

微服務:微服務化之無狀態化與容器化架構實踐


對於數據的存儲,主要包含幾類數據:

  • 會話數據等,主要保存在內存中。
  • 結構化數據,主要是業務邏輯相關
  • 文件圖片數據,比較大,往往通過CDN下發
  • 非結構化數據,例如文本,評論等


如果這些數據都保存在本地,和業務邏輯耦合在一起,就需要在數據分發的時候,將同一個用戶分到同一個進程,這樣就會影響架構的橫向擴展。

微服務:微服務化之無狀態化與容器化架構實踐


對於保存在內存裡的數據,例如Session,可以放在外部統一的緩存中。

微服務:微服務化之無狀態化與容器化架構實踐


對於業務相關的數據,則應該保存在統一的數據庫中,如果性能扛不住,可以進行讀寫分離,如文章微服務化的數據庫設計與讀寫分離

如果性能還是抗住不,則可以使用分佈式數據庫。

微服務:微服務化之無狀態化與容器化架構實踐


對於文件,照片之類的數據,應該存放在統一的對象存儲裡面,通過CDN進行預加載,如文章微服務的接入層設計與動靜資源隔離

對於非結構化數據,可以存在在統一的搜索引擎裡面,例如ElasticSearch。

如果所有的數據都放在外部的統一存儲上,則應用就成了僅僅包含業務邏輯的無狀態應用,可以進行平滑的橫向擴展。

而所有的外部統一存儲,無論是緩存,數據庫,對象存儲,搜索引擎,都有自身的分佈式橫向擴展機制。

微服務:微服務化之無狀態化與容器化架構實踐


在實行了無狀態化之後,就可以將有狀態的集群集中到一起,進行跨機房的部署,實現跨機房的高可用性。而無狀態的部分可以通過Dubbo自動發現,當進程掛掉的時候,自動重啟,自動修復,也可以進行多機房的部署。

三、冪等的接口設計

但是還有一個遺留的問題,就是已經分發,正在處理,但是尚未存儲的數據,肯定會在內存中有一些,在進程重啟的時候,數據還是會丟一些的,那這部分數據怎麼辦呢?

這部分就需要通過重試進行解決,當本次調用過程中失敗之後,前序的進程會進行重試,例如Dubbo就有重試機制。既然重試,就需要接口是冪等的,也即同一次交易,調用兩次轉賬1元,不能最終轉走2元。

接口分為查詢,插入,更新,刪除等操作。

對於查詢接口來講,本身就是冪等的,不用做特殊的判斷。

對於插入接口來講,如果每一個數據都有唯一的主鍵,也能保證插入的唯一性,一旦不唯一,則會報錯。

對於更新操作來講,則比較複雜,分幾種情況。

一種情況是同一個接口,前後調用多次的冪等性。另一種情況是同一個接口,併發環境下調用多次的正確性。

為了保持冪等性,往往要有一個冪等表,通過傳入冪等參數匹配冪等表中ID的方式,保證每個操作只被執行一次,而且在實行最終一致性的時候,可以通過不斷重試,保證最終接口調用的成功。

對於併發條件下,誰先調用,誰後調用,需要通過分佈式鎖如Redis,Zookeeper等來實現同一個時刻只有一個請求被執行,如何保證多次執行結果仍然一致呢?則往往需要通過狀態機,每個狀態只流轉一次。還有就是樂觀鎖,也即分佈式的CAS操作,將狀態的判斷、更新整合在一條語句中,可以保證狀態流轉的原子性。樂觀鎖並不保證更新一定成功,需要有對應的機制來應對更新失敗。

四、容器的技術原理

微服務:微服務化之無狀態化與容器化架構實踐


無狀態化之後,實行容器化就十分順暢了,容器的不可改變基礎設施,以及容器基於容器平臺的掛掉自動重啟,自動修復,都因為無狀態順暢無比。

關鍵技術一:Dockerfile

例如下面的Dockerfile。

微服務:微服務化之無狀態化與容器化架構實踐


為什麼一定要用Dockerfile,而不建議通過保存鏡像的方式來生成鏡像呢?

這樣才能實現環境配置和環境部署代碼化 ,將Dockerfile維護在Git裡面,有版本控制,並且通過自動化的build的過程來生成鏡像,而鏡像中就是環境的配置和環境的部署,要修改環境應先通過Git上面修改Dockerfile的方式進行,這就是IaC。

關鍵技術二:容器鏡像

通過Dockerfile可以生成容器鏡像,容器的鏡像是分層保存,對於Dockerfile中的每一個語句,生成一層容器鏡像,如此疊加,每一層都有UUID。

容器鏡像可以打一個版本號,放入統一的鏡像倉庫。

微服務:微服務化之無狀態化與容器化架構實踐


關鍵技術三:容器運行時

微服務:微服務化之無狀態化與容器化架構實踐


容器運行時,是將容器鏡像之上加一層可寫入層,為容器運行時所看到的文件系統。

容器運行時使用了兩種隔離的技術。

一種是看起來是隔離的技術,稱為namespace,也即每個namespace中的應用看到的是不同的IP地址、用戶空間、程號等。

微服務:微服務化之無狀態化與容器化架構實踐


另一種是用起來是隔離的技術,稱為cgroup,也即明明整臺機器有很多的CPU、內存,而一個應用只能用其中的一部分。

cgroup

微服務:微服務化之無狀態化與容器化架構實踐


五、容器化的本質和容器化最佳架構實踐

很多人會將容器當成虛擬機來用,這是非常不正確的,而且容器所做的事情虛擬機都能做到。

如果部署的是一個傳統的應用,這個應用啟動速度慢,進程數量少,基本不更新,那麼虛擬機完全能夠滿足需求。

  • 應用啟動慢:應用啟動15分鐘,容器本身秒級,虛擬機很多平臺能優化到十幾秒,兩者幾乎看不出差別
  • 內存佔用大:動不動32G,64G內存,一臺機器跑不了幾個。
  • 基本不更新:半年更新一次,虛擬機鏡像照樣能夠升級和回滾
  • 應用有狀態:停機會丟數據,如果不知道丟了啥,就算秒級啟動有啥用,照樣恢復不了,而且還有可能因為丟數據,在沒有修復的情況下,盲目重啟帶來數據混亂。
  • 進程數量少:兩三個進程相互配置一下,不用服務發現,配置不麻煩


如果是一個傳統應用,根本沒有必要花費精去容器化,因為白花了力氣,享受不到好處。

微服務:微服務化之無狀態化與容器化架構實踐


什麼情況下,才應該考慮做一些改變呢?

傳統業務突然被互聯網業務衝擊了,應用老是變,三天兩頭要更新,而且流量增大了,原來支付系統是取錢刷卡的,現在要互聯網支付了,流量擴大了N倍。

沒辦法,一個字:拆

拆開了,每個子模塊獨自變化,少相互影響。

拆開了,原來一個進程扛流量,現在多個進程一起扛。

所以稱為微服務

微服務場景下,進程多,更新快,於是出現100個進程,每天一個鏡像。

容器樂了,每個容器鏡像小,沒啥問題,虛擬機哭了,因為虛擬機每個鏡像太大了。

所以微服務場景下,可以開始考慮用容器了。

微服務:微服務化之無狀態化與容器化架構實踐


虛擬機怒了,老子不用容器了,微服務拆分之後,用Ansible自動部署是一樣的。

這樣說從技術角度來講沒有任何問題。

然而問題是從組織角度出現的。

一般的公司,開發會比運維多的多,開發寫完代碼就不用管了,環境的部署完全是運維負責,運維為了自動化,寫Ansible腳本來解決問題。

然而這麼多進程,又拆又合併的,更新這麼快,配置總是變,Ansible腳本也要常改,每天都上線,不得累死運維。

所以這如此大的工作量情況下,運維很容易出錯,哪怕通過自動化腳本。

這個時候,容器就可以作為一個非常好的工具運用起來。

除了容器從技術角度,能夠使得大部分的內部配置可以放在鏡像裡面之外,更重要的是從流程角度,將環境配置這件事情,往前推了,推到了開發這裡,要求開發完畢之後,就需要考慮環境部署的問題,而不能當甩手掌櫃。

這樣做的好處就是,雖然進程多,配置變化多,更新頻繁,但是對於某個模塊的開發團隊來講,這個量是很小的,因為5-10個人專門維護這個模塊的配置和更新,不容易出錯。

如果這些工作量全交給少數的運維團隊,不但信息傳遞會使得環境配置不一致,部署量會大非常多。

容器是一個非常好的工具,就是讓每個開發僅僅多做5%的工作,就能夠節約運維200%的工作,並且不容易出錯。

然而本來原來運維該做的事情開發做了,開發的老大願意麼?開發的老大會投訴運維的老大麼?

這就不是技術問題了,其實這就是DevOps,DevOps不是不區分開發和運維,而是公司從組織到流程,能夠打通,看如何合作,邊界如何劃分,對系統的穩定性更有好處。

所以微服務,DevOps,容器是相輔相成,不可分割的。

不是微服務,根本不需要容器,虛擬機就能搞定,不需要DevOps,一年部署一次,開發和運維溝通再慢都能搞定。

所以,容器的本質是基於鏡像的跨環境遷移。

鏡像是容器的根本性發明,是封裝和運行的標準,其他什麼namespace,cgroup,早就有了。這是技術方面。

在流程方面,鏡像是DevOps的良好工具。

容器是為了跨環境遷移的,第一種遷移的場景是開發,測試,生產環境之間的遷移。如果不需要遷移,或者遷移不頻繁,虛擬機鏡像也行,但是總是要遷移,帶著幾百G的虛擬機鏡像,太大了。

第二種遷移的場景是跨雲遷移,跨公有云,跨Region,跨兩個OpenStack的虛擬機遷移都是非常麻煩,甚至不可能的,因為公有云不提供虛擬機鏡像的下載和上傳功能,而且虛擬機鏡像太大了,一傳傳一天。

微服務:微服務化之無狀態化與容器化架構實踐


所以如圖為將容器融入持續集成的過程中,形成DevOps的流程。

通過這一章,再加上第一章微服務化的基石——持續集成就構成了微服務,DevOps,容器化三位一體的統一。

微服務:微服務化之無狀態化與容器化架構實踐


對於容器鏡像,我們應該充分利用容器鏡像分層的優勢,將容器鏡像分層構建,在最裡面的OS和系統工具層,由運維來構建,中間層的JDK和運行環境,由核心開發人員構建,而最外層的Dockerfile就會非常簡單,只要將jar或者war放到指定位置就可以了。

這樣可以降低Dockerfile和容器化的門檻,促進DevOps的進度。

六、容器平臺的最佳實踐架構

容器化好了,應該交給容器平臺進行管理,從而實現對於容器的自動化管理和編排。

微服務:微服務化之無狀態化與容器化架構實踐


例如一個應用包含四個服務A,B,C,D,她們相互引用,相互依賴,如果使用了容器平臺,則服務之間的服務發現就可以通過服務名進行了。例如A服務調用B服務,不需要知道B服務的IP地址,只需要在配置文件裡面寫入B服務服務名就可以了。如果中間的節點宕機了,容器平臺會自動將上面的服務在另外的機器上啟動起來。容器啟動之後,容器的IP地址就變了,但是不用擔心,容器平臺會自動將服務名B和新的IP地址映射好,A服務並無感知。這個過程叫做自修復和自發現。如果服務B遭遇了性能瓶頸,三個B服務才能支撐一個A服務,也不需要特殊配置,只需要將服務B的數量設置為3,A還是隻需要訪問服務B,容器平臺會自動選擇其中一個進行訪問,這個過程稱為彈性擴展和負載均衡。

當容器平臺規模不是很大的時候,Docker Swarm Mode還是比較好用的:

  • 集群的維護不需要Zookeeper,不需要Etcd,自己內置
  • 命令行和Docker一樣的,用起來順手
  • 服務發現和DNS是內置的
  • Docker Overlay網絡是內置的


總之Docker幫你料理好了一切,你不用太關心細節,很容易就能夠將集群運行起來。

而且可以通過docker命令,像在一臺機器上使用容器一樣使用集群上的容器,可以隨時將容器當虛擬機來使用,這樣對於中等規模集群,以及運維人員還是比較友好的。

當然內置的太多了也有缺點,就是不好定製化,不好Debug,不好干預。當你發現有一部分性能不行的時候,你需要改整個代碼,全部重新編譯,當社區更新了,合併分支是很頭疼的事情。當出現了問題的時候,由於Manager大包大攬幹了很多活,不知道哪一步出錯了,反正就是沒有返回,停在那裡,如果重啟整個Manager,影響面又很大。

微服務:微服務化之無狀態化與容器化架構實踐


當規模比較大,應用比較複雜的時候,則推薦Kubernetes。

Kubernetes模塊劃分得更細,模塊比較多,而且模塊之間完全的松耦合,可以非常方便地進行定製化。

微服務:微服務化之無狀態化與容器化架構實踐


而且Kubernetes的數據結構的設計層次比較細,非常符合微服務的設計思想。例如從容器->Pods->Deployment->Service,本來簡單運行一個容器,被封裝為這麼多的層次,每次層有自己的作用,每一層都可以拆分和組合,這樣帶來一個很大的缺點,就是學習門檻高,為了簡單運行一個容器,需要先學習一大堆的概念和編排規則。

但是當需要部署的業務越來越複雜時,場景越來越多時,你會發現Kubernetes這種細粒度設計的優雅,使得你能夠根據自己的需要靈活的組合,而不會因為某個組件被封裝好了,從而導致很難定製。例如對於Service來講,除了提供內部服務之間的發現和相互訪問外,還靈活設計了headless service,這使得很多遊戲需要有狀態的保持長連接有了很好的方式,另外訪問外部服務時,例如數據庫、緩存、headless service相當於一個DNS,使得配置外部服務簡單很多。很多配置複雜的大型應用,更復雜的不在於服務之間的相互配置,可以有Spring Cloud或者Dubbo去解決,複雜的反而是外部服務的配置,不同的環境依賴不同的外部應用,External Name這個提供和很好的機制。

包括統一的監控cadvisor,統一的配置confgMap,都是構建一個微服務所必須的。

然而Kubernetes當前也有一個瓶頸——集群規模還不是多麼大,官方說法是幾千個節點,所以超大規模的集群,還是需要有很強的IT能力進行定製化。但是對於中等規模的集群也足夠了。

而且Kubernetes社區的熱度,可以使得使用開源Kubernetes的公司能夠很快地找到幫助,等待到新功能的開發和Bug的解決。



分享到:


相關文章: