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