bRPC基礎介紹。
什么是RPC?
互聯網上的機器大都通過TCP/IP協議相互訪問,但TCP/IP只是往遠端發送了一段二進制數據,為了建立服務還有很多問題需要抽象:
- 數據以什么格式傳輸?不同機器間,網絡間可能是不同的字節序,直接傳輸內存數據顯然是不合適的;隨著業務變化,數據字段往往要增加或刪減,怎么兼容前后不同版本的格式?
- 一個TCP連接可以被多個請求復用以減少開銷么?多個請求可以同時發往一個TCP連接么?
- 如何管理和訪問很多機器?
- 連接斷開時應該干什么?
- 萬一server不發送回復怎么辦?
- …
RPC可以解決這些問題,它把網絡交互類比為“client訪問server上的函數”:client向server發送request后開始等待,直到server收到、處理、回復client后,client又再度恢復并根據response做出反應。
我們來看看上面的一些問題是如何解決的:
- 數據需要序列化,protobuf在這方面做的不錯。用戶填寫protobuf::Message類型的request,RPC結束后,從同為protobuf::Message類型的response中取出結果。protobuf有較好的前后兼容性,方便業務調整字段。http廣泛使用json作為序列化方法。
- 用戶無需關心連接如何建立,但可以選擇不同的連接方式:短連接,連接池,單連接。
- 大量機器一般通過命名服務被發現,可基于DNS,?ZooKeeper,?etcd等實現。在百度內,我們使用BNS (Baidu Naming Service)。brpc也提供“list://“和"file://”。用戶可以指定負載均衡算法,讓RPC每次選出一臺機器發送請求,包括: round-robin, randomized,?consistent-hashing(murmurhash3 or md5)和?locality-aware.
- 連接斷開時可以重試。
- 如果server沒有在給定時間內回復,client會返回超時錯誤。
哪里可以使用RPC?
幾乎所有的網絡交互。
RPC不是萬能的抽象,否則我們也不需要TCP/IP這一層了。但是在我們絕大部分的網絡交互中,RPC既能解決問題,又能隔離更底層的網絡問題。
對于RPC常見的質疑有:
- 我的數據非常大,用protobuf序列化太慢了。首先這可能是個偽命題,你得用profiler證明慢了才是真的慢,其次很多協議支持攜帶二進制數據以繞過序列化。
- 我傳輸的是流數據,RPC表達不了。事實上brpc中很多協議支持傳遞流式數據,包括http中的ProgressiveReader, h2的streams,?streaming rpc, 和專門的流式協議RTMP。
- 我的場景不需要回復。簡單推理可知,你的場景中請求可丟可不丟,可處理也可不處理,因為client總是無法感知,你真的確認這是OK的?即使場景真的不需要,我們仍然建議用最小的結構體回復,因為這不大會是瓶頸,并且追查復雜bug時可能是很有價值的線索。
什么是brpc?
brpc是用c++語言編寫的工業級RPC框架,常用于搜索、存儲、機器學習、廣告、推薦等高性能系統。
你可以使用它:
- 搭建能在一個端口支持多協議的服務, 或訪問各種服務
- restful http/https,?h2/gRPC。使用brpc的http實現比libcurl方便多了。從其他語言通過HTTP/h2+json訪問基于protobuf的協議.
- redis和memcached, 線程安全,比官方client更方便。
- rtmp/flv/hls, 可用于搭建流媒體服務.
- hadoop_rpc(可能開源)
- 支持rdma(即將開源)
- 支持thrift?, 線程安全,比官方client更方便
- 各種百度內使用的協議:?baidu_std,?streaming_rpc, hulu_pbrpc,?sofa_pbrpc, nova_pbrpc, public_pbrpc, ubrpc和使用nshead的各種協議.
- 基于工業級的RAFT算法實現搭建高可用分布式系統,已在braft開源。
- Server能同步或異步處理請求。
- Client支持同步、異步、半同步,或使用組合channels簡化復雜的分庫或并發訪問。
- 通過http界面調試服務, 使用cpu,?heap,?contention?profilers.
- 獲得更好的延時和吞吐.
- 把你組織中使用的協議快速地加入brpc,或定制各類組件, 包括命名服務?(dns, zk, etcd),?負載均衡?(rr, random, consistent hashing)
brpc的優勢
更友好的接口
只有三個(主要的)用戶類:?Server,?Channel,?Controller, 分別對應server端,client端,參數集合. 你不必推敲諸如"如何初始化XXXManager”, “如何組合各種組件”, “XXXController的XXXContext間的關系是什么”。要做的很簡單:
- 建服務? 包含brpc/server.h并參考注釋或示例.
- 訪問服務? 包含brpc/channel.h并參考注釋或示例.
- 調整參數? 看看brpc/controller.h. 注意這個類是Server和Channel共用的,分成了三段,分別標記為Client-side, Server-side和Both-side methods。
我們嘗試讓事情變得更加簡單,以命名服務為例,在其他RPC實現中,你也許需要復制一長段晦澀的代碼才可使用,而在brpc中訪問BNS可以這么寫"bns://node-name"
,DNS是"http://domain-name"
,本地文件列表是"file:///home/work/server.list"
,相信不用解釋,你也能明白這些代表什么。
使服務更加可靠
brpc在百度內被廣泛使用:
- map-reduce服務和table存儲
- 高性能計算和模型訓練
- 各種索引和排序服務
- ….
它是一個經歷過考驗的實現。
brpc特別重視開發和維護效率, 你可以通過瀏覽器或curl查看server內部狀態, 分析在線服務的cpu熱點,?內存分配和鎖競爭, 通過bvar統計各種指標并通過/vars查看。
更好的延時和吞吐
雖然大部分RPC實現都聲稱“高性能”,但數字僅僅是數字,要在廣泛的場景中做到高性能仍是困難的。為了統一百度內的通信架構,brpc在性能方面比其他RPC走得更深。
- 對不同客戶端請求的讀取和解析是完全并發的,用戶也不用區分”IO線程“和”處理線程"。其他實現往往會區分“IO線程”和“處理線程”,并把fd(對應一個客戶端)散列到IO線程中去。當一個IO線程在讀取其中的fd時,同一個線程中的fd都無法得到處理。當一些解析變慢時,比如特別大的protobuf message,同一個IO線程中的其他fd都遭殃了。雖然不同IO線程間的fd是并發的,但你不太可能開太多IO線程,因為這類線程的事情很少,大部分時候都是閑著的。如果有10個IO線程,一個fd能影響到的”其他fd“仍有相當大的比例(10個即10%,而工業級在線檢索要求99.99%以上的可用性)。這個問題在fd沒有均勻地分布在IO線程中,或在多租戶(multi-tenancy)環境中會更加惡化。在brpc中,對不同fd的讀取是完全并發的,對同一個fd中不同消息的解析也是并發的。解析一個特別大的protobuf message不會影響同一個客戶端的其他消息,更不用提其他客戶端的消息了。更多細節看這里。
- 對同一fd和不同fd的寫出是高度并發的。當多個線程都要對一個fd寫出時(常見于單連接),第一個線程會直接在原線程寫出,其他線程會以wait-free的方式托付自己的寫請求,多個線程在高度競爭下仍可以在1秒內對同一個fd寫入500萬個16字節的消息。更多細節看這里。
- 盡量少的鎖。高QPS服務可以充分利用一臺機器的CPU。比如為處理請求創建bthread,?設置超時, 根據回復找到RPC上下文,?記錄性能計數器都是高度并發的。即使服務的QPS超過50萬,用戶也很少在contention profiler中看到框架造成的鎖競爭。
- 服務器線程數自動調節。傳統的服務器需要根據下游延時的調整自身的線程數,否則吞吐可能會受影響。在brpc中,每個請求均運行在新建立的bthread中,請求結束后線程就結束了,所以天然會根據負載自動調節線程數。
brpc和其他實現的性能對比見這里。
這里轉載分享brpc的官方介紹,方便自己學習查看!
官方連接:bRPChttps://brpc.apache.org/zh/