本文主要聊下dubbo的服務(wù)導(dǎo)出部分,即服務(wù)暴露,服務(wù)導(dǎo)出的核心接口是 Protocol 的 export方法泪幌;暴露的方式可以有很多種(tcp/http/rmi/webservice等)惊豺,也可以同時(shí)暴露多種方式,dubbo 基于接口 SPI 的擴(kuò)展非常靈活坚俗,完全看協(xié)議本身實(shí)現(xiàn)笨鸡;
服務(wù)端接口聲明及實(shí)現(xiàn)如下:
public interface DemoService {
String sayHello(String name);
}
public class DemoServiceImpl implements DemoService {
@Override
public String sayHello(String name) {
return "Hello " + name + ", response from provider: " + RpcContext.getContext().getLocalAddress();
}
}
public class Provider {
private static void main(String[] args) {
DemoService demoService = new DemoServiceImpl();
ApplicationConfig application = new ApplicationConfig();
application.setName("demo");
RegistryConfig registry = new RegistryConfig();
registry.setAddress("127.0.0.1:2181");
registry.setProtocol("zookeeper");
ProtocolConfig protocol = new ProtocolConfig();
protocol.setName("dubbo");
protocol.setPort(20880);
protocol.setServer("netty4");
ServiceConfig<DemoService> service = new ServiceConfig<DemoService>();
service.setApplication(application);
service.setRegistry(registry);
service.setProtocol(protocol);
service.setInterface(DemoService.class);
service.setRef(demoService);
service.export();
System.in.read();
}
}
由于 dubbo 最近在做比較大的版本變遷,但是其核心接口并沒變坦冠,所以這里以 2.6.x的版本為例講解形耗,由于我們選用的 server 實(shí)現(xiàn)是netty4,所以我們?cè)?netty 的 AbstractBootstrap.bind 方法加入一個(gè)斷點(diǎn)辙浑,得到的調(diào)用棧如下:
25. bind:264, AbstractBootstrap (io.netty.bootstrap)
24. doOpen:119, NettyServer (com.alibaba.dubbo.remoting.transport.netty4)
23. <init>:88, AbstractServer (com.alibaba.dubbo.remoting.transport)
22. <init>:81, NettyServer (com.alibaba.dubbo.remoting.transport.netty4)
21. bind:33, NettyTransporter (com.alibaba.dubbo.remoting.transport.netty4)
20. bind:-1, Transporter$Adaptive (com.alibaba.dubbo.remoting)
19. bind:60, Transporters (com.alibaba.dubbo.remoting)
18. bind:46, HeaderExchanger (com.alibaba.dubbo.remoting.exchange.support.header)
17. bind:72, Exchangers (com.alibaba.dubbo.remoting.exchange)
16. createServer:430, DubboProtocol (com.alibaba.dubbo.rpc.protocol.dubbo)
15. openServer:393, DubboProtocol (com.alibaba.dubbo.rpc.protocol.dubbo)
14. export:371, DubboProtocol (com.alibaba.dubbo.rpc.protocol.dubbo)
13. export:123, ProtocolFilterWrapper (com.alibaba.dubbo.rpc.protocol)
12. export:59, ProtocolListenerWrapper (com.alibaba.dubbo.rpc.protocol)
11. export:-1, Protocol$Adaptive (com.alibaba.dubbo.rpc)
10. doLocalExport:172, RegistryProtocol (com.alibaba.dubbo.registry.integration)
9.4 - doRegister:140, ZookeeperRegistry (com.alibaba.dubbo.registry.zookeeper)
9.3 - register:150, FailbackRegistry (com.alibaba.dubbo.registry.support)
9.2 - register:129, RegistryProtocol (com.alibaba.dubbo.registry.integration)
9. export:135, RegistryProtocol (com.alibaba.dubbo.registry.integration)
8. export:120, ProtocolFilterWrapper (com.alibaba.dubbo.rpc.protocol)
7. export:56, ProtocolListenerWrapper (com.alibaba.dubbo.rpc.protocol)
6. export:-1, Protocol$Adaptive (com.alibaba.dubbo.rpc)
5. doExportUrlsFor1Protocol:514, ServiceConfig (com.alibaba.dubbo.config)
4. doExportUrls:359, ServiceConfig (com.alibaba.dubbo.config)
3. doExport:318, ServiceConfig (com.alibaba.dubbo.config)
2. export:216, ServiceConfig (com.alibaba.dubbo.config)
1. main:, Provider (com.alibaba.dubbo.demo.provider)
服務(wù)暴露主要分兩個(gè)步驟激涤,一個(gè)是本地服務(wù)暴露,一個(gè)是服務(wù)注冊(cè)判呕,上面的調(diào)用棧主要體現(xiàn)的是本地暴露倦踢,支線 9.2 是服務(wù)注冊(cè)邏輯,下面從棧底依次向上進(jìn)行拆解:
1侠草、 入口函數(shù)main函數(shù)
2-3辱挥、 ServiceConfig 是服務(wù)配置類,承擔(dān)配置的解析边涕、校驗(yàn)晤碘、組裝及服務(wù)暴露的調(diào)用入口,防止重復(fù)導(dǎo)出等
4功蜓、 ServiceConfig.doExportUrls 如果是多協(xié)議則循環(huán)導(dǎo)出
5园爷、 doExportUrlsFor1Protocol 顧名思義,是對(duì)一個(gè)協(xié)議進(jìn)行暴露導(dǎo)出式撼,這里有很多的代碼是對(duì) URL 及其參數(shù)部分進(jìn)了組裝童社,然后還會(huì)進(jìn)行 injvm 的導(dǎo)出,然后進(jìn)行 ProtocolProtocol 導(dǎo)出著隆,這里看起來可能會(huì)有些疑惑扰楼,為什么都是用 protocol.export 進(jìn)行導(dǎo)出的,但是導(dǎo)出的結(jié)果卻不一樣美浦,這里就是 SPI 自適應(yīng)的能力了弦赖,具體來書就是 protocol.export 會(huì)根據(jù)傳入?yún)?shù)的 url 進(jìn)行查找合適的實(shí)現(xiàn)類進(jìn)行 export;本地導(dǎo)出的時(shí)候?qū)?url的protocol設(shè)置為 injvm抵代,注冊(cè)中心導(dǎo)出的時(shí)候?qū)rotocol設(shè)置為 registry就會(huì)分別找到各自的實(shí)現(xiàn)類進(jìn)行導(dǎo)出腾节,這里牽涉到 SPI 的自適應(yīng) @Adaptive 注解的實(shí)現(xiàn),就不展開講了;
6案腺、 Protocol$Adaptive 就是 SPI 自適應(yīng)的實(shí)現(xiàn)庆冕,它是采用代碼生成的方式實(shí)現(xiàn)的,所以這里看不到源碼
7劈榨、 ProtocolListenerWrapper 是 Protocol 的 SPI Wrapper 增強(qiáng)類访递,他會(huì)被自動(dòng)裝飾在 Protocol 實(shí)現(xiàn)類上,這還是 SPI 的功能同辣;在這里因?yàn)槭亲?cè)中心導(dǎo)出邏輯會(huì)直接跳過過濾器鏈的組裝邏輯拷姿,可以查看 ProtocolListenerWrapper.export 方法
8、 ProtocolFilterWrapper 也是 Protocol 的 SPI Wrapper 增強(qiáng)類旱函,他的主要作用是創(chuàng)建過濾器鏈响巢,如果你看過客戶端調(diào)用的那篇文章,這里類也出現(xiàn)過棒妨,總的來說就是為服務(wù)端及客戶端構(gòu)建過濾器鏈踪古;在這里因?yàn)槭亲?cè)中心導(dǎo)出邏輯會(huì)直接跳過過濾器鏈的組裝邏輯,詳情可查看 ProtocolFilterWrapper.export 方法
9券腔、 RegistryProtocol 是注冊(cè)中心導(dǎo)出核心類伏穆, 內(nèi)部封裝了注冊(cè)服務(wù)的通用邏輯,內(nèi)部通過相應(yīng)的 registry 接口適配不同的注冊(cè)中心纷纫;9.2 - 9.4 是注冊(cè)服務(wù)的邏輯枕扫;
10、 RegistryProtocol.doLocalExport 這個(gè)方法有點(diǎn)眼熟辱魁,在 ServiceConfig 中也有一個(gè) exportLocal 的方法是用來導(dǎo)出 injvm Protocol的烟瞧,但是 RegistryProtocol.doLocalExport 的作用是用來導(dǎo)出真實(shí)的應(yīng)用協(xié)議的在這里即是 DubboProtocol
11-13、 又是熟悉的一套 SPI 自適應(yīng)導(dǎo)出協(xié)議商叹,這里url的protocol已經(jīng)是 dubbo 了燕刻,ProtocolListenerWrapper和ProtocolFilterWrapper分別進(jìn)行增強(qiáng)加入監(jiān)聽和過濾器鏈,這里會(huì)進(jìn)行構(gòu)造過濾器鏈的邏輯
14剖笙、 進(jìn)行 DubboProtocol 的導(dǎo)出邏輯,將 Invoker 及過濾器組成的鏈表頭包裝成一個(gè) exporter 進(jìn)行緩存请唱,等真實(shí)業(yè)務(wù)網(wǎng)絡(luò)請(qǐng)求到來時(shí)弥咪,可以在緩存中找到真實(shí)的 Invoker 鏈進(jìn)行調(diào)用
15、 DubboProtocol.openServer 開始暴露網(wǎng)絡(luò)接口十绑,即網(wǎng)絡(luò)監(jiān)聽聚至,由于端口只能監(jiān)聽一次,如果有多個(gè)接口暴露在一個(gè)端口上這里會(huì)進(jìn)行端口復(fù)用本橙,即監(jiān)聽完成后緩存起來扳躬,下次判斷 host:port 是否已緩存監(jiān)聽,如果已監(jiān)聽則復(fù)用
16、 DubboProtocol.createServer 創(chuàng)建server監(jiān)聽邏輯贷币,最終調(diào)用 Exchangers.bind 開啟一個(gè)網(wǎng)絡(luò)端口击胜;值得一提的是 DubboProtocol 中創(chuàng)建了一個(gè) ExchangeHandlerAdapter 類型的 requestHandler 來進(jìn)行網(wǎng)絡(luò)請(qǐng)求處理,他的主要作用是將網(wǎng)絡(luò)請(qǐng)求參數(shù)到 exporter 緩存中找到對(duì)應(yīng)的 Invoker 進(jìn)行調(diào)用然后返回役纹;
17-18偶摔、 Exchangers 是 Exchanger 的工廠來類,通過 SPI 找到 Exchanger 的實(shí)現(xiàn)類 HeaderExchanger 進(jìn)行具體實(shí)現(xiàn)
19-21促脉、 Transporters 是 Transporter 的工廠類辰斋,通過 SPI 找到 Transporter 的實(shí)現(xiàn)類 NettyTransporter 進(jìn)行端口監(jiān)聽,細(xì)心的同學(xué)可能發(fā)現(xiàn)了 這里有一個(gè) Transporter$Adaptive.bind 而 Exchanger 沒有瘸味,這是因?yàn)樗麄儷@取實(shí)現(xiàn)類時(shí)的調(diào)用方式不太一樣宫仗,個(gè)人認(rèn)為更應(yīng)該使用 Adaptive 類進(jìn)行操作,也可能是 Exchanger 的用處比較少旁仿,擴(kuò)展的可能性不大吧锰什,其實(shí)現(xiàn)類默認(rèn)也只有一個(gè),而 Transporter 的實(shí)現(xiàn)類就多了丁逝,擴(kuò)展的可能性也非常大
22-25汁胆、 調(diào)用最終的 nettyServer 實(shí)現(xiàn)進(jìn)行端口監(jiān)聽;