讓咱們先從運維的真實場景出發,來看一下單體應用存在的問題。這裏先分享兩個真實的生產案例:前端
案例一是某核心業務系統,全部的業務邏輯代碼都打包在同一個WAR包裏部署,運行了將近幾百個同構的實例在虛擬機上。某次由於應用包中的一個功能模塊出現異常,致使實例掛起,整個應用都不能用了。由於它是一個單體,因此儘管有幾百個實例在運行,可是這幾百個實例都是異常的。業務系統是通過多年建設起來的,排查起來也很複雜,最終整個業務系統癱瘓了近六個小時才恢復。同時,由於有多個前臺系統也調用了這個後臺系統,致使全部要調用的前臺系統也都所有癱瘓了。設想一下,若是這個場景使用的是微服務架構,每一個微服務都是獨立部署的,那影響的也只是有異常的微服務和其餘相關聯的服務,而不會致使整個業務系統都不能使用。算法
另外的案例是一個客服系統,這個系統有一個特色,早上八點的時候會有大量的客服登陸。這個登陸點是全天中業務併發量最高的時間點,登陸時系統須要讀取一些客戶信息,加載到內存。後來一到早上客服登陸時,系統常常出現內存溢出,進而致使整個客服系統都用不了。當時系統應對這種場景作架構冗餘時,並非根據單獨的業務按需進行擴展,而是按照單體應用的長板進行冗餘。好比說早上八點併發量最高,單點登錄模塊業務需求很是大,爲適應這個時間點這個模塊的業務壓力,系統會由原來的八個實例擴展到十六個實例,這時的擴展是基於整個系統的。但事實上,在其餘時間段,這個單點登錄模塊基本不使用,且監控的數據顯示,主機的資源使用狀況基本在40%如下,形成了很大的資源浪費。因此在一個大型的業務系統中,每一個服務的併發壓力不同,若是都按壓力最大的模塊進行總體擴展,就會形成資源的浪費,而在微服務的模式下,每一個服務都是按自身壓力進行擴展的,就能夠有效的提升資源利用率。數據庫
從這兩個例子中,咱們能夠看出,單體應用存在以下兩個問題:一個是橫向擴展時須要總體擴展,資源分配最大化,不能按需擴展和分配資源;另外一個是若是單體中有一個業務模塊出現問題,就會是全局性災難,由於全部業務跑在同一個實例中,發生異常時不具有故障隔離性,會影響整個業務系統,整個入口都會存在問題。後端
所以,咱們當時考慮把綜合業務拆分,進行更好的資源分配和故障隔離。緩存
下面咱們看一下單體應用和微服務的對比,如圖1所示。這裏從微服務帶來的好處和額外的複雜性來說。網絡
微服務的好處:架構
局部修改,局部更新。當運維對一個單體應用進行修改時,可能要先把整個包給停了,而後再去修改,而微服務只需逐步修改和更新便可;併發
故障隔離,非全局。單體應用是跑在一塊兒,因此只要一個模塊有問題,其餘就都會有問題。而微服務的故障隔離性、業務可持續性都很是高;負載均衡
資源利用率高。單體應用的資源利用率低,而使用微服務,能夠按需分配資源,資源利用率會很是高。框架
微服務帶來的複雜性:
微服務間較強的依賴關係管理。之前單體應用是跑在一塊兒,無依賴關係管理,若是拆成微服務依賴關係該如何處理,好比說某個微服務更新了會不會對整個系統形成影響。
部署複雜。單體應用是集中式的,就一個單體跑在一塊兒,部署和管理的時候很是簡單,而微服務是一個網狀分佈的,有不少服務須要維護和管理,對它進行部署和維護的時候則比較複雜。
如何更好地利用資源。單體應用在資源分配時是總體分配,擴展時也是總體擴展,數量可控,而在使用微服務的狀況下,須要爲每個微服務按需分配資源,那麼該爲每一個微服務分配多少資源,啓動多少個實例呢,這也是很是大的問題。
監控管理難。之前咱們用Java,就是一個單體應用,監控和管理很是簡單,由於它就是一個1,可是使用微服務它就是N個,監控管理變得很是複雜。另外是微服務之間還有一個協做的問題。
使用微服務,第一步是要構建一個一體化的DevOps平臺,如圖2。若是你不使用DevOps作微服務的話,整個環境會變得很是的亂、很是的糟糕。它會給你的整個開發、測試和運維增長不少成本,因此第一步咱們是提升DevOps的能力,可以把它的開發、部署和維護進行很完美的結合,才能夠說咱們真正可以享受到微服務架構的福利。
容器的出現給微服務提供了一個完美的環境,由於咱們能夠:
基於容器作標準化構建和持續集成、持續交付等。
基於標準工具對部署在微服務裏面的容器作服務發現和管理。
透過容器的編排工具對容器進行自動化的伸縮管理、自動化的運維管理。
因此說,容器的出現和微服務的發展是很是相關的,它們共同發展,造成了一個很是好的生態圈。下面詳細講下DevOps的各個模塊。
持續集成與持續發佈
持續集成的關鍵是徹底的自動化,讀取源代碼、編譯、鏈接、測試,整個建立過程自動完成。咱們來看一下如何用Docker、Maven、Jenkins完成持續集成。
如圖3所示。首先是開發人員把程序代碼更新後上傳到Git,而後其餘的事情都將由Jenkins自動完成。那Jenkins這邊發生什麼了呢?Git在接收到用戶更新的代碼後,會把消息和任務傳遞給Jenkins,而後Jenkins會自動構建一個任務,下載Maven相關的軟件包。下載完成後,就開始利用Maven Build新的項目包,而後重建Maven容器,構建新的Image並Push到Docker私有庫中。而後刪除正在運行的Docker容器,再基於新的鏡像從新把Docker容器拉起來,自動完成集成測試。整個過程都是自動的,這樣就簡化了本來複雜的集成工做,一天能夠集成一次,甚至是屢次。
依賴關係管理
前面講到,當微服務多的時候,依賴關係管理也會比較複雜,如今比較流行的是基於消費者驅動的契約管理。在開發一個微服務時,並不須要另一個微服務開發完後再作集成測試,而是使用契約的方式。契約經過提供標準化的輸出,說明請求的內容、回覆的內容、交換的數據,開發微服務時符合契約裏這些條件便可。
如圖4所示,微服務A經過模擬與微服務B的交互,將交互內容保存在契約裏,而微服務B開發時需知足這個契約裏的條件,這樣就不須要A和B徹底完成了才能作測試。當不少微服務與微服務B關聯時,每一個微服務經過契約告訴微服務B請求的內容、正確的響應和請示的數據,而後微服務B經過契約模擬這個測試過程,而其餘的微服務則須要知足這個契約。當微服務進行升級時,也是要先知足全部契約,這樣微服務間的關係就能夠更好的進行管理。
典型的微服務架構
圖5是典型的微服務架構模型,採用的是Kubernetes框架。把業務系統拆分紅不少的微服務,而後經過服務註冊的方式去發現整個生產環境中全部的微服務,經過負載均衡組件進行分發,再用服務調度去進行彈性伸縮,而客戶端則只須要經過API網關訪問微服務。除此以外,微服務的運維也很重要。開發是實現功能性需求,而在實際的生產環境中,咱們更應該關心非功能性的需求。由於即便功能實現了,跑到生產上卻不能用,功能開發再完美也沒有用。
服務發現與負載均衡
服務發現與負載均衡使用的是Kubernetes的架構,如圖6。每個微服務都有一個IP和PORT,當調用一個微服務時,只須要知道微服務的IP,而不須要關心容器的IP,也不須要關心pod的IP。雖然每一個pod也有IP和PORT,但當一個pod啓動時,就會把pod的IP和PORT註冊到服務發現模塊,再進行負載均衡。因此當多個pod啓動時,對於用戶來講仍是隻須要知道service的IP,不須要知道後端啓動了多少pod、IP是多少,這就解決了網絡的問題。
日誌集中式管理
之前單體的狀況下,單體的數量少,日誌數量也相應比較少,而在微服務架構下,由於拆分紅了不少微服務,相應的日誌會很是多且散,這種狀況下須要對日誌進行集中的管理。咱們能夠在每一個容器裏跑日誌監控,把全部日誌採集進行集中管理和存儲,再經過簡易操做的UI界面進行索引和查詢。
監控管理
而後就是監控方面了。微服務的量是很是大的,這個時候如何有效地監控是極其重要的。咱們剛開始作監控的時候,有幾百個實例對同一個關鍵字進行監控,出故障後會收到幾百條短信,由於每個實例都會發一條短信。這時候嚴重的致命性的報警就會看不到,由於手機信息已經爆炸了,因此要對報警進行分級,精確告警,最重要的是儘可能讓故障在發生以前滅亡。所以,在作監控時要對故障提早進行判斷,先自動化處理,再看是否須要人爲處理,而後經過人爲的干預,有效的把故障在發生以前進行滅亡。
但若是全部事情都靠人爲去處理,這個量也是很是大的,因此對故障進行自動化隔離和自動化處理也很重要。咱們在寫自動化故障處理的時候研究了不少常見的故障,寫了不少算法去判斷,精確到全部的故障,這樣基本的常見的故障和能夠策劃處理的故障均可以自動化處理掉。以前沒有出現的故障,出現以後咱們就會去研究是否能夠作成自動化處理。若是生產上作的不精確,對生產會是災難性的,因此咱們對生產的故障自動化處理也作了不少研究。
前面講了不少運維,下面來了解一下Docker和微服務在七牛雲中的實踐,以七牛的文件處理服務架構演變爲例。
文件處理服務是指,用戶把原圖存到七牛的雲存儲以後,而後使用文件處理服務,就能夠把它變成本身想要的服務,好比剪裁、縮放和旋轉等,如圖9。
過去爲適應不一樣的場景,用戶須要針對同一圖片自行處理後上傳全部版本,但若是使用七牛的文件處理服務,則只需提供原圖就能夠了,其餘版本的圖片均可以經過七牛進行處理。如圖10所示,咱們把一張原圖放進去以後,只須要在原圖後面加一個問號和參數,就能夠呈現出想要的方式,好比說加水印、旋轉、縮放和剪裁。
文件處理服務的架構在早期是很是笨拙的,如圖11所示。FopGate是業務的入口,經過work config靜態配置實際進行計算的各類worker集羣的信息,裏面包含了圖片處理、視頻處理、文檔處理等各類處理實例。集羣信息在入口配置中寫死了,後端配置變動會很不靈活,由於是靜態配置,突發請求狀況下,應對可能不及時,且FopGate成爲流量穿透的組件(業務的指令流與數據流混雜在一塊兒),好比說要讀一張圖片,這張圖片會通過FopGate導過來。
因而,咱們本身組建了一個叫Discovery的服務,由它進行負載均衡和服務發現,用於集羣中worker信息的自動發現,每一個worker被添加進集羣都會主動註冊本身,FopGate從Discovery獲取集羣信息,完成對請求的負載均衡。同時在每一個計算節點上新增Agent組件,用於向Discovery組件上報心跳和節點信息,並緩存處理後的結果數據(將數據流從入口分離),另外也負責節點內的請求負載均衡(實例可能會有多個)。此時業務入口只需負責分發指令流,但仍然須要對請求作節點級別的負載均衡。這是第二個階段,如圖12。
在第三個階段,咱們把文件處理架構遷移到了容器平臺上,取消了業務的Discovery服務,轉由平臺自身的服務發現功能。每一個Agent無需和Discovery維護心跳,也再也不須要上報節點信息,每一個Agent對應一個計算worker,並按工種獨立成Service,好比ImageService、VideoService,因爲後端只有一個worker,所以也不須要有節點內的負載均衡邏輯,業務入口無需負載均衡,只需請求容器平臺提供的入口地址便可,如圖13所示。
上一個階段中,每一個Agent仍然和計算實例綁定在一塊兒,而這麼作其實只是爲了方便業務的無痛遷移,由於Agent自己的代碼會有一些邏輯上的假設。在第四階段中,如圖14,咱們進一步分離了Agent和worker,Agent獨立成一個Service,全部的worker按工種獨立成Service。這麼分離的目的在於,Agent可能會有文件內容緩存,屬於有狀態的服務,而全部的worker是真正幹活、無狀態的服務。分離以後的好處在於,worker的數量能夠隨時調整和伸縮,而不影響Agent中攜帶的狀態。能夠看到,相比於最先的架構,業務方只需集中精力開發業務自己,而無需重複造輪子,實現各類複雜的服務發現和各類負載均衡的代碼。另外,自從部署到容器平臺以後,平臺的調度器會自動根據節點的資源消耗情況作實例的遷移,這樣使得計算集羣中每一個節點的資源消耗更加均衡。
踩過的那些坑
圖15所示的是單體應用訪問後端數據庫使用的數據庫鏈接池,鏈接池裏的鏈接是能夠複用的,而單體中全部的業務模塊均可以調用同一個池裏的鏈接。這個數據庫鏈接池能夠進行動態的伸縮,但它鏈接到後端數據庫的鏈接是有上限的。拆分紅微服務以後,可能每個微服務都要建立一個數據庫鏈接池,微服務間的數據庫鏈接池並不能共享。當對微服務進行彈性伸縮時,並不知道它會彈性到什麼程度,這時就可能會對後端數據庫形成鏈接風暴,好比本來只有五千個長鏈接,可是因爲彈性伸縮(可能根據前臺業務狀況進行了很是大的伸縮),會對數據庫發生鏈接風暴,對數據庫形成很是大的壓力。
爲了確保不會因某個服務的鏈接風暴把數據庫拖垮,對其餘服務形成影響,咱們作了一些優化。首先是優化了咱們的一些彈性算法,其次是限定了容器彈性伸縮的範圍,由於沒有限制的伸縮,可能會對後端數據庫形成壓力。另一個是下降鏈接池初始化的大小,好比初始化設定爲10,它往數據庫裏面的長鏈接就是100個,若是設定爲1,往數據庫裏面長鏈接就只有10個,若是太多則可能會對數據庫形成壓力。還有就是數據庫端進行一些限定,當鏈接達到必定數量就進行預警,限制彈性擴展。
我在傳統企業工做了七八年,咱們一直在探討單體應用如何拆分紅微服務。其實對傳統企業來說,他們的單體是很是大的,並且內部的耦合性是很是強的,內部調用很是多。若是咱們冒冒失失地將單體拆分紅微服務,會佔用很是大的網絡傳輸。若是在拆成微服務的時候,沒有進行很好的架構設計,拆成微服務並非享受它帶來的好處,而是災難。因此在拆的時候,好比說前端、後端的拆分、耦合性不是特別強的拆分,咱們提倡先跑到容器裏面,看一下運維容器的時候有什麼坑,而後解決這些坑,而後不斷完善。咱們能夠把耦合性不是那麼高的拆成大的塊,而後把信息密集型的進行拆分,慢慢拆分紅咱們理想的架構。這是一個按部就班的過程,不可一蹴而就。
圖16是微服務的一些設計準則,例如設計提倡最小化功能,小到不能再小。這時須要每個服務標準化的提供出來,有一個標準化的接口和其餘微服務進行通訊。還須要是異步通訊,若是是同步會形成堵塞。每一個微服務都要有獨立的數據存儲。此外,服務還須要是無狀態的,由於若是有狀態,彈性伸縮時可能會有數據丟失,能夠用一些其餘的方法處理這些有狀態的數據。