文檔目的:
公司目前使用的dubbo版本是2.6.2鹤竭,看完dubbo官方文檔中的一些功能,所以就想知道dubbo調(diào)用過程大概是怎么樣的藕漱。
看源碼前提:
一定要先看DUBBO SPI的相關(guān)知識(shí):
內(nèi)容:
一上來必須是看看官方的這三張圖
服務(wù)引用過程:
取名:圖1
調(diào)用過程的兩張:
取名:圖2
取名:圖3
圖2中紅色部分為dubbo服務(wù)調(diào)用過程欲侮。
下面將分析各層的實(shí)現(xiàn)和功能點(diǎn)
Interface:為開發(fā)定義的接口,使用@Reference引用服務(wù)
Proxy:dubbo為我們生成的代理類肋联,應(yīng)該是在引用過程創(chuàng)建的威蕉,這個(gè)之后再分析具體實(shí)現(xiàn)-ReferenceConfig.get()->ReferenceConfig.createProxy()->ProxyFactory.getProxy(Invoker)
Invoker:屬于Cluster層,從圖中得到的信息時(shí)橄仍,先從Register層獲得注冊(cè)了的服務(wù)韧涨,然后通過路由規(guī)則過濾(Router),之后再通過LoadBalance算法得到實(shí)際調(diào)用服務(wù)侮繁。具體邏輯定位:com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker虑粥。dubbo在外層還是用了com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterWrapper來做包裝,支持服務(wù)降級(jí)功能(dubbo中SPI相關(guān)的功能XxxWrapper)宪哩∶浯看默認(rèn)的cluster是failover(失敗重試其他,默認(rèn)重試次數(shù):2锁孟,失敗之后重試兩次彬祖,參見:com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker
相關(guān)功能使用說明:
- 路由規(guī)則:https://dubbo.apache.org/zh/docs/v2.7/user/examples/routing-rule/
- 負(fù)載均衡算法:https://www.cnblogs.com/wyq178/p/9822731.html
- 服務(wù)降級(jí):https://dubbo.apache.org/zh/docs/v2.7/user/examples/service-downgrade/
- Filter:Protocol層茁瘦。定義暴露服務(wù)和引用服務(wù)的邏輯。dubbo定義了三個(gè)Protocol的包裝類储笑,分別是:
- com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper : protocol類型非registry時(shí)甜熔,使用責(zé)任鏈模式,將定義的com.alibaba.dubbo.rpc.Filter集成到Invoker中
- com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper:protocol類型非registry時(shí)突倍,在暴露服務(wù)和引用服務(wù)時(shí)腔稀,將ExporterListener和InvokerListener嵌入到ListenerExporterWrapper和ListenerInvokerWrapper以實(shí)現(xiàn)對(duì)服務(wù)暴露,服務(wù)撤銷和服務(wù)引用羽历,引用銷毀的事件監(jiān)聽烧颖。
- com.alibaba.dubbo.qos.protocol.QosProtocolWrapper:protocol類型為registry時(shí),開啟qos服務(wù)窄陡。詳情參見:https://blog.csdn.net/yuanshangshenghuo/article/details/107563319
我們目前關(guān)注的是ProtocolFilterWrappe中的邏輯,定義的Filter已經(jīng)被放入Invoker的執(zhí)行鏈中拆火。
- Invoker: 取決于具體配置的dubbo的protocol跳夭,圖中使用的protocol是dubbo,對(duì)應(yīng)的Invoker是com.alibaba.dubbo.rpc.protocol.dubbo.DubboInvoker.
我之前使用過injvm的protocol们镜,其暴露服務(wù)直接維護(hù)在內(nèi)存map中币叹,引用的時(shí)候直接從map中取。他在filter層之下模狭,說明使用injvm協(xié)議也會(huì)走Filter邏輯颈抚。
- ExchangeClient:封裝請(qǐng)求響應(yīng)模式,同步轉(zhuǎn)異步
其下之后都是數(shù)據(jù)傳輸相關(guān)嚼鹉,看可以參見各層功能及可擴(kuò)展點(diǎn)
這里分析將protocol使用dubbo時(shí)的請(qǐng)求和返回的處理流程:
1. 部分初始化邏輯
DubboProtocol在引用服務(wù)的時(shí)候贩汉,會(huì)初始化ExchangeClient,
1.1 當(dāng)url中沒有指定:連接數(shù)-Constants.CONNECTIONS_KEY或該值為0時(shí)锚赤,則該應(yīng)用對(duì)提供服務(wù)的應(yīng)用(服務(wù)ip+port)會(huì)使用同一個(gè)ExchangeClient匹舞,參見:DubboProtocol.getClients。這種情況會(huì)在外層包裝一層ReferenceCountExchangeClient线脚,用與計(jì)數(shù)該ExchangeClient被多少Invoker使用赐稽,在銷毀的時(shí)候來判斷最后一個(gè)引用執(zhí)行destory的時(shí)候才真正close掉ExchangeClient。
1.2 其中調(diào)用Exchanges.connect時(shí)浑侥,如果url中沒有Constants.CODEC_KEY時(shí)姊舵,則指定Constants.CODEC_KEY=exchange。但是因?yàn)镈ubboProtocol.initClient時(shí)已經(jīng)指定了Constants.CODEC_KEY=dubbo寓落,所以最終的加解密方式為dubbo括丁,對(duì)應(yīng)DubboCodec。這里的說明是為了之后請(qǐng)求和返回值的加解密邏輯進(jìn)行說明零如,不然無法確定加解密邏輯躏将。參見:DubboProtocol.initClient锄弱。
1.3 創(chuàng)建ExchangerCliient邏輯:DubboProtocol.initClient中Exchanges.connect中有確定Exchanger的邏輯,默認(rèn)使用的是header祸憋,對(duì)應(yīng)HeaderExchangeClient会宪。handler參數(shù)是DubboProtocol.requestHandler, 包裝成HeaderExchangeHandler后又被包裝成DecodeHandler(類型為ChannelHandler)粗恢。后續(xù)在Transporter.connect創(chuàng)建client時(shí)准验,又被自適應(yīng)的AllDispatcher.dispatch方法包裝成AllChannelHandler->HeartbeatHandler->MultiMessageHandler笑诅,參見:new NettyClient()疫稿。
1.3.1 在創(chuàng)建AllChannelHandler時(shí)损话,其父類WrappedChannelHandler會(huì)使用自適應(yīng)的ThreadPool來創(chuàng)建線程池敬惦,然后放進(jìn)自適應(yīng)的DataStore中(componentName=consumer,key=url.getPort())韧掩,該線程池在之后的new AbstractClient()時(shí)取出賦值給AbstractClient.executor后從DataStore移出象浑。線程名稱在new NettyClinet()中的wrapChannelHandler中執(zhí)行了線程名為:DubboClientHandler-ip:port-xxxx荷鼠,線程池類型默認(rèn)值被設(shè)置為cached句携。
1.3.2 創(chuàng)建HeaderExchangeClient時(shí),會(huì)創(chuàng)建維持心跳的定時(shí)任務(wù)放在HeaderExchangeClient.scheduled的靜態(tài)線程池中執(zhí)行允乐。線程名:dubbo-remoting-client-heartbeat-xxxx(核心線程數(shù)=2)
1.4 創(chuàng)建Client邏輯:HeaderExchanger.connect中會(huì)創(chuàng)建client矮嫉,先選擇自適應(yīng)的Transporter,默認(rèn)的是com.alibaba.dubbo.remoting.transport.netty.NettyTransporter牍疏。NettyTransporter中創(chuàng)建的是NettyClient蠢笋。
2. DubboInvoker
經(jīng)過Cluster層和Filter的處理邏輯之后(ClusterInvoker->filter->Invoker),請(qǐng)求來到了DubboInvoker鳞陨。AbstractInvoker.invoke處理了一些通用邏輯之后昨寞,交由DubboInvoker.doInvoke. 其中主要是處理三種調(diào)用方式:return=false,async=true以及正常的調(diào)用厦滤。
3. HeaderExchangeClient
接下來將從正常調(diào)用方式來分析援岩,從1.3可知,默認(rèn)使用的是HeaderExchangeClient馁害。其成員變量channel使用的是HeaderExchangeChannel窄俏,HeaderExchangeChannel中的channel是NettyClient。
HeaderExchangeClient.request
HeaderExchangeChannel.request:封裝Request碘菜,構(gòu)建DefaultFuture返回凹蜈。同步轉(zhuǎn)異步。DefaultFuture起了一個(gè)守護(hù)線程來處理超時(shí)的future忍啸。詳見:DefaultFuture
4. NettyClient
. 1.4中說過使用的client是NettyClient仰坦,現(xiàn)在看看NettyClient的創(chuàng)建過程:
new NettyClinet()的時(shí)候會(huì)執(zhí)行父類AbstractClient以及父類的父類AbstractEndpoint,基本的邏輯是:
4.1. 確定加解密codec的方式(參見:AbstractEndpoint.getChannelCodec)计雌,1.2中說過了codec使用的是DubboCodec悄晃。
4.2. 斷線重連的線程池DubboClientReconnectTimer-xxxx 靜態(tài)的線程池(核心線程數(shù)為2,定時(shí)檢查channel是否連接正常,不正常時(shí)重新連接創(chuàng)建新的channel)妈橄,所有繼承了AbstractClient的client共享(參見:AbstractClient.initConnectStatusCheckCommand)庶近,
4.3. doOpen中主要是初始化ClientBootstrap,學(xué)過netty的都基本能看懂眷蚓,設(shè)置一些連接參數(shù)及pipeline中加入ChannelHandler,分別是讀取時(shí)的解密的decoder鼻种,寫入時(shí)的加密的encoder,數(shù)據(jù)寫入和處理都會(huì)處理的handler-NettyHandler沙热。
4.4. doConnect中與url對(duì)應(yīng)的服務(wù)端建立channel叉钥,賦值給NettyClient.channel,細(xì)節(jié):NettyClient.channel被定義成volatile篙贸,因?yàn)檫B接斷開后重連會(huì)重新賦值投队。
4.5 此處還需要關(guān)注channelFactory的創(chuàng)建,具體參數(shù)理解參照:https://jianshu.com/p/eec2677651c0 中Netty3.x的線程模型爵川。
5. 加密和解密
下面就再看看DubboCodec的加密和解密的邏輯:
5.1 加密:詳細(xì)說明可以參見:http://www.reibang.com/p/99a0bc93eeb6
簡(jiǎn)單描述就是:
5.1.1 先確定序列化的方式敷鸦,還是通過Dubbo SPI的自適應(yīng),找到對(duì)應(yīng)的Serialization寝贡,此處默認(rèn)的是hessian2轧膘。
5.1.2 發(fā)送的數(shù)據(jù)結(jié)構(gòu)是header+body的形式,header中會(huì)保存body的長(zhǎng)度兔甘,序列化方式,及其他信息鳞滨。
header信息洞焙,長(zhǎng)度為16的byte數(shù)組,
頭兩個(gè)字節(jié)存魔數(shù)
第三個(gè)字節(jié):存是否是Request拯啦,是否是Event澡匪,是否是FLAG_TWOWAY(需要等待返回值),以及序列化的方式serialization.getContentTypeId (后五位褒链,也就是能支持32中序列化方式)
第四個(gè)字節(jié):從解密代碼來看唁情,是用來返回時(shí)存放狀態(tài)碼的。
從第五個(gè)字節(jié)開始甫匹,用8個(gè)字節(jié)存request的id(Long)
從第13個(gè)字節(jié)開始甸鸟,用四個(gè)字節(jié)將body的長(zhǎng)度(Integer)
將request對(duì)象序列化后寫入ChannelBuffer,還有個(gè)校驗(yàn)是檢查body長(zhǎng)度不能超過8M
Body:其中就包含客戶端jar包版本號(hào)兵迅,調(diào)用的服務(wù)名抢韭,調(diào)用服務(wù)的version,調(diào)用的方法名恍箭,參數(shù)類型刻恭,參數(shù)值,attachments扯夭。參見下圖代碼:參見:DubboCodec.encodeRequestData
5.2 解密:
解密的邏輯基本就是按加密的結(jié)構(gòu)來解析就行了鳍贾。
[圖片上傳中...(image-b73439-1629862540429-0)]
從header中解析出序列化的方式鞍匾,request的id,以及狀態(tài)碼骑科。
根據(jù)header中的序列化方式解析出返回值橡淑,就是反序列化過程。
其中重要的一點(diǎn)就是纵散,真正的反序列化的調(diào)用并不是在netty的worker線程梳码,而是在DubboCodec.decodeBody的時(shí)候,被包裝成DecodeableRpcResult返回了伍掀,并在7中的線程池中執(zhí)行DecodeHandler.received(Channel, message)時(shí)掰茶,才真正的執(zhí)行返回值的反序列化。
6. HeaderExchangeClient到加密邏輯
下面要整理出3中HeaderExchangeChannel.request是如何走到5.1的加密邏輯的:
3中說明了HeaderExchangeChannel中的channel是NettyClient蜜笤,NettyClient.send(Request)->AbstractClient(NettyClient).send(Request, sent)->NettyChannel.write(Message),至此就會(huì)走到NettyClient初始化時(shí)設(shè)置的channelPipeline了濒蒋,先執(zhí)行NettyHandler.writeRequested
NettyHandler.writeRequested
->OneToOneEncoder(NettyCodecAdapter).handleDownstream
->OneToOneEncoder(NettyCodecAdapter).doEncode
->NettyCodecAdapter$InternalEncoder.encode 之后請(qǐng)求就發(fā)出去了。
之后又處理dubbo自己的邏輯:看完好像是發(fā)送請(qǐng)求之后把兔,回寫一些數(shù)據(jù)
調(diào)用鏈:
NettyHandler.writeRequested
->AbstractPeer(NettyClient).sent(Channel, msg)
->AbstractChannelHandlerDelegate(MultiMessageHandler).sent(Channel, message)
->HeartBeatHandler.sent(Channel, message)
->WrappedChannelHandler(AllChannelHandler).sent(Channel, message)
->AbstractChannelHandlerDelegate(DecodeHandler).sent(Channel, message)
->HeaderExchangeHandler.sent(Channel, message)
->DubboProtocol.requestHandler.sent:空方法
HeartBeatHandler中增加了更新NettyChannel中的發(fā)送數(shù)據(jù)的時(shí)間戳邏輯
HeaderExchangeHandler中調(diào)用DefaultFuture.sent方法更HeaderExchangeChannel.request時(shí)創(chuàng)建的DefaultFuture中的發(fā)送請(qǐng)求時(shí)間沪伙。
7. 數(shù)據(jù)解密之后如何將結(jié)果返回給調(diào)用線程:
可以debug,也可以通過之前梳理的創(chuàng)建過程來查看調(diào)用鏈县好。
NettyHandler.messageReceived
->AbstractPeer(NettyClient).received(Channel, msg)
->MultiMessageHandler.received(Channel, msg)
->HeartBeatHandler.received(Channel, msg)
->AllChannelHandler.received(Channel, msg){用1.3.1中創(chuàng)建的線程池執(zhí)行ChannelEventRunnable任務(wù)}
->之后的流程只在線程池中執(zhí)行的:
DecodeHandler.received(Channel, message) :反序列化RpcResult
->HeaderExchangeHandler.received(Channel, msg)
->DefaultFuture.received(Channel, Response)
->DefaultFuture.doReceived(Response){設(shè)置結(jié)果围橡,喚醒調(diào)用線程,如果有callback缕贡,執(zhí)行callback邏輯}
->之后是調(diào)用線程邏輯:(Invoker->filter->ClusterInvoker)
DubboInvoker.doInvoke()
->Filter邏輯
->FailoverClusterInvoker.doInvoke(){處理重試邏輯}
->MockClusterInvoker.invoke(){之前說過的mock邏輯}
HeaderExchangeHandler.received:
HeaderExchangeHandler.handleResponse:
DefaultFuture.received: 可以看出Request和Response是通過Request.id來對(duì)應(yīng)起來的翁授,生成規(guī)則參見:com.alibaba.dubbo.remoting.exchange.Request.newId()
8. 部分Filter邏輯:
具體Cosumer端配置了哪些Filter,可以看platform-util的jar中META-INFO/dubbo/com.alibaba.dubbo.rpc.Filter和 dubbo的jar中META-INFO/dubbo/internal/com.alibaba.dubbo.rpc.Filter中的配置
ConsumerRecordFilter
ResponseConsumerFilter (cat打點(diǎn):Consumer.code和Consumer.success)
DubboAppContextFilter (dubboApplication參數(shù)放入attachment)
SentinelDubboConsumerFilter(sentinel 限流)
MonitorFilter: 如果url中帶了monitor參數(shù)晾咪,則上報(bào)調(diào)用和返回值信息
FutureFilter: 支持下面三種事件的觸發(fā)收擦, url中指定的onthrow.method,onreturn.method,oninvoke.method ,具體使用待確定
CatTransaction:PigeonCall的cat數(shù)據(jù)上報(bào),接口調(diào)用信息
ConsumerContextFilter:調(diào)用前初始化RpcContext中部分信息谍倦,調(diào)用后清理掉RpcContext中的attachments
com.yupaopao.platform.util.dubbo.filter.ExceptionFilter:對(duì)返回值類型是Response的調(diào)用塞赂,統(tǒng)一處理異常
參考知識(shí):
XxxWrapper裝飾類在dubbo中的實(shí)現(xiàn)方式:https://www.cnblogs.com/killbug/p/7341968.html
-
各層說明:(來自官網(wǎng))
config 配置層:對(duì)外配置接口,以 ServiceConfig, ReferenceConfig 為中心昼蛀,可以直接初始化配置類宴猾,也可以通過 spring 解析配置生成配置類
proxy 服務(wù)代理層:服務(wù)接口透明代理,生成服務(wù)的客戶端 Stub 和服務(wù)器端 Skeleton, 以 ServiceProxy 為中心叼旋,擴(kuò)展接口為 ProxyFactory
registry 注冊(cè)中心層:封裝服務(wù)地址的注冊(cè)與發(fā)現(xiàn)鳍置,以服務(wù) URL 為中心,擴(kuò)展接口為 RegistryFactory, Registry, RegistryService
cluster 路由層:封裝多個(gè)提供者的路由及負(fù)載均衡送淆,并橋接注冊(cè)中心税产,以 Invoker 為中心,擴(kuò)展接口為 Cluster, Directory, Router, LoadBalance
monitor 監(jiān)控層:RPC 調(diào)用次數(shù)和調(diào)用時(shí)間監(jiān)控,以 Statistics 為中心辟拷,擴(kuò)展接口為 MonitorFactory, Monitor, MonitorService
protocol 遠(yuǎn)程調(diào)用層:封裝 RPC 調(diào)用撞羽,以 Invocation, Result 為中心,擴(kuò)展接口為 Protocol, Invoker, Exporter
exchange 信息交換層:封裝請(qǐng)求響應(yīng)模式衫冻,同步轉(zhuǎn)異步诀紊,以 Request, Response 為中心,擴(kuò)展接口為 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer
transport 網(wǎng)絡(luò)傳輸層:抽象 mina 和 netty 為統(tǒng)一接口隅俘,以 Message 為中心邻奠,擴(kuò)展接口為 Channel, Transporter, Client, Server, Codec
serialize 數(shù)據(jù)序列化層:可復(fù)用的一些工具,擴(kuò)展接口為 Serialization, ObjectInput, ObjectOutput, ThreadPool
-
2.6.2 Dubbo線程模型:
參見: