我們以dubbo 的xml配置為例:
dubbo服務發布只需在spring.xml中如下配置即可:
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" />
通過dubbo于spring的融合可以了解到<dubbo:service>標簽是通過ServiceBean解析封裝。
ServiceBean這個類繼承 了ServiceConfig實現了spring的5個接口
InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware 來融入到spring的啟動過程。
ServiceBean實現了ApplicationListener接口,當spring容器觸發了ContextRefreshedEvent事件時,
就會調用ServiceConfig中的export()方法發布申明的dubbo服務,
ServiceConfig中的export()方法部分源碼如下,如果申明了delay(延遲多少),那么延遲調用doExport()發布這個服務,如果沒有設置則直接調用doExport()發布服務:
接下來看ServiceConfig的doExport()方法
1,檢查中是否配置了interface, 如果為空,那么拋出異常:
if (interfaceName == null || interfaceName.length() == 0) { throw new IllegalStateException("interface not allow null!");}
2,檢查接口類型必需為接口
if(! interfaceClass.isInterface()) {
throw new IllegalStateException("The interface class " + interfaceClass + " is not a interface!");
}
3,檢查方法是否在接口中存在
4,檢查引用不為空,并且引用必需實現接口 interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"
5,檢查checkApplication(); checkRegistry(); checkProtocol();有效性。
6,調用ServiceConfig.doExportUrls()發布dubbo服務
ServiceConfig.doExportUrls()如下:
通過調用loadRegistries(true)得到所有registry的url地址,例如配置了
<dubbo:registry address="zookeeper://127.0.0.1:2181">
配置結果為dubbo.registry.address=zookeeper://127.0.0.1:2181;
protocols就是將要發布服務的協議集合(dubbo服務可以同時暴露多種協議),例如配置了
<dubbo:protocol name="dubbo" port="20880">
dubbo.protocol.name=dubbo ,? dubbo.protocol.port=20880
ServiceConfig.doExportUrlsFor1Protocol()
先把application、module、provider、protocol、exporter、registries、monitor所有屬性封裝到Map中例如protocol=dubbo,host=10.0.0.1,port=20880,path=com.alibaba.dubbo.demo.TestService等,然后構造dubbo定義的統一數據模型URL:
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
這個url非常重要,貫穿整個dubbo服務的發布和調用過程,可以在服務發布后在dubbo-monitor中看到;
ServiceConfig.doExportUrlsFor1Protocol()中根據scope判斷服務的發布范圍:
如果配置scope = none, 那么不需要發布這個dubbo服務;
沒有配置scope = none,且配置的scope != remote, 那么本地暴露 這個dubbo服務;
沒有配置scope = none,且配置的scope != remote且配置的scope != local,那么遠程暴露這個dubbo服務(例如遠程暴露這個服務到zk上,默認情況下scope沒有配置,就是在這里發布服務);
以上如果執行成功,會把dubbo服務到zookeeper上,invoker.getUrl()的值為
registry://10.0.53.87:2188/com.alibaba.dubbo.registry.RegistryService?application=dubbo-test&dubbo=2.0.0&export=dubbo%3A%2F%2F10.52.16.218%3A20886%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddubbo-test%26dubbo%3D2.0.0%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26loadbalance%3Droundrobin%26methods%3DsayHello%26owner%3Dafei%26pid%3D2380%26side%3Dprovider%26timestamp%3D1509953019382&owner=afei&pid=2380?istry=zookeeper×tamp=150995301934:
接下來我們分析
Protocol.export()暴露服務接口:
然后調用RegistryProtocol.export():
核心調用registry.register(registedProviderUrl)。
調用AbstractRegistry.register(URL),把這次需要注冊的URL加到Set registered中,即本地緩存新的注冊URL;
在ZookeeperRegistry.doRegister(URL)調用AbstractZookeeperClient.create(),toUrlPath將URL形式的地址轉換成zookeeper路徑,最終在AbstractZookeeperClient中把需要發布的服務的URL保存到zookeeper:
ZookeeperRegistry.doRegister(url)注冊服務如果失敗:
如果開啟了啟動檢查check=true,那么直接拋出異常;
如果沒有開啟啟動檢查,那么將失敗的注冊請求記錄到失敗列表,定時重試;
核心調用registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener):
對發布的dubbo服務的這個url進行監聽, 當服務變化有時通知重新暴露服務, 以zookeeper為例,暴露服務會在zookeeper生成一個節點,當節點發生變化的時候會觸發overrideSubscribeListener的notify方法重新暴露服務
注冊服務失敗的重試機制:
注冊服務失敗后,會將url加入重試url集合中,failedRegistered.add(url);重試任務在FailbackRegistry中實現:
注冊的監聽機制:
訂閱并設置監聽registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
--> FailbackRegistry.subscribe(URL url, NotifyListener listener)
--> ZookeeperRegistry.doSubscribe(final URL url, final NotifyListener listener),部分實現源碼如下:
當服務有變化的時候:
doNotify(url, listener, urls);
AbstractRegistry.notify(URL url, NotifyListener listener, List urls)
--> RegistryDirectory.notify(List urls)
--> RegistryDirectory.refreshInvoker(List invokerUrls),這里調用toMethodInvokers(Map> invokersMap)的實現比較重要,將invokers列表轉成與方法的映射關系,且每個方法對應的List需要通過Collections.sort(methodInvokers, InvokerComparator.getComparator());排序,然后,還要將其轉為unmodifiable的map
其中InvokerComparator的定義如下,即直接根據url進行比較排序
dubbo協議發布服務會調用DubboProtocol.export()的過程:
從Invoker中獲取URL: URL url = invoker.getUrl();
根據URL得到key, 由暴露的服務接口+端口組成,例如com.alibaba.dubbo.demo.DemoService:20886 ;? String key = serviceKey(url);
構造DubboExporter存到Map中local cache化:
DubboExporter exporter = new DubboExporter(invoker, key, exporterMap); exporterMap.put(key, exporter);
調用DubboProtocol.openServer()開啟netty(默認)服務保持通信,并設置requestHandler處理consumer對provider的調用請求;
DubboProtocol.openServer():
key的值就是IP:Port,例如10.52.17.167:20886,根據key從serverMap中如果取不到ExchangeServer,表示還沒綁定服務端口,需要調用createServer(url)-->Exchangers.bind(url, requestHandler)-->Transporters.getTransporter().bind(url, handler)(dubbo支持mina,netty,grizzly,默認實現是netty) --> NettyTransporter.bind(URL, ChannelHandler) --> NettyServer.open();
dubbo默認調用的是netty
Netty服務幾個重要的地方
構造ChannelPipeline時指定了編碼&解碼,其中編碼為NettyCodecAdapter.getEncoder(),解碼為NettyCodecAdapter.getDncoder();
指定了handler為final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);處理請求;