《LINUX教學(xué):gRPC客戶端創(chuàng)建和調(diào)用原理解析》要點(diǎn):
本文介紹了LINUX教學(xué):gRPC客戶端創(chuàng)建和調(diào)用原理解析,希望對(duì)您有用。如果有疑問(wèn),可以聯(lián)系我們。
gRPC是在HTTP/2之上實(shí)現(xiàn)的RPC框架,HTTP/2是第7層(應(yīng)用層)協(xié)議,它運(yùn)行在TCP(第4層 - 傳輸層)協(xié)議之上,相比于傳統(tǒng)的REST/JSON機(jī)制有諸多的優(yōu)點(diǎn):
此外,gRPC還提供了很多擴(kuò)展點(diǎn),用于對(duì)框架進(jìn)行功能定制和擴(kuò)展,例如,通過(guò)開(kāi)放負(fù)載均衡接口可以無(wú)縫的與第三方組件進(jìn)行集成對(duì)接(Zookeeper、域名解析服務(wù)、SLB服務(wù)等).
一個(gè)完整的RPC挪用流程示例如下:
(點(diǎn)擊放年夜圖像)
圖1-1 通用RPC挪用流程
gRPC的RPC調(diào)用與上述流程相似,下面我們一起學(xué)習(xí)下gRPC的客戶端創(chuàng)立和服務(wù)調(diào)用流程.
以gRPC入門級(jí)的helloworld Demo為例,客戶端發(fā)起RPC調(diào)用的代碼主要包含如下幾部分:
1) 根據(jù)hostname和port創(chuàng)立ManagedChannelImpl
2) 根據(jù)helloworld.proto文件生成的GreeterGrpc創(chuàng)立客戶端Stub,用來(lái)發(fā)起RPC調(diào)用
3) 使用客戶端Stub(GreeterBlockingStub)發(fā)起RPC挪用,獲取響應(yīng).
相關(guān)示例代碼如下所示:
(點(diǎn)擊放年夜圖像)
gRPC的客戶端調(diào)用主要包括基于Netty的HTTP/2客戶端創(chuàng)建、客戶端負(fù)載均衡、哀求消息的發(fā)送和響應(yīng)接收處理四個(gè)流程.
gRPC的客戶端挪用總體流程如下圖所示:
(點(diǎn)擊放年夜圖像)
圖1-2 gRPC總體挪用流程
gRPC的客戶端挪用流程如下:
1) 客戶端Stub(GreeterBlockingStub)挪用sayHello(request),發(fā)起RPC挪用
2) 經(jīng)由過(guò)程DnsNameResolver進(jìn)行域名解析,獲取服務(wù)端的地址信息(列表),隨后使用默認(rèn)的LoadBalancer策略,選擇一個(gè)具體的gRPC服務(wù)端實(shí)例
3) 如果與路由選中的服務(wù)端之間沒(méi)有可用的連接,則創(chuàng)立NettyClientTransport和NettyClientHandler,發(fā)起HTTP/2連接
4) 對(duì)哀求消息使用PB(Protobuf)做序列化,通過(guò)HTTP/2 Stream發(fā)送給gRPC服務(wù)端
5) 接管到服務(wù)端響應(yīng)之后,使用PB(Protobuf)做反序列化
6) 回調(diào)GrpcFuture的set(Response)辦法,喚醒阻塞的客戶端調(diào)用線程,獲取RPC響應(yīng)
必要指出的是,客戶端同步阻塞RPC調(diào)用阻塞的是調(diào)用方線程(通常是業(yè)務(wù)線程),底層Transport的I/O線程(Netty的NioEventLoop)仍然是非阻塞的.
ManagedChannel是對(duì)Transport層SocketChannel的抽象,Transport層負(fù)責(zé)協(xié)議消息的序列化和反序列化,以及協(xié)議消息的發(fā)送和讀取.ManagedChannel將處理后的哀求和響應(yīng)傳遞給與之相關(guān)聯(lián)的ClientCall進(jìn)行上層處理,同時(shí),ManagedChannel提供了對(duì)Channel的生命周期管理(鏈路創(chuàng)建、空閑、關(guān)閉等).
ManagedChannel提供了接口式的切面ClientInterceptor,它可以攔截RPC客戶端調(diào)用,注入擴(kuò)展點(diǎn),以及功能定制,便利框架的使用者對(duì)gRPC進(jìn)行功能擴(kuò)展.
ManagedChannel的主要實(shí)現(xiàn)類ManagedChannelImpl創(chuàng)立流程如下:
(點(diǎn)擊放年夜圖像)
圖1-3 ManagedChannelImpl創(chuàng)立流程
流程癥結(jié)技術(shù)點(diǎn)解讀:
ManagedChannel實(shí)例構(gòu)造完成之后,即可創(chuàng)建ClientCall,發(fā)起RPC調(diào)用.
完成ManagedChannelImpl創(chuàng)建之后,由ManagedChannelImpl發(fā)起創(chuàng)建一個(gè)新的ClientCall實(shí)例.ClientCall的用途是業(yè)務(wù)應(yīng)用層的消息調(diào)度和處置,它的典型用法如下:
call = channel.newCall(unaryMethod, callOptions); call.start(listener, headers); call.sendMessage(message); call.halfClose(); call.request(1); // wait for listener.onMessage()
ClientCall實(shí)例的創(chuàng)立流程如下所示:
(點(diǎn)擊放年夜圖像)
圖1-4 ClientCallImpl創(chuàng)立流程
流程癥結(jié)技術(shù)點(diǎn)解讀:
ClientCallImpl實(shí)例創(chuàng)建完成之后,就可以調(diào)用ClientTransport,創(chuàng)建HTTP/2 Client,向gRPC服務(wù)端發(fā)起遠(yuǎn)程服務(wù)調(diào)用.
gRPC客戶端底層基于Netty4.1的HTTP/2協(xié)議棧框架構(gòu)建,以便可以使用HTTP/2協(xié)議來(lái)承載RPC消息,在滿足尺度化規(guī)范的前提下,提升通信性能.
gRPC HTTP/2協(xié)議棧(客戶端)的癥結(jié)實(shí)現(xiàn)是NettyClientTransport和NettyClientHandler,客戶端初始化流程如下所示:
(點(diǎn)擊放年夜圖像)
圖1-5 HTTP/2 Client創(chuàng)立流程
流程癥結(jié)技術(shù)點(diǎn)解讀:
1.NettyClientHandler的創(chuàng)立:級(jí)聯(lián)創(chuàng)立Netty的Http2FrameReader、Http2FrameWriter和Http2Connection,用于構(gòu)建基于Netty的gRPC HTTP/2客戶端協(xié)議棧.
2.HTTP/2 Client啟動(dòng):仍然基于Netty的Bootstrap來(lái)初始化并啟動(dòng)客戶端,但是有兩個(gè)細(xì)節(jié)必要注意:
3. WriteQueue創(chuàng)建:Netty的NioSocketChannel初始化并向Selector注冊(cè)之后(發(fā)起HTTP連接之前),立即由NettyClientHandler創(chuàng)建WriteQueue,用于接收并處理gRPC內(nèi)部的各種Command,例如鏈路關(guān)閉指令、發(fā)送Frame指令、發(fā)送Ping指令等.
HTTP/2 Client創(chuàng)建完成之后,即可由客戶端根據(jù)協(xié)商策略發(fā)起HTTP/2連接.如果連接創(chuàng)建勝利,后續(xù)即可復(fù)用該HTTP/2連接,進(jìn)行RPC調(diào)用.
HTTP/2在TCP連接之初通過(guò)協(xié)商的方式進(jìn)行通信,只有協(xié)商成功,能力進(jìn)行后續(xù)的業(yè)務(wù)層數(shù)據(jù)發(fā)送和接收.
HTTP/2的版本標(biāo)識(shí)分為兩類:
HTTP/2連接創(chuàng)建,分為兩種:通過(guò)協(xié)商升級(jí)協(xié)議方式和直接連接方式.
假如不知道服務(wù)端是否支持HTTP/2,可以先使用HTTP/1.1進(jìn)行協(xié)商,客戶端發(fā)送協(xié)商哀求消息(只含消息頭),報(bào)文示例如下:
GET / HTTP/1.1 Host: 127.0.0.1 Connection: Upgrade, HTTP2-Settings Upgrade: h2c HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
服務(wù)端接收到協(xié)商哀求之后,如果不支持HTTP/2,則直接按照HTTP/1.1響應(yīng)返回,雙方通過(guò)HTTP/1.1進(jìn)行通信,報(bào)文示例如下:
HTTP/1.1 200 OK Content-Length: 28 Content-Type: text/css body...
如果服務(wù)端支持HTTP/2,則協(xié)商勝利,返回101結(jié)果碼,通知客戶端一起升級(jí)到HTTP/2進(jìn)行通信,示例報(bào)文如下:
HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: h2c [ HTTP/2 connection...
101響應(yīng)之后,服務(wù)需要發(fā)送SETTINGS幀作為連接序言,客戶端接收到101響應(yīng)之后,也必需發(fā)送一個(gè)序言作為回應(yīng),示例如下:
PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n SETTINGS幀
客戶端序言發(fā)送完成之后,可以不需要等待服務(wù)端的SETTINGS幀,而直接發(fā)送業(yè)務(wù)哀求Frame.
假如客戶端和服務(wù)端已經(jīng)約定使用HTTP/2,則可以免去101協(xié)商和切換流程,直接提議HTTP/2連接,具體流程如下所示:
(點(diǎn)擊放年夜圖像)
圖1-6 HTTP/2 直接連接進(jìn)程
幾個(gè)癥結(jié)點(diǎn):
gRPC支持三種Protocol Negotiator策略:
下面我們以PlaintextNegotiator為例,了解下基于Netty的HTTP/2連接創(chuàng)建流程:
(點(diǎn)擊放年夜圖像)
圖1-7 基于Netty的HTTP/2直連流程
總體上看,RPC的負(fù)載均衡策略有兩年夜類:
外部負(fù)載均衡模式如下所示:
(點(diǎn)擊放年夜圖像)
圖1-8 代理經(jīng)銷負(fù)載均衡模式示意圖
以代理LB模式為例:RPC客戶端向負(fù)載均衡代理發(fā)送哀求,負(fù)載均衡代理按照指定的路由策略,將哀求消息轉(zhuǎn)發(fā)到后端可用的服務(wù)實(shí)例上.負(fù)載均衡代理負(fù)責(zé)維護(hù)后端可用的服務(wù)列表,如果發(fā)現(xiàn)某個(gè)服務(wù)不可用,則將其剔除出路由表.
代理LB模式的優(yōu)點(diǎn)是客戶端不需要實(shí)現(xiàn)負(fù)載均衡策略算法,也不需要維護(hù)后端的服務(wù)列表信息,不直接跟后端的服務(wù)進(jìn)行通信,在做網(wǎng)絡(luò)平安邊界隔離時(shí),非常實(shí)用.例如通過(guò)Ngix做L7層負(fù)載均衡,將互聯(lián)網(wǎng)前端的流量平安的接入到后端服務(wù)中.
代理經(jīng)銷LB模式通常支持L4(Transport)和L7(Application)層負(fù)載均衡,兩者各有優(yōu)缺點(diǎn),可以根據(jù)RPC的協(xié)議特點(diǎn)靈活選擇.L4/L7層負(fù)載均衡對(duì)應(yīng)場(chǎng)景如下:
客戶端負(fù)載均衡策略由客戶端內(nèi)置負(fù)載均衡能力,通過(guò)靜態(tài)配置、域名解析服務(wù)(例如DNS服務(wù))、訂閱發(fā)布(例如Zookeeper服務(wù)注冊(cè)中心)等方式獲取RPC服務(wù)端地址列表,并將地址列表緩存到客戶端內(nèi)存中.每次RPC調(diào)用時(shí),根據(jù)客戶端配置的負(fù)載均衡策略由負(fù)載均衡算法從緩存的服務(wù)地址列表中選擇一個(gè)服務(wù)實(shí)例,發(fā)起RPC調(diào)用.
客戶端負(fù)載平衡策略工作原理示例如下:
(點(diǎn)擊放年夜圖像)
圖1-9 客戶端負(fù)載平衡策略示意圖
gRPC默認(rèn)采納客戶端負(fù)載均衡策略,同時(shí)提供了擴(kuò)展機(jī)制,使用者通過(guò)自定義實(shí)現(xiàn)NameResolver和LoadBalancer,即可覆蓋gRPC默認(rèn)的負(fù)載均衡策略,實(shí)現(xiàn)自定義路由策略的擴(kuò)展.
gRPC提供的負(fù)載平衡策略實(shí)現(xiàn)類如下所示:
gRPC負(fù)載均衡流程如下所示:
(點(diǎn)擊放年夜圖像)
圖1-10 gRPC客戶端負(fù)載平衡流程圖
流程癥結(jié)技術(shù)點(diǎn)解讀:
1.負(fù)載均衡功能模塊的輸入是客戶端指定的hostName、需要調(diào)用的接口名和辦法名等參數(shù),輸出是執(zhí)行負(fù)載均衡算法后獲得的NettyClientTransport.通過(guò)NettyClientTransport可以創(chuàng)建基于Netty HTTP/2的gRPC客戶端,發(fā)起RPC調(diào)用.
2.gRPC系統(tǒng)默認(rèn)提供的是DnsNameResolver,它通過(guò)InetAddress.getAllByName(host)獲取指定host的IP地址列表(當(dāng)?shù)谼NS服務(wù)).
對(duì)于擴(kuò)展者而言,可以承繼NameResolver實(shí)現(xiàn)自定義的地址解析服務(wù),例如使用Zookeeper替換DnsNameResolver,把Zookeeper作為動(dòng)態(tài)的服務(wù)地址配置中心,它的偽代碼示例如下:
第一步:繼承NameResolver,實(shí)現(xiàn)start(Listener listener)辦法:
void start(Listener listener) { //獲取ZooKeeper地址,并連接 //創(chuàng)建Watcher,并實(shí)現(xiàn)process(WatchedEvent event),監(jiān)聽(tīng)地址變更 //根據(jù)接口名和辦法名,調(diào)用getChildren辦法,獲取發(fā)布該服務(wù)的地址列表 //將地址列表加到List中 // 調(diào)用NameResolver.Listener.onAddresses(),通知地址解析完成
第二步:創(chuàng)建ManagedChannelBuilder時(shí),指定Target的地址為Zookeeper服務(wù)端地址,同時(shí)設(shè)置nameResolver為Zookeeper NameResolver,示例代碼如下所示:
this(ManagedChannelBuilder.forTarget(zookeeperAddr) .loadBalancerFactory(RoundRobinLoadBalancerFactory.getInstance()) .nameResolverFactory(new ZookeeperNameResolverProvider()) .usePlaintext(false));
3. LoadBalancer負(fù)責(zé)從nameResolver中解析獲得的服務(wù)端URL中依照指定路由策略,選擇一個(gè)目標(biāo)服務(wù)端地址,并創(chuàng)建ClientTransport.同樣,可以通過(guò)覆蓋handleResolvedAddressGroups實(shí)現(xiàn)自定義負(fù)載均衡策略.
通過(guò)LoadBalancer + NameResolver,可以實(shí)現(xiàn)靈活的負(fù)載均衡策略擴(kuò)展.例如基于Zookeeper、etcd的分布式配置服務(wù)中心計(jì)劃.
gRPC默認(rèn)基于Netty HTTP/2 + PB進(jìn)行RPC調(diào)用,哀求消息發(fā)送流程如下所示:
(點(diǎn)擊放年夜圖像)
圖1-11 gRPC哀求消息發(fā)送流程圖
流程癥結(jié)技術(shù)點(diǎn)解讀:
gRPC客戶端響應(yīng)消息的接收入口是NettyClientHandler,它的處理流程如下所示:
(點(diǎn)擊放年夜圖像)
圖1-12 gRPC響應(yīng)消息接管流程圖
流程癥結(jié)技術(shù)點(diǎn)解讀:
gRPC客戶端調(diào)用原理并不復(fù)雜,但是代碼卻相對(duì)比較繁雜.下面圍繞關(guān)鍵的類庫(kù),對(duì)主要功能點(diǎn)進(jìn)行源碼分析.
NettyClientTransport的主要功能如下:
以啟動(dòng)HTTP/2客戶端為例進(jìn)行講解:
(點(diǎn)擊放年夜圖像)
根據(jù)啟動(dòng)時(shí)配置的HTTP/2協(xié)商策略,以NettyClientHandler為參數(shù)創(chuàng)立ProtocolNegotiator.Handler.
創(chuàng)建Bootstrap,并設(shè)置EventLoopGroup,必要指出的是,此處并沒(méi)有使用EventLoopGroup,而是它的一種實(shí)現(xiàn)類EventLoop,原因在前文中已經(jīng)說(shuō)明,相關(guān)代碼示例如下:
(點(diǎn)擊放年夜圖像)
創(chuàng)立WriteQueue并設(shè)置到NettyClientHandler中,用于接收內(nèi)部的各種QueuedCommand,初始化完成之后,發(fā)起HTTP/2連接,代碼如下:
(點(diǎn)擊放年夜圖像)
NettyClientHandler繼承自Netty的Http2ConnectionHandler,是gRPC接收和發(fā)送HTTP/2消息的關(guān)鍵實(shí)現(xiàn)類,也是gRPC和Netty的交互橋梁,它的主要功能如下所示:
協(xié)議消息的發(fā)送:無(wú)論是業(yè)務(wù)哀求消息,還是協(xié)議指令消息,都統(tǒng)一封裝成QueuedCommand,由NettyClientHandler攔截并處理,相關(guān)代碼如下所示:
(點(diǎn)擊放年夜圖像)
協(xié)議消息的接管:NettyClientHandler通過(guò)向Http2ConnectionDecoder注冊(cè)FrameListener來(lái)監(jiān)聽(tīng)RPC響應(yīng)消息和協(xié)議指令消息,相關(guān)接口如下:
(點(diǎn)擊放年夜圖像)
FrameListener回調(diào)NettyClientHandler的相關(guān)辦法,實(shí)現(xiàn)協(xié)議消息的接收和處理:
(點(diǎn)擊放年夜圖像)
需要指出的是,NettyClientHandler并沒(méi)有實(shí)現(xiàn)所有的回調(diào)接口,對(duì)于需要特殊處理的幾個(gè)辦法進(jìn)行了重載,例如onDataRead和onHeadersRead.
ProtocolNegotiator用于HTTP/2連接創(chuàng)建的協(xié)商,gRPC支持三種策略并有三個(gè)實(shí)現(xiàn)子類:
(點(diǎn)擊放年夜圖像)
gRPC的ProtocolNegotiator實(shí)現(xiàn)類完全遵循HTTP/2相關(guān)規(guī)范,以PlaintextUpgradeNegotiator為例,通過(guò)設(shè)置Http2ClientUpgradeCodec,用于101協(xié)商和協(xié)議進(jìn)級(jí),相關(guān)代碼如下所示:
(點(diǎn)擊放年夜圖像)
LoadBalancer負(fù)責(zé)客戶端負(fù)載均衡,它是個(gè)抽象類,gRPC框架的使用者可以通過(guò)繼承的方式進(jìn)行擴(kuò)展.
gRPC當(dāng)前已經(jīng)支持PickFirstBalancer和RoundRobinLoadBalancer兩種負(fù)載均衡策略,將來(lái)不排除會(huì)提供更多的策略.
以RoundRobinLoadBalancer為例,它的工作原理如下:依據(jù)PickSubchannelArgs來(lái)選擇一個(gè)Subchannel:
(點(diǎn)擊放年夜圖像)
再看下Subchannel的選擇算法:
(點(diǎn)擊放年夜圖像)
即通過(guò)次序的方式從服務(wù)端列表中獲取一個(gè)Subchannel.
如果用戶必要定制負(fù)載均衡策略,則可以在RPC調(diào)用時(shí),使用如下代碼:
(點(diǎn)擊放年夜圖像)
ClientCalls提供了各種RPC調(diào)用方式,包括同步、異步、Streaming和Unary方式等,相關(guān)辦法如下所示:
(點(diǎn)擊放年夜圖像)
下面一起看下RPC哀求消息的發(fā)送和應(yīng)答接收相關(guān)代碼.
哀求調(diào)用主要有兩步:哀求Frame構(gòu)造和Frame發(fā)送,哀求Frame構(gòu)造代碼如下所示:
(點(diǎn)擊放年夜圖像)
使用PB對(duì)哀求消息做序列化,生成InputStream,構(gòu)造哀求Frame:
(點(diǎn)擊放年夜圖像)
Frame發(fā)送代碼如下所示:
(點(diǎn)擊放年夜圖像)
NettyClientHandler接收到發(fā)送變亂之后,調(diào)用Http2ConnectionEncoder將Frame寫(xiě)入Netty HTTP/2協(xié)議棧:
(點(diǎn)擊放年夜圖像)
響應(yīng)消息的接收入口是NettyClientHandler,包含HTTP/2 Header和HTTP/2 DATA Frame兩部分,代碼如下:
(點(diǎn)擊放年夜圖像)
如果參數(shù)endStream為True,闡明Stream已經(jīng)結(jié)束,調(diào)用transportTrailersReceived,通知Listener close,代碼如下所示:
(點(diǎn)擊放年夜圖像)
讀取到HTTP/2 DATA Frame之后,挪用MessageDeframer的deliver對(duì)Frame進(jìn)行解析,代碼如下:
(點(diǎn)擊放年夜圖像)
將Frame 轉(zhuǎn)換成InputStream之后,通知ClientStreamListenerImpl,挪用messageRead(final InputStream message),將InputStream反序列化為響應(yīng)對(duì)象,相關(guān)代碼如下所示:
(點(diǎn)擊放年夜圖像)
當(dāng)接收到endOfStream之后,通知ClientStreamListenerImpl,調(diào)用它的close辦法,如下所示:
(點(diǎn)擊放年夜圖像)
最終調(diào)用UnaryStreamToFuture的onClose辦法,set響應(yīng)對(duì)象,喚醒阻塞的調(diào)用方線程,完成RPC調(diào)用,代碼如下:
(點(diǎn)擊放年夜圖像)
李林鋒,華為軟件平臺(tái)開(kāi)放實(shí)驗(yàn)室架構(gòu)師,有多年Java NIO、平臺(tái)中間件、PaaS平臺(tái)、API網(wǎng)關(guān)設(shè)計(jì)和開(kāi)發(fā)經(jīng)驗(yàn).精曉Netty、Mina、分???式服務(wù)框架、云計(jì)算等,目前從事軟件公司的API開(kāi)放相關(guān)的架構(gòu)和設(shè)計(jì)工作.
接洽方式:新浪微博 Nettying 微信:Nettying
Email:neu_lilinfeng@sina.com
本文永遠(yuǎn)更新鏈接地址:
更多LINUX教程,盡在維易PHP學(xué)院專欄。歡迎交流《LINUX教學(xué):gRPC客戶端創(chuàng)建和調(diào)用原理解析》!
轉(zhuǎn)載請(qǐng)注明本頁(yè)網(wǎng)址:
http://www.fzlkiss.com/jiaocheng/7048.html