天天看點

微服務化之無狀态化與容器化

本文由  網易雲 釋出。

作者:劉超,網易雲解決方案架構師

本文章為《網際網路高并發微服務化架構實踐》系列課程的第四篇

前三篇為:

微服務化的基石——持續內建

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

微服務化的資料庫設計與讀寫分離

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

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

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

阻礙單體架構變為分布式架構的關鍵點就在于狀态的處理。如果狀态全部儲存在本地,無論是本地的記憶體,還是本地的硬碟,都會給架構的橫向擴充帶來瓶頸。

狀态分為分發,處理,存儲幾個過程,如果對于一個使用者的所有的資訊都儲存在一個程序中,則從分發階段,就必須将這個使用者分發到這個程序,否則無法對這個使用者進行處理,然而當一個程序壓力很大的時候,根本無法擴容,新啟動的程序根本無法處理那些儲存在原來程序的使用者的資料,不能分擔壓力。

是以要講整個架構分成兩個部分,無狀态部分和有狀态部分,而業務邏輯的部分往往作為無狀态的部分,而将狀态儲存在有狀态的中間件中,如緩存,資料庫,對象存儲,大資料平台,消息隊列等。

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

微服務化之無狀态化與容器化

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

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

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

微服務化之無狀态化與容器化

對于資料的存儲,主要包含幾類資料:

會話資料等,主要儲存在記憶體中。

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

檔案圖檔資料,比較大,往往通過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的解決。

了解 網易雲 :

網易雲官網:https://www.163yun.com/

新使用者大禮包:https://www.163yun.com/gift

網易雲社群:https://sq.163yun.com/

繼續閱讀