飼育員へのサービス輸出登録
- ServiceConfig#export エクスポートは、レイヤーごとに最後まで呼び出し、RegistryProtocol#export を呼び出してサービスをエクスポートします。前の記事で説明し、コンテナーの起動についても説明しました。
- 私たちが使用している飼育員登録センターにより、ダボはサービス情報を登録センターに登録します。
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
URL registryUrl = getRegistryUrl(originInvoker); //
URL providerUrl = getProviderUrl(originInvoker); // dubbo://192.168.40.17:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-provider-application&bean.name=ServiceBean:org.apache.dubbo.demo.DemoService&bind.ip=192.168.40.17&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&logger=log4j&methods=sayHello&pid=27656&release=2.7.0&side=provider&timeout=3000×tamp=1590735956489
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
// export invoker
// 根据动态配置重写了providerUrl之后,就会调用DubboProtocol或HttpProtocol去进行导出服务了
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
// url to registry
// 得到注册中心-ZookeeperRegistry
final Registry registry = getRegistry(originInvoker);
// 得到存入到注册中心去的providerUrl,会对服务提供者url中的参数进行简化
final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);
// 将当前服务提供者Invoker,以及该服务对应的注册中心地址,以及简化后的服务url存入ProviderConsumerRegTable
ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
registryUrl, registeredProviderUrl);
//to judge if we need to delay publish
//是否需要注册到注册中心
boolean register = providerUrl.getParameter(REGISTER_KEY, true);
if (register) {
// 注册服务,把简化后的服务提供者url注册到registryUrl中去
register(registryUrl, registeredProviderUrl);
providerInvokerWrapper.setReg(true);
}
//监听内容
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);
}
Registry#getRegistry(originInvoker)
目的: レジストリ インスタンス オブジェクトを取得する;
作業:
- レジストリ URL を取得します。
- ファクトリ モードを通じて、レジストリ オブジェクトを作成します。
private Registry getRegistry(final Invoker<?> originInvoker) {
URL registryUrl = getRegistryUrl(originInvoker);
return registryFactory.getRegistry(registryUrl);
}
private URL getRegistryUrl(Invoker<?> originInvoker) {
// 将registry://xxx?xx=xx®istry=zookeeper 转为 zookeeper://xxx?xx=xx
URL registryUrl = originInvoker.getUrl();
if (REGISTRY_PROTOCOL.equals(registryUrl.getProtocol())) {
String protocol = registryUrl.getParameter(REGISTRY_KEY, DEFAULT_REGISTRY);
registryUrl = registryUrl.setProtocol(protocol).removeParameter(REGISTRY_KEY);
}
return registryUrl;
}
RegistryFactory#getRegistry(registryUrl)
インターフェースです。
@SPI("dubbo")
public interface RegistryFactory {
@Adaptive({
"protocol"})
Registry getRegistry(URL url);
}
- ZookeeperRegistryFactory クラスは AbstractRegistryFactory 抽象クラスを実装し、AbstractRegistryFactory は RegistryFactory インターフェイスを実装します。
- getRegistry メソッドを呼び出すと、ZookeeperRegistryFactory 親クラス AbstractRegistryFactory の getRegistry メソッドが呼び出されます。
- AbstractRegistryFactory#getRegistry は、レジストリを作成するための共通ロジックを定義します。レジストリにはいくつかの種類があるため、レジストリの createRegistry ロジックはサブクラスに渡されて実装されます。これは、ファクトリ モードの通常の方法です。
- ReentrantLock を使用してスレッドの安全性を確保し、レジストリ インスタンスの繰り返し作成を回避します。
@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 = 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();
}
}
protected abstract Registry createRegistry(URL url);
ZookeeperRegistryFactory#createRegistry
ZookeeperRegistry インスタンスを作成しました。
public class ZookeeperRegistryFactory extends AbstractRegistryFactory {
private ZookeeperTransporter zookeeperTransporter;
/**
* Invisible injection of zookeeper client via IOC/SPI
* @param zookeeperTransporter
*/
public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
this.zookeeperTransporter = zookeeperTransporter;
}
@Override
public Registry createRegistry(URL url) {
return new ZookeeperRegistry(url, zookeeperTransporter);
}
}
ZookeeperRegistry インスタンスを作成します。
- super(url) を呼び出して、いくつかのパブリック設定をロードします。
- グループ名を取得します。デフォルト値は「dubbo」です。
- グループ名が「/」で始まるかどうかを確認し、そうでない場合は「/」を結合します。
- Zookeeper レジストリのルート ノードはグループ名 group に設定されます。
- 登録センターに接続します。
- 接続が成功したかどうかを監視し、接続が失敗した場合は再接続します。
public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
super(url);
if (url.isAnyHost()) {
throw new IllegalStateException("registry address == null");
}
String group = url.getParameter(GROUP_KEY, DEFAULT_ROOT);
if (!group.startsWith(PATH_SEPARATOR)) {
group = PATH_SEPARATOR + group;
}
this.root = group;
zkClient = zookeeperTransporter.connect(url);
zkClient.addStateListener(state -> {
if (state == StateListener.RECONNECTED) {
try {
recover();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
});
}
ZookeeperRegistry インスタンスを作成した後、レイヤーごとに戻り、レジストリを取得した場所に戻ります。
RegistryProtocol#getRegisteredProviderUrl
目的: URL 内の不要なデータを簡略化して削除するため。
// 得到存入到注册中心去的providerUrl,会对服务提供者url中的参数进行简化
final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);
仕事:
- RegistryProtocol が Simplified で構成されているかどうかを確認します。これはデフォルトでは false です。
- URL の「.」で始まるキーと、monitor、bind.ip、bind.port、qos.enable、qos_host、qos_port、qos.accept.foreign.ip、validation、interfaces のキーをフィルタリングします。フィルタリング後、残りのキーは削除するキーです。
- URL 内の削除するキーを削除します。
- 戻る
/**
* Return the url that is registered to the registry and filter the url parameter once
* 得到能存入到注册中心去的providerUrl
* @param providerUrl
* @return url to registry.
*/
private URL getRegisteredProviderUrl(final URL providerUrl, final URL registryUrl) {
//The address you see at the registry
// 默认都是走这里
if (!registryUrl.getParameter(SIMPLIFIED_KEY, false)) {
// 移除key以"."开始的参数,以及其他跟服务本身没有关系的参数,比如监控,绑定ip,qosd等等
return providerUrl.removeParameters(getFilteredKeys(providerUrl)).removeParameters(
MONITOR_KEY, BIND_IP_KEY, BIND_PORT_KEY, QOS_ENABLE, QOS_HOST, QOS_PORT, ACCEPT_FOREIGN_IP, VALIDATION_KEY,
INTERFACES);
} else {
String extraKeys = registryUrl.getParameter(EXTRA_KEYS_KEY, "");
// if path is not the same as interface name then we should keep INTERFACE_KEY,
// otherwise, the registry structure of zookeeper would be '/dubbo/path/providers',
// but what we expect is '/dubbo/interface/providers'
// 如果path不等于interface的值,那么则把INTERFACE_KEY添加到extraKeys中
if (!providerUrl.getPath().equals(providerUrl.getParameter(INTERFACE_KEY))) {
if (StringUtils.isNotEmpty(extraKeys)) {
extraKeys += ",";
}
extraKeys += INTERFACE_KEY;
}
// paramsToRegistry包括了DEFAULT_REGISTER_PROVIDER_KEYS和extraKeys
String[] paramsToRegistry = getParamsToRegistry(DEFAULT_REGISTER_PROVIDER_KEYS
, COMMA_SPLIT_PATTERN.split(extraKeys));
// 生成只含有paramsToRegistry的对应的参数,并且该参数不能为空
return URL.valueOf(providerUrl, paramsToRegistry, providerUrl.getParameter(METHODS_KEY, (String[]) null));
}
}
//过滤掉以"."开头的key, 返回一个数组;
private static String[] getFilteredKeys(URL url) {
Map<String, String> params = url.getParameters();
// 过滤url的参数,找到startsWith(HIDE_KEY_PREFIX)的key
if (CollectionUtils.isNotEmptyMap(params)) {
return params.keySet().stream()
.filter(k -> k.startsWith(HIDE_KEY_PREFIX))
.toArray(String[]::new);
} else {
return new String[0];
}
}
ProviderConsumerRegTable.registerProvider(originInvoker,registryUrl, registeredProviderUrl)
目的: 現在のサービス プロバイダーの呼び出し者、サービスに対応する登録センターのアドレス、および簡略化されたサービス URL は、ProviderConsumerRegTable ワークに保存されます
。
- ProviderInvokerWrapper インスタンスを作成します。これは、登録された URL、サービス エグゼキュータ、および簡略化された URL をラップします。
- ProviderUrl で serviceKey を取得します。
- キーに従ってキャッシュ ProviderInvokers からマップを取得します。
- 空の場合は、空の ConcurrentHashMap を入力し、キーに従って ConcurrentHashMap を取り出します。
- 呼び出し元をキーとして、wrapperInvoker ラッパー インスタンスを値として受け取り、それを呼び出し元、つまり新しく作成された ConcurrenthashMap に入れます。
public static <T> ProviderInvokerWrapper<T> registerProvider(Invoker<T> invoker, URL registryUrl, URL providerUrl) {
ProviderInvokerWrapper<T> wrapperInvoker = new ProviderInvokerWrapper<>(invoker, registryUrl, providerUrl);
String serviceUniqueName = providerUrl.getServiceKey();
// 根据
ConcurrentMap<Invoker, ProviderInvokerWrapper> invokers = providerInvokers.get(serviceUniqueName);
if (invokers == null) {
providerInvokers.putIfAbsent(serviceUniqueName, new ConcurrentHashMap<>());
invokers = providerInvokers.get(serviceUniqueName);
}
invokers.put(invoker, wrapperInvoker);
return wrapperInvoker;
}
register(registryUrl, registeredProviderUrl)
Zookeeper レジストリに登録します。
- REGISTER_KEY の値を取得して、登録するかどうかを決定します。
- 登録が必要な場合は、簡略化されたサービス URL を登録センターに入力します。
boolean register = providerUrl.getParameter(REGISTER_KEY, true);
if (register) {
// 注册服务,把简化后的服务提供者url注册到registryUrl中去
register(registryUrl, registeredProviderUrl);
providerInvokerWrapper.setReg(true);
}
- まず、registryFactory.getRegistry を呼び出してレジストリ インスタンスを取得します。レジストリ インスタンスは作成されてキャッシュに配置されているため、このステップではマップから直接取得できます。
- ZookeeperRegistry#register メソッドが呼び出されます。
public void register(URL registryUrl, URL registeredProviderUrl) {
Registry registry = registryFactory.getRegistry(registryUrl);
// 调用ZookeeperRegistry的register方法
registry.register(registeredProviderUrl);
}
ZookeeperRegistry#register(登録済みプロバイダー URL)
- ZookeeperRegistry 自体には register メソッドはありませんが、FailbackRegistry の親クラスを継承しており、親クラスに register メソッドが実装されています
- したがって、ZookeeperRegistry を呼び出すと、実際には親クラス FailbackRegistry の register メソッドが呼び出されます。
- doRegister(url) メソッドが呼び出されます。登録センターが複数あるため汎用的な操作は不可能なので、実装ロジックはサブクラスによって実装されます。つまり、ZookeeperRegistry#doRegister(url) メソッドが呼び出されます。
- 個人的な意見: 共通のファクトリーモデルは設計コンセプトでもあります。
//FailbackRegistry#register
@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;
//省略部分代码;
// Record a failed registration request to a failed list, retry regularly
addFailedRegistered(url);
}
}
ZookeeperRegistry#doRegister(url)
目的: Zookeeper にリクエストを送信し、toUrlPath(url) の値が動的である構成ノードを作成します。
@Override
public void doRegister(URL url) {
try {
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);
}
}
toUrlPath(url)
ノードの値を取得します。
//"/dubbo/com.demo.DemoService/providers/#{url}"
// toCategoryPath(url) ⇒ "/dubbo/com.demo.DemoService/providers"
// PATH_SEPARATOR ==> "/"
//URL.encode(url.toFullString())==>对url进行中文编码
private String toUrlPath(URL url) {
return toCategoryPath(url) + PATH_SEPARATOR + URL.encode(url.toFullString());
}
//返回的结果:"/dubbo/com.demo.DemoService/providers"
// toServicePath(url) ⇒ "/dubbo/com.demo.DemoService"
// PATH_SEPARATOR ==> "/"
// 获取URL的catogory的值, 默认为providers;
private String toCategoryPath(URL url) {
return toServicePath(url) + PATH_SEPARATOR + url.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY);
}
//结果: "/dubbo/com.demo.DemoService"
//toRootDir() ---> "/dubbo/"
//URL.encode(name) --> 对URL的name属性进行URL编码; name的值为 com.demo.DemoService
private String toServicePath(URL url) {
String name = url.getServiceInterface();
if (ANY_VALUE.equals(name)) {
return toRootPath();
}
return toRootDir() + URL.encode(name);
}
//结果为:根节点 的值 + “/”, 这里为 "/dubbo/"
private String toRootDir() {
if (root.equals(PATH_SEPARATOR)) {
return root;
}
return root + PATH_SEPARATOR;
}
サービスエクスポートのソースコードプロセス
- ServiceBean.export() メソッドはエクスポートのエントリ メソッドです。ServiceConfig.export() メソッドを実行してサービスのエクスポートを完了します。エクスポートが完了すると、Spring イベント ServiceBeanExportedEvent が発行されます。
- ServiceConfig.export() メソッドでは、checkAndUpdateSubConfigs() が最初に呼び出されます。このメソッドは主に AbstractConfig のパラメータの更新 (構成センターからのパラメータの取得など) を完了します。AbstractConfig は、ApplicationConfig、ProtocolConfig、ServiceConfig などを指します。更新すると、スタブ、ローカル、モック、その他のパラメーターが正しく構成されているかどうかがチェックされます
- パラメーターが更新されチェックされた後、サービスのエクスポートが開始されます。遅延エクスポートが構成されている場合は、ScheduledExecutorService を使用して、指定された時刻に遅延エクスポートが実行されます。
- それ以外の場合は、 doExport() を呼び出してサービスをエクスポートします。
- サービスのエクスポートのために doExportUrls() を呼び出し続けます。
- まず、loadRegistries() メソッドを使用して、構成された登録センターの URL を取得します。構成センターが複数ある場合があるため、現在エクスポートされているサービスを各構成センターに登録する必要があります。ここでは、登録センターは URL で表されます。はい、使用される登録センター、登録センターのアドレスとポート、登録センターに設定されたパラメータなどが URL 上に存在し、この URL は registry:// で始まります。
- レジストリの registryURL を取得した後、現在のサービスのすべての ProtocolConfig を走査し、 doExportUrlsFor1Protocol(protocolConfig, registryURLs); メソッドを呼び出して、各プロトコルと各レジストリに従って現在のサービスをエクスポートします。
doExportUrlsFor1Protocol() メソッドでは、 a. サービスのプロトコル dubbo://、
b. サービスの IP とポート、IP、
c. およびサービスの PATH を含むサービス URL が最初に構築されます。 PATH パラメータが指定されていない場合は、インターフェイス名
d. とサービスのパラメータを受け取ります。パラメータには、サービスのパラメータとサービス内のメソッドのパラメータ
e が含まれます。結果の URL は次のようになります: dubbo:///192.168 .1.110:20880/com.tuling.DemoService?timeout=3000&&sayHello.loadbalance=random- サービスの URL を取得した後、サービス URL がパラメータとして registryURL に追加され、次に registryURL、サービス インターフェイス、および現在のサービス実装クラス参照から Invoker プロキシ オブジェクトが生成され、プロキシ オブジェクトと現在の ServiceConfig オブジェクトは DelegateProviderMetaDataInvoker オブジェクトにパッケージ化されます。DelegateProviderMetaDataInvoker は完全なサービスを表します
- 次に、プロトコルを使用してエクスポート サービスをエクスポートします。エクスポート後、Exporter オブジェクトを取得します (Exporter オブジェクトは、主にサービスをアンエクスポート (アンエクスポート) するために使用されると理解できます。サービスはいつアンインストールされますか? Dubbo アプリケーションを正常に実行します)
- 次に、Protocol がサービスをエクスポートする方法を詳しく見てみましょう。
- ただし、protocol.export(wrapperInvoker) メソッドが呼び出されるとき、protocol は Protocol インターフェースの Adaptive オブジェクトであるため、この時点では、wrapperInvoker の genUrl メソッドに従って URL が取得され、対応する拡張ポイントは次のように検索されます。この URL のプロトコル。現時点では、拡張ポイントは RegistryProtocol ですが、Protocol インターフェイスには ProtocolFilterWrapper と ProtocolListenerWrapper の 2 つのラッパー クラスがあるため、エクスポート メソッドが呼び出されるとき、これら 2 つのラッパー クラスのエクスポート メソッドはと進みますが、この2つのラッパークラスのexportメソッドではレジストリプロトコルが判断されてあまり処理は行わないので、最終的にはRegistryProtocolのexport(InvokeroriginInvoker)メソッドを直接呼び出します。
- RegistryProtocol の import(InvokeroriginInvoker) メソッドでは、主に次のことが行われます:
a. 動的構成センターでこのサービスのパラメーター データの変更を監視するためのリスナーを生成し、変更が検出されると、サービス URL がb . 書き換えられ
た URL を取得した後、doLocalExport() を呼び出してサービスをエクスポートします。このメソッドでは、DubboProtocol のエクスポート メソッドが呼び出され、サービスをエクスポートします。エクスポートが成功すると、ExporterChangeableWrapper を取得します
ⅰ. DubboProtocol のエクスポート メソッドで行う主な作業は、NettyServer を起動し、一連の RequestHandler を設定して、リクエストを受信したときにこれらの RequestHandler によって順番に処理できるようにすることです ii
.これらの RequestHandler は上記で整理されています
c. ZookeeperRegistry など、originInvoker からレジストリの実装クラスを取得します
d. 書き換えられたサービス URL を簡素化し、レジストリに保存する必要のないパラメータを削除します
e. ZookeeperRegistry.registry を呼び出します簡略化されたサービス URL の () メソッド 登録センターに登録し、
f に進みます。最後に、ExporterChangeableWrapper を DestroyableExporter オブジェクトとしてカプセル化して返し、サービスのエクスポートを完了します。
エクスポーターのアーキテクチャ
サービスが正常にエクスポートされると、対応するエクスポーターが生成されます。
- DestroyableExporter: Exporter の最も外側のラッパー クラス。このクラスの主な機能は、unexporter に対応するサービスに使用されます。
- ExporterChangeableWrapper: このクラスは主に、レジストリからサービス URL を削除し、対応するサービスをアンエクスポートする前にサービスに対応する動的構成リスナーを削除する役割を果たします。
- ListenerExporterWrapper: このクラスは主に、対応するサービスをアンエクスポートした後にサービス エクスポート リスナーを削除する役割を果たします。
- DubboExporter: このクラスには、対応するサービスの Invoker オブジェクトと現在のサービスの一意の識別子が格納されます。NettyServer がリクエストを受信すると、リクエスト内のサービス情報に従ってサービスに対応する DubboExporter オブジェクトを検索し、オブジェクトからの呼び出し元オブジェクト。
要約する
- サービスのエクスポートではコンストラクター モード、リフレクション、およびファクトリー モードが使用され、コードも非常に多用途です。
- サービス エクスポートのプロセスについては大体理解できましたが、Dubbo 設定の動的な変更に関しては、Zookeeper の主要な機能である監視メカニズムが関係しています。registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener) レジストリ構成をサブスクライブすると、同時にリスナーが存在します。構成が変更されると、overrideSubscribeListener リスナーがトリガーされて構成が再インポートされます。