歡迎光臨
每天分享高質量文章

晁嶽攀—基於go的 rpc框架實踐

晁嶽攀:軟體開發的老兵,Scala集合技術手冊(簡/繁版)的作者, 高效能的服務治理rpcx (Go)框架的開發者,先前在同方、Motorola、comcast從事軟體開發工作,現在在微博平臺研發部做基礎平臺的研發工作。經常在網上和個人網站(http://colobu.com)發表一些技術文章。

前言

      大家好,我是來自微博研發中心的平臺架構組的晁嶽攀,我這次給大家分享的是grpc框架的開發。我所在的組比較傾向於後臺平臺軟體開發。大家知道微博前端基本都是用 php 做開發,它屬於另外一個部門負責,我們這邊平臺研發部門全部是做後臺開發的,主要使用的語言是Java,最近這幾年,像我們平臺架構組開始的新專案都開始用 go 開發,我們去年做的是原來微博的配置中心平臺Vintage。微博的Motan是Java的RPC服務治理框架,它的服務發現中心是我們組負責的配置中心軟體Vintage,我們不用做EPCD,是因為我們主要面對的服務高可用性的產品,而不是資料一致性優先的服務,所以我們自己開發微博的配置中心軟體。最開始Java+Redis的實現,經常會有一些問題,去年用go開發的新的版本,已經將原來的產品替換了,經過了明星出軌還有春節等等一些熱點事件,用我們那邊的網紅說的,能支援8個明星出軌的場景是沒有問題的,是很穩定的平臺。後來用go還做了訊息雲平臺。

      我從事軟體開發也有18年了,從2000年畢業就一直在做這方面的相關工作,做了十幾年的 Java,用 Scala 去做的大資料,又做 go,總的來說我覺得語言不是給大家打的一個標簽,而是一個稱手的工具。大家需要做一個專案的時候,會選擇一個正確的語言。來到這的各位,肯定是對go是特別感興趣,或者工作語言主要是go的,大家是最聰明的一群人,會選擇正確的語言,同時大家也是最勤奮的人,在星期六的時候,還來參加這次技術分享,我衷心的感謝你們。

      我們先回顧一下 rpc 的歷史,再介紹我們常用的兩個Go RPC框架。一個是 rpc 本身的官方庫,還有就是大家常用的 Grpc 框架,我並不想在這個分享會推廣我的框架,而是想跟大家分享一下我在做這個框架的過程中考慮的一些問題。大家如果沒有用過 rpc 的框架的話可以瞭解一下這方面的知識,正在用的同學也可以熟悉一下使用rpc框架的時候需要考慮的方面, 如果碰巧你正在做rpc框架,可以互相交流一下,看看其他人是怎麼做rpc框架的,瞭解一下別人做的工作。

      在介紹之前,可以分享一個 rpc 的改造故事。

一個RPC改造的故事

      這是今年年初春節的時候,一個網友的故事,在QQ群裡面分享的一個真實的勵志故事。這是一位遊戲開發界知名的網友,現在在一家香港公司主要做海外遊戲的專案,他們面臨一個問題:他們部署了很多遊戲伺服器,但是當大家登入的時候會有一些惡意的攻擊,他要攔截相關所有的操作把日誌發到一個叢集裡面,存到資料庫做分析。一開始用 Java 去做,然後出現諸多的問題,效能也非常低下,在去年的時候,他們用了 rpcx2.0 框架去做了改造,這是改造完執行半年的資料一張資料截圖,應該是不到一天的統計量,有32億的請求量。年終的時候他們老闆算了一下,透過這次改造減少了一半的伺服器的數量,費用從一個月100萬港元減少到50萬港元租賃伺服器頻寬等等費用,這給公司的執行成本帶來不少好處,也給開發和運維人員帶來了非常大的便利。我覺得它是非常好、典型的例子,它在半年的運營過程中,沒有發現一個問題,非常穩定。只有一個問題是其中一個路由器壞了閃斷了一下,但是和我們的軟體沒有任何關係。

RPC的歷史

     我們看看rpc的歷史演變過程,最開始起源應該從網際網路建立的時候就有這種萌芽,那時候不叫 rpc ,發一個request訊息到另外一臺機器上,傳回相應的結果response。

1970年-1995年

      1980年的時候SUN提出一個 onerpc 的概念,他在做NFS的時候用到了rpc的概念,接下來的幾年,相繼提出了好幾個RFC規範,但是rpc的概念真正提出的是是1981年施樂公司的Bruce Jay Nelson,也可以稱之為rpc之父,施樂公司大家都清楚那是一家傳奇性的公司,這個人提出RPC概念以後,在九幾年獲得ACM的大獎,但是他不到50歲的時候因為一場病去世了,後來1991年的時候, 比較老一點的程式員都瞭解CORBA的服務呼叫,但是非常的複雜,以前我們給美國做影片專案的時候,就使用CORBA,使用起來特別的難,我覺得基本上就算被歷史淘汰了。

1997年-2010年

      1997年各家公司都在炒分散式服務呼叫的概念,windows 的DCOM+等等,Java提供了 RMI 遠端呼叫的概念,1998年IBM、微軟、谷歌等公司提出了webservice的概念。多年前的SOA,各家公司都拼命的推自己的標準,這裡面形成了一批以 WS 開頭的標準,雖然號稱當時還是比較成功的,但是從現在看起來,過於複雜,而且各個公司之間互相的利益的關係, 給SOA的應用帶來了制約。同時也有一些開發者提供了一些簡化性的 XMRPC 的開發,但是我覺得大家記得比較清楚的是2000年 RESTFUL 這個概念,對資源來說,我們常做CRUD 的開發,建立、查詢、修改、刪除等操作,然後對映到 Post、Get、Put、Delete、Patch 等HTTP方法上,它把操作物件看成一種資源,這種風格一直影響到現在,現在有很多rpc的框架都是以這種方式提供的。Facebook 提出一個 rpc 的框架 THRIFT。 json -rpc 推出2.0。Go 標準庫裡面有json-rpc 1.0的支援,json-rpc 2.0有一些第三方庫。

2011年-2018年

      DUBBO在2011年開源,當時淘寶內部不使用了,但是牆內開花牆外香,外面很多公司,尤其是電商方面的公司,反而相應fork 進行維護,印象中噹噹網和京東最初都使用了 dubbo rpc 的框架,dubbo 在去年的時候,這個專案重新做維護,也專門有了相應的維護開發人員,一些依賴包的版本也做了升級,現在又活躍起來了。當時的時候,基本上處於一種不再維護的狀態。Spring cloud這種框架很多在使用。2015年Grpc出現,2017年騰訊和百度也推出了自己的 Grpc 的框架,但是屬於C++的框架,流行度還不廣,基本上都在自家使用,現在像淘寶的螞蟻金服和淘寶本身相當於兩個公司,螞蟻金服也在推自己的rpc的框架。rpc 的框架太多了,這裡也就不一一列舉了。

Client&Server;

      rpc本身呼叫流程是非常簡單的,在 Client端利用stub,相當於把方法的呼叫傳遞給伺服器,再傳回到Client。

      內部的處理流程,一般會使用Stub,將呼叫過程封裝起來,介面呼叫會透過一個框架去實現。

官方RPC庫

      首先來介紹 golang 標準庫的rpc,  也是由當初的大牛開發的,功能強大,但是程式碼非常的簡便非常的好。利用標準庫開發rpc非常簡單,我們首先定義傳入引數和傳出引數,再定義一個物件,實現它的服務方法就可以了。

      client首先連線你的伺服器,直接把你的服務方法的名字和引數傳進來,傳出的內容作為最後一個引數,官方的庫是非常好的,但是沒有人維護,所有的功能都已經凍結了,除非有特殊的 BUG。如果我們自己要寫簡單rpc程式 建議你用官方的 rpc 庫去開發。

      如果要自己定義庫或者是方法,可以實現這兩個介面,但是要想做進一步的擴充套件比較困難,所以後來我自己寫核心傳輸的協議,不再包裝它,之前我看到有人想這個包裝一下實現rpc框架,我建議你不要去做,因為被限制住了,可以參考它的之後寫自己的rpc框架。

GRPC-Go

GRPC-GO

      Grpc 是跨語言rpc框架,提供很多流行程式語言的支援,dubbo和Motan則是偏重服務治理的框架。Grpc是透過IDL檔案,定義我們的服務,內部的通訊是透過HTTP/2進行通訊的,效率是非常高的,去年的時候又專門針對效能做了進一步的最佳化,花了很長時間提升了效能,我測試了一下,大約有50%的效能的提升,所以說效能提升非常的高。本身的資料編碼方式透過ProTobuf 編碼協議實現的,我們一般使用 gogo-protobuf 第三方庫做開發。它的效能比官方的效能更好。這是Grpc庫的服務端的程式碼,幾行就可以實現服務。

GRPC-GO streaming

      CLIENT 服務呼叫也非常簡單,提供了流的控制,可以是單向的也可以是雙向的,grpc是支援雙向的。

HTTP

       HTTP 本身有效能的瓶頸, 即使設定長連線, 客戶端也需要等待前一個訊息傳回才能處理下一個(可能有些框架,比如fasthttp支援pipelining)。而GRPC, 是透過HTTP/2的傳輸方式,可以並行的傳送請求,極大的提高了服務的並行呼叫。 它將一些元資料放在head frame中,payload放在data frame中。用 wireshark 抓包分析,可以看到grpc的具體的協議的實現。

GRPC-GO protocol

       wireshark已經支援了GRPC協議,你可以方便的看它的協議以及protobuf的資料。

服務治理的RPC框架的實現

      下麵我講一下開發rpcx這個框架過程中一些考量和技巧,希望對大家做rpc的框架開發有幫助。

RPC框架

      當我們談論RPC的時候,比如官方的rpc或者GRPC, 一般談論的是rpc的一個範圍很小的概念。但是當我們從大的方面去考慮的時候,比如說微服務, 我們需要考慮很多方面。在這一點上,rpc和grpc很欠缺,雖然grpc開始增加一些load balancer的實驗性的功能,但是從服務治理的方面來講,還是很欠缺的,我覺得這一欠缺導致了grpc不會大規模的在企業中應用,除非再做進一步的開發。


Protocol

    協議可以採用http restful api的方式,或者HTTP/2,但是從效能上考慮,最好採用TCP。當然從便利性上考慮,http restful api還是很不錯的。透過Magic number可以對每一幀進行校驗,貌似dubbo的magic number是DUBB。MagicNumber的多少是Tradeoff的決策。rpcx只是用它來輔助校驗,所以只使用了一個位元組。

      最好接著就是version欄位,這樣不同的版本後面可能是不同的資料格式, 如果放在最後就限制死了。

一些引數儘量壓縮以節省空間。


Transport

     我們看傳輸方式,一個是TCP方式傳輸,另外是 UDP 協議,UDP 是不可靠的傳輸方式,中間有丟包的概念,有很多庫試下了可信賴的UDP,相當於它做了很多的檢查,常用的有谷歌的 quic,騰訊有的部門也在使用,還有KCP,rudp等。


codec

      可用的編碼方式有很多,但是首先要支援跨語言的處理,msgpack 是常用的編碼方式,protobuf也是常用的編碼方式,json是通用的實現方式,thrift也是跨語言的,但是追求極致效能的話可以用go特有的編碼庫,有些比protobuf效能高出一倍,右側列了很多專案,大家可以關註一下,這裡面有很多效能優異的編碼庫,但不是跨語言的,只能在go裡面使用。

註冊中心

      另一個非常重要的是註冊中心,做服務治理,註冊中心是非常重要的一個元件,我們需要把服務登記到註冊中心裡面,之後客戶端才能感知到相應的服務節點,比較常用的是 etcd 、consul、zookeeper,有人問是ETCD 好還是 consul 好,這個很難說,本身都是很優秀的框架,但是微博裡面都沒有使用,因為這些框架追求的是要資料一致性,而我們追求的是高可用性,我們自己做配置開發平臺,跨資料中心的。

路由/負載均衡

隨機

      當有多個伺服器時,甚至分佈在多個資料中心,我們需要一定的演演算法從中挑選出來給客戶端呼叫,在這列舉幾個常用的演演算法,最簡單的是隨機演演算法,隨機選擇一個,分配到誰就到誰。

輪詢

      還有一個是輪詢,保證每個伺服器平均得到訪問呼叫的機會。

一致性雜湊

      一致性雜湊,可以根據使用者的請求,計算雜湊值,可以對映到對應的伺服器上面,可以保證相同的請求能路由到相同的節點,根據雜湊選擇的不同,可以有不同的演演算法;或者根據clientIP地址計算相同的IP就路由到同一臺機器上。

基於權重的輪詢

      基於權重的輪詢,這個比較好的演演算法是nginx的平滑的基於權重的輪訓演演算法,即使在很短的時間內,也不會出現強求都落在一臺機器上,避免雪崩現象。

基於地理位置

      基於地理位置選擇,就是北京的機器client儘量訪問北京的機房,如果北京和廣州之間要訪問有幾十毫秒的延遲,美國東海岸和西海岸的機房,有上百的資料延遲。所以要儘量選擇同一地區的機房的資料。

基於網路質量

      但是有時基於地理位置,同一個機房,同是在北京,分佈在不同的地方,不同的網路服務商,可能訪問也是不太一樣的,所以有時候可能會基於網路質量去訪問,可以呼叫服務,看看反饋花費時間是多少,進行相應權重的分配,這也是一種常見的演演算法。

定製路由演演算法

      定製路由演演算法:有些使用者提出來某一個服務,要分配到其中一個機房的其中一臺測試機器上,可以提供一個介面讓使用者定製他的演演算法,要把相應的引數要傳遞過來,基於這些引數做一個選擇。


失敗處理

      失敗的處理完全是參考 dubbo 的實現,但是額外加了一些功能。比如說 failbackup。如果服務節點意外掛了或者服務失敗了,設定failfast的話就會馬上反饋給 client ;如果是failtry在網路閃斷情況下有重試的功能;failover,訪問第一個節點失敗可以繼續訪問第二個節點;還有failbackup,就是利用這種資源換延遲,來一個請求之後,如果等十毫秒還沒有結果的話,立馬把這個請求傳送給第二個節點,這兩個節點誰先傳回過來,我就把結果傳回回去,提高延遲,但是有時候會佔用雙倍資源。


超時、容錯

      微服務呼叫有連線超時的時候,網路的原因還有惡意攻擊問題,比如很多客戶端連線你的伺服器,也不斷掉,會佔用很多資源,處理這些請求,相當於佔用你大量的伺服器的資源,如果設定超時,可以在一定的時間內把一些關閉。還有 Context 超時是常用的方式。斷路器的樣式,如果一個伺服器暫時不可用的話,最好把它標記為在一定時間內不可用,這樣我以後的服務不會再選擇它做路由,這樣可以避免大量的請求傳送到無用或者暫時不可用的伺服器上,這個熔斷器可能在一定的時間內要進行重試,在可用的時候要恢復過來。熔斷器也有好幾個框架實現。


跨語言

      跨語言,是一個很嚴重的問題,在初創公司裡面,沒有太大的問題,都用go開發,沒有語言的問題,但是在大的公司裡面有很多不同語言,這種情況下要考慮跨語言的問題。現在小米的張志勇負責做rpcx-java的開發,支援純java方式實現rpcx,做的很不錯,還有gateway,http 介面等方式提供http介面,以便支援跨語言呼叫。


雙向呼叫

       本身rpc不設定雙向呼叫,但是有一些需求,要求伺服器發一些通知給客戶端,所以需要雙向的通訊,這一功能grpc也支援。


管理介面

      提供UI管理服務,禁用一些服務,進行一些管理的東西,都是透過註冊中心拉取過來資料。

      另外微服務很重要的一點,是要對服務有trace的概念,我們一般記錄節點的一些資訊,trace會中間請求一些資訊傳送過去。Java可以往很多伺服器傳送資料,我們也可以加相應的資料展示出來進行監控。


安全

      其他還有一些安全的東西,比如 Auth,需要許可權驗證;另外還有限流。還有訊息大小的限制,假設提供一個服務裡面有一個欄位,本來是希望用255個位元組資料,假設有個人傳個2G的葫蘆娃的資料,會對你本身的伺服器的資源極大的佔用,要對伺服器接收的資料大小進行控制,另外提供白名單和黑名單進行控制。


壓縮

      zip 是普遍的壓縮方式,snappy 追求流的壓縮方式。


benchmark

      去年做過一些對於rpc框架 benchmark 的測試。因為我們不可能做一個改寫各種各樣的測試場景,這裡只是提供一個特定的場景。假設服務是有一個10毫秒的消耗的話或者30毫秒的消耗(sleep),標準庫效能最好,其次是rpcx, grpc和thrift稍差一些,但也比http restful api高很多。

      我分享的原因就是大家以後在使用rpc框架開發的時候,或者是自己做rpc框架的時候,可以從這幾個角度進行考慮。

Q&A;

      提問:之前我們也做過 go rpc,用的是你剛才提的 gateway 的方式,對client提供http介面,使用http和grpc兩種方式跟後面的微服務通訊。我們剛開始在一個服務上用了HTTP跟gsrpc這兩種方式,但是測試結果顯示,用grpc跟http相比,效能提升不是很大。還有 Gateway 連結服務的時候是一條連結還是多條連結?自己去做一個連結還是一個連結池?

      晁嶽攀:首先回答你第二個問題:一個連結就足夠了。很多同學說,為什麼用一個連結?從資料傳輸來說,一個連結是足夠的,因為是非同步地,效能不會阻塞在網路資料的傳輸上。現在回答第一個問題:可能你有兩個原因,一是你的服務佔用時間較長,第二個原因可能你的client通訊方式是瓶頸, 你gateway提供給client是http方式。

      提問:rpcx 和 grpc 出來的定位是什麼? rpcx支援protobuf3嗎?

      晁嶽攀:grpc 不會在企業大規模使用,或者說不會直接在企業中規模很大的服務中的推廣。因為本身它的服務治理很弱。rpcx支援protobuf3, 而且預設提供了protobuf3的編碼器實現。

      提問:比如說春節的時候做降級的時候,具體動作是什麼,我們這邊沒有降級,我剛才想了一下,去年天貓雙十一的時候,看過阿裡那邊的雙十一資料,前半小時,比如說你開啟介面,是不會給你展示有幾個紅包給你傳送,客戶端做了處理,比如流量比較大的時候,有一些不是特別重要的服務直接關掉,客戶端做一些處理,會把模組隱藏掉,做限流,幾個容器在做處理,其他的容器在客戶端,容器數量比較少的,是小範圍提供,大範圍的使用者在用服務的時候怎麼用,客戶端要做怎樣的處理?

      晁嶽攀:每個公司處理方式不一樣,拿微博舉例子,春節的時候,會把不太重要的功能暫時關掉,我們把業務分成核心和非核心的,有選擇的去降級,在後臺就標記這個服務是不可用的狀態。

贊(0)

分享創造快樂