Java生鮮電商平臺-高可用微服務系統如何設計?

Java生鮮電商平臺-高可用微服務系統如何設計?數據庫

 

說明:Java生鮮電商平臺高可用架構每每有如下的要求:後端

高可用。這類的系統每每須要保持必定的 SLA,7*24 時不間斷運行不表明徹底不掛,而是有必定的百分比的。緩存

例如咱們常說的可用性需達到 4 個 9(99.99%),整年停機總計不能超過 1 小時,約爲 53 分鐘,也即服務停用時間小於 53 分鐘,就說明高可用設計合格。服務器

用戶分佈在全國。大規模微服務系統所支撐的用戶通常在全國各地,於是每一個地區的人,都但願可以就近訪問,因此通常不會一套系統服務全國,而是每一個地區都要有相應的業務單元,使得用戶能夠就近訪問。網絡

併發量大,存在波峯波谷。微服務之因此規模比較大,實際上是承載的壓力比較大,並且須要根據請求的波峯波谷進行彈性伸縮。架構

有故障性能診斷和快速恢復的機制。大規模微服務場景下,運維人員很難進行命令式手動運維來控制應用的生命週期,應該採用聲明式的運維方法。併發

另一旦有了性能瓶頸或者故障點,應該有自動發現定位的機制,迅速找到瓶頸點和故障點,及時修復,才能保障 SLA。負載均衡

戰略設計運維

爲了知足以上的要求,這個系統毫不是運維組努力一把,或者開發組努力一把,就能解決的,是一個端到端的,各個部門共同完成的一個目標,因此咱們常稱爲戰略設計。異步

研發

一個能支撐高併發,高可用的系統,必定是須要從研發環節就開始下功夫的。

首先,每個微服務都有實現良好的無狀態化處理,冪等服務接口設計

狀態分爲分發,處理,存儲幾個過程,若是對於一個用戶的全部的信息都保存在一個進程中,則從分發階段,就必須將這個用戶分發到這個進程,不然沒法對這個用戶進行處理。

然而當一個進程壓力很大的時候,根本沒法擴容,新啓動的進程根本沒法處理那些保存在原來進程的用戶的數據,不能分擔壓力。

因此要將整個架構分紅兩個部分,無狀態部分和有狀態部分,而業務邏輯的部分每每做爲無狀態的部分,而將狀態保存在有狀態的中間件中,如緩存,數據庫,對象存儲,大數據平臺,消息隊列等。

這樣無狀態的部分能夠很容易的橫向擴展,在用戶分發的時候,能夠很容易分發到新的進程進行處理,而狀態保存到後端。

然後端的中間件是有狀態的,這些中間件設計之初,就考慮了擴容的時候,狀態的遷移,複製,同步等機制,不用業務層關心。

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

會話數據等,主要保存在內存中。對於保存在內存裏的數據,例如 Session,能夠放在外部統一的緩存中。

結構化數據,主要是業務邏輯相關。對於業務相關的數據,則應該保存在統一的數據庫中。

文件圖片數據,比較大,每每經過 CDN 下發。對於文件,照片之類的數據,應該存放在統一的對象存儲裏面。

非結構化數據,例如文本,評論等。對於非結構化數據,能夠存在統一的搜索引擎裏面,例如 ElasticSearch。

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

這部分就須要經過重試進行解決,當本次調用過程當中失敗以後,前序的進程會進行重試,例如 Dubbo 就有重試機制。

既然重試,就須要接口是冪等的,也即同一次交易,調用兩次轉帳 1 元,不能最終轉走 2 元。

接口分爲查詢,插入,更新,刪除等操做:

對於查詢接口來說,自己就是冪等的,不用作特殊的判斷。

對於插入接口來說,若是每個數據都有惟一的主鍵,也能保證插入的惟一性,一旦不惟一,則會報錯。

對於更新操做來說,則比較複雜,分兩種狀況。一種狀況是同一個接口,先後調用屢次的冪等性。另外一種狀況是同一個接口,併發環境下調用屢次的正確性。

 

爲了保持冪等性,每每要有一個冪等表,經過傳入冪等參數匹配冪等表中 ID 的方式,保證每一個操做只被執行一次,並且在實行最終一致性的時候,能夠經過不斷重試,保證最終接口調用的成功。

對於併發條件下,誰先調用,誰後調用,須要經過分佈式鎖如 Redis,ZooKeeper 等來實現同一個時刻只有一個請求被執行,如何保證屢次執行結果仍然一致呢?則每每須要經過狀態機,每一個狀態只流轉一次。

還有就是樂觀鎖,也即分佈式的 CAS 操做,將狀態的判斷、更新整合在一條語句中,能夠保證狀態流轉的原子性。樂觀鎖並不保證更新必定成功,須要有對應的機制來應對更新失敗。

其次,根據服務重要度實現熔斷降級、限流保護策略

服務拆分多了,在應用層面就會遇到如下問題:

服務雪崩:即一個服務掛了,整個調用鏈路上的全部的服務都會受到影響。

大量請求堆積、故障恢復慢:即一個服務慢,卡住了,整個調用鏈路出現大量超時,要長時間等待慢的服務恢復到正常狀態。

爲了解決這些問題,咱們在應用層面實施瞭如下方案:

經過熔斷機制,當一個服務掛了,被影響的服務可以及時熔斷,使用 Fallback 數據保證流程在非關鍵服務不可用的狀況下,仍然能夠進行。

經過線程池和消息隊列機制實現異步化,容許服務快速失敗,當一個服務由於過慢而阻塞,被影響服務能夠在超時後快速失敗,不會影響整個調用鏈路。

當發現整個系統的確負載太高的時候,能夠選擇降級某些功能或某些調用,保證最重要的交易流程的經過,以及最重要的資源所有用於保證最核心的流程。

還有一種手段就是限流,當既設置了熔斷策略,又設置了降級策略,經過全鏈路的壓力測試,應該可以知道整個系統的支撐能力。

於是就須要制定限流策略,保證系統在測試過的支撐能力範圍內進行服務,超出支撐能力範圍的,可拒絕服務。

當你下單的時候,系統彈出對話框說 「系統忙,請重試」,並不表明系統掛了,而是說明系統是正常工做的,只不過限流策略起到了做用。

其三,每一個服務都要設計有效探活接口,以便健康檢查感知到服務狀態

當咱們部署一個服務的時候,對於運維部門來說,能夠監控機器的狀態或者容器的狀態是否處於啓動狀態,也能夠監控到進程是否啓動,端口是否監聽等。

可是對於已經啓動的進程,是否可以正常服務,運維部門沒法感知,須要開發每一個服務的時候,設計一個有效探活接口,讓運維的監控系統能夠經過調用這個接口,來判斷進程可以正常提供服務。

這個接口不要直接返回,而是應該在進程內部探查提供服務的線程是否出去正常狀態,再返回相應的狀態編碼。

只有這樣,開發出來的服務和運維才能合做起來,保持服務處於某個副本數,不然若是一部分服務雖然啓動,可是處於假死狀態,會使得其餘正常服務,沒法承受壓力。

其四,經過制定良好的代碼檢查規範和靜態掃描工具,最大化限制由於代碼問題形成的系統不可用

要保持線上代碼的高可用性,代碼質量是關鍵,大部分線上問題,不管是性能問題,仍是穩定性問題,都是代碼形成的,而非基礎設施形成的。

並且基礎設施的可用率爲 99.95%,可是服務層要求的可用率高於這個值,因此必須從業務層高可用來彌補。

除了下面的高可用架構部分,對於每個服務來說,制定良好的代碼檢查規範和靜態掃描工具,經過大量的測試用例,最大化限制由於代碼問題形成的系統不可用,是必須的,是高可用的基礎。

高可用架構設計

在系統的每個部分,都要避免單點。系統冗餘每每分管控面和數據面,並且分多個層次,每每每個層次都須要進行高可用的設計。

 
 

在機房層面,爲了高可用應該部署在多個區域,或者多個雲,每一個區域分多個可用區進行部署。

對於雲來說,雲的管控要多機房高可用部署,使得任何一個機房故障,都會使得管控依然可使用。

這就須要管控的組件分佈於至少兩個機房,管控的數據庫和消息隊列跨機房進行數據同步。

對於雲的數據面來說,入口的網關要和機房網絡配合作跨機房的高可用,使得入口公網 IP 和負載均衡器,在一個機房故障的狀況下,能夠切換至另外一個機房。

 
 

在雲之上要部署 Kubernetes 平臺,管控層面 Kubernetes 要實現高可用部署,etcd 要跨機房高可用部署,Kubernetes 的管控組件也要跨機房部署。

固然還有一種狀況是機房之間距離比較遠,須要在每個機房各部署一套 Kubernetes。

這種狀況下,Kubernetes 的管控依然要實現高可用,只不過跨機房的高可用就須要應用層來實現了。

在應用層,微服務的治理平臺,例如註冊發現,ZooKeeper 或者 Euraka,APM,配置中心等都須要實現跨機房的高可用。另外就是服務要跨機房部署,實現城市級機房故障遷移能力。

運維

運維一個大規模微服務系統也有不同的挑戰。

首先,建議使用的是 Kubernetes 編排的聲明式的運維方式,而非 Ansible 之類命令式的運維方式。

另外,對於系統的發佈,要進行灰度、藍綠髮布,下降系統上線發佈風險。要有這樣的理念,任何一個新上線的系統,都是不可靠的。

 
 

因此能夠經過流量分發的模式,逐漸切換到新的服務,從而保障系統的穩定。

其三,完善監控及應對機制,對系統各節點、應用、組件全面地監控,可以第一時間快速發現並解決問題。

 
 

監控絕非只有基礎設施的 CPU,網絡,磁盤的監控,應用的,業務的,調用鏈的監控都應該有。

並且對於緊急事件,應該有應急預案,應急預案是在高可用已經考慮過以後,仍然出現異常狀況下,應該採起的預案,例如三個 etcd 全掛了的狀況。

其四,持續關注線上系統網絡使用、服務器性能、硬件存儲、中間件、數據庫燈指標,重點關注臨界狀態,也即當前還健康,可是立刻可能出問題的狀態。

例如網關 PPS 達到臨界值,下一步就要開始丟包了,數據庫快滿了,消息出現大量堆積等等。

DBA

對於一個在線業務系統來說,數據庫是重中之重,不少的性能瓶頸定位到最後,均可能是數據庫的問題。因此 DBA 團隊要對數據庫的使用,進行把關。

形成數據庫性能問題,一方面是 SQL 語句的問題,一方面是容量的問題。

例如查詢沒有被索引覆蓋,或者在區分度不大的字段上創建的索引,是否持鎖時間過長,是否存在鎖衝突等等,都會致使數據庫慢的問題。

於是全部上線的 SQL 語句,都須要 DBA 提早審覈,而且要對於數據庫的性能作持續的監控,例如慢 SQL 語句等。

另外對於數據庫中的數據量也要持續的監控,到必定的量就須要改分佈式數據庫 DDB,進行分庫分表,到必定的階段須要對分佈式數據庫進行擴容。

故障演練和性能壓測

再好的規劃也比不上演練,再好的性能評估也比不上在線的性能壓測。

性能問題每每是經過線上性能壓測發現的。線上壓力測試須要有一個性能測試的平臺,作多種形式的壓力測試。

例如容量測試,經過梯度的加壓,看到何時實在不行。摸高測試,測試在最大的限度之上還能承受多大的量,有必定的餘量會保險一些,內心相對比較有底。

再就是穩定性測試,測試峯值的穩定性,看這個峯值可以撐一分鐘,兩分鐘仍是三十分鐘。還有秒殺場景測試,限流降級演練測試等。

只有通過性能壓測,才能發現線上系統的瓶頸點,經過不斷的修復和擴容瓶頸點,最終才能知道服務之間應該以各類副本數的比例部署,才能承載指望的 QPS。

對於可能遇到的故障,能夠進行故障演練,故意模擬一些故障,來看系統如何反應,是否會由於自修復,多副本,容錯等機制,使得這些故障對於客戶端來說沒有影響。

戰術設計

下面,咱們就從架構的每一個層次,進行戰術設計。咱們先來看一下高可用部署架構選型以及他們的優劣:

 
 

高可用性要求和系統的負載度和成本是強相關的。越簡單的架構,部署成本越低的架構,高可用性越小,例如上面的單體應用。

而微服務化,單元化,異地多活,必然致使架構複雜難以維護,機房成本比較高,因此要使用多少成本實現什麼程度的高可用,是一個權衡。

高可用的實現須要多個層次一塊兒考慮:

 
 

首先是應用層,能夠經過異地多活單元保證城市級高可用,這樣使得一個城市由於災難宕機的時候,另一個城市能夠提供服務。

另外每一個多活單元採用雙機房保證機房級高可用,也即同城雙機房,使得一個城市中一個機房宕機,另外一個機房能夠提供服務。

再者每一個機房中採用多副本保證明例級高可用,使得一個副本宕機的時候,其餘的副本能夠提供服務。

其次是數據庫層,在數據中心之間,經過主從複製或 MGR 實現數據異步複製,在每一個集羣單元中採用 DDB 分庫分表,分庫分表中的每一個實例都是有數據庫同步複製。

其三是緩存層,在數據中心之間,緩存採用多集羣單元化複製,在每一個集羣單元中採用多副本主從複製。

其四微服務治理平臺層,平臺組件異地多活單元保證了城市級高可用,平臺組件每一個多活單元採用雙機房保證機房級高可用,平臺組件每一個機房中採用多副本保證明例級高可用。

當有了以上高可用方案以後,則如下的故障等級以及影響時間以下表格:

 
 

接下來,咱們每一個層次詳細論述。

應用層

下圖以最複雜的場景,假設有三個城市,每一個城市都有兩個徹底對等的數據中心。三個城市的數據中心也是徹底對等的。

咱們將整個業務數據按照某個維度分紅 A,B,C 三部分。這樣任何一部分所有宕機,其餘部分照樣能夠提供服務。

對於有的業務,若是省級別的服務中斷徹底不能忍受,市級別的服務中斷要求恢復時間至關短,而區縣級別的服務中斷恢復時間能夠相對延長。

在這種場景下,能夠根據地區來區分維度,使得一個區縣和另一個區縣的數據屬於不一樣的單元。

爲了節約成本,模型可能會更加簡化。中心節點和單元化節點不是對稱的。中心節點能夠實現同城雙活,而異地單元化的部分只部署一個機房便可。這樣是能知足大部分高可用性需求的。

這種架構要求實現中間件層和數據庫層單元化,這個咱們後面會仔細講。

 
 

接入層

單元化要求 App 層或者在機房入口區域的接入層,實現中心單元和其餘單元節點的流量分發。

對於初始請求沒有任何路由標記的,能夠隨機分發給任何一個單元,也能夠根據地區或者運營商在 GSLB 中分發給某個就近的單元。

應用層接收到請求之後,根據本身所在的單元生成路由信息,將路由信息返回給接入層或者 App。

接下來 App 或者接入層的請求,都會帶着路由信息,選擇相應的單元進行發送,從而實現了請求的處理集中在本單元。

 
 

中間件層

在中間件層,咱們以 ZooKeeper 爲例,分爲如下兩個場景:

場景一:ZooKeeper 單元化主從多活

在這種場景下,主機房和單元化機房距離相隔較近,時延很小,能夠當作一個機房來對待。能夠採用 ZooKeeper 高可用保障經過多 ZooKeeper 實例部署來達成。

如圖所示,主機房 ZooKeeper 有 Leader 和 Follower,單元化機房的 ZooKeeper 僅爲 Observer。

 
 

場景二:ZooKeeper 單元化多集羣複製

兩個機房相距較遠,每一個機房部署一套 ZooKeeper 集羣,集羣之間進行數據同步。

各機房應用鏈接機房內的 ZooKeeper 集羣,註冊的信息經過數據同步,可以被其餘機房應用獲取到。

單一機房 ZooKeeper 集羣不可用,其他機房不受影響。當前不考慮作不一樣機房之間的集羣切換。

 
 

數據庫層

在數據庫層,首先要解決的問題是,分佈式數據庫 DDB 集羣多機房同步複製。

在單元內採用同城主從複製模式,跨單元採用 DTS/NDC 實現應用層數據雙向同步能力。

 
 

對於數據的 ID 分配,應該採起全局惟一 ID 分配,有兩種實現方式,若是主機房和單元化機房距離較近,可採用 ID 分配依然採用中心式, 全部機房的單元所有向同一中心服務申請 ID 的方式。

若是主機房和單元化機房相隔較遠,可採用每一個單元各自分配, 經過特定規則保證每一個機房獲得的最終 ID 不衝突的方式。

 
 

緩存層

在緩存層,有兩種方式:

方式一是集羣熱備,新增 Redis 集羣做爲熱備份集羣。

 
 

主集羣與備份集羣之間在服務端進行數據同步,經過 Redis Replication 協議進行同步處理。

離線監聽主集羣狀態,探測到故障則進行主備之間切換,信息經過配置中心下達客戶端,類哨兵方式進行監聽探活。

在這種場景下,集羣之間數據在服務端進行同步,正常狀況下,集羣之間數據會一致。但會存在必定的複製時延。

在故障切換時,可能存在極短期內的數據丟失。若是將緩存僅僅當緩存使用,不要作內存數據庫使用,則沒有問題。

第二種方式,集羣多活。新增集羣做爲多活集羣,正常狀況下客戶端根據 Key 哈希策略選擇分發到不一樣集羣。

 
 

客戶端經過 Proxy 鏈接集羣中每個節點,Proxy 的用處是區分客戶端寫入與集羣複製寫入。

集羣之間在服務端進行數據雙向複製,數據變動經過 Redis Replication 協議獲取。

離線監聽主集羣狀態,探測到故障則進行切換,信息經過配置中心下達客戶端,類哨兵方式進行監聽探活。

此方案應用於單純的集羣間高可用時,同一個 Key 在同一段時間內只會路由到同一個集羣,數據一致性能夠保證。

在故障切換狀況下,可能存在極端時間內的數據丟失。

微服務治理平臺

做爲大規模微服務的微服務治理平臺,一方面本身要實現單元化,另一方面要實現流量在不一樣單元之間的染色與穿梭。

從 API 網關,NSF 服務治理和管理中心,APM 性能管理,GXTS 分佈式事務管理,容器平臺的管控都須要進行跨機房單元化部署。

 
 

當請求到達一個單元以後,API 網關上就帶有此單元的路由信息,NSF 服務治理與管理平臺在服務之間相互調用的時候,一樣會插入此單元的路由信息。

當一個單元某實例全掛的時候,能夠穿梭到另外一個單元進行調用,並在下一跳調用回本單元,這種方式稱爲流量染色。

 

聯繫QQ:137071249

QQ羣:793305035

相關文章
相關標籤/搜索