如何為分散式系統優雅的更換RPC

haolujun發表於2018-04-25

為啥需要更換RPC?

很多小夥伴都遇到過需要為分散式系統呼叫更換RPC的問題,為什麼會遇到這種事呢?其實,在系統搭建初期,需求簡單,架構簡單,最重要的是請求量也少,所以很多系統都採用快速原型開發模式,對rpc的要求不高,隨便找一個順手的或者熟悉的rpc框架套進系統中即可。但是隨著業務複雜度增高,系統承載的請求量增高,可能一開始所採用的RPC框架顯現出一些致命的問題,比如大扇出問題。我們以Thrift為例。例如隨著業務複雜度的增長,我們面臨著如下的需求。
如何為分散式系統優雅的更換RPC
如圖所示,每一次請求,上游服務都要獲取下游A~Z一共26個服務的結果,然後把這26個服務的結果拼裝返回給前端服務。有人說,26個服務是不是有些誇張了,我的系統中根本沒有遇到過這個情況。這實際一點不誇張,一個業務複雜的系統經過服務拆分,最後拆成一些高內聚低耦合的獨立服務,非常容易達到這樣一個服務種類數,而且26還遠遠不是很多。那麼遇到這種問題,傳統的同步的RPC怎麼解決這個問題呢?

以Thrift為例,如果需要訪問26個服務,為了保證請求處理速度,必須要並行訪問各個下游服務(不能序列請求,因為這將導致 一次請求的響應時間至少為timeA + timeB + ...... + timeZ),那麼我們只能通過多執行緒進行併發。

如何為分散式系統優雅的更換RPC

通過多執行緒併發請求,我們基本能夠達到處理一次請求至多需要 max(timeA, timeB, ......, timeZ),但是實際上要比這個稍多。看樣子我們必須弄一個請求執行緒池,可是這個池子要多大呢?假如現在前端請求速率為 P,那麼為了保證每個請求處理時間都儘可能快,我們需要一個大小為 26 * P的執行緒池。雖然,初看起來可能還可以應付,畢竟請求執行緒在傳送網路請求後,會阻塞在IO,它會放棄CPU,從而使得計算執行緒獲得CPU,不會浪費多少CPU的資源,但是當P太大就不好了。比如P為100或者1000,這個時候執行緒數過多可能就會造成CPU排程開銷增大,因為它會增加CPU的執行緒切換負擔。

所以,我們更換RPC,當且僅當,當前的RPC已經造成了系統負擔,對於業務量不大的系統,RPC的更換並沒有必要,但是為了技術提升你也可以更換RPC,只不過收益可能不大。

需要什麼樣的RPC?

考慮到Thrift對於大扇出並不合適,我們可能需要下面這樣工作模式的RPC。
如何為分散式系統優雅的更換RPC
這種反應器模型(只是簡單舉例子)可以減少請求執行緒數。這種RPC使用系統的Epoll進行後端服務的請求以及資料的接收,這樣無論多少請求,只使用一個執行緒完成,通過Epoll的機制在資料到來或者可傳送的情況下通知使用者程式,只不過最後需要把接收到的資料返回給計算執行緒使用。這種模型其實要比Thrift那種那好一些。我自己也在業餘時間實現了一個簡單的RPC框架:http://www.cnblogs.com/haolujun/p/7527313.html ,比較粗糙但是足夠小。
還有有很多開源的RPC框架,fbthrift,GRPC都可以應對大扇出,找到適合你的系統,並且改動量和後期維護成本最低的那個。

如何遷移到新的RPC?

把系統遷移到新的RPC上,除了改動程式碼外,就是要做到相容,系統在遷移過程中可能需要在兩套RPC框架上執行,並且必須做到平滑遷移。例如,一般的分散式系統可能會長成如下的樣子。
如何為分散式系統優雅的更換RPC
服務B1~B4把自己的地址寫入到ETCD中,但是由於我們一開始並未考慮到RPC的遷移,所以value對應的是服務的地址,沒有服務使用的rpc型別等等。

方案1 新增新key

對於A1~A2,B1~B4,可以先選擇一部分進行平滑過渡,例如我們選擇A1,B1~B2進行遷移。
如何為分散式系統優雅的更換RPC
上線步驟如下:

  • 下線A1,B1,B2。
  • 更新A1配置,使其從新的key:service_new_rpc中讀取後端服務列表。
  • 更新B1,B2配置,使其在新的key:service_new_rpc中註冊自己。
  • 啟動B1,B2。
  • 啟動A1。
  • 對於A2,B3,B4重複如上步驟。

通過這種方式,我們可以平滑的進行服務遷移。但是它的缺點很明顯,需要一個新的key,而且後期還需要一點點把服務挪回到舊的key上。

方案2 程式碼相容

這個方案必須更改一些解析程式碼,使其能夠相容新的ETCD中value的格式,如下圖。
如何為分散式系統優雅的更換RPC

  • 首先改造A程式碼,使其能夠相容新地址解析格式。新地址格式在每個地址後加上RPC型別標識:T(Thrift),G(GRPC),新格式和舊格式的相容很容易,只需在解析的時候找一下分割符,並判斷分隔符最後一部分是T是G還是什麼都沒有,沒有就預設為T。
  • 改造A程式碼,使其能夠根據後端服務在ETCD中的RPC型別使用不同的RPC框架呼叫後端。
  • 改造B1~B4的配置,在ETCD中註冊自己的時候把RPC型別順便加上。
  • 改造B1~B2,使用新RPC作為服務端,並且在註冊的時候把RPC型別設定為G。
  • 改造B3~B4,使用新RPC作為服務端,並且在註冊的時候把RPC型別設定為G。

通過這個步驟,我們就能做到RPC的平滑遷移。這個方式的缺點也有:需要同時維護兩套RPC框架,直到其中一種RPC徹底下線。但是優點也有,沒有增加新key。

總結

更換RPC並不像想象中的那樣困難,只要理清前後邏輯,一點點的遷移,最終你的服務會全部搞定。最重要的問題是你的系統真的達到了非得換RPC的地步了麼?

相關文章