GRPC協議的相關原理

? ? ? GRPC的Client與Server,均通過Netty Channel作為數據通信,序列化、反序列化則使用Protobuf,每個請求都將被封裝成HTTP2的Stream,在整個生命周期中,客戶端Channel應該保持長連接,而不是每次調用重新創建Channel、響應結束后關閉Channel(即短連接、交互式的RPC),目的就是達到鏈接的復用,進而提高交互效率。

? ??1、Server端

? ? 我們通常使用NettyServerBuilder,即IO處理模型基于Netty,將來可能會支持其他的IO模型。Netty Server的IO模型簡析:

? ? 1)創建ServerBootstrap,設定BossGroup與workerGroup線程池

? ? 2)注冊childHandler,用來處理客戶端鏈接中的請求成幀

? ? 3)bind到指定的port,即內部初始化ServerSocketChannel等,開始偵聽和接受客戶端鏈接。

? ? 4)BossGroup中的線程用于accept客戶端鏈接,并轉發(輪訓)給workerGroup中的線程。

? ? 5)workerGroup中的特定線程用于初始化客戶端鏈接,初始化pipeline和handler,并將其注冊到worker線程的selector上(每個worker線程持有一個selector,不共享)

? ? 6)selector上發生讀寫事件后,獲取事件所屬的鏈接句柄,然后執行handler(inbound),同時進行拆封package,handler執行完畢后,數據寫入通過,由outbound handler處理(封包)通過鏈接發出。 ? ?注意每個worker線程上的數據請求是隊列化的。

? ? GRPC而言,只是對Netty Server的簡單封裝,底層使用了PlaintextHandler、Http2ConnectionHandler的相關封裝等。具體Framer、Stream方式請參考Http2相關文檔。

? ? 1)bossEventLoopGroup:如果沒指定,默認為一個static共享的對象,即JVM內所有的NettyServer都使用同一個Group,默認線程池大小為1。

? ? 2)workerEventLoopGroup:如果沒指定,默認為一個static共享的對象,線程池大小為coreSize * 2。這兩個對象采用默認值并不會帶來問題;通常情況下,即使你的application中有多個GRPC Server,默認值也一樣能夠帶來收益。不合適的線程池大小,有可能會是性能受限。

? ? 3)channelType:默認為NioServerSocketChannel,通常我們采用默認值;當然你也可以開發自己的類。如果此值為NioServerSocketChannel,則開啟keepalive,同時設定SO_BACKLOG為128;BACKLOG就是系統底層已經建立引入鏈接但是尚未被accept的Socket隊列的大小,在鏈接密集型(特別是短連接)時,如果隊列超過此值,新的創建鏈接請求將會被拒絕(有可能你在壓力測試時,會遇到這樣的問題),keepalive和BACKLOG特性目前無法直接修改。

Java代碼??
  1. [root@sh149?~]#?sysctl?-a|grep?tcp_keepalive??
  2. net.ipv4.tcp_keepalive_time?=?60??##單位:秒??
  3. net.ipv4.tcp_keepalive_probes?=?9??
  4. net.ipv4.tcp_keepalive_intvl?=?75?##單位:秒??
  5. ##可以在/etc/sysctl.conf查看和修改相關值??
  6. ##tcp_keepalive_time:最后一個實際數據包發送完畢后,首個keepalive探測包發送的時間。??
  7. ##如果首個keepalive包探測成功,那么鏈接會被標記為keepalive(首先TCP開啟了keepalive)??
  8. ##此后此參數將不再生效,而是使用下述的2個參數繼續探測??
  9. ##tcp_keepalive_intvl:此后,無論通道上是否發生數據交換,keepalive探測包發送的時間間隔??
  10. ##tcp_keepalive_probes:在斷定鏈接失效之前,嘗試發送探測包的次數;??
  11. ##如果都失敗,則斷定鏈接已關閉。??

? ? 對于Server端,我們需要關注上述keepalive的一些設置;如果Netty Client在空閑一段時間后,Server端會主動關閉鏈接,有可能Client仍然保持鏈接的句柄,將會導致RPC調用時發生異常。這也會導致GRPC客戶端調用時偶爾發生錯誤的原因之一。

? ? 4)followControlWindow:流量控制的窗口大小,單位:字節,默認值為1M,HTTP2中的“Flow Control”特性;連接上,已經發送尚未ACK的數據幀大小,比如window大小為100K,且winow已滿,每次向Client發送消息時,如果客戶端反饋ACK(攜帶此次ACK數據的大小),window將會減掉此大小;每次向window中添加亟待發送的數據時,window增加;如果window中的數據已達到限定值,它將不能繼續添加數據,只能等待Client端ACK。

? ? 5)maxConcurrentCallPerConnection:每個connection允許的最大并發請求數,默認值為Integer.MAX_VALUE;如果此連接上已經接受但尚未響應的streams個數達到此值,新的請求將會被拒絕。為了避免TCP通道的過度擁堵,我們可以適度調整此值,以便Server端平穩處理,畢竟buffer太多的streams會對server的內存造成巨大壓力。

? ? 6)maxMessageSize:每次調用允許發送的最大數據量,默認為100M。

? ? 7)maxHeaderListSize:每次調用允許發送的header的最大條數,GRPC中默認為8192。

? ? 對于其他的比如SSL/TSL等,可以參考其他文檔。

? ? GRPC Server端,還有一個最終要的方法:addService。【如下文service代理模式】

? ? 在此之前,我們需要介紹一下bindService方法,每個GRPC生成的service代碼中都有此方法,它以硬編碼的方式遍歷此service的方法列表,將每個方法的調用過程都與“被代理實例”綁定,這個模式有點類似于靜態代理,比如調用sayHello方法時,其實內部直接調用“被代理實例”的sayHello方法(參見MethodHandler.invoke方法,每個方法都有一個唯一的index,通過硬編碼方式執行);bindService方法的最終目的是創建一個ServerServiceDefinition對象,這個對象內部位置一個map,key為此Service的方法的全名(fullname,{package}.{service}.{method}),value就是此方法的GRPC封裝類(ServerMethodDefinition)。

Java代碼?
  1. private?static?final?int?METHODID_SAY_HELLO?=?0;??
  2. private?static?class?MethodHandlers<Req,?Resp>?implements??
  3. ??????...?{??
  4. ????private?final?TestRpcService?serviceImpl;//實際被代理實例??
  5. ????private?final?int?methodId;??
  6. ??
  7. ????public?MethodHandlers(TestRpcService?serviceImpl,?int?methodId)?{??
  8. ??????this.serviceImpl?=?serviceImpl;??
  9. ??????this.methodId?=?methodId;??
  10. ????}??
  11. ??
  12. ????@java.lang.SuppressWarnings("unchecked")??
  13. ????public?void?invoke(Req?request,?io.grpc.stub.StreamObserver<Resp>?responseObserver)?{??
  14. ??????switch?(methodId)?{??
  15. ????????case?METHODID_SAY_HELLO:????????//通過方法的index來判定具體需要代理那個方法??
  16. ??????????serviceImpl.sayHello((com.test.grpc.service.model.TestModel.TestRequest)?request,??
  17. ??????????????(io.grpc.stub.StreamObserver<com.test.grpc.service.model.TestModel.TestResponse>)?responseObserver);??
  18. ??????????break;??
  19. ????????default:??
  20. ??????????throw?new?AssertionError();??
  21. ??????}??
  22. ????}??
  23. ????....??
  24. ??}??
  25. ??
  26. ??public?static?io.grpc.ServerServiceDefinition?bindService(??
  27. ??????final?TestRpcService?serviceImpl)?{??
  28. ????return?io.grpc.ServerServiceDefinition.builder(SERVICE_NAME)??
  29. ????????.addMethod(??
  30. ??????????METHOD_SAY_HELLO,??
  31. ??????????asyncUnaryCall(??
  32. ????????????new?MethodHandlers<??
  33. ??????????????com.test.grpc.service.model.TestModel.TestRequest,??
  34. ??????????????com.test.grpc.service.model.TestModel.TestResponse>(??
  35. ????????????????serviceImpl,?METHODID_SAY_HELLO)))??
  36. ????????.build();??
  37. ??}??

? ? addService方法可以添加多個Service,即一個Netty Server可以為多個service服務,這并不違背設計模式和架構模式。addService方法將會把service保存在內部的一個map中,key為serviceName(即{package}.{service}),value就是上述bindService生成的對象。

? ? 那么究竟Server端是如何解析RPC過程的?Client在調用時會將調用的service名稱 + method信息保存在一個GRPC“保留”的header中,那么Server端即可通過獲取這個特定的header信息,就可以得知此stream需要請求的service、以及其method,那么接下來只需要從上述提到的map中找到service,然后找到此method,直接代理調用即可。執行結果在Encoder之后發送給Client。(參見:NettyServerHandler)

? ??因為是map存儲,所以我們需要在定義.proto文件時,盡可能的指定package信息,以避免因為service過多導致名稱可能重復的問題。

? ??2、Client端

? ? 我們使用ManagedChannelBuilder來創建客戶端channel,ManagedChannelBuilder使用了provider機制,具體是創建了哪種channel有provider決定,可以參看META-INF下同類名的文件中的注冊信息。當前Channel有2種:NettyChannelBuilder與OkHttpChannelBuilder。本人的當前版本中為NettyChannelBuilder;我們可以直接使用NettyChannelBuilder來構建channel。如下描述則針對NettyChannelBuilder:

? ? 配置參數與NettyServerBuilder基本類似,再次不再贅言。默認情況下,Client端默認的eventLoopGroup線程池也是static的,全局共享的,默認線程個數為coreSize * 2。合理的線程池個數可以提高客戶端的吞吐能力。

? ??ManagedChannel是客戶端最核心的類,它表示邏輯上的一個channel;底層持有一個物理的transport(TCP通道,參見NettyClientTransport),并負責維護此transport的活性;即在RPC調用的任何時機,如果檢測到底層transport處于關閉狀態(terminated),將會嘗試重建transport。(參見TransportSet.obtainActiveTransport())

? ? 通常情況下,我們不需要在RPC調用結束后就關閉Channel,Channel可以被一直重用,直到Client不再需要請求位置或者Channel無法真的異常中斷而無法繼續使用。當然,為了提高Client端application的整體并發能力,我們可以使用連接池模式,即創建多個ManagedChannel,然后使用輪訓、隨機等算法,在每次RPC請求時選擇一個Channel即可。(備注,連接池特性,目前GRPC尚未提供,需要額外的開發)

? ? 每個Service客戶端,都生成了2種stub:BlockingStub和FutureStub;這兩個Stub內部調用過程幾乎一樣,唯一不同的是BlockingStub的方法直接返回Response Model,而FutureStub返回一個Future對象。BlockingStub內部也是基于Future機制,只是封裝了阻塞等待的過程:

Java代碼?
  1. try?{??
  2. ????????//也是基于Future??
  3. ??????ListenableFuture<RespT>?responseFuture?=?futureUnaryCall(call,?param);??
  4. ??????//阻塞過程??
  5. ??????while?(!responseFuture.isDone())?{??
  6. ????????try?{??
  7. ??????????executor.waitAndDrain();??
  8. ????????}?catch?(InterruptedException?e)?{??
  9. ??????????Thread.currentThread().interrupt();??
  10. ??????????throw?Status.CANCELLED.withCause(e).asRuntimeException();??
  11. ????????}??
  12. ??????}??
  13. ??????return?getUnchecked(responseFuture);??
  14. ????}?catch?(Throwable?t)?{??
  15. ??????call.cancel();??
  16. ??????throw?t?instanceof?RuntimeException???(RuntimeException)?t?:?new?RuntimeException(t);??
  17. }??

? ??創建一個Stub的成本是非常低的,我們可以在每次請求時都通過channel創建新的stub,這并不會帶來任何問題(只不過是創建了大量對象);其實更好的方式是,我們應該使用一個Stub發送多次請求,即Stub也是可以重用的;直到Stub上的狀態異常而無法使用。最常見的異常,就是“io.grpc.StatusRuntimeException: DEADLINE_EXCEEDED”,即表示DEADLINE時間過期,我們可以為每個Stub配置deadline時間,那么如果此stub被使用的時長超過此值(不是空閑的時間),將不能再發送請求,此時我們應該創建新的Stub。很多人想盡辦法來使用“withDeadlineAfter”方法來實現一些奇怪的事情,此參數的主要目的就是表明:此stub只能被使用X時長,此后將不能再進行請求,應該被釋放。所以,它并不能實現類似于“keepAlive”的語義,即使我們需要keepAlive,也應該在Channel級別,而不是在一個Stub上。

? ? 如果你使用了連接池,那么其實連接池不應該關注DEADLINE的錯誤,只要Channel本身沒有terminated即可;就把這個問題交給調用者處理。如果你也對Stub使用了對象池,那么你就可能需要關注這個情況了,你不應該向調用者返回一個“DEADLINE”的stub,或者如果調用者發現了DEADLINE,你的對象池應該能夠移除它。

? ? 1)實例化ManagedChannel,此channel可以被任意多個Stub實例引用;如上文說述,我們可以通過創建Channel池,來提高application整體的吞吐能力。此Channel實例,不應該被shutdown,直到Client端停止服務;在任何時候,特別是創建Stub時,我們應該判定Channel的狀態。

Java代碼??收藏代碼
  1. synchronized?(this)?{??
  2. ????if?(channel.isShutdown()?||?channel.isTerminated())?{??
  3. ????????channel?=?ManagedChannelBuilder.forAddress(poolConfig.host,?poolConfig.port).usePlaintext(true).build();??
  4. ????}??
  5. ????//new?Stub??
  6. }??
  7. ??
  8. //或者??
  9. ManagedChannel?channel?=?(ManagedChannel)client.getChannel();??
  10. if(channel.isShutdown()?||?channel.isTerminated())?{??
  11. ????client?=?createBlockStub();??
  12. }??
  13. client.sayHello(...)??

? ? 因為Channel是可以多路復用,所以我們用Pool機制(比如commons-pool)也可以實現連接池,只是這種池并非完全符合GRPC/HTTP2的設計語義,因為GRPC允許一個Channel上連續發送對個Requests(然后一次性接收多個Responses),而不是“交互式”的Request-Response模式,當然這么使用并不會有任何問題。

? ? 2)對于批量調用的場景,我們可以使用FutureStub,對于普通的業務類型RPC,我們應該使用BlockingStub。

? ? 3)每個RPC方法的調用,比如sayHello,調用開始后,將會為每個調用請求創建一個ClientCall實例,其內部封裝了調用的方法、配置選項(headers)等。此后將會創建Stream對象,每個Stream都持有唯一的streamId,它是Transport用于分揀Response的憑證。最終調用的所有參數都會被封裝在Stream中。

? ? 4)檢測DEADLINE,是否已經過期,如果過期,將使用FailingClientStream對象來模擬整個RPC過程,當然請求不會通過通道發出,直接經過異常流處理過程。

? ? 5)然后獲取transport,如果此時檢測到transport已經中斷,則重建transport。(自動重練機制,ClientCallImpl.start()方法)

? ? 6)發送請求參數,即我們Request實例。一次RPC調用,數據是分多次發送,但是ClientCall在創建時已經綁定到了指定的線程上,所以數據發送總是通過一個線程進行(不會亂序)。

? ? 7)將ClientCall實例置為halfClose,即半關閉,并不是將底層Channel或者Transport半關閉,只是邏輯上限定此ClientCall實例上將不能繼續發送任何stream信息,而是等待Response。

? ? 8)Netty底層IO將會對reponse數據流進行解包(Http2ConnectionDecoder),并根據streamId分揀Response,同時喚醒響應的ClientCalls阻塞。(參見ClientCalls,GrpcFuture)

? ? 9)如果是BlockingStub,則請求返回,如果響應中包含應用異常,則封裝后拋出;如果是網絡異常,則可能觸發Channel重建、Stream重置等。

?

轉載自:http://shift-alt-ctrl.iteye.com/blog/2292862

轉載于:https://www.cnblogs.com/junjiang3/p/9164513.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/251597.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/251597.shtml
英文地址,請注明出處:http://en.pswp.cn/news/251597.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Echarts --- 各個省份的坐標

純手打…效果如下 1.新疆: [86.61 , 40.79] 2.西藏:[89.13 , 30.66] 3.黑龍江:[128.34 , 47.05] 4.吉林:[126.32 , 43.38] 5.遼寧:[123.42 , 41.29] 6.內蒙古:[112.17 , 42.81] 7.北京:[116.40 , 40.40 ] 8.寧夏:[106.27 , 36.76] 9.山西:[111.95,37.65] 10.河北:[115.21 , 38.…

xxx征集系統項目目標文檔

問題 每四人一組&#xff0c;討論結束后&#xff0c;每人根據課堂討論結果提交一份系統利益相關者案例。撰寫撰寫項目目標文檔&#xff08;目標&#xff0c;好處&#xff0c;度量標準。&#xff09; 項目目標文檔 目標&#xff1a; &#xff08;1&#xff09;需求填報 &#xf…

高并發大流量專題---10、MySQL數據庫層的優化

高并發大流量專題---10、MySQL數據庫層的優化 一、總結 一句話總結&#xff1a; mysql先考慮做分布式緩存&#xff0c;過了緩存后就做mysql數據庫層面的優化 1、mysql數據庫層的優化的前面一層是什么&#xff1f; 數據庫緩存&#xff1a;突破了數據庫緩存就需要做mysql數據庫層…

【彩彩只能變身隊】后端工作總結

2018.06.09 早上8點到晚上10點 沖刺前后端交互(vueexpressmysql) 8&#xff1a;00-12&#xff1a;00 &#xff1a; 前端把請求寫好&#xff1a; <template> <div class"LoginForm"> <el-form ref"form" label-width"80px"…

web安全

web安全 DOS命令 web攻防必備課筆記 慕課xss學習 阮一峰&#xff1a;MVC、MVP和MVVM的圖示轉載于:https://www.cnblogs.com/hanxuming/p/7774092.html

JavaScript --- 渲染數據量大的數組

很多時候&#xff0c;需要在頁面上展示從后臺來的大量數據,如果一次性渲染&#xff0c;會影響用戶的體驗。(而且瀏覽器中的JS嚴格限制了資源) /* *使用分組的思想來渲染大量的數組 *parmas array 要處理的數組 *params process 對數組中每一個item進行的操作 *parmas context …

Jquery操作select小結

每次操作select都要查資料&#xff0c;干脆總結一下。 為select設置placeholder <select class"form-control selOP" placeholder"Pick Orchestration Plan"><option value"" disabled selected styledisplay:none;>Pick Orchestrat…

第六講:PrintClient工具的使用

一些簡單命令&#xff1a; cp -rf 源目錄 目的目錄 chmod -R 777 文件名 motelist 查看節點路徑 make telosb 編譯代碼 make telosb reinstall 下載但不編譯 make telosb install 編譯并且下載 make telosb install, 2 bsl,/dev/ttyUSB0 下載指定路徑 java net.tinyos.tools.Li…

SQL Server

查看數據庫服務器名稱&#xff1a;tracert 192.168.10.01 轉載于:https://www.cnblogs.com/hongwei2085/p/9174760.html

css --- 選擇器

標簽選擇器 // 標簽選擇器是最簡單的選擇器, 它的命名只要和對應的HTML標簽相同即可 h1 {font-size: 30px;color: #333; }類選擇器 // 類選擇器也稱為class選擇器,它的語法非常簡單,在class名稱前面加上一個"."符號 <div class"red content"></…

C++標準輸入流、輸出流以及文件流

1、流的控制 iomanip 在使用格式化I/O時應包含此頭文件。 stdiostream 用于混合使用C和C 的I/O機制時&#xff0c;例如想將C程序轉變為C程序 2、類繼承關系 ios是抽象基類&#xff0c;由它派生出istream類和ostream類&#xff0c; iostream類支持輸入輸出操作&…

Hadoop學習筆記—8.Combiner與自定義Combiner

一、Combiner的出現背景 1.1 回顧Map階段五大步驟 在第四篇博文《初識MapReduce》中&#xff0c;我們認識了MapReduce的八大步湊&#xff0c;其中在Map階段總共五個步驟&#xff0c;如下圖所示&#xff1a; 其中&#xff0c;step1.5是一個可選步驟&#xff0c;它就是我們今天需…

6-12mysql庫的操作

1&#xff0c;mysql庫的各種分類: nformation_schema&#xff1a; 虛擬庫&#xff0c;不占用磁盤空間&#xff0c;存儲的是數據庫啟動后的一些參數&#xff0c;如用戶表信息、列信息、權限信息、字符信息等.  performance_schema&#xff1a; MySQL 5.5開始新增一個數據庫&am…

css --- 行內框和內容區

css規定font-size的大小實際上是字體的高度 可以將內容區理解為font-size的大小. 行內高可以理解為 ( (line-height) - (font-size) ) /2 然后再font-size 的上下加上前面的值 看下面的例子 <p style"font-size:12px;line-height:12px;">this is text, <em&…

DotNetTextBox V3.0 所見即所得編輯器控件 For Asp.Net2.0(ver 3.0.7Beta) 增加多語言!

英文名&#xff1a;DotNetTextBox V3.0 WYSWYG Web Control For Asp.Net2.0 中文名&#xff1a;DotNetTextBox V3.0 所見即所得編輯器控件 For Asp.Net2.0 類型: 免費控件(保留版權) 作者: 小寶.NET 2.0(Terry Deng) 主頁&#xff1a;http://www.aspxcn.com.cn 控件演示頁面: h…

phantomjs

npm 安裝 phantomjs失敗&#xff0c;解決辦法是到http://phantomjs.org/download.html 下載需要的壓縮包&#xff0c;然后放到%appData%\Local\Temp\phantomjs\下&#xff0c;重新執行npm i 轉載于:https://www.cnblogs.com/tellme/p/7777626.html

js動態刪除行錯誤

Uncaught TypeError: Failed to execute removeChild on Node: parameter 1 is not of type Node. js中出現如上錯誤&#xff0c;檢查驗證document.getElementById&#xff08;&#xff09;中的參數是否傳遞正確&#xff0c; 例&#xff1a; var textnumber parseInt(docume…

css --- 浮動元素與 塊框/行內框重疊時的細節

塊框,可以認為是塊級元素(如div、h1)的內容區 內邊距 行內框可以認為是行內元素(如span)的內容區 內邊距 當 塊級框/行內框 和一個浮動元素重疊時&#xff0c;行內框的邊框、背景和內容都在幅度元素之上&#xff0c;塊級框的邊框和背景都在浮動元素的下面&#xff0c;但內容在…

Android 禁止Viewpager左右滑動功能

做項目要求某種情況下ViewPager不能滑動 百度后發現重寫ViewPager&#xff0c;覆蓋ViewPager的onInterceptTouchEvent(MotionEvent arg0)方法和onTouchEvent(MotionEvent arg0)方法&#xff0c;這兩個方法的返回值都是boolean類型的&#xff0c;只需要將返回值改為false&#x…

error C1853: “Debug\BigBuffer.pch”預編譯頭文件來自編譯器的早期版本,或者預編譯頭為 C++ 而在 C 中使用它(或相反)...

<pre id"best-content-1299104064" mb-10""" style"font-size: 14px; line-height: 28px; ">該錯誤是因為當項目中混合了 .cpp 和 .c 文件時&#xff0c;編譯器會對它們采取不同的編譯方式&#xff08;主要是因為對函數聲明的處理方式…