分佈式筆記

CAP定理

CAP定理是由加州大學伯克利分校Eric Brewer教授提出來的,他指出WEB服務無法同時滿足一下3個屬性:

  • 一致性(Consistency):客戶端知道一系列的操作都會同時發生(生效)
  • 可用性(Availability) : 每個操作都必須以可預期的響應結束
  • 分區容錯性(Partition tolerance) : 即使出現單個組件無法可用,操作依然可以完成

BASE理論

在分佈式系統中,我們往往追求的是可用性,它的重要程序比一致性要高,那麼如何實現高可用性呢? 前人已經給我們提出來了另外一個理論,就是BASE理論,它是用來對CAP定理進行進一步擴充的。BASE理論指的是:

  • Basically Available(基本可用)
  • Soft state(軟狀態)
  • Eventually consistent(最終一致性)

分佈式事務

兩段提交協議(2PC:Two-Phrase Commit)

  • 第一階段(投票階段):該階段的主要目的在於打探數據庫集羣中的各個參與者是否能夠正常的執行事務
    1. 協調者向所有的參與者發送事務執行請求,並等待參與者反饋事務執行結果。
    2. 事務參與者收到請求之後,執行事務,但不提交,並記錄事務日誌。
    3. 參與者將自己事務執行情況反饋給協調者,同時阻塞等待協調者的後續指令。
  • 第二階段(事務提交階段):各個參與者會回覆自己事務的執行情況,這時候存在三種可能
    1. 所有的參與者回覆能夠正常執行事務
    2. 一個或多個參與者回覆事務執行失敗
    3. 協調者等待超時。

    對於第一種情況,協調者將向所有的參與者發出提交事務的通知,具體步驟如下
    1. 協調者向各個參與者發送commit通知,請求提交事務。
    2. 參與者收到事務提交通知之後,執行commit操作,然後釋放佔有的資源。
    3. 參與者向協調者返回事務commit結果信息。


    對於第二、三種情況,協調者均認爲參與者無法正常成功執行事務,爲了整個集羣數據的一致性,所以要向各個參與者發送事務回滾通知,具體步驟如下
    1. 協調者向各個參與者發送事務rollback通知,請求回滾事務。
    2. 參與者收到事務回滾通知之後,執行rollback操作,然後釋放佔有的資源。
    3. 參與者向協調者返回事務rollback結果信息。


    RPC

RPC的全稱是Remote Procedure Call,它是一種進程間通信方式。允許像調用本地服務一樣調用遠程服務,它的具體實現方式可以不同,例如Spring的Http Invoker,Facebook的Thrift二進制私有通信協議
RPC框架實現的幾個核心技術點:

  • 遠程服務提供者需要以某種形式提供服務調用相關的信息,包括但不限於接口定義,數據結構或者中間態的服務定義文件。
  • 遠程代理對象:服務調用者調用的服務實際上是遠程服務的本地代理。
  • 通信:RPC框架與具體的協議無關。
  • 序列化:遠程通信,需要將對象轉換成二進制碼流進行網絡傳輸。

SOA服務化架構

SOA是一種粗粒度,鬆耦合的以服務爲中心的架構,接口之間通過定義明確的協議和接口進行通信。
SAO面向服務的一般原則

  • 服務可複用:不管是否存在即時複用的機會,服務均被設計成支持潛在的可複用
  • 服務共享一個標準契約
  • 服務是鬆耦合的
  • 服務是底層邏輯的抽象
  • 服務是可組合課編排的
  • 服務是自治的
  • 服務是無狀態的
  • 服務是可被自動發現的

灰度發佈

灰度發佈,又名金絲雀發佈,或者灰度測試,是指在黑與白之間能夠平滑過渡的一種發佈方式。在其上可以進行A/B testing,即讓一部分用戶繼續用產品特性A,一部分用戶開始用產品特性B,如果用戶對B沒有什麼反對意見,那麼逐步擴大範圍,把所有用戶都遷移到B上面來。

灰度發佈是對某一產品的發佈逐步擴大使用羣體範圍,也叫灰度放量。灰度發佈可以保證整體系統的穩定,在初始灰度的時候就可以發現、調整問題,以保證其影響度。

灰度期:灰度發佈開始到結束期間的這一段時間,稱爲灰度期。

灰度發佈主要作用

  • 解決服務升級不兼容的問題
  • 及早獲得用戶的意見反饋,完善產品功能,提示產品服務
  • 縮小服務升級所影響的用戶範圍,降低升級風險
  • 讓用戶及早參與產品測試,加強用戶互動

灰度環境準備

  • 系統運維人員通過管理員賬號登錄登錄灰度發佈Portal或者進入服務治理的灰度發佈界面
  • 在生產環境中圈定本輪灰度發佈的範圍,通常它是一個應用集羣,包括前後臺服務
  • 將選擇的服務灰度發佈範圍信息保存到服務註冊中心,用於後續規則下發和灰度升級記錄查詢等
  • 通知幅度升級範圍內的服務實例下線,通常會採用優雅停機的方式讓待升級的服務下線,保證升級不中斷業務
  • 應用進程收到優雅停機指令後,將本地進程內緩存的消息處理完,然後優雅退出
  • 從軟件倉庫選擇需要升級的服務安裝鏡像包,用於灰度環境的版本升級
  • 將升級包批量上傳到灰度環境中,把原來的業務軟件包做本地備份之後升級服務版本

灰度規則設置

  • 按照門戶類型分類,例如手機客戶端,自助業務辦理終端等

  • 按終端類型分,例如安卓,IOS
  • 按照區域進行劃分,東北,華北華中等


 

應用架構

MVC架構:當業務規模很小時,將所有功能都部署在同一個進程中,通過雙機或者前置負載均衡器實現負載分流;此時,用於分離前後臺邏輯的MVC架構是關鍵。
RPC架構:當垂直應用越來越多,應用之間交互不可避免,將核心和公共業務抽取出來,作爲獨立的服務,實現前後臺邏輯分離。此時,用於提高業務複用及拆分的RPC架構是關鍵。
SOA架構:隨着業務發展,服務數量越來越多,服務生命週期管控和運行態的治理成爲瓶頸,此時用於提高服務質量的SOA服務治理是關鍵。
爲服務架構:隨着敏捷開發、持續交付、DevOps理論的發展和實踐,以及基於Docker等輕量級容器部署應用和服務的成熟,爲服務架構開始流行,逐漸成爲應用架構的未來演進方向。
 

通信框架、序列化反序列化的協議棧的關係

協議描述了分佈式服務框架的通信契約,序列化和反序列化用於協議消息對象和二進制數組之間的相互轉換能力,通信框架在技術上承載協議,協議要落地,需要依賴通信框架提供的基礎通信能力。

協議棧:

通常協議棧的消息模型分爲兩部分,消息頭和消息體。消息頭存放協議公共字段和用戶擴展字段,消息體則用於承載消息內容。

 

負載均衡

  • 隨機
    採用隨機算法進行負載均衡。
  • 輪詢
    輪詢,按公約後的權重輪詢比率,到達邊界之後,繼續繞接。輪詢策略的實現非常簡單,它的原理就是按照權重,順序遍歷服務提供者列表,到達上限之後重新歸零,繼續順序循環。
  • 服務調用時延
    消費者緩存所有服務提供者的服務調用時延,週期性的計算服務調用平均時延,然後計算每個服務提供者服務調用時延與平均時延的差值,根據差值大小動態調整權重,保證服務時延大的服務提供者接受更少的消息,防止消息堆積。
  • 一致性哈希
  • 粘滯連接
    客戶端首次和服務端創建鏈路時,將該鏈路標記爲粘滯連接,每次路由時直接選擇粘滯連接,不執行負載均衡接口。當鏈路中斷時,更新粘滯連接爲不可用,重新尋找下一個可用的連接,將其標記爲粘滯連接。

 

容災策略

Failover(失敗自動切換)

服務調用失敗自動切換策略指的是當發生RPC調用異常時,重新選路,查找下一個可用的服務提供者。
failover策略的設計思路如下:
消費者路由操作完成之後,獲得目標地址,調用通信框架的消息發送接口發送請求,監聽服務端應答。如果返回的結果是RPC調用異常(超時,流控,解碼失敗等系統異常),根據消費者集羣容錯的策略進行容錯路由,如果是Failover則重新返回到路由Handler的入口,從路由結點繼續執行。選路完成之後,對目標地址進行對比,防止重新路由到故障服務節點,過濾掉上次的故障服務提供者之後,調用通信框架的消息發送接口發送請求消息。

Failback(失敗通知)

服務框架獲取到服務提供者返回的RPC異常響應之後,根據策略進行容錯。如果是Failback模式,則不再重試其他服務提供者,而是將RPC異常通知給消費者,由消費者捕獲異常進行後續處理。

Failcache(失敗緩存)

  • 服務有狀態路由,必須定點發送到指定的服務提供者。當發生鏈路中斷,流控等服務暫時不可用時,服務框架將消息臨時緩存起來,等待週期T,重新發送,直到服務提供者能夠正常處理該消息。
  • 對時延要求不敏感的服務。系統服務調用失敗,通常是鏈路暫時不可用,服務流控,GC掛住服務提供者進程等,這種失敗不是永久性的失敗,它的恢復是可預期的。如果消費者對服務調用時延不敏感,可以考慮採用自動恢復模式,即先緩存,再等待,最後重試。

爲了保證可靠性,Failchche策略在設計的時候需要考慮如下幾個要素:

  • 緩存時間,緩存對象上限數等做出限制,防止內存溢出
  • 緩存淘汰算法的選擇,是否支持用戶配置
  • 定時重試的週期T、重試的最大次數等需要做出限制並支持用戶指定。重試達到最大上限仍失敗,需要丟棄消息,記錄異常日誌。

Failfast(快速失敗)

在業務高峯期,對於一些非核心的服務,希望只調用一次,失敗也不再重試,爲重要的核心服務節約寶貴的運行資源。此時快速失敗是個不錯的選擇。快速失敗的策略比較簡單,獲取到服務調用異常之後,直接忽略異常,記錄異常日誌。

服務調用方式

同步服務調用

  1. 消費者調用服務端發佈的接口,接口調用由分佈式服務框架包裝成動態代理,發起遠程服務調用。
  2. 消費者線程調用通信框架的消息發送接口之後,直接或間接調用wait()方法,同步阻塞等待應答。
  3. 通信框架的IO線程通過網絡將請求消息發送給服務器。
  4. 服務端返回應答消息給消費者,由通信框架負責應答消息的反序列化。
  5. IO線程獲取到應答消息之後,根據消息上下文找到之前同步阻塞的業務線程,notify()阻塞的業務線程,返回應答給消費者,完成服務調用。

異步服務調用

  1. 消費者調用服務端發佈的接口,接口調用由分佈式框架包裝成動態代理,發起遠程服務調用。
  2. 通信框架異步發送請求消息,如果沒有發生IO異常,返回。
  3. 請求消息發送成功之後,IO線程構造Future對象,設置到RPC上下文中。
  4. 用戶線程通過RPC上下文獲取Future對象。
  5. 構造Listener對象,將其添加到Future中,用於服務端應答異步回調通知。
  6. 用戶線程返回,不阻塞等待應答。
  7. 服務端返回應答消息,通信框架複雜反序列化等。
  8. IO線程將應答設置到Future對象的操作結果中。
  9. Future對象掃描註冊的監聽器列表,循環調用監聽器的operationComplete方法,將結果通知給監聽器,監聽器獲取到結果之後,繼續後續業務邏輯的執行,異步服務調用結束。

服務註冊中心

服務註冊中心是分佈式服務框架的目錄服務器,相比於傳統的目錄服務器,它有以下幾個特點:

  1. 高HA:支持數據持久化,支持集羣
  2. 數據一致性問題:集羣中所有的客戶端應該看到同一份數據,不能出現讀或者寫數據不一致。
  3. 數據變更主動推送:當註冊中心的數據發生變更時(增加,刪除,修改)需要能夠及時將變化的數據通知給客戶端。

訂閱發佈機制

服務註冊中心需要支持服務的訂閱發佈,對於服務提供者,可以根據服務名等信息動態發佈服務;對於消費者,可以根據訂閱關係主動獲得服務發佈者的地址信息等。
訂閱發佈機制還有一個比較重要的機制就是對變化的監聽和主動推送能力:

  • 消費者可以監聽一個或多個服務目錄,當目錄名稱,內容發生變更時,消費者可以及時的獲得變更的數據或者變更後的結果信息。
  • 服務提供者可以發佈一個或多個服務,動態修改服務名稱、服務內容等,可以主動將修改後的數據或修改後的結果推送給所有監聽此服務目錄的消費者。
  • 透明化路由:服務提供者和消費者相互解耦,服務提供者位置透明,消費者不需要再硬編碼服務提供者地址。
  • 服務健康狀態檢測:服務註冊中心可以實時檢測發佈服務的質量,如果服務提供者宕機,由服務註冊中心實時通知消費者。

可靠性

服務註冊中心需要支持對等集羣,任意一臺宕機後,服務都能自動切換到其他正常的服務註冊中心。如果服務註冊中心全部宕機,隻影響新的服務註冊,已發佈服務的下線,不影響服務的正常運行和調用,消費者可以依靠本地緩存的服務路由表進行路由。除了服務註冊中心自身的可靠性外,服務提供者的健康狀態檢測也由服務註冊中心負責。服務註冊中心通過長鏈接心跳檢測服務提供者的存在。服務提供者宕機,註冊中心將立即推送服務下線事件通知消費者。消費者將下線的服務提供者地址從緩存的路由表中刪除,新接入的消息將不再路由到故障節點,實現實時故障隔離。

 

ZooKeeper

Zookeeper是Apache Hadoop的一個子項目,它主要用來解決分佈式應用中經常遇到的一些數據管理問題,如統一命名服務,狀態同步服務,集羣管理,分佈式應用統一配置等。
Zookeeper通常由2n+1臺Server組成,每臺server都知道彼此的存在。每臺server都維護內存狀態鏡像以及持久化存儲的事務日誌和快照,對於2n+1臺server,只要n+1臺(大多數)server可用,整個集羣就保持可用。
系統啓動時,集羣中的server會選舉出一臺server爲Leader,其他的就作爲Follower。接着由Follower來服務client的請求,對於不改變系統一致性狀態的讀操作,由Follower的本地內存數據庫直接給client返回結果;對於會改變系統狀態的更新操作。則交給Leader進行提議投票,超過半數通過後返回結果給client。
Zookeeper集羣管理的核心是原子廣播,這個機制保證了各個server之間的數據同步。實現這個機制的協議叫作Zab協議。Zab協議有兩種模式,分別是恢復模式和廣播模式。
當服務啓動或者Leader崩潰後,Zab就進入恢復模式,當Leader被選舉出來,且大多數Follower完成了和Leader的狀態同步之後,恢復模式就結束了。狀態同步保證了Follower和Leader具有相同的系統狀態。一旦Leader和多數Follower狀態同步後它就可以廣播消息了,即進入廣播模式。Zookeeper一直維持在廣播模式,直到Leader崩潰或者失去大部分Follower的支持。

 

服務多版本管理設計

服務多版本管理對象包括服務提供者和消費者

  1. 服務提供者:發佈服務的時候,支持制指定務版本
  2. 服務消費者:消費服務的時候,支持指定引用的服務版本號或版本範圍

服務版本號是有序的,在服務名相同的情況下,兩個相同服務名的不同服務版本的版本號可以比較大小。完整的版本號由”主版本(Major)“+”副版本(Minor)“+”微版本號(Micro)“

  1. 主版本號:表示重大特性或功能變更,接口或者功能可能會不兼容(實際開發中應當避免這種不兼容)
  2. 副版本號:發生了少部分功能變更,或者新增了一些功能
  3. 微版本號:主要用於BUG修復,對應於常見的SP補丁包

服務版本比較的原則:從前往後逐項比較,當且僅當服務名,服務主版本號,副版本號,服務微版本號全部相同時,兩個服務纔是同一個服務,否則以第一個出現差異的版本號的大小決定服務版本的大小。

 

模塊化開發

在OSGI中,我們以模塊化的方式去開發一個系統,每個模塊被稱爲Bundle,OSGI提供了對Bundle的整個生命週期管理,包括打包,部署,運行升級,停止等。
模塊化的核心並不是簡單地把系統拆分成不同的模塊,如果僅僅是拆分,原生的jar包+Eclipse工程就能夠解決問題。更爲重要的是要考慮到模塊中接口的導出,隱藏,依賴。版本管理,打包,部署,運行升級等全生命週期管理,對於原生的jar包是不支持的。傳統開發模式的問題:在maven之前,模塊的劃分通常有兩種方式。
1):定義一個大而全的工程包含多個子模塊,不同模塊之間使用package來進行隔離
2):定義多個子工程,工程之間通過引用的方式進行以來管理
這兩種方式都存在一個問題,無法實現資源的精細劃分和對依賴做統一管理。以jar包爲例,依賴一個jarbao就意味着這個jar包中所有的public資源都可能被引用,但事實上也許只需要依賴jar包中的幾個public接口。我們無法對資源做細粒度,精確的管控,無法控制哪些接口是內部使用,哪些是開放給外部訪問的,我們也不知道public接口都被哪些模塊依賴和使用,消費者是誰。
 

插件熱部署和升級

分佈式服務框架實現服務熱部署和升級的原理如下:

  1. 服務是分佈式集羣部署的,通常也是無狀態的,停掉其中某一個服務節點,並不會影響系統整體的運行質量
  2. 服務自動發現和隔離機制,當有新的節點加入時,服務註冊中心會向消費者集羣推送新的服務地址信息,當有服務節點宕機時,服務註冊中心會發送服務下線通知給消費者集羣,消費者會將下線服務自動隔離
  3. 優雅停機功能,在進程退出之前,處理完消息隊列積壓的消息,不會再接受新的消息,最大限度保障不丟失消息
  4. 集羣容錯功能,如果服務提供者正在應答消息時退出了,消費者會發生服務調用超時,集羣容錯功能會根據策略重試其他正常的服務節點,保證流程不會因爲某個服務實例宕機而中斷
  5. 服務多版本管理,支持集羣中同一個服務的多個版本同時運行,支持路由規則定製,不同的消費者可以消費不同的服務版本

流量控制

當資源成爲瓶頸時,服務框架需要對消費者做限流,啓動流控保護機制。流量控制有多種策略,比較常用的有:針對訪問速率的靜態流控,針對資源佔用的動態流控、針對消費者併發連接數的連接控制和針對並行訪問數的併發控制。

靜態流控

靜態流控主要針對客戶端訪問速率進行控制,它通常根據服務質量等級(SLA)中約定的QPS做全局流量控制,例如訂單服務的靜態流控閾值爲100QPS,則無論集羣有多少個訂單實例,它們總的處理速率之和不能超過100QPS。

傳統靜態流控方案:在服務部署時,根據集羣服務節點個數和靜態流控閾值進行流控,對於超出流控閾值的請求則拒絕訪問
動態配額分配製:由服務註冊中心以流控週期T爲單位,動態推送每個節點分配的流控閾值QPS。當服務節點發生變更時,會觸發服務註冊中心重新計算每個節點的配額,然後進行推送。
動態配額申請制:系統部署時,根據服務節點數和靜態流控QPS閾值,拿出一定比例的配額做初始分配,剩餘的配額放在配額資源池中。哪個服務節點使用完了配額,就主動向服務註冊中心申請配額。配額的申請策略是,如果流控週期爲T,則將週期T分成更小的週期T/N,當前的服務節點數爲M個,則申請的配額爲(總QPS配額-已經分配的QPS配額)/M*T/N。總的配額如果被申請完,則返回0配額給各個申請配額的服務節點,服務節點對新接入的請求消息進行流控。

動態流控

動態流控的最總目標是爲了保命,並不是對流量或者訪問速度做精確控制。觸發動態流控的因子是資源,資源又分爲系統資源和應用資源兩大類,根據不同的資源負載情況,動態流控又分爲多個級別,每個級別流控係數都不同,也就是被拒絕掉的消息比列不同。
分級流控:通常動態流控是分級別的,不同級別拒掉的消息比例不同,這取決於資源的負載使用情況。例如當發生一級流控時,拒絕掉1/8的消息;發生二級流控時,拒絕掉1/4的消息。不同的級別有不同的流控閾值,不同流控因子的流控閾值不同,業務上線之後通常會根據現場的實際情況做閾值調優,因此流控閾值需要支持在線的修改和動態生效。

 

併發控制

併發控制針對線程的併發執行數進行控制,它的本質是限制對某個服務或者服務的方法過度消費,耗用過多的資源而影響其他服務的正常與運行。併發控制有兩種形式:

  1. 針對服務提供者的全局控制
  2. 針對服務消費者的局部控制

屏蔽降級

在一個應嚴實例中,服務往往是合設的,儘管可以通過線程池隔離等方式保證服務之間的資源隔離,但是100%的隔離是不現實的。特別是對緩存,網絡IO,磁盤IO數據庫連接資源等公共依賴無法隔離。在業務高峯期或者大促時,服務之間往往存在激烈的競爭,此時需要對非核心業務做強制降級,不發起遠程調用,直接返回空,異常或者執行特定的本地邏輯,減少自身對公共資源的消費,把資源釋放出來供核心服務使用。

容錯降級

當非核心業務不可用時,可以對故障業務邏輯放通,分佈式服務框架的業務放通實際屬於容錯降級的一種。
容錯降級不僅僅用於業務放通,它也常用於服務提供方在客戶端執行容錯邏輯,容錯邏輯主要包括兩種。

  • RPC異常:通常指超時異常,消息解碼異常,流控異常,系統擁塞保護異常等
  • Service異常:例如登陸校驗失敗異常,數據庫操作失敗異常等

容錯降級與屏蔽降級的主要差異:

  1. 觸發條件不同:容錯降級是根據服務調用結果,自動匹配觸發的;而屏蔽降級往往是通過人工根據系統運行情況手工操作觸發的。
  2. 作用不同:容錯降級是當服務提供者不可用時,讓消費者執行業務放通;屏蔽降級的只要目的是將原屬於降級業務的資源調配出來供核心業務使用。
  3. 調用機制不同:一個發起遠程服務調用,一個只做本地調用。

服務優先級調度

當系統當前資源非常有限時,爲了保證高優先級的服務能夠正常運行,保障服務SLA,需要降低一些非核心業務的調度頻次,釋放部分資源佔用,保障系統的整體運行平穩。服務優先級有多種策略。

  1. 基於線程調度器的優先級調度策略
  2. 基於優先級隊列的優先級調度策略
  3. 基於加權配置的優先級調度策略
  4. 基於服務遷入遷出的優先級調度策略

線程調度器方案

線程優先級被線程調度器用來判斷何時運行哪個線程,理論上,優先級高的線程比優先級低的線程獲得更多的CPU時間。實際上線程獲得的CPU時間通常包括優先級在內的多個因素決定,一個優先級高的線程自然比優先級低的線程獲得更多的CPU執行時間,理論上,相同優先級的線程被調度執行的機會是均等的。在服務發佈的時候,可以根據用戶的優先級配置策略,將服務優先級映射到線程優先級中,然後創建多個不同的優先級線程,分別調度對應的服務。

Java優先級隊列

Java的PriorityQueue是一個基於優先級堆的無界優先級隊列。優先級隊列的元素按照其自然排序進行排序,或者根據構造隊列時提供的Comparator接口進行排序,具體取決於所使用的構造方法。優先級隊列不允許使用null元素,依靠自然排序的優先級隊列還不允許插入不可比較的對象。
優先級隊列的頭是指定排序方式的最小元素,如果多個元素都是最小值,則頭是其中一個元素。隊列操作poll,remove,peck和element訪問處於隊列頭的元素。優先級隊列是無界的,但是有一個內部容量,控制着用於存儲隊列元素的數組大小。它通常至少等於隊列的大小,隨着不斷向優先級隊列添加元素,其容量會自動增加,無需指定容量增加策略的細節。
PriorityQueue的一個主要缺點就是如果持續有優先級高的消息需要處理,會導致優先級低的消息得不到及時處理而積壓。當積壓到一定程度之後,優先級低的消息可能已經超時,即便後續得到執行機會,由於已經超時也需要丟棄掉,在此之前它會一直佔用優先級隊列的堆內存,同時導致客戶端業務線程被掛住等待應答消息直到超時,從資源調度層面看,PriorityQueue的算法並不太合適分佈式服務框架。

加權優先級隊列

分佈式服務框架的服務優先級調度並不是只處理優先級高的消息,而是按照一定比例優先調度高優先級的服務,採用加權優先級隊列可以很好地滿足這個需求。
加權優先級隊列的工作原理如下:它由一系列的普通隊列組成,每個隊列與服務優先級1:1對應。當服務端收到客戶端請求消息時,根據消息對應的服務優先級將消息投遞到指定的優先級隊列中。非法和沒有設置優先級屬性的消息,投遞到默認的優先級隊列中。
工作線程按照服務優先級的加權值,按照比例從各個優先級隊列中獲取消息,然後按照優先級的高低將消息設置到工作線程的待處理消息數組中,由於只有工作線程會讀寫消息數組,因此該數組是線程安全的。
加權優先級隊列的主要缺點是如果優先級等級比較多,對應的優先級隊列就會膨脹,如果優先級隊列發生積壓,這將導致內存佔用迅速飆升。爲了防止由於優先級等級設置過多帶來的優先級隊列膨脹,優先級隊列與優先級可以按照1:N的比例進行設置。單個優先級隊列內部再按照優先級的加權值不同做排序,此時就不能採用普通隊列來實現,需要自定義實現新的基於加權比重的優先級隊列。

服務遷入遷出

基於服務遷入遷出是利用分佈式服務框架的服務動態發現機制,通過調整服務運行實例數來實現優先級調度。
工作原理如下:

  1. 當系統資源緊張時,通過服務治理的服務遷入遷出界面,將優先級服務的部分運行實例從服務註冊中心中遷出,也就是動態註冊
  2. 消費者動態發現去註冊的服務,將這部分服務實例的地址信息從路由表中刪除,後續消息將不會路由到已經遷出的服務實例上
  3. 由於只遷出部分服務實例,被遷出的低優先級服務仍然能夠正常處理,只不過由於部署實例的減少,得到調度的機會就會被同比降低了很多,釋放的資源將被高優先級服務使用。通過資源的動態調配,實現服務的優先級調度。
  4. 當業務高峯期結束之後,通過服務治理將遷出的服務重新遷入,低優先級的消息恢復正常執行,優先級調度結束。

遷入遷出方案對性能的影響最小,靈活度最高。缺點是需要人工調整遷入遷出比例來實現資源動態調配,對於運維人員的經驗積累和操作水平都有很高的要求,自動化程度較低。

 

服務的優先級調配和動態流控不同,流控最終會拒絕消息,導致部分請求失敗。優先級調度是在資源緊張時,優先執行高優先級的服務,在保障高優先級服務能夠被合理調度的同時,也兼顧處理部分優先級的消息,他們之間存在一定的比例關係。

 

分佈式消息跟蹤系統設計

分佈式消息跟蹤系統的核心就是調用鏈,每次業務請求都生成一個全局唯一的TraceID,通過跟蹤ID將不同節點之間的日誌串接起來,形成一個完整的日誌調用鏈,通過對調用鏈日誌做實時採集,彙總和大數據分析,提取各種緯度的價值數據,爲系統運維和運維提供大數據支撐。整體架構由四部分組成。

  1. 調用鏈埋點日誌
  2. 分佈式採集和存儲埋點日誌
  3. 在線,離線大數據計算,對調用鏈數據進行分析和彙總
  4. 調用鏈的界面展示,排序和檢索等

埋點日誌

埋點日誌就是分佈式消息跟蹤系統在當前節點的上下文信息,埋點可以分爲兩類

  • 客戶端埋點,客戶端發送請求消息時生成的上下文,包括TraceID,調用方上下文信息,服務端處理的耗時,處理結果等信息
  • 服務端埋點,服務端返回應答消息時在當前節點生成的上下文,包括TraceID,調用上下文信息,服務端處理的耗時,處理結果等信息

消息跟蹤ID通常由調用首節點生成,本JVM之內通過線程上線問傳遞TraceID,跨節點傳遞時,往往通過分佈式服務框架的顯示傳參傳遞到下游節點,實現消息跟蹤上線問的跨界點傳遞。埋點日誌通常需要包含如下內容:

  • TraceID,RPCID,調用開始時間,調用類型,協議類型,調用方IP和端口,被調用方IP和端口,請求方接口名,被調用方服務名等信息。
  • 調用耗時,調用結果,異常信息,處理的消息報文大小等
  • 可擴展字段,通常用於應用擴展埋點上下文信息

消息跟蹤ID是關聯一次完整應用調用的唯一標識,需要在整個集羣內唯一,它的取值策略有很多,例如UUID,UUID(Universally Unique Identifier)即全局唯一標識符,是指在一臺機器上生成的數字,它保證對於同一時空中的所有機器都是唯一的。按照OSF制定的標準計算,用到了以太網卡地址,納秒級時間,芯片ID碼和許多可能的數字。由以下幾個部分組成:當前日期和時間(UUID的第一部分與時間有關,如果你在生成一個UUID之後,過幾秒又生成一個UUID,則第一部分不同,其餘相同),時鐘序列,全局唯一的IEEE機器標識號(如果有網卡則從網卡獲得,沒有網卡以其他方式獲得),UUID的唯一缺陷在於生成的結果串會比較長。

採樣率

對於高QPS的應用,服務調用埋點本身的性能損耗也不容忽視,爲了解決100%全採樣帶來的性能損耗,可以通過採樣率來實現埋點低損耗的目標。
採樣包括靜態採樣和動態採樣兩種,靜態採樣就是系統上線時設置的一個採樣率,無論負載高低,均按照該採樣率執行。動態採樣率根據系統的負載可以自動調整,當負載比較低的時候可以實現100%全採樣,在負載非常重時甚至可以降低到0採樣。
 

服務狀態檢測

在分佈式服務調用時,某個服務提供者可能已經宕機,如果採用隨機路由策略,消息會繼續發送給已經宕機的服務提供者,導致消息發送失敗.爲了保證路由的正確性,消費者需要能夠實時獲取服務提供者的狀態,當某個服務提供者不可用時,將它從緩存的路由表中刪除,不再向其發送消息,知道對方恢復正常。

鏈路有效性狀態檢測機制

分佈式服務框架的服務消費者和提供者之間默認往往採用長鏈接,並且通過雙向心跳檢測保障鏈路的可靠性。
在一些特殊場景中,服務提供者和註冊中心之間網絡可達,服務消費者和註冊中心網絡也可達,但是服務提供者和消費者之間網絡不可達,或者服務提供者和消費者之間的鏈路已經中斷。此時服務註冊中心並不能檢測到服務提供者異常,但是如果消費者仍舊向鏈路中斷的提供者發送消息,寫操作將會失敗。爲了解決該問題,通常需要使用服務註冊中心檢測+服務提供者和消費者之間的有效性檢測來保障系統的可靠性。當消費者通過雙向心跳檢測發現鏈路故障之後,會主動釋放鏈接,並將對應的服務提供者從路由緩存表中刪除。當鏈路恢復之後,重新將恢復的故障服務提供者地址信息加入地址緩存表中。

服務健康檢測

在集羣組網環境下,由於硬件性能差異,各服務提供者的負載不均等問題,如果採用隨機路由分發策略,會導致負載較重的服務提供者不堪重負被壓垮。利用服務的健康檢測,可以對集羣的所有服務實例進行體檢,根據體檢結果對健康度打分,得分較低的亞健康服務節點,路由權重會被自動調低,發送到對應節點的消息相對於其他健康節點會少很多。
服務的健康度檢測通常需要採集如下性能KPI指標:

  1. 服務調用時延
  2. 服務QPS
  3. 服務調用成功率
  4. 基礎資源使用情況,例如堆內存,CPU使用率等。

服務故障隔離

  1. 進程級故障隔離
  2. VM級故障隔離
  3. 物理機故障隔離
  4. 機房故障隔離

進程級故障隔離

進程內部,主要通過將服務部署到不同的線程池實現故障隔離。對於訂單,購物車等核心服務可以獨立部署到一個線程池中,與其他服務做線程調度隔離。對於非核心業務,可以合設共享同一個/多個線程池,防止因爲服務數過多導致線程數過度膨脹。儘管可以通過不同的線程池實現服務的故障隔離,但是這種隔離並不充分,例如某個故障服務發生內存泄漏異常,它會導致整個進程不可用,即便實現資源調度層的隔離,仍然無法保證其他服務不受影響。

VM級故障隔離

通過將基礎設施層虛擬化,服務化,將應用部署到不同的VM中,利用VM對資源層的隔離,可以實現更高層次的服務故障隔離。將核心業務和非核心業務分別部署到不同的VM中,利用VM的cpu調度,網絡IO,磁盤和內存等資源限制,實現物理資源層的隔離。

物理機故障隔離

當組網規模足夠大,硬件足夠多的時候,硬件的故障就由小概率事件轉變成爲普通事件。如果要保證服務器宕機時不影響部署在上面運行的服務,需要採用分佈式集羣部署,而且要採用非親和性安裝:即服務實例需要部署到不同的物理機上,通常至少需要3臺物理機,假如單臺物理機的故障發生概率爲0.1%,則三臺同時發生故障的概率爲0.001%,服務的可靠性將會達到99.999%,完全滿足大多數應用場景的可靠性要求。

 

性能和時延問題

在服務化之前,業務通常都是本地API調用,本地方法調用性能損耗較小。服務化之後,服務提供者和消費者之間採用遠程網絡通信,增加了額外的性能損耗。

  1. 客戶端需要對消息進行序列化,主要佔用CPU計算資源
  2. 序列化時需要創建二維數組,耗費JVM堆內存或者堆外內存
  3. 客戶端需要將序列化之後的二進制數組發送給服務端,佔用網絡帶寬資源
  4. 服務端讀取碼流之後,需要將請求數據報反序列化成請求對象,佔用CPU計算資源
  5. 服務端通過反射的方式調用服務提供者實現類,反射本身對性能影響就比較大
  6. 服務端將響應結果序列化,佔用PCU計算資源
  7. 服務端將應答碼流發送給客戶端,佔用網絡帶寬資源
  8. 客戶端讀取應答碼流,反序列化成響應消息,佔用CPU資源

RPC框架高性能設計

影響RPC框架性能的主要因素有三個

  1. I/O調度模型:同步阻塞i/O還是非阻塞
  2. 序列化框架的選擇:文本協議,二進制協議或壓縮二進制協議
  3. 線程調度模型:串行調度還是並行調度,鎖競爭還是無鎖化算法

1:

Netty是一個開源的高性能NIO通信框架。它的性能優化措施有如下幾點

  1. 零拷貝:Netty的接收和發送ByteBuffer採用DIRECT BUFFERS,使用堆外直接內存進行Socket讀寫,不需要進行字節緩衝區的二次拷貝。如果使用傳統的堆內存進行Socket讀寫,JVM會將內存Buffer拷貝一份到直接內存中,然後才寫入Socket中。相比於堆外直接內存,消息在發送過程中多了一次緩衝區的內存拷貝。Netty的文件傳輸採用了transferTo方法,它可以直接將文件緩衝區的數據發送到目標Channel,避免了傳統通過循環write方式導致的內存拷貝問題。
  2. 內存池:隨着JVM和JIT即時編譯器技術的發展,對象的分配和回收是個非常輕量級的工作。但是對於緩衝區Buffer,情況卻稍有不同,特別是對於對外內存的分配和回收,是一件耗時的操作。爲了儘量重用緩衝區,Netty提供了基於內存池的緩衝池重用機制。
  3. 無鎖化的串行設計:Netty採用了串行無鎖化設計,在I/O線程內部進行串化操作,避免多線程競爭導致的性能下降。表面上看串行化設計似乎cpu利用率不高,併發程度不高。但是通過調整NIO線程池的線程參數,可以同時啓動多個串行化的線程並行運行,這種局部無鎖化的串行線程設計相比一個隊列-多個工作線程模型性能更優。
  4. 高效的併發編程:volatile的大量、正確使用;CAS和原子類的廣泛使用;線程安全容器的使用;通過讀寫鎖提升併發性能

2:

高性能序列化框架:在序列化框架的技術選型中,如無特殊要求,儘量選擇性能更優的二進制序列化框架,碼流是否壓縮,則需要根據通信內容做靈活選擇,對於圖片,音頻,有大量重複內容的文本文件可以採用碼流壓縮,常用的壓縮算法包括GZip、Zig-Zag等

 

正向代理

一個位於客戶端和原始服務器之間的服務器,爲了從原始服務器取得內容,客戶端向代理髮送一個請求並制定目標(原始服務器),然後代理向原始服務器轉發請求並將獲得的內容返回給客戶端,客戶端才能使用正向代理。我們平時說的代理就是指正向代理。 

反向代理

以代理服務器來接受internet上的連接請求,然後將請求轉發給內部網絡上的服務器,並將從服務器上得到的結果返回給internet上請求的客戶端,此時代理服務器對外表現爲一個反向代理服務器。 

區別:
簡單來說,正向代理代理的對象是客戶端,服務器並不知道也不關心實際發起請求的客戶端。反向代理代理的是服務端,客戶端並不知道自己要請求的數據來自哪一臺服務器。

實際運用:

  • 平時使用藍燈翻牆就是正向代理的一種應用,我明確知道要訪問的地址卻無法訪問,這是委託藍燈作爲代理請求我所需要的數據。
  • 一般web項目的部署是使用反向代理實現,通常情況下不會把tomcat的8080端口開放,而是通過nginx反向代理,把請求從80端口轉發到8080端口上。比如,在使用大管加的時候,用戶並不需要知道和關心自己的請求是發往哪一臺服務器,反向代理會替用戶在相應的服務器拿到想要的數據。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章