作者:黃金
Dubbo是一款典型的高擴充套件、高效能、高可用的RPC微服務架構,用於解決微服務架構下的服務治理與通訊問題。其核心模組包含 【RPC通訊】 和 【服務治理】 ,其中服務治理又分為服務註冊與發現、服務容錯、負載均衡、流量排程等。今天將重點介紹Dubbo的服務註冊與發現。
在介紹服務註冊發現之前,先簡單介紹一下貫穿整個Dubbo原始碼,也是Dubbo實現自適應擴充套件的核心--SPI機制,下圖為Dubbo SPI實現的簡單類圖。
•1、Dubbo SPI原理:通過讀取相應的組態檔找到具體實現類,然後通過以下兩種方式範例化物件:(1)通過自適應的動態位元組碼編譯技術,生成相應的動態代理類,(2)利用反射機制實現範例化。相較於Java SPI,Dubbo SPI實現了內部的IoC和Aop
•2、Dubbo SPI 優點:(1)高擴充套件:使用者可以根據實際業務需求擴充套件相應的實現模組,包含位元組碼編譯技術、rpc協定、通訊方式、註冊方式等,(2)解耦: 通過封裝SPI呼叫機制,架構上實現了上層應用與底層邏輯之間的解耦,為高擴充套件提供了支撐條件
•3、Dubbo SPI 常用樣例(以getExtension和getAdaptiveExtension為例)
組態檔內容
test1=com.dubbo.demo.service.TestServiceimpl
test2=com.dubbo.demo.service.TestServiceImpl2
一、通過getExtension方法生成範例
ExtensionLoader<TestService> extensionLoader = ExtensionLoader.getExtensionLoader(TestService.class);
TestService t1 = extensionLoader.getExtension("test1");
TestService t2 = extensionLoader.getExtension("test2");
二、通過getAdaptiveExtension生成範例(方法中需要@Adaptive註解,引數會對URL校驗)
TestService testService = ExtensionLoader.getExtensionLoader(TestService.class).getAdaptiveExtension();
URL url = new URL("test", "localhost", 8080, new String[]{"test.service", "test1"});
testService.sayHello("bbb", url);
呼叫getAdaptiveExtension方法最終會生成相應的代理類,最終生成的代理類會根據URL引數裡面的protocol決定(以內部Protocol為例)
首先需要範例化ServiceConfig範例,宣告「註冊介面、介面範例、註冊中心設定」,其中「ServiceBean」是實現Spring與Dubbo整合的橋樑。然後會由DubboBootstrap呼叫initialize方法實現configManager和Environment的初始化,其中就包括將ServiceConfig中的設定轉換成內部封裝的協定(ApplicationModel、ProviderModel等)
private static void startWithExport() throws InterruptedException {
//初始化設定
ServiceConfig<DemoServiceImpl> service = new ServiceConfig<>();
service.setInterface(DemoService.class);
service.setRef(new DemoServiceImpl());
service.setApplication(new ApplicationConfig("dubbo-demo-api-provider"));
service.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
//服務註冊入口
service.export();
}
public synchronized void export() {
if (bootstrap == null) {
bootstrap = DubboBootstrap.getInstance();
// compatible with api call.
if (null != this.getRegistry()) {
bootstrap.registries(this.getRegistries());
}
//初始化設定()
bootstrap.initialize();
}
......
if (shouldDelay()) {
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
//服務註冊
doExport();
}
exported();
}
根據初始化設定組轉註冊介面服務的URL。其中URL也是Dubbo內部通過@Adaptive註解實現SPI的核心,通過修改URL的頭部協定(如:register、dubbo、injvm等),在呼叫
private static final Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
PROTOCOL.export(wrapperInvoker)
該方法的時候,會根據不同的協定切換不通的實現類,實現了Dubbo技術架構與業務邏輯的解耦。
private void doExportUrls() {
//組裝後的URL格式樣例
//registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-api-provider&dubbo=2.0.2&pid=26212®istry=zookeeper×tamp=1663049763199
List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
int protocolConfigNum = protocols.size();
for (ProtocolConfig protocolConfig : protocols) {
//組裝pathKey : org.apache.dubbo.demo.DemoService
String pathKey = URL.buildKey(getContextPath(protocolConfig)
.map(p -> p + "/" + path)
.orElse(path), group, version);
//儲存介面服務
repository.registerService(pathKey, interfaceClass);
//服務註冊
doExportUrlsFor1Protocol(protocolConfig, registryURLs, protocolConfigNum);
}
}
通過內建的動態位元組碼編譯(預設javassist)生成Invoker代理類,然後通過反射機制生成Wrapper範例。其中Invoker是Dubbo的核心模型,Invoker是Dubbo中的實體域,也就是真實存在的。其他模型都向它靠攏或轉換成它
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs, int protocolConfigNum) {
......
//組裝新的URL
//dubbo://2.0.0.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&bind.ip=2.0.0.1&bind.port=20880&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=46528&release=&service.name=ServiceBean:/org.apache.dubbo.demo.DemoService&side=provider×tamp=1663051456562
URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
......
//Invoker封裝
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass,
registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
//wrapper
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
//服務註冊(此時URL頭部協定變成了register,實際會呼叫RegistryProtocol)
Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
exporters.add(exporter);
}
# PROXY_FACTORY
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// 動態代理類生成,反射生成範例
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
此時會依次呼叫RegistryProtocol 、DubboProtocol 將Invoker封裝成Exporter,並將封裝後的Exporter儲存到本地map中(類似於spring bean)。然後會呼叫底層通訊服務(預設netty)進行埠監聽,此時會通過責任鏈模式封裝Exchanger與Transporter,用於處理網路傳輸訊息的編碼/解碼。
# RegistryProtocol : export
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
......
//此時URL頭部協定已變成dubbo
//dubbo://2.0.0.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&bind.ip=2.0.0.1&bind.port=20880&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=56036&release=&service.name=ServiceBean:/org.apache.dubbo.demo.DemoService&side=provider×tamp=1663052353098
providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
// export invoker
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
// 此時Registry範例預設是ZookeeperRegistry
final Registry registry = getRegistry(originInvoker);
final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);
// decide if we need to delay publish
boolean register = providerUrl.getParameter(REGISTER_KEY, true);
if (register) {
//底層呼叫ZK,建立node節點
registry.register(registeredProviderUrl);
}
....
}
# RegistryProtocol : doLocalExport
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
String key = getCacheKey(originInvoker);
return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
//此時會呼叫DubboProtocol進行exporter封裝
return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
});
}
# DubboProtocol : export
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
......
// export service.
String key = serviceKey(url);
//exporter封裝
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.put(key, exporter);
......
//開啟服務監聽
openServer(url);
optimizeSerialization(url);
return exporter;
}
(5)步驟五:註冊服務節點
封裝Exporter並開啟伺服器埠監聽後,會呼叫註冊中心(預設Zookeeper)註冊服務節點資訊
# RegistryProtocol : export
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
......
//此時URL頭部協定已變成dubbo
//dubbo://2.0.0.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&bind.ip=2.0.0.1&bind.port=20880&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=56036&release=&service.name=ServiceBean:/org.apache.dubbo.demo.DemoService&side=provider×tamp=1663052353098
providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
// export invoker
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
// 此時Registry範例預設是ZookeeperRegistry
final Registry registry = getRegistry(originInvoker);
final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);
// decide if we need to delay publish
boolean register = providerUrl.getParameter(REGISTER_KEY, true);
if (register) {
//底層呼叫ZK,建立node節點
registry.register(registeredProviderUrl);
}
....
}
至此,Dubbo服務註冊的整體流程已大致結束,文中如有不當或者錯誤觀點,歡迎大家評論區指出。感興趣的同學,可以關注後續「Dubbo架構設計與原始碼解析」系列的文章。