前言
在前面的 SOFA 源碼分析 —— 服務(wù)發(fā)布過(guò)程 文章中,我們分析了 SOFA 的服務(wù)發(fā)布過(guò)程,一個(gè)完整的 RPC 除了發(fā)布服務(wù),當(dāng)然還需要引用服務(wù)麦轰。 So,今天就一起來(lái)看看 SOFA 是如何引用服務(wù)的期虾。實(shí)際上原朝,基礎(chǔ)邏輯和我們之前用 Netty 寫(xiě)的 RPC 小 demo 類(lèi)似。有興趣可以看看這個(gè) demo—— 自己用 Netty 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 RPC镶苞。
示例代碼
ConsumerConfig<HelloService> consumerConfig = new ConsumerConfig<HelloService>()
.setInterfaceId(HelloService.class.getName()) // 指定接口
.setProtocol("bolt") // 指定協(xié)議
.setDirectUrl("bolt://127.0.0.1:9696"); // 指定直連地址
HelloService helloService = consumerConfig.refer();
while (true) {
System.out.println(helloService.sayHello("world"));
try {
Thread.sleep(2000);
} catch (Exception e) {
}
}
同樣的喳坠,代碼例子來(lái)自 SOFA-RPC 源碼,位于com.alipay.sofa.rpc.quickstart.QuickStartClient
茂蚓。
很簡(jiǎn)單壕鹉,創(chuàng)建一個(gè)消費(fèi)者配置類(lèi),然后使用這個(gè)配置引用一個(gè)代理對(duì)象聋涨,調(diào)用
代理對(duì)象的方法晾浴,實(shí)際上就是調(diào)用了遠(yuǎn)程服務(wù)。
我們就通過(guò)上面這個(gè)簡(jiǎn)單的例子牍白,看看 SOFA 是如何進(jìn)行服務(wù)引用的脊凰。注意:我們今天的目的是看看主流程,有些細(xì)節(jié)可能暫時(shí)就一帶而過(guò)了,比如負(fù)載均衡狸涌,錯(cuò)誤重試啥的切省,我們以后再詳細(xì)分析,實(shí)際上帕胆,Client 相對(duì)有 Server朝捆,還是要復(fù)雜一些的,因?yàn)樗紤]更多的情況懒豹。
好芙盘,開(kāi)始吧!
源碼分析
首先看這個(gè) ConsumerConfig 類(lèi)脸秽,和前面的 ProviderConfig 類(lèi)似儒老,甚至于實(shí)現(xiàn)的接口和繼承的抽象類(lèi)都是一樣的。
上面的例子設(shè)置了一些屬性记餐,比如接口名稱(chēng)贷盲,協(xié)議,直連地址剥扣。
關(guān)鍵點(diǎn)來(lái)了, refer 方法铝穷。
這個(gè)方法就是返回了一個(gè)代理對(duì)象钠怯,代理對(duì)象中包含了之后遠(yuǎn)程調(diào)用中需要的所有信息,比如過(guò)濾器曙聂,負(fù)載均衡等等晦炊。
然后,調(diào)用動(dòng)態(tài)代理的方法宁脊,進(jìn)行遠(yuǎn)程調(diào)用断国,如果是 JDK 的動(dòng)態(tài)代理的話,就是一個(gè)實(shí)現(xiàn)了 InvocationHandler 接口的類(lèi)榆苞。這個(gè)類(lèi)的 invoke 方法會(huì)攔截并進(jìn)行遠(yuǎn)程調(diào)用稳衬,自然就是使用 Netty 的客戶(hù)端對(duì)服務(wù)端發(fā)起調(diào)用并得到數(shù)據(jù)啦。
先看看 refer 方法坐漏。
從 refer 方法開(kāi)始源碼分析
該方法類(lèi)套路和 provider 的套路類(lèi)似薄疚,都是使用了一個(gè) BootStrap 引導(dǎo)。即單一職責(zé)赊琳。
public T refer() {
if (consumerBootstrap == null) {
consumerBootstrap = Bootstraps.from(this);
}
return consumerBootstrap.refer();
}
ConsumerBootstrap 是個(gè)抽象類(lèi)街夭,SOFA 基于他進(jìn)行擴(kuò)展,目前有 2 個(gè)擴(kuò)展點(diǎn)躏筏,bolt 和 rest板丽。默認(rèn)是 bolt。而 bolt 的實(shí)現(xiàn)則是 BoltConsumerBootstrap趁尼,目前來(lái)看 bolt 和 rest 并沒(méi)有什么區(qū)別埃碱,都是使用的一個(gè)父類(lèi) DefaultConsumerBootstrap猖辫。
所以,來(lái)看看 DefaultConsumerBootstrap 的 refer 方法乃正。代碼我就不貼了住册,因?yàn)楹荛L(zhǎng)∥途撸基本上引用服務(wù)的邏輯全部在這里了荧飞,類(lèi)似 Spring 的 refresh 方法。邏輯如下:
- 根據(jù) ConsumerConfig 創(chuàng)建 key 和 appName名党。檢查參數(shù)合法性叹阔。對(duì)調(diào)用進(jìn)行計(jì)數(shù)。
- 創(chuàng)建一個(gè) Cluster 對(duì)象传睹,這個(gè)對(duì)象非常重要耳幢,該對(duì)象管理著核心部分的信息。詳細(xì)的后面會(huì)說(shuō)欧啤,而構(gòu)造該對(duì)象的參數(shù)則是 BootStrap睛藻。
- 設(shè)置一些監(jiān)聽(tīng)器。
- 初始化 Cluster邢隧。其中包括設(shè)置路由店印,地址初始化,連接管理倒慧,過(guò)濾器鏈構(gòu)造按摘,重連線程等。
- 創(chuàng)建一個(gè) proxyInvoker 執(zhí)行對(duì)象纫谅,也就是初始調(diào)用對(duì)象炫贤,作用是注入到動(dòng)態(tài)代理的攔截類(lèi)中,以便動(dòng)態(tài)代理從此處開(kāi)始調(diào)用付秕。構(gòu)造參數(shù)也是 BootStrap兰珍。
- 最后,創(chuàng)建一個(gè)動(dòng)態(tài)代理對(duì)象盹牧,目前動(dòng)態(tài)代理有 2 個(gè)擴(kuò)展點(diǎn)俩垃,分別是 JDK,javassist汰寓。默認(rèn)是 JDK口柳,但似乎 javassist 的性能會(huì)更好一點(diǎn)。如果是 JDK 的話有滑,攔截器則是 JDKInvocationHandler 類(lèi)跃闹,構(gòu)造方法需要代理類(lèi)和剛剛創(chuàng)建的 proxyInvoker 對(duì)象。proxyInvoker 的作用就是從這里開(kāi)始鏈?zhǔn)秸{(diào)用。
其中望艺,關(guān)鍵的對(duì)象是 Cluster苛秕。該對(duì)象需要重點(diǎn)關(guān)注。
Cluster 是個(gè)抽象類(lèi)找默,也是個(gè)擴(kuò)展點(diǎn)艇劫,實(shí)現(xiàn)了 Invoker, ProviderInfoListener, Initializable, Destroyable 等接口。而他目前的具體擴(kuò)展點(diǎn)有 2 個(gè): FailFastCluster(快速失敵图ぁ)店煞, FailoverCluster(故障轉(zhuǎn)移和重試)。默認(rèn)是后者风钻。當(dāng)然顷蟀,還有一個(gè)抽象父類(lèi),AbstractCluster骡技。
該類(lèi)有個(gè)重要的方法鸣个, init 方法,初始化 Cluster布朦,Cluster 可以理解成客戶(hù)端囤萤,封裝了集群模式、長(zhǎng)連接管理是趴、服務(wù)路由阁将、負(fù)載均衡等抽象類(lèi)。
init 方法代碼如下右遭,不多。
public synchronized void init() {
if (initialized) { // 已初始化
return;
}
// 構(gòu)造Router鏈
routerChain = RouterChain.buildConsumerChain(consumerBootstrap);
// 負(fù)載均衡策略 考慮是否可動(dòng)態(tài)替換缤削?
loadBalancer = LoadBalancerFactory.getLoadBalancer(consumerBootstrap);
// 地址管理器
addressHolder = AddressHolderFactory.getAddressHolder(consumerBootstrap);
// 連接管理器
connectionHolder = ConnectionHolderFactory.getConnectionHolder(consumerBootstrap);
// 構(gòu)造Filter鏈,最底層是調(diào)用過(guò)濾器
this.filterChain = FilterChain.buildConsumerChain(this.consumerConfig,
new ConsumerInvoker(consumerBootstrap));
// 啟動(dòng)重連線程
connectionHolder.init();
// 得到服務(wù)端列表
List<ProviderGroup> all = consumerBootstrap.subscribe();
if (CommonUtils.isNotEmpty(all)) {
// 初始化服務(wù)端連接(建立長(zhǎng)連接)
updateAllProviders(all);
}
// 啟動(dòng)成功
initialized = true;
// 如果check=true表示強(qiáng)依賴(lài)
if (consumerConfig.isCheck() && !isAvailable()) {
}
}
可以看到窘哈,干的活很多。每一步都值得花很多時(shí)間去看亭敢,但看完所有不是我們今天的任務(wù)滚婉,我們今天關(guān)注一下調(diào)用,上面的代碼中帅刀,有一段構(gòu)建 FilterChain 的代碼是值得我們今天注意的让腹。
創(chuàng)建了一個(gè) ConsumerInvoker 對(duì)象,作為最后一個(gè)過(guò)濾器調(diào)用扣溺,關(guān)于過(guò)濾器的設(shè)計(jì)骇窍,我們之前已經(jīng)研究過(guò)了,不再贅述锥余,詳情 SOFA 源碼分析 —— 過(guò)濾器設(shè)計(jì)腹纳。
我們主要看看 ConsumerInvoker 類(lèi),該類(lèi)是離 Netty 最近的過(guò)濾器。實(shí)際上嘲恍,他也是擁有了一個(gè) BootStrap足画,但,注意佃牛,擁有了 BootStrap 淹辞,相當(dāng)于挾天子以令諸侯,啥都有了俘侠,在他的 invoke 方法中象缀,會(huì)直接獲取 Boostrap 的 Cluster 向 Netty 發(fā)送數(shù)據(jù)。
代碼如下:
return consumerBootstrap.getCluster().sendMsg(providerInfo, sofaRequest);
厲害吧兼贡。
那么攻冷,Cluster 是如何進(jìn)行 sendMsg 的呢?如果是 bolt 類(lèi)型的 Cluster 的話遍希,就直接使用 bolt 的 RpcClient 進(jìn)行調(diào)用了等曼,而 RpcClient 則是使用的 Netty 的 Channel 的 writeAndFlush 方法發(fā)送數(shù)據(jù)。如果是同步調(diào)用的話凿蒜,就阻塞等待數(shù)據(jù)禁谦。
總的流程就是這樣,具體細(xì)節(jié)留到以后慢慢分析废封。
下面看看拿到動(dòng)態(tài)代理對(duì)象后州泊,如何進(jìn)行調(diào)用。
動(dòng)態(tài)代理如何調(diào)用漂洋?
當(dāng)我們調(diào)用的時(shí)候遥皂,肯定會(huì)被 JDKInvocationHandler 攔截。攔截方法則是 invoke 方法刽漂。方法很簡(jiǎn)單演训,主要就是使用我們之前注入的 proxyInvoker 的 invoke 方法。我們之前說(shuō)了贝咙,proxyInvoker 的作用其實(shí)就是一個(gè)鏈表的頭样悟。而他主要了代理了真正的主角 Cluster,所以庭猩,你可以想到窟她,他的 invoke 方法肯定是調(diào)用了 Cluster 的 invoke 方法。
Cluster 是真正的主角(注意:默認(rèn)的 Cluster 是 FailoverCluster)蔼水,那么他的調(diào)用肯定是一連串的過(guò)濾器震糖。目前默認(rèn)有兩個(gè)過(guò)濾器:ConsumerExceptionFilter, RpcReferenceContextFilter。最后一個(gè)過(guò)濾器則是我們之前說(shuō)的趴腋,離 Netty 最近的過(guò)濾器 —— ConsumerInvoker试伙。
ConsumerInvoker 會(huì)調(diào)用 Cluster 的 sendMsg 方法嘁信,Cluster 內(nèi)部包含一個(gè) ClientTransport ,這個(gè) ClientTransport 就是個(gè)膠水類(lèi)疏叨,融合 bolt 的 RpcClient潘靖。所以,你可以想到蚤蔓,當(dāng) ConsumerInvoker 調(diào)用 sendMsg 方法的時(shí)候卦溢,最后會(huì)調(diào)用 RpcClient 的 invokeXXX 方法,可能是異步秀又,也可能是同步的单寂,bolt 支持多種調(diào)用方式。
而 RpcClient 最后則是調(diào)用 Netty 的 Channel 的 writeAndFlush 方法向服務(wù)提供者發(fā)送數(shù)據(jù)吐辙。
取一段 RpcClietn 中同步(默認(rèn))執(zhí)行的代碼看看:
protected RemotingCommand invokeSync(final Connection conn, final RemotingCommand request,
final int timeoutMillis) throws RemotingException,
InterruptedException {
final InvokeFuture future = createInvokeFuture(request, request.getInvokeContext());
conn.addInvokeFuture(future);
conn.getChannel().writeAndFlush(request).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture f) throws Exception {
if (!f.isSuccess()) {
conn.removeInvokeFuture(request.getId());
future.putResponse(commandFactory.createSendFailedResponse(
conn.getRemoteAddress(), f.cause()));
}
}
});
// 阻塞等待
RemotingCommand response = future.waitResponse(timeoutMillis);
return response;
}
通過(guò) Netty 的 Channel 的 writeAndFlush 方法發(fā)送數(shù)據(jù)宣决,并添加一個(gè)監(jiān)聽(tīng)器,如果失敗了昏苏,就向 future 中注入一個(gè)失敗的對(duì)象尊沸。
在異步執(zhí)行后,主線程開(kāi)始等待贤惯,內(nèi)部使用 countDownLatch 進(jìn)行阻塞洼专。而 countDownLatch 的初始化參數(shù)為 1。什么時(shí)候喚醒 countDownLatch 呢孵构?
在 putResponse 方法中屁商,會(huì)喚醒 countDownLatch。
而 putResponse 方法則會(huì)被很多地方使用颈墅。比如在 bolt 的 RpcResponseProcessor 的 doProcess 方法中就會(huì)調(diào)用蜡镶。而這個(gè)方法則是在 RpcHandler 的 channelRead 方法中間接調(diào)用。
所以恤筛,如果 writeAndFlush 失敗了帽哑,會(huì) putResponse ,沒(méi)有失敗的話叹俏,正常執(zhí)行,則會(huì)在 channelRead 方法后簡(jiǎn)介調(diào)用 putResponse.
總結(jié)一下調(diào)用的邏輯吧僻族,樓主畫(huà)了一張圖粘驰,大家可以看看,畫(huà)的不好述么,還請(qǐng)見(jiàn)諒蝌数。
紅色線條是調(diào)用鏈,從 JDKInvocationHandler 開(kāi)始度秘,直到 Netty顶伞。綠色部分是 Cluster饵撑,和 Client 的核心。大紅色部分是 bolt 和 Netty唆貌。
好了滑潘,關(guān)于 SOFA 的服務(wù)引用主流程我們今天就差不多介紹完了,當(dāng)然锨咙,有很多精華還沒(méi)有研究到语卤。我們以后會(huì)慢慢的去研究。
總結(jié)
看完了 SOFA 的服務(wù)發(fā)布和服務(wù)引用酪刀,相比較 SpringCloud 而言粹舵,真心覺(jué)得很輕量。上面的一幅圖基本就能展示大部分調(diào)用過(guò)程骂倘,這在 Spring 和 Tomcat 這種框架中眼滤,是不可想象的。而 bolt 的隔離也讓 RPC 框架有了更多的選擇历涝,通過(guò)不同的擴(kuò)展點(diǎn)實(shí)現(xiàn)诅需,你可以使用不同的網(wǎng)絡(luò)通信框架。這時(shí)候睬关,有必要上一張 SOFA 官方的圖了:
從上圖可以看出诱担,我們今天比較熟悉的地方,比如 Cluster电爹,包含了過(guò)濾器蔫仙,負(fù)載均衡,路由丐箩,然后調(diào)用 remoting 的遠(yuǎn)程模塊摇邦,也就是 bolt。 通過(guò) sendMsg 方法屎勘。
而 Cluster 的外部模塊施籍,我們今天就沒(méi)有仔細(xì)看了,這個(gè)肯定是要留到今后看的概漱。比如地址管理丑慎,連接管理等等。
好啦瓤摧,今天就到這里竿裂。如有不對(duì)之處,還請(qǐng)指正照弥。