參考:http://dubbo.apache.org/zh-cn/docs/source_code_guide/export-service.html
官方文檔是先分析服務導出,看了看官方文檔的源碼分析,服務導出是在是太複雜了,咱們暫且先看看服務註冊,
本節我們來分析服務註冊過程,服務註冊操作對於 Dubbo 來說不是必需的,通過服務直連的方式就可以繞過註冊中心。但通常我們不會這麼做,直連方式不利於服務治理,僅推薦在測試服務時使用。對於 Dubbo 來說,註冊中心雖不是必需,但卻是必要的。因此,關於註冊中心以及服務註冊相關邏輯,我們也需要搞懂。
本篇內容以 Zookeeper 註冊中心作爲分析目標,其他類型註冊中心大家可自行分析。下面從服務註冊的入口方法開始分析,我們把目光移到 RegistryProtocol 的 export 方法上。
如下:
/** * 此方法是dubbo服務導出與註冊的方法 * @param originInvoker * @param <T> * @return * @throws RpcException */ @Override public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException { // ${導出服務} // 省略其他代碼 //to judge if we need to delay publish boolean register = registeredProviderUrl.getParameter("register", true); if (register) { //註冊服務 register(registryUrl, registeredProviderUrl); providerInvokerWrapper.setReg(true); } // Deprecated! Subscribe to override rules in 2.6.x or before. registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener); exporter.setRegisterUrl(registeredProviderUrl); exporter.setSubscribeUrl(overrideSubscribeUrl); //Ensure that a new exporter instance is returned every time export return new DestroyableExporter<>(exporter); }
更進register方法
/** * register 方法包含兩步操作,第一步是獲取註冊中心實例,第二步是向註冊中心註冊服務。接下來分兩節內容對這兩步操作進行分析。 * @param registryUrl * @param registeredProviderUrl */ public void register(URL registryUrl, URL registeredProviderUrl) { // 獲取 Registry Registry registry = registryFactory.getRegistry(registryUrl); // 註冊服務 registry.register(registeredProviderUrl); }
1、創建註冊中心
本節內容以 Zookeeper 註冊中心爲例進行分析。下面先來看一下 getRegistry 方法的源碼,這個方法由 AbstractRegistryFactory 實現。
@Override public Registry getRegistry(URL url) { url = URLBuilder.from(url) .setPath(RegistryService.class.getName()) .addParameter(INTERFACE_KEY, RegistryService.class.getName()) .removeParameters(EXPORT_KEY, REFER_KEY) .build(); String key = url.toServiceStringWithoutResolving(); // Lock the registry access process to ensure a single instance of the registry LOCK.lock(); try { // 訪問緩存 Registry registry = REGISTRIES.get(key); //看是否緩存是否命中,命中就直接返回 if (registry != null) { return registry; } //create registry by spi/ioc // 緩存未命中創建 Registry 實例 registry = createRegistry(url); if (registry == null) { throw new IllegalStateException("Can not create registry " + url); } // 寫回入緩存 REGISTRIES.put(key, registry); return registry; } finally { // Release the lock LOCK.unlock(); } }
跟進createRegistry方法,看看是怎麼實現的
是一個模板方法,具體功能由子類實現。
可以看出註冊有很多,比如redis等,這裏我就以Zk的爲例。
點進去看zk的實現
@Override public Registry createRegistry(URL url) { // 創建 ZookeeperRegistry return new ZookeeperRegistry(url, zookeeperTransporter); }
直接調用了一個ZookeeperRegistry的構造函數。繼續跟進如下:
public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) { super(url); if (url.isAnyHost()) { throw new IllegalStateException("registry address == null"); } // 獲取組名,默認爲 dubbo String group = url.getParameter(GROUP_KEY, DEFAULT_ROOT); if (!group.startsWith(PATH_SEPARATOR)) { // group = "/" + group group = PATH_SEPARATOR + group; } this.root = group; // 創建 Zookeeper 客戶端,默認爲 CuratorZookeeperTransporter /** * 這裏的 zookeeperTransporter 類型爲自適應拓展類,因此 connect 方法會在被調用時決定加載什麼類型的 ZookeeperTransporter 拓展, * 默認爲 CuratorZookeeperTransporter。 */ zkClient = zookeeperTransporter.connect(url); // 添加狀態監聽器 zkClient.addStateListener(state -> { //判斷狀態是否是再次連接 if (state == StateListener.RECONNECTED) { try { recover(); } catch (Exception e) { logger.error(e.getMessage(), e); } } }); }
在上面的代碼代碼中,我們重點關注 ZookeeperTransporter 的 connect 方法調用,這個方法用於創建 Zookeeper 客戶端。創建好 Zookeeper 客戶端,意味着註冊中心的創建過程就結束了。接下來,再來分析一下 Zookeeper 客戶端的創建過程。
這裏的 zookeeperTransporter 類型爲自適應拓展類,因此 connect 方法會在被調用時決定加載什麼類型的 ZookeeperTransporter 拓展,默認爲 CuratorZookeeperTransporter。下面我們到 CuratorZookeeperTransporter 中看一看。
跟進connect方法
@SPI("curator") public interface ZookeeperTransporter { @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY}) ZookeeperClient connect(URL url); }
這是dubbo的SPI調用,找到實現類
@Override public ZookeeperClient connect(URL url) { ZookeeperClient zookeeperClient; List<String> addressList = getURLBackupAddress(url); // The field define the zookeeper server , including protocol, host, port, username, password //字段定義了zookeeper服務器,包括協議、主機、端口、用戶名、密碼 if ((zookeeperClient = fetchAndUpdateZookeeperClientCache(addressList)) != null && zookeeperClient.isConnected()) { logger.info("find valid zookeeper client from the cache for address: " + url); return zookeeperClient; } // avoid creating too many connections, so add lock //避免創建時出現併發問題,所以添加lock synchronized (zookeeperClientMap) { if ((zookeeperClient = fetchAndUpdateZookeeperClientCache(addressList)) != null && zookeeperClient.isConnected()) { logger.info("find valid zookeeper client from the cache for address: " + url); return zookeeperClient; } zookeeperClient = createZookeeperClient(toClientURL(url)); logger.info("No valid zookeeper client found from cache, therefore create a new client for url. " + url); writeToClientMap(addressList, zookeeperClient); } return zookeeperClient; }
CuratorZookeeperClient 構造方法主要用於創建和啓動 CuratorFramework 實例。以上基本上都是 Curator 框架的代碼。
不熟悉Curator框架的可以看一篇這個博客:地址:https://blog.csdn.net/qq_34021712/article/details/82872311
本節分析了 ZookeeperRegistry 實例的創建過程。現在註冊中心實例創建好了,接下來要做的事情是向註冊中心註冊服務,我們繼續往下看。
2、節點創建
以 Zookeeper 爲例,所謂的服務註冊,本質上是將服務配置數據寫入到 Zookeeper 的某個路徑的節點下。
閱讀服務註冊的代碼。服務註冊的接口爲 register(URL),這個方法定義在 FailbackRegistry 抽象類中。代碼如下:
//服務註冊的接口爲 register(URL) @Override public void register(URL url) { super.register(url); removeFailedRegistered(url); removeFailedUnregistered(url); try { // Sending a registration request to the server side // 模板方法,由子類實現 doRegister(url); } catch (Exception e) { Throwable t = e; // If the startup detection is opened, the Exception is thrown directly. // 獲取 check 參數,若 check = true 將會直接拋出異常 boolean check = getUrl().getParameter(Constants.CHECK_KEY, true) && url.getParameter(Constants.CHECK_KEY, true) && !CONSUMER_PROTOCOL.equals(url.getProtocol()); boolean skipFailback = t instanceof SkipFailbackWrapperException; if (check || skipFailback) { if (skipFailback) { t = t.getCause(); } throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t); } else { logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t); } // Record a failed registration request to a failed list, retry regularly // 記錄註冊失敗的鏈接 addFailedRegistered(url); } }
上訴代碼,我們關注doRegister方法,這個也是個模板方法,畢竟do開頭的方法一般都是具體的實現邏輯
找到Zk實現下,代碼如下:
@Override public void doRegister(URL url) { try { // 通過 Zookeeper 客戶端創建節點,節點路徑由 toUrlPath 方法生成,路徑格式如下: // /${group}/${serviceInterface}/providers/${url} // 比如 // /dubbo/org.apache.dubbo.DemoService/providers/dubbo%3A%2F%2F127.0.0.1...... zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true)); } catch (Throwable e) { throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e); } }
上訴代碼主要是看create方法,ZookeeperRegistry 在 doRegister 中調用了 Zookeeper 客戶端創建服務節點。節點路徑由 toUrlPath 方法生成,跟進如下:
@Override public void create(String path, boolean ephemeral) { if (!ephemeral) { //判斷節點路徑是否已經被包含了 if(persistentExistNodePath.contains(path)){ return; } // 如果要創建的節點類型非臨時節點,那麼這裏要檢測節點是否存在 if (checkExists(path)) { persistentExistNodePath.add(path); return; } } int i = path.lastIndexOf('/'); if (i > 0) { // 遞歸創建上一級路徑 create(path.substring(0, i), false); } // 根據 ephemeral 的值創建臨時或持久節點 if (ephemeral) { //臨時節點 createEphemeral(path); } else { //持久節點 createPersistent(path); //添加持久節點 persistentExistNodePath.add(path); } }
使用過zk的走着的zk節點有兩種,一種是臨時節點,重啓就消失,一種是持久節點,不刪除會一直存在
跟進createEphemeral方法,此是創建臨時節點
@Override public void createEphemeral(String path) { try { // 通過 Curator 框架創建節點 client.create() .withMode(CreateMode.EPHEMERAL)//節點類型,持久節點 .forPath(path); } catch (NodeExistsException e) { } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); } }
跟進createPersistent方法,此是創建持久節點
//創建持久節點 @Override public void createPersistent(String path) { try { client.create() .forPath(path); } catch (NodeExistsException e) { } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); } }
到此本篇也就結束了,主要是參考了官網http://dubbo.apache.org/zh-cn/docs/source_code_guide/export-service.html文檔,自己也在學習,跟着記錄一下。