服務暴露文檔地址html
服務發佈-原理
發佈步驟:java
1. 暴露本地服務 2. 暴露遠程服務 3. 啓動netty 4. 打開鏈接zookeeper 5. 到zk註冊 6. 監聽zk
暴露本地服務和暴露遠程服務有什麼區別?apache
zk持久化節點 和 臨時節點有什麼區別?
持久化節點: 一旦被建立,除非主動刪除,不然一直存儲在zk裏面
臨時節點:與客戶端會話綁定,一旦客戶端會話失效,這個客戶端所建立的全部臨時節點都會被刪除bootstrap
Dubbo 服務導出過程始於 Spring 容器發佈刷新事件,Dubbo 在接收到事件後,會當即執行服務導出邏輯。整個邏輯大體可分爲三個部分,第一部分是前置工做,主要用於檢查參數,組裝 URL。第二部分是導出服務,包含導出服務到本地 (JVM),和導出服務到遠程兩個過程。第三部分是向註冊中心註冊服務,用於服務發現析。緩存
服務導出的入口方法是 ServiceBean 的 onApplicationEvent。onApplicationEvent 是一個事件響應方法,該方法會在收到 Spring 上下文刷新事件後執行服務導出操做。服務器
ServiceBean.onApplicationEvent -->ServiceBean.export -->ServiceConfig.export() --> ServiceConfig.doExport -->ServiceConfig.doExportUrls -->loadRegistries(true)//組裝registry的url信息 -->doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) -->exportLocal(URL url) //本地暴露 -->proxyFactory.getInvoker(ref, (Class) interfaceClass, local) -->ExtensionLoader.getExtensionLoader(ProxyFactory.class) .getExtension("javassist"); -->extension.getInvoker(arg0, arg1, arg2) -->StubProxyFactoryWrapper.getInvoker(T proxy, Class<T> type, URL url) //AOP的緣由進入到了StubProxyFactoryWrapper中 -->proxyFactory.getInvoker(proxy, type, url) -->JavassistProxyFactory.getInvoker(T proxy, Class<T> type, URL url) -->Wrapper.getWrapper(com.alibaba.dubbo.demo.provider.DemoServiceImpl) //這裏getWrapper方法會使用JavassistProxy生成一個動態類 -->Wrapper.makeWrapper(Class<?> c) -->return new AbstractProxyInvoker<T>(proxy, type, url) -->protocol.export -->Protocol$Adpative.export -->ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("injvm"); -->extension.export(arg0) -->ProtocolFilterWrapper.export -->ProtocolFilterWrapper.buildInvokerChain //獲取8個filter, 造成過濾連 -->ProtocolListenerWrapper.export -->QosProtocolWrapper.export -->InjvmProtocol.export -->return new InjvmExporter<T> (invoker, invoker.getUrl().getServiceKey(), exporterMap) //若是配置不是local則暴露爲遠程服務.(配置爲local,則表示只暴露本地服務) -->proxyFactory.getInvoker -->protocol.export(invoker) //遠程暴露 原理和本地暴露同樣都是爲了獲取一個Invoker對象 -->Protocol$Adpative.export -->ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("registry"); -->extension.export(arg0) -->ProtocolFilterWrapper.export -->ProtocolListenerWrapper.export -->QosProtocolWrapper.export -->RegistryProtocol.export -->doLocalExport(originInvoker) //讀取 dubbo://192.168.100.51:20880/ -->getCacheKey(originInvoker) -->protocol.export -->Protocol$Adpative.export -->ExtensionLoader .getExtensionLoader(Protocol.class).getExtension("dubbo"); -->extension.export(arg0) // new ListenerExporterWrapper -->ProtocolListenerWrapper.export -->QosProtocolWrapper.export // 這個aop內會生成過濾連 //buildInvokerChain(invoker, service.filter, provider) -->ProtocolFilterWrapper.export ----一、netty服務暴露開始------------ -->DubboProtocol.export(invoker, key, exporterMap) // invoker對象是 -->serviceKey(url) 組裝key=com.alibaba.dubbo.demo.DemoService:20880
關於getExtension方法,會調用createExtension會給目標擴展類添加鏈式的對象 實現AOP網絡
本地暴露和遠程暴露都將 須要暴露的對象添加到了exporterMap中, 在遠程調用時,會從exporterMap中獲取暴露的對象來執行。app
代碼裏本地暴露目的:jvm
exporterMap.put(key,this)//key=com.alibaba.dubbo.demo.DemoService, this=InjvmExporteride
遠程暴露的目的:
exporterMap.put(key, this)//key=com.alibaba.dubbo.demo.DemoService:20880, this=DubboExporter
下面繼續
-->serviceKey(url) -->openServer(url) -->createServer(url) -->Exchangers.bind(url, requestHandler) //exchaanger是一個信息交換層 -- 二、信息交換層開始 --- // bind方法裏的requestHandler用於服務調用時調用目標發方法 -->getExchanger(url) -->getExchanger(type) -->ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension("header") -->HeaderExchanger.bind -->Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))) -->new ChannelHandlerDispatcher(handlers); -->new DecodeHandler -->new AbstractChannelHandlerDelegate //this.handler = handler ----3.網絡傳輸層開始------ -->Transporters.bind -->getTransporter() -->ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension() -->Transporter$Adpative.bind -->ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class) .getExtension("netty"); -->extension.bind(arg0, arg1) -->NettyTransporter.bind -->new NettyServer(url, listener); -->AbstractPeer //this.url = url; this.handler = handler; -->AbstractEndpoint//codec timeout=1000 connectTimeout=3000 -->AbstractServer //bindAddress accepts=0 idleTimeout=600000 -->doOpen ------------------- 四、打開通道,暴露netty服務------------------ -->bootstrap.bind(getBindAddress()); -->new HeaderExchangeServer (Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))); -->this.server=NettyServer -->this.heartbeat = server.getUrl().getParameter("heartbeat", 0); //心跳檢查週期時間 -->heartbeatTimeout 默認180000 -->startHeatbeatTimer()//這是一個心跳定時器,採用了線程池,若是斷開就心跳重連。
到這裏,關於暴露部分的服務端netty就開啓了 ,綁定的地址爲URL裏的IP+URL裏的端口
接下來是zookeeper的鏈接
RegistryProtocol.export -->RegistryProtocol.getRegistry(registryUrl) -->registryFactory.getRegistry(registryUrl) -->RegistryFactory$Adaptive.getRegistry -->ExtensionLoader.getExtensionLoader(RegistryFactory.class).getExtension("zookeeper"); -->extension.getRegistry(arg0) -->AbstractRegistryFactory.getRegistry//建立一個註冊中心,存儲在REGISTRIES -->createRegistry(url) -->new ZookeeperRegistry(url, zookeeperTransporter) -->AbstractRegistry() 調用無參構造 -->loadProperties() //加載 「暴露信息的緩存文件」 中的信息, 見下面說明 -->notify(url.getBackupUrls()) //回調訂閱的listner (若是配置了的話) -->FailbackRegistry() 調用無參構造 -->retryExecutor.scheduleWithFixedDelay(new Runnable() //創建線程池,檢測並鏈接註冊中心,若是失敗了就重連 -->ZookeeperRegistry() 調用無參構造 -->zookeeperTransporter.connect(url) -->ZookeeperTransporter$Adaptive.connect -->ExtensionLoader.getExtensionLoader(ZookeeperTransporter.class) .getExtension("zkclient"); -->extension.connect(arg0); -->ZkclientZookeeperTransporter.connect -->new ZkclientZookeeperClient(url) -->AbstractZookeeperClient(url) //構造器 this.url = url -->ZkclientZookeeperClient //構造器 -->ZkClientWrapper //構造器 -->new ZkClient //zookeeper客戶端 -->zkClient.addStateListener(new StateListener() { stateChanged() {recover()} //失敗重連 } -->RegistryProtocol.register (export方法裏面調用register) -->registry.register(registedProviderUrl)//建立節點 -->AbstractRegistry.register -->FailbackRegistry.register -->doRegister(url) //向zk服務器端發送註冊請求 -->ZookeeperRegistry.doRegister -->zkClient.create -->AbstractZookeeperClient.create //dubbo/per.qiao.service.TestService/providers/ dubbo%3A%2F%2F192.168.100.52%3A20880%2F............. -->createEphemeral(path) //建立臨時節點 dubbo%3A%2F%2F192.168.100.52%3A20880%2F............. -->createPersistent(path);//建立永久節點 dubbo/per.qiao.service.TestService/providers -->registry.subscribe //訂閱ZK -->AbstractRegistry.subscribe -->FailbackRegistry.subscribe -->doSubscribe(url, listener)// 向服務器端發送訂閱請求 -->ZookeeperRegistry.doSubscribe -->new ChildListener() //訂閱的節點發生變化時通知的具體方法 -->zkClient.create(path, false); //第一步:先建立持久化節點/dubbo/per.qiao.service.TestService/configurators -->zkClient.addChildListener(path, zkListener) -->AbstractZookeeperClient.addChildListener -->createTargetChildListener(path, listener) //第三步:收到訂閱後的處理,交給FailbackRegistry.notify處理 -->ZkclientZookeeperClient.createTargetChildListener -->new IZkChildListener() //實現了 handleChildChange //收到訂閱後的處理 //listener.childChanged(parentPath, currentChilds); //實現並執行ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, //parentPath, currentChilds)); 也就是上面new ChildListener()這個方法 //具體是調用的FailbackRegistry.notify(收到訂閱後處理) -->addTargetChildListener(path, targetListener) //第二步 -->ZkclientZookeeperClient.addTargetChildListener -->ZkClientWrapper.subscribeChildChanges -->client.subscribeChildChanges(path, listener) // 注意: 這裏纔是真正的訂閱zk的configurators目錄 這裏的client是ZkClient對象 -->notify(url, listener, urls); -->FailbackRegistry.notify -->doNotify(url, listener, urls); -->AbstractRegistry.notify -->saveProperties(url); //把服務端註冊url的信息更新到「暴露信息的緩存文件」 見下面說明 -->registryCacheExecutor.execute(new SaveProperties(version)); //採用線程池來處理保存操做
說明:
暴露信息的緩存文件路徑:爲:System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getParameter(Constants.APPLICATION_KEY) + "-" + url.getAddress() + ".cache"
好比個人用戶爲qiao,暴露的服務名爲serviceImpl,暴露在地址爲127.0.0.1; 那麼路徑爲C:\Users\qiao.dubbo\dubbo-registry-serviceImpl-127.0.0.1.cache
以上就是服務暴露(導出)的全部操做
主要操做有:
1. 本地暴露 2. 遠程暴露 3. 啓動netty 4. 註冊暴露地址到zookeeper 5. 訂閱(監聽)zk的configurators節點 (詳見 AbstractRegistry#notify)
步驟不少很複雜,建議邊debug跟着斷點走
主要是RegistryProtocol#export方法和DubboProtocol#export方法
RegistryProtocol#export
@Override public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException { //暴露服務 委託DubboProtocol使用netty暴露 final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker); URL registryUrl = getRegistryUrl(originInvoker); // 註冊到zookeeper和訂閱事件 final Registry registry = getRegistry(originInvoker); final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker); //是否已註冊到zookeeper boolean register = registeredProviderUrl.getParameter("register", true); //保存 服務暴露信息 到 本地映射表 ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl); //若是已經註冊了 建立節點 if (register) { register(registryUrl, registeredProviderUrl); ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true); } // Subscribe the override data // FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call the same service. Because the subscribed is cached key with the name of the service, it causes the subscription information to cover. final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl); final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker); overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener); //訂閱ZK registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener); //返回服務暴露後的實例 //p1: 已暴露結果對象, p2: 原須要暴露的對象, p3: 訂閱地址, p4: 服務註冊地址 return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl); }
Registryprotocol進入到DubboProtocol的中間方法
Registryprotocol#doLocalExport
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) { //暴露的服務信息 String key = getCacheKey(originInvoker); ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key); if (exporter == null) { synchronized (bounds) { exporter = (ExporterChangeableWrapper<T>) bounds.get(key); if (exporter == null) { //將暴露信息封裝成一個invoker(包含url和invoker對象) //裏面的url其實是配置的暴露信息,toString後與上面key的字符串如出一轍 final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker)); //protocol.export調用到Protocol$Adaptive的export而後再調到DubboProtocol#export exporter = new ExporterChangeableWrapper<T> ((Exporter<T>) protocol.export(invokerDelegete), originInvoker); bounds.put(key, exporter); } } } return exporter; }
暴露的服務信息(url):
dubbo://192.168.199.223:9000/per.qiao.service.TestService?anyhost=true&application=serviceImpl&bean.name=per.qiao.service.TestService&bind.ip=192.168.199.223&bind.port=9000&dubbo=2.0.2&generic=false&interface=per.qiao.service.TestService&methods=getData&pid=13604&revision=1.0-SNAPSHOT&side=provider&timeout=10000×tamp=1558757097945
DubboProtocol#export
/** * @param invoker 須要暴露的對象 * @return 返回暴露結果對象 */ @Override public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { //暴露的URL對象 URL url = invoker.getUrl(); // export service. String key = serviceKey(url); //暴露的結果對象 DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap); //保存暴露的對象 exporterMap.put(key, exporter); //export an stub service for dispatching event Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT); Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false); if (isStubSupportEvent && !isCallbackservice) { String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY); if (stubServiceMethods == null || stubServiceMethods.length() == 0) { ... } else { stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods); } } // 打開netty服務 openServer(url); optimizeSerialization(url); return exporter; }