歡迎光臨
每天分享高質量文章

微服務化之前需要先無狀態化和容器化

本文是微服務實戰系列文章的第四篇,前三篇連結如下:

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

很多應用拆分成微服務,是為了承載高併發,往往一個行程扛不住這麼大的量,因而需要拆分成多組行程,每組行程承載特定的工作,根據併發的壓力用多個副本公共承擔流量。
將一個行程變成多組行程,每組行程多個副本,需要程式的修改支撐這種分散式的架構,如果架構不支援,僅僅在資源層建立多個副本是解決不了問題的。
很多人說,支撐雙十一是靠堆機器,誰不會?真正經歷過的會覺得,能夠靠堆機器堆出來的,都不是問題,怕的是機器堆上去了,因為架構的問題,併發量仍然上不去。
阻礙單體架構變為分散式架構的關鍵點就在於狀態的處理。如果狀態全部儲存在本地,無論是本地的記憶體,還是本地的硬碟,都會給架構的橫向擴充套件帶來瓶頸。
狀態分為分發、處理、儲存幾個過程,如果對於一個使用者的所有的資訊都儲存在一個行程中,則從分發階段,就必須將這個使用者分發到這個行程,否則無法對這個使用者進行處理,然而當一個行程壓力很大的時候,根本無法擴容,新啟動的行程根本無法處理那些儲存在原來行程的使用者的資料,不能分擔壓力。
所以要講整個架構分成兩個部分,無狀態部分和有狀態部分,而業務邏輯的部分往往作為無狀態的部分,而將狀態儲存在有狀態的中介軟體中,如快取、資料庫、物件儲存、大資料平臺、訊息佇列等。
這樣無狀態的部分可以很容易的橫向擴充套件,在使用者分發的時候,可以很容易分發到新的行程進行處理,而狀態儲存到後端。而後端的中介軟體是有狀態的,這些中介軟體設計之初,就考慮了擴容的時候,狀態的遷移,複製,同步等機制,不用業務層關心。

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


二、無狀態化的幾個要點

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

對於資料的儲存,主要包含幾類資料:
  • 會話資料等,主要儲存在記憶體中

  • 結構化資料,主要是業務邏輯相關

  • 檔案圖片資料,比較大,往往透過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,統一的配置ConfigMap,都是構建一個微服務所必須的。
然而Kubernetes當前也有一個瓶頸——叢集規模還不是多麼大,官方說法是幾千個節點,所以超大規模的叢集,還是需要有很強的IT能力進行定製化。但是對於中等規模的叢集也足夠了。
而且Kubernetes社群的熱度,可以使得使用開源Kubernetes的公司能夠很快地找到幫助,等待到新功能的開發和Bug的解決。
本文轉載自公眾號:劉超的通俗雲端計算,點選檢視原文

Kubernetes 實戰培訓

本次培訓內容包括:Docker容器的原理與基本操作;容器網路與儲存解析;Kubernetes的架構與設計理念詳解;Kubernetes的資源物件使用說明;Kubernetes 中的開放介面CRI、CNI、CSI解析;Kubernetes監控、網路、日誌管理;容器應用的開發流程詳解等,點選識別下方二維碼加微信好友瞭解具體培訓內容

3月23日開始上課,還剩最後5個名額,點選閱讀原文連結即可報名。
贊(0)

分享創造快樂