grpc一次請求處理的過程

1, 先從protobuf開始吧。
protobuf是一個高效的序列化協(xié)議,protobuf分兩部分,一部分是用c++編寫的protoc編譯器,用于把proto文件編譯為java/c#/go/c++等語言的支持序列化反序列化的對象。


protoc.png

protoc -I [proto文件路徑] --java_out [生成的類文件路徑] [proto文件路徑]
生成的java類都繼承自protobuf-java中的類。

以后有時間分析一下protoc的源碼!??!

2, 如正題,先從源碼中的helloword開始。


helloword.png

下面開始分析server啟動過程以及一個請求的處理過程。
3, 從類名就可以看出使用的構(gòu)造器設(shè)計模式,此模式特別適用于配置參數(shù)特別多的類。lombok中的@Builder注解非常方便。


spi加載ServerProvider.png
spi加載ServerProvider.png

從源碼中可以看到,此處通過jdk中的spi加載ServerProvider,最終加載到的是NettyServerProvider類。NettyServerProvider的builderForPort返回NettyServerBuilder類。


ServerProvider類層次結(jié)構(gòu).png

ServerProvider里的方法不多,用于注冊服務(wù),添加攔截器,初始化server狀態(tài)。
最后調(diào)用build構(gòu)造出server對象。構(gòu)造的過程如下:


構(gòu)造server.png
構(gòu)造server.png

ServerImpl維護著整個server的狀態(tài),最關(guān)鍵的屬性如下:
1,ObjectPool<? extends Executor> executorPool:執(zhí)行請求的服務(wù)方法的線程池。
2,InternalHandlerRegistry registry:服務(wù)方法主注冊表,運行時不能修改。
3,HandlerRegistry fallbackRegistry:fallback注冊表,運行時可以動態(tài)修改。
4,List<ServerTransportFilter> transportFilters:監(jiān)聽client連接ready和terminal事件。
5, ServerInterceptor[] interceptors:服務(wù)攔截器,添加順序和執(zhí)行順序相反,服務(wù)方法執(zhí)行時以裝飾器設(shè)計模式嵌入執(zhí)行流程。
6, InternalServer transportServer:NettyServer。
7, Collection<ServerTransport> transports:accept到的client連接。
8, DecompressorRegistry decompressorRegistry:解碼器注冊表。
9, CompressorRegistry compressorRegistry:編碼器注冊表。
10, BinaryLogProvider binlogProvider:記錄log。
11, Channelz channelz:記錄server,channel的狀態(tài)。
12, CallTracer serverCallTracer:統(tǒng)計rpc調(diào)用次數(shù)。

4, server構(gòu)造之后開始調(diào)用start方法啟動。


startup.png

此處的重點是啟動傳輸層時傳入了ServerListener,用來client連接建立或終結(jié)時從傳輸層回調(diào)server。


netty.png

netty啟動過程,重點是childHandler設(shè)置的處理client請求的pipeline。

設(shè)置childHandler.png

此處的重點是ServerTransportListener,在連接建立和收到client請求數(shù)據(jù)時從傳輸層回調(diào)server。

設(shè)置childHandler.png
設(shè)置childHandler.png

洗盡鉛華,發(fā)現(xiàn)最終處理client的handler只有NettyServerHandler而已。


NettyServerHandler.png

從類層次上看以看到NettyServerHandler只是一個解碼器decoder,具體點就是Http2ConnectionHandler,由此可見,grpc的傳輸協(xié)議是http2,相比與http1.1效率有很大提升,據(jù)說主要在兩方面:利用IO多播使一個連接可以請求多個資源,header壓縮,有空詳細分析http2協(xié)議。
在NettyServerHandler的構(gòu)造過程中有一句至關(guān)重要: decoder().frameListener(new FrameListener())。熟悉netty codec機制的一看就明白,就是根據(jù)http2協(xié)議,每次解析到一幀都會通知。


Http2FrameListener.png

重點是onDataRead,onHeadersRead,onSettingsRead(tcp握手完成)顧名思義。
FrameListener.png

現(xiàn)在萬事具備了,server已經(jīng)啟動,啟動的過程主要在注冊各種服務(wù),添加服務(wù)攔截器等,初始化netty。

當有請求到來時,ByteToMessageDecoder會按http2協(xié)議解析幀,當收到http header時,


收到http header.png

此處最關(guān)鍵的時構(gòu)造了NettyServerStream,此類的作用是一個請求完成后把響應(yīng)信息返回給client,最重要的屬性是WriteQueue writeQueue,響應(yīng)信息的隊列,緩存響應(yīng)信息,等全部處理完,調(diào)用flush把所有響應(yīng)信息返回給client。
另一個重點是通過listener回調(diào)通知server已經(jīng)收到了數(shù)據(jù)包,第一個數(shù)據(jù)包永遠都是header。header都有啥?


header.png

最關(guān)鍵的是method,服務(wù)方法的全限定名。

現(xiàn)在的流程從傳輸層轉(zhuǎn)到了server。


設(shè)置listener.png

此處設(shè)置了一個非常關(guān)鍵的JumpToApplicationThreadServerStreamListener,當IO線程收齊http body之后回調(diào)。

查找方法.png

很明顯,找到請求的服務(wù),調(diào)用它。很神奇,只收到header就開始調(diào)用服務(wù)?。?!netty的io是全異步的,調(diào)用的過程中已經(jīng)在接收http body啦。

調(diào)用服務(wù).png
調(diào)用服務(wù).png

裝飾器設(shè)計模式的應(yīng)用,套了一層又一層,此處很容易明白為啥服務(wù)攔截器添加順序和執(zhí)行順序相反啦。

終于開始調(diào)用方法了,根據(jù)請求的方法的類型調(diào)用對應(yīng)的ServerCallHandler。


服務(wù)方法類型.png

以最簡單又最常見的UNARY為例吧,一個請求跟隨一個響應(yīng)。


UnaryServerCallHandler.png

此處的call.request(2)至關(guān)重要,2表示應(yīng)該收到2個幀,注釋里解釋里原因。

UnaryServerCallListener.png

當收到http body時,通過一大串listener的通知,最后到達onMessage,此時的request已經(jīng)經(jīng)過里protobuf的反序列化,轉(zhuǎn)成里請求對象。

根據(jù)tcp協(xié)議,client發(fā)送完請求后,如果不是長連接,會關(guān)閉輸入端,等待響應(yīng)。一旦輸入端關(guān)閉,就知道所有請求都發(fā)送完啦,終于可以調(diào)用服務(wù)方法啦。
那這個UnaryServerCallListener是怎么被調(diào)用呢。
就在上面的call.request(2)這句啦,


request.png
deliver.png
循環(huán)讀.png

一直循環(huán)直到讀完所有數(shù)據(jù),但是這個循環(huán)本身并不直接操作IO,它只是把CompositeReadableBuffer unprocessed里面的內(nèi)容收起起來判斷是否讀到了需要的數(shù)據(jù)。

毫無疑問,unprocessed里的內(nèi)容是IO線程讀出來的。

此時FrameListener收到header之后正忙著收http body。


收到http body.png

通知listener收到了新的數(shù)據(jù)包

收到http body.png
收到http body.png

把收到的數(shù)據(jù)都放到CompositeReadableBuffer unprocessed緩存起來。

一個在監(jiān)聽幀,然后讀數(shù)據(jù)放到unprocessed里緩存,一個是循環(huán)匯總unprocessed里的數(shù)據(jù),終于交織在一起啦。

等http body讀完之后,開始通知之前建立的一大串listener。中間會經(jīng)過protobuf反序列化的過程,最終到達UnaryServerCallListener.onMessage。


通知listener.png

然后等待netty收到輸入關(guān)閉事件,通過一串listener,最終到達UnaryServerCallListener.onHalfClose事件。然后真正的到了執(zhí)行服務(wù)方法的時候,method.invoke(request, responseObserver);服務(wù)方法執(zhí)行完,得到的結(jié)果會回調(diào)responseObserver.onNext方法,


觸發(fā)onNext.png
觸發(fā)onNext.png
protobuf序列化.png
writeFrame.png
響應(yīng)隊列.png
觸發(fā)channel的flush.png

最后觸發(fā)channel的flush,返回響應(yīng)信息。

5, 再縷一下整個請求處理流程。
1,server在啟動netty時傳入了ServerListenerImpl,用于接收client連接建立的回調(diào)通知。
2, netty通知server client連接建立時,server返回給netty的是ServerTransportListenerImpl,用于netty在收到http請求幀時回調(diào)通知server。
3,netty收到http header時創(chuàng)建NettyServerStream,然后通過回調(diào)通知server,server根據(jù)http header里的method屬性查詢請求的服務(wù)方法,給NettyServerStream又設(shè)置一個JumpToApplicationThreadServerStreamListener,用于當收到http body時回調(diào)。
4, 收到http header時開始startCall,UnaryServerCallHandler.startCall時通過調(diào)用ServerCall.request方法,進入MessageDeframer的request方法,然后一直循環(huán)檢查是否收到了指定幀數(shù)的http body,UnaryServerCallHandler.startCall返回UnaryServerCallListener,返回的listener又被wrap到ServerStreamListenerImpl里,wrap之后又被set到JumpToApplicationThreadServerStreamListener里。
5,整個listener鏈已經(jīng)設(shè)置好了,就等著netty收到http body啦。
6, netty最終收齊了http body。然后第4步中startCall里那個循環(huán)檢測到http body收齊了,結(jié)束循環(huán)。
7, 然后開始沿著listener鏈通知下去,請求信息在protobuf反序列化之后最終到達UnaryServerCallListener.onMessage保存起來
8, 等待netty收到輸入端關(guān)閉的事件,然后執(zhí)行服務(wù)方法。
9, 服務(wù)方法執(zhí)行完,響應(yīng)信息通過ServerCallStreamObserverImpl.onNext方法protobuf序列化之后,觸發(fā)netty channel的flush事件,返回響應(yīng)給client。

從請求到響應(yīng)的整個流程結(jié)束?。?!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容