聊聊分散式服務框架

隨著業務的增長,傳統的MVC的架構模式已經不能很好的滿足我們的需求,對於高併發,大流量的應用場景中,我們逐漸就需要使用RPC架構模式,採用當今比較流行的微服務的概念,將業務一個一個的拆分為微小的服務共業務方調用。RPC的全稱是Remote Procedure Call ,就是遠程的進程調用,允許像調用本地服務一樣調用遠程服務。他是一種模式,實現這種模式的方式有很多種,比如大名鼎鼎的facebook的Thrift,阿里的Dubbo,還有58的Scf,這些都是很不錯的Rpc實現框架。

Rpc的目標就是讓遠程過程調用更加簡單透明,RPC框架負責屏蔽底層的通信協議,序列化反序列化方式,傳輸方式。框架的使用者只需要知道在什麼位置要調用哪個服務的哪個介面即可,工程師不需要關心底層的通信細節和調用過程。

Advertisements

Rpc框架的幾個核心技術點:

  • 服務提供者需要提供以某種形式提供服務調用的文件,比如需要讓調用者知道介面定義,數據結構等,比如Thrift的IDL文件,Dubbo的xml配置文件,Scf的scfkey等。

  • 遠程代理對象。對於調用者來說,請求一個遠程服務,該服務返回的是一個Object類型的對象,調用者可以通過Java的動態代理的攔截機制,將本地調用轉化為遠程調用。

  • 通信,rpc框架一般底層都是以NIO的方式進行互相間的通信。

  • 序列化,因為是遠程的調用,所以需要把請求信息轉化成能在網路中傳輸的二進位流,比如性能被業界認可的Proto buf ,還有比 protoBuf性能更好的Scf的自研的序列化協議。

凡事總需要問個為什麼,為什麼好端端的本地調用放著不用,非要在網路通信上產生一點耗時來遠程調用呢?對於業務,併發或者性能來說是一樣的,甚至本地調用可能還會更好一點呢。

Advertisements

因為我們的業務發展迅速,就會出現很多場景用到了相同的服務。比如發簡訊,我想在用戶登錄成功后給用戶發個簡訊提醒,在做運營活動的時候用戶中獎了想給用戶發個簡訊提醒,在監控系統中我們的服務出現異常工程師希望收到簡訊提醒……很多場景都需要用到這個服務,不同的只是參數不同,也就是手機號不同,而且調用者都不在一個系統里,登錄場景是在登錄系統,運營活動是在運營系統,監控是在監控系統,每個系統都不一樣,傳統的方式就是把這份代碼copy三份放在三個地方,也能實現需求。但萬一哪天,簡訊服務裡面需要修改點什麼東西,那就需要工程師先找出這份代碼在哪些地方有,然後改第一個,copy一下粘貼到第二個。。。。很煩。

遠程調用就能很好的解決這個問題,三個不同的系統都去調用簡訊服務,哪一天我要修改簡訊服務只需要修改服務本身就好,很輕鬆(這個地方只是做一些小改動,大改動的話調用者也需要跟著一起改)。

基本上所有的rpc框架都會分為四個部分,server,client,通信協議,序列化。其中server和client沒啥說的,無非的用動態代理的方式來發起調用和處理結果等,每個rpc框架最不同的地方也是在通信和序列化這兩個地方。

先說通信,是長連接還是短連接?大多數都會選擇長連接,因為長連接一旦建立之後就不會輕易的斷開,當前請求可以用當前這個連接,請求處理完了,下一個請求還可以繼續使用這個連接,達到了連接能被複用的狀態,繼而能夠節省資源。因為在創建連接的時候需要創建鏈路,發起握手,關閉鏈路等,會消耗很大的系統資源。長連接只會在第一次創建做這些操作,而短連接每次都需要,所以摒棄了短連接。還有一點,長連接一直保持在那裡,可以做服務端和客戶端的心跳檢測,可以感知是不是哪個機器抽風掛了。

選擇了長連接之後還會面臨一個問題 NIO還是BIO?BIO叫Blocking IO,同步阻塞IO,這種方式是一個線程佔用一個IO,這種方式顯然不適合互聯網這種快速發展的業務。NIO的全稱是non-blocking IO,也就是非阻塞IO,和BIO不同的就是不需要為每個套接字分配一個線程,而可以在一個線程中處理多個Socket套接字相關的工作。他是統一通過Reactor對所有客戶端的Socket套接字的事件做處理,然後派發到不同的線程中去。

BIO雖然性能差,但是開發簡單;NIO性能很棒,但是開發負責,對工程師朋友不友好,怎麼辦?開源的力量總是很強大,有一個NIO的框架叫Netty,API使用簡單,開發簡單;性能好;成熟穩定,還把jdk的NIO的一個bug給修復了;迭代快,社區非常活躍;質量得到驗證,各大互聯網公司都得到了成功的商用。

接下來說說序列化,序列化可以理解成編碼,他將對象序列化成位元組數組用於網路傳輸;反序列化就相當於解碼,將位元組數組轉化成相應的對象,方便後續的業務操作。

舉個最簡單的例子,序列化可以是json或xml協議,就是把對象轉化成json或xml格式的數據流用於網路傳輸;另一端再根據相應的協議轉化成原有的對象。

一般來說,序列化需要從功能,跨語言,兼容性,性能等多個角度進行考量。

  • 功能,要滿足各式各樣不同的業務場景,不能說我只能序列化基本類型,來了一個Map對象就手足無措,那不行;需要所有對象類型都能被序列化。

  • 跨語言,眾所周知,Java語言自帶Serializable介面,它也是一種序列化方式,而且開發簡單使用門檻低,性能也還不錯,但是!它只支持java語言的開發,萬一我們的第三方開發團隊用php,那麼我們的服務他將不能被調用,那也不符合我們的場景。現在流行的MessagePack,PB,Thrift等都支持多種語言,這也是他們能夠流行的一個原因。

  • 兼容性,業務發展比較快,db里的表欄位可能不夠用需要加欄位來滿足業務,要有向前的兼容性。比如PB協議,不必破壞已部署的,依靠老數據格式的工程就可以對數據結構進行升級,這樣服務升級的時候就不用擔心因為實體的改變而造成的大規模的調用方工程的改動。

  • 性能,序列化后的碼流大小,序列化速度,序列化時佔用的內存資源等三個指標,pb的性能比一般的協議要好很多,甩開json,xml好幾條街。所以很多大廠的rpc架構的序列化就用的是PB。

好的,到這裡我們大概知道了整體的一個結構。那我們在思考一下,client發起調用,比如服務是分散式部署,部署在了好幾台機器,那麼這時候怎麼獲取這些服務地址呢?拿到服務地址后,請求在面臨那麼多機器的時候該如何抉擇呢?

一般都是基於註冊中心(例如Zookeeper)的訂閱發布,服務註冊中心用於存儲服務提供者地址信息,服務發布相關的屬性信息,消費者通過主動查詢和被動通知的方式獲取服務者的地址信息。

註冊中心是一個第三方的組件,server端和client端都需要與它建立鏈路,server提供者需要將發布的服務信息和屬性列表寫入註冊中心,消費者根據本地引用的介面名信息,從註冊中心拿到服務提供者列表,緩存到本地。

為什麼要緩存到本地?每次請求一遍自然也可以,但還是老問題會耗費很多資源,緩存起來就不會資源消耗的問題。但是說到緩存容易想到數據一致性的問題,萬一哪台服務提供者機器壞了,他不能提供服務了,我緩存里卻還有它的地址在,那落到這台壞機器上的請求都將會失敗。

一個成熟的註冊中心將這個問題很好的解決了,前文說過,服務端會和註冊中心保持連接,進行心跳檢測,PING-PONG,萬一哪天抽風沒有響應PONG,那麼註冊中心就會認為這台機器壞了,這個時候他利用和客戶端的連接通知客戶端,這台機器壞了, 你把它從緩存里去掉吧。客戶端收到通知后更新緩存,便不會有流量被分配到這台機器了。如果服務提供者加了機器,也是同理,客戶端也會更新緩存,流量也會被分配到新機器。

這解決了怎麼獲取服務地址的問題,那獲得了那麼多服務地址請求怎麼分發呢?也就是怎麼做負載均衡的?

一般有這麼幾種方式:

  • 隨機,顧名思義,客戶端會隨機把請求分配到某台機器,好處就是流量平均分配,但這樣就導致了各節點的負載不均勻(比如A機器配置好,能處理500個請求/s,B機器配置差,只能處理100請求/s,但是隨機演算法對於這兩台機器是同等的)。

2.輪詢

這種方式會有一個每台機器的比重,按照權重,順序遍歷服務提供者列表,到達上限后重新歸零,繼續順序循環。他的缺點就是 存在慢的提供者累積請求的問題。比如B機器很慢,但是沒有掛,當請求調到第二台時就卡在那,時間一長所有的請求都會卡在第二台上。

3.服務時延調用

正對於輪詢這種機制的問題的解決方案。消費者緩存所有機器的服務時延,周期性的計算服務調用平均時延,然後計算每個服務提供者的服務調用時延和平均時延的差值,根據差值大小動態調整權重,保證服務時延大的服務提供者接收到更少的請求,防止消息堆積。

4.一致性哈希

當某一台機器宕機時,原本發往該節點的請求,基於虛擬節點,平攤到其他提供者,而不會引起劇烈變動。

至此為止我們可以把整個流程給串聯起來,client端發起請求,找到服務地址,將請求分配到其中一台機器,序列化,socket通信,server端拿到請求信息,反序列化,業務處理,將結果返回給調用端。

但是,業務發展是在太快了,服務一下子變得好多好多,服務端不知道被誰調用了,萬一服務端想要升級下服務,連自己的調用者是誰都很難查清。沒有許可權控制,一些核心服務都暴露在外面,工程師都可以調用,這當然是不允許的。

所以我們需要一個服務治理的平台。所有的服務發布在該平台完成,平台負責告訴註冊中心(服務下線同理);所有服務的調用也都要在平台申請,並且需要提供方同意才能正常調用。互聯網的場景需要灰度的支持,該治理平台能夠提供灰度的功能,比如現在有A B C D 四台機器部署了服務,ABC都是老服務,D是新服務,我們想看看新上的功能效果怎麼樣,那麼就需要新功能的介面全部落到D機器,相當於線上同時能夠出現兩個不同的功能,對於敏捷迭代來說很重要。

當然服務治理還需要監控每個服務的響應時長,異常量等一些服務屬性的特徵。

本文比較粗的講了下RPC框架的架構,這樣的架構模式已經能夠滿足「微服務」的需求,服務與調用者之間都能夠僅僅有條的負責各自的邏輯,也起到了解耦的作用,且能夠滿足互聯網業務小步快走的迭代特點。

本文鏈接:http://generalcode.cn/archives/175

Advertisements

你可能會喜歡