摘要:根據的值,進行服務暴露。如果配置為則不暴露,如果服務未配置成,則本地暴露如果未配置成,則暴露遠程服務。提供者向注冊中心訂閱所有注冊服務當注冊中心有此服務的覆蓋配置注冊進來時,推送消息給提供者,重新暴露服務,這由管理頁面完成。
概覽
dubbo暴露服務有兩種情況,一種是設置了延遲暴露(比如delay=”5000”),另外一種是沒有設置延遲暴露或者延遲設置為-1(delay=”-1”):
設置了延遲暴露,dubbo在Spring實例化bean(initializeBean)的時候會對實現了InitializingBean的類進行回調,回調方法是afterPropertySet()。ServiceBean實現了如果InitializingBean接口,重寫了afterPropertySet()方法。如果設置了延遲暴露,dubbo在這個方法中進行服務的發布。
沒有設置延遲或者延遲為-1,dubbo會在Spring實例化完bean之后,在刷新容器最后一步發布ContextRefreshEvent事件的時候,通知實現了ApplicationListener的類進行回調onApplicationEvent,dubbo會在這個方法中發布服務。(ServiceBean實現了ApplicationListener接口)
但是不管延遲與否,都是使用ServiceConfig的export()方法進行服務的暴露。使用export初始化的時候會將Bean對象轉換成URL格式,所有Bean屬性轉換成URL的參數。
暴露流程首先將服務的實現封裝成一個Invoker,Invoker中封裝了服務的實現類。
將Invoker封裝成Exporter,并緩存起來,緩存里使用Invoker的url作為key。
服務端Server啟動,監聽端口。(請求來到時,根據請求信息生成key,到緩存查找Exporter,就找到了Invoker,就可以完成調用。)
Spring容器初始化調用當Spring容器實例化bean完成,走到最后一步發布ContextRefreshEvent事件的時候,ServiceBean會執行onApplicationEvent方法,該方法調用ServiceConfig的export方法,從而進行服務的暴露。
export的步驟簡介首先會檢查各種配置信息,填充各種屬性,總之就是保證我在開始暴露服務之前,所有的東西都準備好了,并且是正確的。
加載所有的注冊中心,因為我們暴露服務需要注冊到注冊中心中去。
根據配置的所有協議和注冊中心url分別進行服務暴露,暴露為 本地服務 或者 遠程服務
3.1 不管是本地還是遠程服務暴露,首先都會獲取Invoker。
3.2 獲取完Invoker之后,轉換成對外的Exporter,緩存起來。
加載所有的注冊中心export方法先判斷是否需要延遲暴露,如果是不延遲暴露,會執行doExport方法。
doExport方法先執行一系列的檢查方法,然后調用doExportUrls方法。
doExportUrls方法先調用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根據配置的協議進行服務暴露
然后遍歷調用doExportUrlsFor1Protocol方法。doExportUrlsFor1Protocol根據不同的協議將服務轉換為URL形式,一些配置參數會附在URL后面。
根據scope的值,進行服務暴露。
如果scope配置為none則不暴露,
如果服務未配置成remote,則本地暴露exportLocal;
如果未配置成local,則暴露遠程服務。
疑惑點:
(1)scope的值默認是null,按照代碼的邏輯,會進行本地暴露,又進行遠程暴露,為什么呢?
(2)關于(1)的解答,我覺得既然用戶不設置為“不暴露”,也不設置為“只暴露遠程服務”,也不設置為“只暴露本地服務”,那么dubbo就認為所以都需要進行暴露。
如果是暴露遠程服務,則經過上面的操作之后,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暴露為遠程服務
先獲取Invoker,然后導出成Exporter
// 根據服務具體實現,實現接口,以及registryUrl通過ProxyFactory將XXXServiceImpl封裝成一個本地執行的Invoker // invoker是對具體實現的一種代理。 Invoker> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString())); // 使用Protocol將invoker導出成一個Exporter // 暴露封裝服務invoker // 調用Protocol生成的適配類的export方法 Exporter> exporter = protocol.export(invoker);
這里會調用com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory#getInvoker獲取
publicInvoker getInvoker(T proxy, Class type, URL url) { // 封裝一個Wrapper類,如果類是以$開頭,就使用接口類型獲取,其他的使用實現類獲取 final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf("$") < 0 ? proxy.getClass() : type); // 返回一個Invoker實例,doInvoke方法中直接返回 wrapper的invokeMethod // 關于生成的wrapper,請看下面列出的生成的代碼,其中invokeMethod方法中就有實現類對實際方法的調用 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動態代理技術,生成的代碼如下:
public Object invokeMethod(Object o, String n, Class[] p, Object[] v) { // 持有實際對象的引用 com.huang.yuan.dubbo.service.impl.DemoService2Impl w; // 獲取實際調用對象(進行類型轉換) w = ((com.huang.yuan.dubbo.service.impl.DemoService2Impl)$1); // 執行真正的方法調用 (demoTest是接口中真正需要執行的方法) w.demoTest((java.lang.String)$4[0]); }
由此可見,Invoker執行方法的時候,會調用doInvoke方法,會調用Wrapper的invokeMethod,這個方法中會有的實現類調用真實方法的代碼。
(代碼可以從com.alibaba.dubbo.common.bytecode.Wrapper#makeWrapper中獲得)
本地暴露private void exportLocal(URL url) { if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) { // 這時候轉成本地暴露的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,然后導出成Exporter,并緩存 // 這里的proxyFactory實際是JavassistProxyFactory // 導出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轉成了Invoker對象
導出Invoker為Exporter Registry類型的Invoker處理過程org.apache.dubbo.registry.integration.RegistryProtocol#export方法
@Override publicExporter export(final Invoker originInvoker) throws RpcException { // 將invoker轉成exporter final ExporterChangeableWrapper exporter = doLocalExport(originInvoker); // 獲取RegistryUrl URL registryUrl = getRegistryUrl(originInvoker); /* * 根據invoker中的url獲取Registry實例 * 并且連接到注冊中心 * 此時提供者作為消費者 引用 注冊中心的核心服務 RegistryService */ final Registry registry = getRegistry(originInvoker); //注冊到注冊中心的URL,如 dubb://XXXXX final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker); // 判斷是否延遲發布 boolean register = registeredProviderUrl.getParameter("register", true); // 將originInvoker加入本地緩存 ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl); if (register) { /* * 調用遠端注冊中心的register方法進行服務注冊 * 若有消費者訂閱此服務,則推送消息讓消費者引用此服務。 * 注冊中心緩存了所有提供者注冊的服務以供消費者發現。 */ register(registryUrl, registeredProviderUrl); ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true); } /* * 訂閱override數據 * * 提供者訂閱時,會影響 同一JVM即暴露服務,又引用同一服務的的場景, * 因為subscribed以服務名為緩存的key,導致訂閱信息覆蓋。 */ final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl); final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker); overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener); /* * 提供者向注冊中心訂閱所有注冊服務 * 當注冊中心有此服務的覆蓋配置注冊進來時,推送消息給提供者,重新暴露服務,這由管理頁面完成。 */ 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) { // 調用代碼中的protocol.export方法,會根據協議名選擇調用具體的實現類 // 這里會調用 DubboProtocol 的export方法 // 導出完之后,返回一個新的ExporterChangeableWrapper實例 exporter = new ExporterChangeableWrapper ((Exporter ) protocol.export(invokerDelegete), originInvoker); ...... }
DubboProtocol的export方法實現:
@Override publicExporter export(Invoker invoker) throws RpcException { // 獲取需要暴露的url URL url = invoker.getUrl(); /* * 通過url獲取key * * key的例子:com.huang.yuan.api.service.DemoService2:1.0:23801 * 在服務調用的時候,同樣通過這個key獲取exporter */ String key = serviceKey(url); // 生成exporter實例 DubboExporter exporter = new DubboExporter (invoker, key, exporterMap); // 緩存exporter exporterMap.put(key, exporter); /* * todo 沒理解 Constants.STUB_EVENT_KEY * 是否支持本地存根 * 項目使用遠程服務后,客戶端通常只剩下接口,而實現全在服務器端 * * 但提供方有些時候想在客戶端也執行部分邏輯, * 比如:做ThreadLocal緩存,提前驗證參數,調用失敗后偽造容錯數據等等,此時就需要在API中帶上Stub * * 客戶端生成Proxy實例,會把Proxy通過構造函數傳給Stub,然后把Stub暴露給用戶,Stub可以決定要不要去調Proxy */ 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) { 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); } } // 根據URL綁定IP與端口,建立NIO框架的Server openServer(url); optimizeSerialization(url); return exporter; }
/* * 根據invoker中的url獲取Registry實例 * 并且連接到注冊中心 * 此時提供者作為消費者 引用 注冊中心的核心服務 RegistryService */ final Registry registry = getRegistry(originInvoker);
具體的操作在中org.apache.dubbo.registry.integration.RegistryProtocol
/** * 根據invoker的地址獲取registry實例 * * @param originInvoker * @return */ private Registry getRegistry(final Invoker> originInvoker) { URL registryUrl = getRegistryUrl(originInvoker); /* * 根據SPI機制獲取具體的Registry實例, * 會調用到com.alibaba.dubbo.registry.support.AbstractRegistryFactory#getRegistry * 這里獲取到的是ZookeeperRegistry */ return registryFactory.getRegistry(registryUrl); }
這里會調用到org.apache.dubbo.registry.support.AbstractRegistry#AbstractRegistry的構造方法
public AbstractRegistry(URL url) { // 設置注冊中心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做注冊中心,如果注冊中心集群都掛掉,那發布者和訂閱者還能通信嗎? * * 能,dubbo會將zookeeper的信息緩存到本地, * 作為一個緩存文件,并且轉換成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()) { // 不存在則創建 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注冊服務到注冊中心(這里注冊中心是Zookeeper,因此最終的注冊操作是在org.apache.dubbo.registry.zookeeper.ZookeeperRegistry中執行,代碼如下)
@Override protected void doRegister(URL url) { try { /* * 這里zkClient就是我們上面調用構造的時候生成的 ZkClientZookeeperClient * ZkClientZookeeperClient 保存著連接到Zookeeper的zkClient實例 * 開始注冊,也就是在Zookeeper中創建節點s * 這里toUrlPath獲取到的path為:(類似) * /dubbo/com.huang.yuan.api.service.DemoService2/provider..... * 這就是在Zookeeper上面創建的文件夾路徑及節點 * 默認創建的節點是臨時節點 */ 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向注冊中心訂閱服務
public void subscribe(URL url, NotifyListener listener) { super.subscribe(url, listener); removeFailedSubscribed(url, listener); try { /* * 想注冊中心發送訂閱請求 * 這里有一個地方比較有意思,就是自己的服務、依賴外部的服務,都會進行訂閱。 * 這一步之后就會在/dubbo/dubbo.common.hello.service/XXXService節點下多一個configurators節點 */ doSubscribe(url, listener); } catch (Exception e) { ...... } }
在org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#doSubscribe完成訂閱特定服務的操作。
Listurls = new ArrayList (); /* * toCategoriesPath是獲取分類路徑 * 比如說獲取的path數組,含有下面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); } // 監聽器 ChildListener zkListener = listeners.get(listener); if (zkListener == null) { listeners.putIfAbsent(listener, new ChildListener() { @Override public void childChanged(String parentPath, List currentChilds) { // 這里設置了監聽回調的地址,即回調給FailbackRegistry中的notify ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds)); } }); zkListener = listeners.get(listener); } // 根據路徑path創建節點 zkClient.create(path, false); // 添加子節點監聽器 List children = zkClient.addChildListener(path, zkListener); if (children != null) { urls.addAll(toUrlsWithEmpty(url, path, children)); } } // 通知消費者 // 在org.apache.dubbo.registry.support.FailbackRegistry.notify中發布操作 notify(url, listener, urls); }
會創建監聽器,當訂閱的服務有變更的時候,注冊中心就會調用com.alibaba.dubbo.registry.NotifyListener#notify方法,告訴消費者
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72629.html
摘要:服務暴露過程目標從源碼的角度分析服務暴露過程。導出服務,包含暴露服務到本地,和暴露服務到遠程兩個過程。其中服務暴露的第八步已經沒有了。將泛化調用版本號或者等信息加入獲得服務暴露地址和端口號,利用內數據組裝成。 dubbo服務暴露過程 目標:從源碼的角度分析服務暴露過程。 前言 本來這一篇一個寫異步化改造的內容,但是最近我一直在想,某一部分的優化改造該怎么去撰寫才能更加的讓讀者理解。我覺...
摘要:將標記為服務,使用對象來提供具體的服務。這整個過程算是該類的典型的執行過程。從上面得知服務發布的第一二個過程獲取注冊中心信息和協議信息。對于端來說,上述服務發布的第步中要解決的問題是根據指定協議向注冊中心注冊服務。 showImg(https://segmentfault.com/img/remote/1460000015274828?w=646&h=413); 上圖是服務提供者暴露服...
摘要:整體流程以調試來演示服務的發布流程。暴露遠程服務假如服務沒有配置了屬性,或者配置了但是值不是,就會執行遠程暴露。封裝了一個服務的相關信息,是一個服務可執行體。是一個服務域,他是引用和暴露的主要入口,它負責的生命周期管理。 整體流程以調試 om.alibaba.dubbo.demo.provider.DemoProvider來演示dubbo服務的發布流程。 1、啟動Spring容器 參照...
摘要:啟動容器,加載,運行服務提供者。服務提供者在啟動時,在注冊中心發布注冊自己提供的服務。注冊中心返回服務提供者地址列表給消費者,如果有變更,注冊中心將基于長連接推送變更數據給消費者。 一 為什么需要 dubbo 很多時候,其實我們使用這個技術的時候,可能都是因為項目需要,所以,我們就用了,但是,至于為什么我們需要用到這個技術,可能自身并不是很了解的,但是,其實了解技術的來由及背景知識,對...
摘要:統計服務的調用次調和調用時間的監控中心。調用關系說明服務容器負責啟動,加載,運行服務提供者。服務提供者在啟動時,向注冊中心注冊自己提供的服務。調度中心基于訪問壓力自動增減服務提供者。 隨著互聯網的發展,網站應用的規模不斷擴大,常規的垂直應用架構已無法應對,分布式服務架構以及流動計算架構勢在必行,亟需一個治理系統確保架構有條不紊的演進。showImg(https://segmentfau...
閱讀 3773·2021-11-23 09:51
閱讀 4386·2021-11-15 11:37
閱讀 3523·2021-09-02 15:21
閱讀 2746·2021-09-01 10:31
閱讀 879·2021-08-31 14:19
閱讀 852·2021-08-11 11:20
閱讀 3308·2021-07-30 15:30
閱讀 1689·2019-08-30 15:54