Dubbo源碼分析之 服務註冊(例如:Zookeeper 爲註冊中心)

參考: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文檔,自己也在學習,跟着記錄一下。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章