摘要:根據(jù)的值,進行服務(wù)暴露。如果配置為則不暴露,如果服務(wù)未配置成,則本地暴露如果未配置成,則暴露遠程服務(wù)。提供者向注冊中心訂閱所有注冊服務(wù)當注冊中心有此服務(wù)的覆蓋配置注冊進來時,推送消息給提供者,重新暴露服務(wù),這由管理頁面完成。
概覽
dubbo暴露服務(wù)有兩種情況,一種是設(shè)置了延遲暴露(比如delay=”5000”),另外一種是沒有設(shè)置延遲暴露或者延遲設(shè)置為-1(delay=”-1”):
設(shè)置了延遲暴露,dubbo在Spring實例化bean(initializeBean)的時候會對實現(xiàn)了InitializingBean的類進行回調(diào),回調(diào)方法是afterPropertySet()。ServiceBean實現(xiàn)了如果InitializingBean接口,重寫了afterPropertySet()方法。如果設(shè)置了延遲暴露,dubbo在這個方法中進行服務(wù)的發(fā)布。
沒有設(shè)置延遲或者延遲為-1,dubbo會在Spring實例化完bean之后,在刷新容器最后一步發(fā)布ContextRefreshEvent事件的時候,通知實現(xiàn)了ApplicationListener的類進行回調(diào)onApplicationEvent,dubbo會在這個方法中發(fā)布服務(wù)。(ServiceBean實現(xiàn)了ApplicationListener接口)
但是不管延遲與否,都是使用ServiceConfig的export()方法進行服務(wù)的暴露。使用export初始化的時候會將Bean對象轉(zhuǎn)換成URL格式,所有Bean屬性轉(zhuǎn)換成URL的參數(shù)。
暴露流程首先將服務(wù)的實現(xiàn)封裝成一個Invoker,Invoker中封裝了服務(wù)的實現(xiàn)類。
將Invoker封裝成Exporter,并緩存起來,緩存里使用Invoker的url作為key。
服務(wù)端Server啟動,監(jiān)聽端口。(請求來到時,根據(jù)請求信息生成key,到緩存查找Exporter,就找到了Invoker,就可以完成調(diào)用。)
Spring容器初始化調(diào)用當Spring容器實例化bean完成,走到最后一步發(fā)布ContextRefreshEvent事件的時候,ServiceBean會執(zhí)行onApplicationEvent方法,該方法調(diào)用ServiceConfig的export方法,從而進行服務(wù)的暴露。
export的步驟簡介首先會檢查各種配置信息,填充各種屬性,總之就是保證我在開始暴露服務(wù)之前,所有的東西都準備好了,并且是正確的。
加載所有的注冊中心,因為我們暴露服務(wù)需要注冊到注冊中心中去。
根據(jù)配置的所有協(xié)議和注冊中心url分別進行服務(wù)暴露,暴露為 本地服務(wù) 或者 遠程服務(wù)
3.1 不管是本地還是遠程服務(wù)暴露,首先都會獲取Invoker。
3.2 獲取完Invoker之后,轉(zhuǎn)換成對外的Exporter,緩存起來。
加載所有的注冊中心export方法先判斷是否需要延遲暴露,如果是不延遲暴露,會執(zhí)行doExport方法。
doExport方法先執(zhí)行一系列的檢查方法,然后調(diào)用doExportUrls方法。
doExportUrls方法先調(diào)用loadRegistries獲取所有的注冊中心url。
private void doExportUrls() { ListregistryURLs = loadRegistries(true); for (ProtocolConfig protocolConfig : protocols) { doExportUrlsFor1Protocol(protocolConfig, registryURLs); } }
url如下:
registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=springProviderApplication&check=false&compiler=javassist&dubbo=2.0.2&logger=slf4j&organization=huangyuan&owner=huangyuan&pid=1689®ister=true®istry=zookeeper&session=60000&subscribe=true×tamp=1544326825698根據(jù)配置的協(xié)議進行服務(wù)暴露
然后遍歷調(diào)用doExportUrlsFor1Protocol方法。doExportUrlsFor1Protocol根據(jù)不同的協(xié)議將服務(wù)轉(zhuǎn)換為URL形式,一些配置參數(shù)會附在URL后面。
根據(jù)scope的值,進行服務(wù)暴露。
如果scope配置為none則不暴露,
如果服務(wù)未配置成remote,則本地暴露exportLocal;
如果未配置成local,則暴露遠程服務(wù)。
疑惑點:
(1)scope的值默認是null,按照代碼的邏輯,會進行本地暴露,又進行遠程暴露,為什么呢?
(2)關(guān)于(1)的解答,我覺得既然用戶不設(shè)置為“不暴露”,也不設(shè)置為“只暴露遠程服務(wù)”,也不設(shè)置為“只暴露本地服務(wù)”,那么dubbo就認為所以都需要進行暴露。
如果是暴露遠程服務(wù),則經(jīng)過上面的操作之后,url變成:
dubbo://192.168.1.4:23801/com.huang.yuan.api.service.DemoService2?anyhost=true&application=springProviderApplication&bind.ip=192.168.1.4&bind.port=23801&compiler=javassist&default.cluster=failover&default.delay=-1&default.loadbalance=random&default.proxy=javassist&default.retries=2&default.serialization=hessian2&default.timeout=3000&delay=-1&dubbo=2.0.2&generic=false&interface=com.huang.yuan.api.service.DemoService2&logger=slf4j&methods=demoTest&organization=huangyuan&owner=huangyuan&pid=1831&revision=1.0-SNAPSHOT&side=provider×tamp=1544327969839&version=1.0暴露為遠程服務(wù)
先獲取Invoker,然后導(dǎo)出成Exporter
// 根據(jù)服務(wù)具體實現(xiàn),實現(xiàn)接口,以及registryUrl通過ProxyFactory將XXXServiceImpl封裝成一個本地執(zhí)行的Invoker // invoker是對具體實現(xiàn)的一種代理。 Invoker> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString())); // 使用Protocol將invoker導(dǎo)出成一個Exporter // 暴露封裝服務(wù)invoker // 調(diào)用Protocol生成的適配類的export方法 Exporter> exporter = protocol.export(invoker);
這里會調(diào)用com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory#getInvoker獲取
publicInvoker getInvoker(T proxy, Class type, URL url) { // 封裝一個Wrapper類,如果類是以$開頭,就使用接口類型獲取,其他的使用實現(xiàn)類獲取 final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf("$") < 0 ? proxy.getClass() : type); // 返回一個Invoker實例,doInvoke方法中直接返回 wrapper的invokeMethod // 關(guān)于生成的wrapper,請看下面列出的生成的代碼,其中invokeMethod方法中就有實現(xiàn)類對實際方法的調(diào)用 return new AbstractProxyInvoker (proxy, type, url) { @Override protected Object doInvoke(T proxy, String methodName, Class>[] parameterTypes, Object[] arguments) throws Throwable { return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); } }; }
這里生成Wrapper對象,利用了javasisst動態(tài)代理技術(shù),生成的代碼如下:
public Object invokeMethod(Object o, String n, Class[] p, Object[] v) { // 持有實際對象的引用 com.huang.yuan.dubbo.service.impl.DemoService2Impl w; // 獲取實際調(diào)用對象(進行類型轉(zhuǎn)換) w = ((com.huang.yuan.dubbo.service.impl.DemoService2Impl)$1); // 執(zhí)行真正的方法調(diào)用 (demoTest是接口中真正需要執(zhí)行的方法) w.demoTest((java.lang.String)$4[0]); }
由此可見,Invoker執(zhí)行方法的時候,會調(diào)用doInvoke方法,會調(diào)用Wrapper的invokeMethod,這個方法中會有的實現(xiàn)類調(diào)用真實方法的代碼。
(代碼可以從com.alibaba.dubbo.common.bytecode.Wrapper#makeWrapper中獲得)
本地暴露private void exportLocal(URL url) { if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) { // 這時候轉(zhuǎn)成本地暴露的url:injvm://127.0.0.1/dubbo.common.hello.service.HelloService?anyhost=true&...... URL local = URL.valueOf(url.toFullString()) .setProtocol(Constants.LOCAL_PROTOCOL) .setHost(NetUtils.LOCALHOST) .setPort(0); // 首先還是先獲得Invoker,然后導(dǎo)出成Exporter,并緩存 // 這里的proxyFactory實際是JavassistProxyFactory // 導(dǎo)出expter使用com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol#export Exporter> exporter = protocol.export( proxyFactory.getInvoker(ref, (Class) interfaceClass, local)); exporters.add(exporter); logger.info("Export dubbo service " + interfaceClass.getName() +" to local registry"); } }
到這里就把url轉(zhuǎn)成了Invoker對象
導(dǎo)出Invoker為Exporter Registry類型的Invoker處理過程org.apache.dubbo.registry.integration.RegistryProtocol#export方法
@Override publicExporter export(final Invoker originInvoker) throws RpcException { // 將invoker轉(zhuǎn)成exporter final ExporterChangeableWrapper exporter = doLocalExport(originInvoker); // 獲取RegistryUrl URL registryUrl = getRegistryUrl(originInvoker); /* * 根據(jù)invoker中的url獲取Registry實例 * 并且連接到注冊中心 * 此時提供者作為消費者 引用 注冊中心的核心服務(wù) RegistryService */ final Registry registry = getRegistry(originInvoker); //注冊到注冊中心的URL,如 dubb://XXXXX final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker); // 判斷是否延遲發(fā)布 boolean register = registeredProviderUrl.getParameter("register", true); // 將originInvoker加入本地緩存 ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl); if (register) { /* * 調(diào)用遠端注冊中心的register方法進行服務(wù)注冊 * 若有消費者訂閱此服務(wù),則推送消息讓消費者引用此服務(wù)。 * 注冊中心緩存了所有提供者注冊的服務(wù)以供消費者發(fā)現(xiàn)。 */ register(registryUrl, registeredProviderUrl); ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true); } /* * 訂閱override數(shù)據(jù) * * 提供者訂閱時,會影響 同一JVM即暴露服務(wù),又引用同一服務(wù)的的場景, * 因為subscribed以服務(wù)名為緩存的key,導(dǎo)致訂閱信息覆蓋。 */ final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl); final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker); overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener); /* * 提供者向注冊中心訂閱所有注冊服務(wù) * 當注冊中心有此服務(wù)的覆蓋配置注冊進來時,推送消息給提供者,重新暴露服務(wù),這由管理頁面完成。 */ registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener); /* * 保證每次export都返回一個新的exporter實例 * 返回暴露后的Exporter給上層ServiceConfig進行緩存,便于后期撤銷暴露。 */ return new DestroyableExporter (exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl); }
## org.apache.dubbo.registry.integration.RegistryProtocol#doLocalExport方法 privateExporterChangeableWrapper doLocalExport(final Invoker originInvoker) { // 調(diào)用代碼中的protocol.export方法,會根據(jù)協(xié)議名選擇調(diào)用具體的實現(xiàn)類 // 這里會調(diào)用 DubboProtocol 的export方法 // 導(dǎo)出完之后,返回一個新的ExporterChangeableWrapper實例 exporter = new ExporterChangeableWrapper ((Exporter ) protocol.export(invokerDelegete), originInvoker); ...... }
DubboProtocol的export方法實現(xiàn):
@Override publicExporter export(Invoker invoker) throws RpcException { // 獲取需要暴露的url URL url = invoker.getUrl(); /* * 通過url獲取key * * key的例子:com.huang.yuan.api.service.DemoService2:1.0:23801 * 在服務(wù)調(diào)用的時候,同樣通過這個key獲取exporter */ String key = serviceKey(url); // 生成exporter實例 DubboExporter exporter = new DubboExporter (invoker, key, exporterMap); // 緩存exporter exporterMap.put(key, exporter); /* * todo 沒理解 Constants.STUB_EVENT_KEY * 是否支持本地存根 * 項目使用遠程服務(wù)后,客戶端通常只剩下接口,而實現(xiàn)全在服務(wù)器端 * * 但提供方有些時候想在客戶端也執(zhí)行部分邏輯, * 比如:做ThreadLocal緩存,提前驗證參數(shù),調(diào)用失敗后偽造容錯數(shù)據(jù)等等,此時就需要在API中帶上Stub * * 客戶端生成Proxy實例,會把Proxy通過構(gòu)造函數(shù)傳給Stub,然后把Stub暴露給用戶,Stub可以決定要不要去調(diào)Proxy */ Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT); // 是否回調(diào)服務(wù) 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) { if (logger.isWarnEnabled()) { logger.warn(new IllegalStateException("consumer [" + url.getParameter(Constants.INTERFACE_KEY) + "], has set stubproxy support event ,but no stub methods founded.")); } } else { stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods); } } // 根據(jù)URL綁定IP與端口,建立NIO框架的Server openServer(url); optimizeSerialization(url); return exporter; }
/* * 根據(jù)invoker中的url獲取Registry實例 * 并且連接到注冊中心 * 此時提供者作為消費者 引用 注冊中心的核心服務(wù) RegistryService */ final Registry registry = getRegistry(originInvoker);
具體的操作在中org.apache.dubbo.registry.integration.RegistryProtocol
/** * 根據(jù)invoker的地址獲取registry實例 * * @param originInvoker * @return */ private Registry getRegistry(final Invoker> originInvoker) { URL registryUrl = getRegistryUrl(originInvoker); /* * 根據(jù)SPI機制獲取具體的Registry實例, * 會調(diào)用到com.alibaba.dubbo.registry.support.AbstractRegistryFactory#getRegistry * 這里獲取到的是ZookeeperRegistry */ return registryFactory.getRegistry(registryUrl); }
這里會調(diào)用到org.apache.dubbo.registry.support.AbstractRegistry#AbstractRegistry的構(gòu)造方法
public AbstractRegistry(URL url) { // 設(shè)置注冊中心url setUrl(url); // 是否開啟"文件保存定時器" syncSaveFile = url.getParameter(Constants.REGISTRY_FILESAVE_SYNC_KEY, false); /* * 保存的文件名稱為: * /Users/huangyuan/.dubbo/dubbo-registry-springProviderApplication-127.0.0.1:2181.cache * * dubbo中zookeeper做注冊中心,如果注冊中心集群都掛掉,那發(fā)布者和訂閱者還能通信嗎? * * 能,dubbo會將zookeeper的信息緩存到本地, * 作為一個緩存文件,并且轉(zhuǎn)換成properties對象方便使用 * * 該文件在注冊中心通知的時候,進行存儲更新 notify(url.getBackupUrls()); */ String filename = url.getParameter(Constants.FILE_KEY, System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getParameter(Constants.APPLICATION_KEY) + "-" + url.getAddress() + ".cache"); File file = null; if (ConfigUtils.isNotEmpty(filename)) { file = new File(filename); if (!file.exists() && file.getParentFile() != null && !file.getParentFile().exists()) { // 不存在則創(chuàng)建 if (!file.getParentFile().mkdirs()) { throw new IllegalArgumentException("Invalid registry store file " + file + ", cause: Failed to create directory " + file.getParentFile() + "!"); } } } this.file = file; /* * 加載文件中的屬性,key-value形式,示例如下: * "group1/com.huang.yuan.api.service.DemoService:1.0" -> "empty://192.168.1.2:23801/com.huang.yuan.api.service.DemoService?anyhost=true&application=springProviderApplication..." */ loadProperties(); // 通知訂閱 // url.getBackupUrls() 獲取的是注冊中心的url notify(url.getBackupUrls()); }
RegistryProtocol注冊服務(wù)到注冊中心(這里注冊中心是Zookeeper,因此最終的注冊操作是在org.apache.dubbo.registry.zookeeper.ZookeeperRegistry中執(zhí)行,代碼如下)
@Override protected void doRegister(URL url) { try { /* * 這里zkClient就是我們上面調(diào)用構(gòu)造的時候生成的 ZkClientZookeeperClient * ZkClientZookeeperClient 保存著連接到Zookeeper的zkClient實例 * 開始注冊,也就是在Zookeeper中創(chuàng)建節(jié)點s * 這里toUrlPath獲取到的path為:(類似) * /dubbo/com.huang.yuan.api.service.DemoService2/provider..... * 這就是在Zookeeper上面創(chuàng)建的文件夾路徑及節(jié)點 * 默認創(chuàng)建的節(jié)點是臨時節(jié)點 */ zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true)); } catch (Throwable e) { throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e); } }
com.alibaba.dubbo.registry.support.FailbackRegistry向注冊中心訂閱服務(wù)
public void subscribe(URL url, NotifyListener listener) { super.subscribe(url, listener); removeFailedSubscribed(url, listener); try { /* * 想注冊中心發(fā)送訂閱請求 * 這里有一個地方比較有意思,就是自己的服務(wù)、依賴外部的服務(wù),都會進行訂閱。 * 這一步之后就會在/dubbo/dubbo.common.hello.service/XXXService節(jié)點下多一個configurators節(jié)點 */ doSubscribe(url, listener); } catch (Exception e) { ...... } }
在org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#doSubscribe完成訂閱特定服務(wù)的操作。
Listurls = new ArrayList (); /* * toCategoriesPath是獲取分類路徑 * 比如說獲取的path數(shù)組,含有下面3個值 * /dubbo/com.huang.yuan.api.service.DemoService2/providers * /dubbo/com.huang.yuan.api.service.DemoService2/configurators * /dubbo/com.huang.yuan.api.service.DemoService2/routers */ for (String path : toCategoriesPath(url)) { // 獲取訂閱者 ConcurrentMap listeners = zkListeners.get(url); if (listeners == null) { zkListeners.putIfAbsent(url, new ConcurrentHashMap ()); listeners = zkListeners.get(url); } // 監(jiān)聽器 ChildListener zkListener = listeners.get(listener); if (zkListener == null) { listeners.putIfAbsent(listener, new ChildListener() { @Override public void childChanged(String parentPath, List currentChilds) { // 這里設(shè)置了監(jiān)聽回調(diào)的地址,即回調(diào)給FailbackRegistry中的notify ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds)); } }); zkListener = listeners.get(listener); } // 根據(jù)路徑path創(chuàng)建節(jié)點 zkClient.create(path, false); // 添加子節(jié)點監(jiān)聽器 List children = zkClient.addChildListener(path, zkListener); if (children != null) { urls.addAll(toUrlsWithEmpty(url, path, children)); } } // 通知消費者 // 在org.apache.dubbo.registry.support.FailbackRegistry.notify中發(fā)布操作 notify(url, listener, urls); }
會創(chuàng)建監(jiān)聽器,當訂閱的服務(wù)有變更的時候,注冊中心就會調(diào)用com.alibaba.dubbo.registry.NotifyListener#notify方法,告訴消費者
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/72629.html
摘要:服務(wù)暴露過程目標從源碼的角度分析服務(wù)暴露過程。導(dǎo)出服務(wù),包含暴露服務(wù)到本地,和暴露服務(wù)到遠程兩個過程。其中服務(wù)暴露的第八步已經(jīng)沒有了。將泛化調(diào)用版本號或者等信息加入獲得服務(wù)暴露地址和端口號,利用內(nèi)數(shù)據(jù)組裝成。 dubbo服務(wù)暴露過程 目標:從源碼的角度分析服務(wù)暴露過程。 前言 本來這一篇一個寫異步化改造的內(nèi)容,但是最近我一直在想,某一部分的優(yōu)化改造該怎么去撰寫才能更加的讓讀者理解。我覺...
摘要:將標記為服務(wù),使用對象來提供具體的服務(wù)。這整個過程算是該類的典型的執(zhí)行過程。從上面得知服務(wù)發(fā)布的第一二個過程獲取注冊中心信息和協(xié)議信息。對于端來說,上述服務(wù)發(fā)布的第步中要解決的問題是根據(jù)指定協(xié)議向注冊中心注冊服務(wù)。 showImg(https://segmentfault.com/img/remote/1460000015274828?w=646&h=413); 上圖是服務(wù)提供者暴露服...
摘要:整體流程以調(diào)試來演示服務(wù)的發(fā)布流程。暴露遠程服務(wù)假如服務(wù)沒有配置了屬性,或者配置了但是值不是,就會執(zhí)行遠程暴露。封裝了一個服務(wù)的相關(guān)信息,是一個服務(wù)可執(zhí)行體。是一個服務(wù)域,他是引用和暴露的主要入口,它負責的生命周期管理。 整體流程以調(diào)試 om.alibaba.dubbo.demo.provider.DemoProvider來演示dubbo服務(wù)的發(fā)布流程。 1、啟動Spring容器 參照...
摘要:啟動容器,加載,運行服務(wù)提供者。服務(wù)提供者在啟動時,在注冊中心發(fā)布注冊自己提供的服務(wù)。注冊中心返回服務(wù)提供者地址列表給消費者,如果有變更,注冊中心將基于長連接推送變更數(shù)據(jù)給消費者。 一 為什么需要 dubbo 很多時候,其實我們使用這個技術(shù)的時候,可能都是因為項目需要,所以,我們就用了,但是,至于為什么我們需要用到這個技術(shù),可能自身并不是很了解的,但是,其實了解技術(shù)的來由及背景知識,對...
摘要:統(tǒng)計服務(wù)的調(diào)用次調(diào)和調(diào)用時間的監(jiān)控中心。調(diào)用關(guān)系說明服務(wù)容器負責啟動,加載,運行服務(wù)提供者。服務(wù)提供者在啟動時,向注冊中心注冊自己提供的服務(wù)。調(diào)度中心基于訪問壓力自動增減服務(wù)提供者。 隨著互聯(lián)網(wǎng)的發(fā)展,網(wǎng)站應(yīng)用的規(guī)模不斷擴大,常規(guī)的垂直應(yīng)用架構(gòu)已無法應(yīng)對,分布式服務(wù)架構(gòu)以及流動計算架構(gòu)勢在必行,亟需一個治理系統(tǒng)確保架構(gòu)有條不紊的演進。showImg(https://segmentfau...
閱讀 3783·2021-11-23 09:51
閱讀 4421·2021-11-15 11:37
閱讀 3534·2021-09-02 15:21
閱讀 2756·2021-09-01 10:31
閱讀 888·2021-08-31 14:19
閱讀 865·2021-08-11 11:20
閱讀 3318·2021-07-30 15:30
閱讀 1699·2019-08-30 15:54