本片文章主要讲解dubbo 服务提供者如何发布服务的,本文是基于dubbo2.7.8release版本剖析
服务发布的入口:
DubboBootstrapApplicationListener:这个就是在spring项目加载阶段dubbo服务发布的入口,是基于spring的事件机制实现的,也就是在spring的上下文刷新完成后,spring会发布一个上下文刷新完成事件ContextRefreshedEvent。
第1步:DubboBootstrapApplicationListener接收到spring会发布一个上下文刷新完成事件ContextRefreshedEvent:
@Override
public void onApplicationContextEvent(ApplicationContextEvent event) {
if (event instanceof ContextRefreshedEvent) {
onContextRefreshedEvent((ContextRefreshedEvent) event);
} else if (event instanceof ContextClosedEvent) {
onContextClosedEvent((ContextClosedEvent) event);
}
}
DubboBootstrapApplicationListener构造函数:
public DubboBootstrapApplicationListener() {
this.dubboBootstrap = DubboBootstrap.getInstance();
}
第2步:DubboBootstrapApplicationListener中onContextRefreshedEvent:
private void onContextRefreshedEvent(ContextRefreshedEvent event) {
而创建dubboBootstrap的时候,DubboBootstrap的构造函数中会获取到一个
配置管理ConfigManager + Environment实例赋值到dubboBootstrap实例的
成员属性中。
dubboBootstrap.start();
}
DubboBootstrap构造函数:
private DubboBootstrap() {
configManager = ApplicationModel.getConfigManager();
environment = ApplicationModel.getEnvironment();
DubboShutdownHook.getDubboShutdownHook().register();
ShutdownHookCallbacks.INSTANCE.addCallback(new ShutdownHookCallback() {
@Override
public void callback() throws Throwable {
DubboBootstrap.this.destroy();
}
});
}
第3步:DubboBootstrap的start()方法:
public DubboBootstrap start() {
如果当前dubbo服务没有被启动过,那就准备启动,使用CAS的原因是避免多线程的时候造成重复 启动,可借鉴思想
if (started.compareAndSet(false, true)) {
ready.set(false);
1、初始化,比如启动配置中心、载入远程配置、检查全局配置、启动元数据中心、初始化元数据服务等等。。。。
initialize();
if (logger.isInfoEnabled()) {
logger.info(NAME + " is starting...");
}
// 1. export Dubbo Services
2、开发提供者服务。
exportServices();
// Not only provider register
if (!isOnlyRegisterProvider() || hasExportedServices()) {
// 2. export MetadataService
3、如果不仅仅是服务提供者,那就发布元数据服务MetadataService
exportMetadataService();
//3. Register the local ServiceInstance if required
4、如果需要的话注册服务实例在本地。
registerServiceInstance();
}
5、引用服务,如果一个应用即是提供者也是消费者那么此处就需要服务消费,服务消费后面我们在进行详细分析。
referServices();
if (asyncExportingFutures.size() > 0) {
new Thread(() -> {
try {
this.awaitFinish();
} catch (Exception e) {
logger.warn(NAME + " exportAsync occurred an exception.");
}
ready.set(true);
if (logger.isInfoEnabled()) {
logger.info(NAME + " is ready.");
}
}).start();
} else {
ready.set(true);
if (logger.isInfoEnabled()) {
logger.info(NAME + " is ready.");
}
}
if (logger.isInfoEnabled()) {
logger.info(NAME + " has started.");
}
}
return this;
}
第4步:DubboBootstrap的exportServices()方法:
private void exportServices() {
从配置管理器中获取到需要发布的服务列表,然后循环进行每一个服务发布,dubbo的每一个配置
在出海刷完成后都会将其添加到configManager的configsCache的Map属性中,当然初始化的过程也是使用
spring的一些基础组件来实现的,因此我们能够在这里通过configManager获取到我们所需要发布的服务列
表,服务列表中都是使用一个ServiceBean来进行封装的,ServiceBean又继承了ServiceConfig类,因此也
就有了服务发布的功能。
configManager.getServices().forEach(sc -> {
// TODO, compatible with ServiceConfig.export()
1、先将ServiceBean实例强转成父类ServiceConfig,然后设置这个ServiceBean的bootstrap属性为当前的Bootstrap实例。
ServiceConfig serviceConfig = (ServiceConfig) sc;
serviceConfig.setBootstrap(this);
if (exportAsync) {
2、如果需要异步发布就获取一个服务发布线程池进行服务的异步发布。
ExecutorService executor = executorRepository.getServiceExporterExecutor();
Future<?> future = executor.submit(() -> {
sc.export();
exportedServices.add(sc);
});
asyncExportingFutures.add(future);
} else {
3、同步进行服务发布。
sc.export();
4、记录以及发布好的服务。
exportedServices.add(sc);
}
});
}
第5步:ServiceConfig的export()方法:
public synchronized void export() {
if (!shouldExport()) {
return;
}
1、如果当前ServiceBean的bootstrap 属性等于null,那就现场直播创建+初始化一个,一般此处
应该不会为空,因为在前面一步O我们进行了设置。
if (bootstrap == null) {
bootstrap = DubboBootstrap.getInstance();
bootstrap.initialize();
}
2、检查当前发布的服务的配置的子项是否正确。
checkAndUpdateSubConfigs();
3、设置当前发布的服务的元数据。
//init serviceMetadata
serviceMetadata.setVersion(getVersion());
serviceMetadata.setGroup(getGroup());
serviceMetadata.setDefaultGroup(getGroup());
serviceMetadata.setServiceType(getInterfaceClass());
serviceMetadata.setServiceInterfaceName(getInterface());
serviceMetadata.setTarget(getRef());
if (shouldDelay()) {
4、如果配置了当前服务进行延迟发布的话,那就是用延迟发布线程池来进行延迟发布。
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
5、正常发布服务。
doExport();
}
6、发布当前服务完成后,进行一个处理,就是发布一个服务发布完成事件
ServiceConfigExportedEvent
exported();
}
第6步:ServiceConfig的doExport()方法:
protected synchronized void doExport() {
if (unexported) {
throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
}
if (exported) {
return;
}
exported = true;
if (StringUtils.isEmpty(path)) {
path = interfaceName;
}
开始发布各种url
doExportUrls();
}
第7步:ServiceConfig的doExportUrls()方法:
private void doExportUrls() {
1、获取一个服务仓库,ServiceRepository的构造函数会默认添加回声测试服务EchoService +
泛化服务GenericService两个服务到当前的服务仓库中。
ServiceRepository repository = ApplicationModel.getServiceRepository();
2、注册当前所需发布的服务到服务仓库中。
ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass());
3、注册当前提供者到服务仓库中。
repository.registerProvider(
getUniqueServiceName(),
ref,
serviceDescriptor,
this,
serviceMetadata
);
4、获取服务注册与发现的服务URL列表,由于返回是一个集合,因此代表dubbo可支持多注册中
心。注意此处返回的URL实例是"registry"协议的,url样例:registry://127.0.0.1:8848/org.apache
.dubbo.registry.RegistryService?application=provider&dubbo=2.0.2&pid=16036&qos.enable
=false®istry=nacos&release=2.7.8×tamp=1599662197066, 比如我们配置的是:
nacos://127.0.0.1:8848 经过处理后就是样例中的结果。
List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
5、循环进行协议发布,这里是dubbo支持多协议的实现。
for (ProtocolConfig protocolConfig : protocols) {
构建一个pathKey,pathKey的格式:服务名称/group/version
String pathKey = URL.buildKey(getContextPath(protocolConfig)
.map(p -> p + "/" + path)
.orElse(path), group, version);
// In case user specified path, register service one more time to map it to path.
注册解析好group+version的服务注册到服务仓库中。
repository.registerService(pathKey, interfaceClass);
// TODO, uncomment this line once service key is unified
设置当前服务的元数据的服务key。
serviceMetadata.setServiceKey(pathKey);
发布当前协议的服务到服务的注册与发现中心。
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
第8步:ServiceConfig的doExportUrlsFor1Protocol(...)方法:使用协议配置+注册中心地址列表来执行发布
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
1、获取协议名称,如果没有配置默认会赋值为dubbo协议。
String name = protocolConfig.getName();
if (StringUtils.isEmpty(name)) {
name = DUBBO;
}
2、创建一个Map用来添加参数,参数的意思就是服务的配置信息。
Map<String, String> map = new HashMap<String, String>();
map.put(SIDE_KEY, PROVIDER_SIDE); 添加side=provide
ServiceConfig.appendRuntimeParameters(map); 添加运行的参数到map中,包含dubbo版本信
息,当前服务的进程id,启动是时间信息。
AbstractConfig.appendParameters(map, getMetrics()); 添加监控的配置到map中。
AbstractConfig.appendParameters(map, getApplication()); 添加应用信息到map中。
AbstractConfig.appendParameters(map, getModule()); 添加模块信息到map中。
// remove 'default.' prefix for configs from ProviderConfig
// appendParameters(map, provider, Constants.DEFAULT_KEY);
AbstractConfig.appendParameters(map, provider); 添加提供者信息到map中。
AbstractConfig.appendParameters(map, protocolConfig); 添加协议信息到map中。
AbstractConfig.appendParameters(map, this); 将当前ServiceBean实例添加到map中。
MetadataReportConfig metadataReportConfig = getMetadataReportConfig(); 添加元数据存储服务信息添加到map中。
如果配置了元数据存储服务地址的话就默认将添加"metadata-type"属性="remote"表示元数据存储在远程,不存储在本地。
if (metadataReportConfig != null && metadataReportConfig.isValid()) {
map.putIfAbsent(METADATA_KEY, REMOTE_METADATA_STORAGE_TYPE);
}
3、如果单独配置了方法,就做如下处理。
if (CollectionUtils.isNotEmpty(getMethods())) {
for (MethodConfig method : getMethods()) {
AbstractConfig.appendParameters(map, method, method.getName());
String retryKey = method.getName() + ".retry";
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
if ("false".equals(retryValue)) {
map.put(method.getName() + ".retries", "0");
}
}
List<ArgumentConfig> arguments = method.getArguments();
if (CollectionUtils.isNotEmpty(arguments)) {
for (ArgumentConfig argument : arguments) {
// convert argument type
if (argument.getType() != null && argument.getType().length() > 0) {
Method[] methods = interfaceClass.getMethods();
// visit all methods
if (methods.length > 0) {
for (int i = 0; i < methods.length; i++) {
String methodName = methods[i].getName();
// target the method, and get its signature
if (methodName.equals(method.getName())) {
Class<?>[] argtypes = methods[i].getParameterTypes();
// one callback in the method
if (argument.getIndex() != -1) {
if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
AbstractConfig.appendParameters(map, argument, method.getName() + "." + argument.getIndex());
} else {
throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
}
} else {
// multiple callbacks in the method
for (int j = 0; j < argtypes.length; j++) {
Class<?> argclazz = argtypes[j];
if (argclazz.getName().equals(argument.getType())) {
AbstractConfig.appendParameters(map, argument, method.getName() + "." + j);
if (argument.getIndex() != -1 && argument.getIndex() != j) {
throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
}
}
}
}
}
}
}
} else if (argument.getIndex() != -1) {
AbstractConfig.appendParameters(map, argument, method.getName() + "." + argument.getIndex());
} else {
throw new IllegalArgumentException("Argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
}
}
}
} // end of methods for
}
4、添加泛化配置到map中
if (ProtocolUtils.isGeneric(generic)) {
map.put(GENERIC_KEY, generic);
map.put(METHODS_KEY, ANY_VALUE);
} else {
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put(REVISION_KEY, revision);
}
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
if (methods.length == 0) {
logger.warn("No method found in service interface " + interfaceClass.getName());
map.put(METHODS_KEY, ANY_VALUE);
} else {
map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
}
/**
* Here the token value configured by the provider is used to assign the value to ServiceConfig#token
*/
5、如果需要使用token来验证当前服务,就将其添加到map中。
if(ConfigUtils.isEmpty(token) && provider != null) {
token = provider.getToken();
}
if (!ConfigUtils.isEmpty(token)) {
if (ConfigUtils.isDefault(token)) {
map.put(TOKEN_KEY, UUID.randomUUID().toString());
} else {
map.put(TOKEN_KEY, token);
}
}
//init serviceMetadata attachments
6、将装有所有配置项的map里的数据全部添加到元数据中。
serviceMetadata.getAttachments().putAll(map);
// export service
7、获取当前机器ip。
String host = findConfigedHosts(protocolConfig, registryURLs, map);
8、获取当前服务所配置的需要监听的端口,如20880,如果是dubbo协议,不配置的话默认是20880端口。
Integer port = findConfigedPorts(protocolConfig, name, map);
9、使用协议名称、ip、端口、协议的contextPath、还有装有所有配置项的map来构建一个URL实
例, URL字符格式:协议名称(例如dubbo)://ip:port/服务名称?各种配置项参数,例如:
dubbo://192.168.236.2:20880/com.wzy.dubbo.ISayHiService?
anyhost=true&application=provider&bind.ip=192.168.236.2&bind.port=20880&deprecated=false&
dubbo=2.0.2&dynamic=true&generic=false&interface=com.wzy.dubbo.ISayHiService&metadata-
type=remote&methods=sayHi&pid=15232&qos.enable=false&release=2.7.8&side=provider×tam
p=1599901504761
URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
// You can customize Configurator to append extra parameters
10、配置扩展,支持自定义工厂来对配置项进行拦截修改。
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
11、获取当前发布服务所配置的scope属性值,表示当前服务发布的返回,是远程还是本地。
String scope = url.getParameter(SCOPE_KEY);
// don't export when none is configured
12、如果scope 配置为"none" 将不会发布当前服务。
if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
// export to local if the config is not remote (export to remote only when config is remote)
13、如果没有配置为"none" 或者"remote" ,那就发布到本地,不配置也会发布到本地。发布
到本的话会将协议改为"injvm", ip改成"127.0.0.1", 端口改为"0",然后再发布,最终的
url=injvm://127.0.0.1/serviceName?.....。
if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
exportLocal(url);
}
// export to remote if the config is not local (export to local only when config is local)
14、发布服务到远程remote,发布到远程会将服务信息注册到注册中心。
if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
if (CollectionUtils.isNotEmpty(registryURLs)) {
15、循环所有处理过的注册中心地址进行发布,这里也体现了多注册中心的特性,此
处的注册中心的地址,是之前构建的registry://ip:port/org...RegistryService?registry的配置项。
for (URL registryURL : registryURLs) {
//if protocol is only injvm ,not register
16、如果需要发布的服务协议是injvm就不发布。
if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
continue;
}
17、使用处理过的注册中心的URL中的参数dynamic替换掉当前发布的接口提供者服务URL中的dynamic参数,前提是当前发布的接口提供者服务的URL没有配置此配置项。
url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
19、加载dubbo monitor的URL实例。
URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);
if (monitorUrl != null) {
url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
}
if (logger.isInfoEnabled()) {
if (url.getParameter(REGISTER_KEY, true)) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
} else {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
}
// For providers, this is used to enable custom proxy to generate invoker
20、获取当前dubbo 服务URL中的proxy参数,如果不为空,就将配置的proxy添
加到处理过的注册中心URL实例的参数列表中。
String proxy = url.getParameter(PROXY_KEY);
if (StringUtils.isNotEmpty(proxy)) {
registryURL = registryURL.addParameter(PROXY_KEY, proxy);
}
21、使用一个ProxyFactory来获取一个Invoker,这里是核心,后面会详细分析,在这里会将服务的URL字符串以参数key=export添加到注册中心地址的参数中。
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
包装invoker 成为一个DelegateProviderMetaDataInvoker实例,这个实例包含了上面构建的invoker 实例 以及接口的元数据。
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
22、使用获取的自适应的Protocol实例进行发布,在这个自适应的Protocol实
例里面会使用wrapperInvoker获取到registry的URL实例,从而获取到协议,那么此时获取到的应该
是"registry"协议,那么肯定就会执行到RegistryProtocol的发布方法中,当然会经过包装,层层执行最终
执行到RegistryProtocol的export(...)方法中。
Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
exporters.add(exporter);
}
} else {
23、如果没有配置注册中心地址,那就直接使用服务地址来获取一个Invoker实例,在
包装成DelegateProviderMetaDataInvoker实例,然后使用此实例以入参去使用PROTOCOL实例去发布,由于
此时url是服务的URL实例,那么获取在这个自适应的协议实例里面最终获取到的真正的协议就是dubbo协议实
现。
if (logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
exporters.add(exporter);
}
/**
* @since 2.7.0
* ServiceData Store
*/
24、存储元数据,如果服务配置了metadata-type=remote那就回存储在远程,不配置或者
配置为local将会存储在本地缓存中。
WritableMetadataService metadataService = WritableMetadataService.getExtension(url.getParameter(METADATA_KEY, DEFAULT_METADATA_STORAGE_TYPE));
if (metadataService != null) {
metadataService.publishServiceDefinition(url);
}
}
}
this.urls.add(url);
}
第9步:Exporter<?> exporter = PROTOCOL.export(wrapperInvoker); 这里我们说了正常情况下,wrapperInvoker里面封装的URL是处理过的注册中心的地址,且将dubbo服务的地址以key=export添加到处理过的注册中心的地址后面,案例:registry://127.0.0.1:8848/org.apache.dubbo.registry.RegistryService?application=provider&dubbo=2.0.2&export=dubbo%3A%2F%2F192.168.236.2%3A20880%2Fcom.wzy.dubbo.ISayHiService%3Fanyhost%3Dtrue%26application%3Dprovider%26bind.ip%3D192.168.236.2%26bind.port%3D20880%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dcom.wzy.dubbo.ISayHiService%26metadata-type%3Dremote%26methods%3DsayHi%26pid%3D5776%26qos.enable%3Dfalse%26release%3D2.7.8%26side%3Dprovider%26timestamp%3D1599903144794&pid=5776&qos.enable=false®istry=nacos&release=2.7.8×tamp=1599903132438
既然协议是"registry" 那么PROTOCOL这个自适应协议里面真正获取到的Protocol实现就应该是经过包装过后的RegistryProtocol,运行截图如下:
那么执行顺序就该是:QosProtocolWrapper.export(...)
---->ProtocolFilterWrapper.export(...)
----->ProtocolListenerWrapper.export(...)
----->RegistryProtocol.export(...)
至于层层包装里的每一个包装干了啥,我们先不说,我们们来到RegistryProtocol.export(...)方法:
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
这里的originInvoker就是前面包装的DelegateProviderMetaDataInvoker实例。
1、先获取注册中心地址,案例:nacos://127.0.0.1:8848/org.apache.dubbo.registry.RegistryService?
application=provider&dubbo=2.0.2&export=dubbo%3A%2F%2F192.168.236.2%3A20880%2Fcom.wzy.dub
bo.ISayHiService%3Fanyhost%3Dtrue%26application%3Dprovider%26bind.ip%3D192.168.236.2%26bi
nd.port%3D20880%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%2
6interface%3Dcom.wzy.dubbo.ISayHiService%26metadata-
type%3Dremote%26methods%3DsayHi%26pid%3D5776%26qos.enable%3Dfalse%26release%3D2.7.8%26sid
e%3Dprovider%26timestamp%3D1599903144794&pid=5776&qos.enable=false&release=2.7.8×tam
p=1599903132438
URL registryUrl = getRegistryUrl(originInvoker);
// url to export locally
2、获取当前接口提供者服务的地址,就是之前以参数key=export添加到处理过后的注册中心的参数
中的当前接口提供者的地址。
URL providerUrl = getProviderUrl(originInvoker);
// Subscribe the override data
// FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call
// the same service. Because the subscribed is cached key with the name of the service, it causes the
// subscription information to cover.
3、获取订阅的URL,案例:provider://192.168.236.2:20880/com.wzy.dubbo.ISayHiService?
anyhost=true&application=provider&bind.ip=192.168.236.2&bind.port=20880&category=configur
ators&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.w
zy.dubbo.ISayHiService&metadata-type=remote&methods=sayHi&pid=5776&qos.enable=false&release=2.7.8&side=provider×tamp
=1599903144794
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
4、创建一个覆盖订阅的监听器,当服务发布完成后,会将上述获取到的订阅的URL进行覆盖,意义就
是当服务重新发布后需要修改注册中心的注册信息为最新数据。
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
5、同样需要添加service配置覆盖监听器 以及提供者配置信息发覆盖监听器。
providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
//export invoker
6、发布服务并返回一个发布器的包装类实例,发布器指的是什么,Protocol接口的export(...)发
布方法就是返回一个发布器,发布器就是包装了执行实例Invoker以及提供了销毁Invoker实例而已。
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
// url to registry
7、获取一个注册器,实际上是根据处理过后的注册中心地址来获取,主要是根据协议,比如NacosRegistry。
final Registry registry = getRegistry(originInvoker);
8、获取需要注册到注册中心的提供者的服务地址,案例:dubbo://192.168.236.2:20880/com.wzy.dubbo.ISayHiService?
anyhost=true&application=provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false
&interface=com.wzy.dubbo.ISayHiService&metadata-
type=remote&methods=sayHi&pid=5776&release=2.7.8&side=provider×tamp=1599903144794
final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);
// decide if we need to delay publish
9、如果需要注册那就将服务提供者的地址registeredProviderUrl 注册到注册中心。
boolean register = providerUrl.getParameter(REGISTER_KEY, true);
if (register) {
register(registryUrl, registeredProviderUrl);
}
// register stated url on provider model
10、将已经注册的服务添加到provider model提供者模型中。
registerStatedUrl(registryUrl, registeredProviderUrl, register);
exporter.setRegisterUrl(registeredProviderUrl);
exporter.setSubscribeUrl(overrideSubscribeUrl);
// Deprecated! Subscribe to override rules in 2.6.x or before.
11、通知服务注册中心进行当前发布的服务的数据进行覆盖。
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
12、然后再通知当前的Registry协议的监听器,这个是如果我们配置了,通过SPI获取到然后执行其中的onExport(...)方法。
notifyExport(exporter);
//Ensure that a new exporter instance is returned every time export
return new DestroyableExporter<>(exporter);
}
第10步:RegiatryProtocol中使用DelegateProviderMetaDataInvoker + 服务提供者的地址来进行发布并返回一个发布器:
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
1、获取一个缓存key,这个缓存是CHM类型的bounds变量,key=此处的key,value就是发布器的包装实例ExporterChangeableWrapper。
String key = getCacheKey(originInvoker);
return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
2、构建一个执行的委派类InvokerDelegate。
Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
3、使用当前RegistryProtocol中的protocol自适应协议实例来进行发布,也许有人会问这个实
例什么时候创建的,其实在获取RegistryProtocol实例的时候回对当前的RegistryProtocol实例进行依赖注
入,就是在依赖注入阶段构建的protocol自适应协议实例。由于这个时候invokerDelegate 实例的url已经是服
务提供者的地址了,那么就是dubbo://这样的协议了,那么这个时候真正执行的就是被层层包装的
DubboProtocol,那么最终就会来到DubboProtocol的export(...)方法。
return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
});
}
第11步:DubboProtocol中使用InvokerDelegate来进行服务提供者发布并返回一个发布器:
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
1、获取服务提供者的地址。
URL url = invoker.getUrl();
// export service.
2、构建一个DubboExporter dubbo发布器,然后缓存到exporterMap的CHM中。
String key = serviceKey(url);
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.put(key, exporter);
//export an stub service for dispatching event
3、本地存根的实现。
Boolean isStubSupportEvent = url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT);
Boolean isCallbackservice = url.getParameter(IS_CALLBACK_SERVICE, false);
if (isStubSupportEvent && !isCallbackservice) {
String stubServiceMethods = url.getParameter(STUB_EVENT_METHODS_KEY);
if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
if (logger.isWarnEnabled()) {
logger.warn(new IllegalStateException("consumer [" + url.getParameter(INTERFACE_KEY) +
"], has set stubproxy support event ,but no stub methods founded."));
}
}
}
4、启动dubbo服务。
openServer(url);
5、优化序列化。
optimizeSerialization(url);
return exporter;
}
第12步:DubboProtocol中使用服务提供者地址启动dubbo服务器:
private void openServer(URL url) {
// find server.
1、获取到地址作为服务进程的缓存key,如192.168.236.2:20880
String key = url.getAddress();
//client can export a service which's only for server to invoke
2、如果服务的提供者地址中设置了isserver=false 将不会启动dubbo服务进程。
boolean isServer = url.getParameter(IS_SERVER_KEY, true);
if (isServer) {
3、先访问服务进程的缓存,如果没有就先创建服务进程,然后进行缓存。
ProtocolServer server = serverMap.get(key);
if (server == null) {
synchronized (this) {
server = serverMap.get(key);
if (server == null) {
创建并缓存服务进程。
serverMap.put(key, createServer(url));
}
}
} else {
// server supports reset, use together with override
4、如果服务进程已经有了就重置,比如当我们发布多个接口服务的时候,从第二个就开始就会开始进行重置,应为发布徐第一个的时候端口已经被监听了!!!!!!!!!!!!!!!
server.reset(url);
}
}
}
第13步:DubboProtocol中使用服务提供者地址创建dubbo服务器:
private ProtocolServer createServer(URL url) {
1、先配置一些服务特性:
1.1、如果当前服务关闭的时候如果没有配置,默认就配置需要发送只读事件
1.2、如果没有配置云消费端的心跳发送间隔时间的话,将会默认配置为60s心跳检查一次。
url = URLBuilder.from(url)
// send readonly event when server closes, it's enabled by default
.addParameterIfAbsent(CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString())
// enable heartbeat by default
.addParameterIfAbsent(HEARTBEAT_KEY, String.valueOf(DEFAULT_HEARTBEAT))
.addParameter(CODEC_KEY, DubboCodec.NAME)
.build();
2、从提供者的地址中获取服务的类型,没有配置的话,默认是netty服务类型。
String str = url.getParameter(SERVER_KEY, DEFAULT_REMOTING_SERVER);
3、先判断服务类型的传输层实现,比如是netty服务类型,就是判断有没有netty这样的传输层实现。
if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
throw new RpcException("Unsupported server type: " + str + ", url: " + url);
}
ExchangeServer server;
try {
使用Exchangers进行服务进程绑定,requestHandler是直接new的一个匿名内部类,专门负责处理消费端的连接请求、断开请求等等。。。
server = Exchangers.bind(url, requestHandler);
} catch (RemotingException e) {
throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
}
4、检查当前的服务提供者配置的client配置在当前框架中是否支持,不支持就抛出异常,client配
置指的是期待消费者那边的传输实现是什么,比如配置了client=netty 当前Transporter是有name=netty的
SPI实现的。
str = url.getParameter(CLIENT_KEY);
if (str != null && str.length() > 0) {
Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
if (!supportedTypes.contains(str)) {
throw new RpcException("Unsupported client type: " + str);
}
}
return new DubboProtocolServer(server);
}
第14步:Exchangers中使用服务提供者地址 + 客户端请求处理器ExchangeHandler实例来绑定当前dubbo服务进程:
public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handler == null) {
throw new IllegalArgumentException("handler == null");
}
1、如果当前服务提供者没有配置codec属性,那就默认使用"exchange"。
url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
2、先获取一个信息交换器,然后使用此信息交换器进行绑定。
return getExchanger(url).bind(url, handler);
}
获取信息交换器的实现:
public static Exchanger getExchanger(URL url) {
1、先从服务提供者的地址中获取exchanger配置,如果没有配置默认是"header"
String type = url.getParameter(Constants.EXCHANGER_KEY, Constants.DEFAULT_EXCHANGER);
2、根据信息交换器的类型获取到实现,还是SPI的的方式。
return getExchanger(type);
}
public static Exchanger getExchanger(String type) {
return ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type);
}
第15步:使用默认的HeaderExchanger信息交换器进行服务绑定:
@Override
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
使用Transporters来进行绑定,入参是创建一个解码处理器。
return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}
第16步:Transporters进行服务绑定:
public static RemotingServer bind(URL url, ChannelHandler... handlers) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handlers == null || handlers.length == 0) {
throw new IllegalArgumentException("handlers == null");
}
ChannelHandler handler;
if (handlers.length == 1) {
handler = handlers[0];
} else {
handler = new ChannelHandlerDispatcher(handlers);
}
获取一个信息传输器Transporter来绑定服务,此处的handler就是一个解码处理器。
return getTransporter().bind(url, handler);
}
获取信息传输器的实现:默认是NettyTransporter注意是netty4
public static Transporter getTransporter() {
还是使用SPI,只不过是自适应,会根据url 中对协议的配置来决定,默认是netty4的传输层实现。
return ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();
}
第17步:默认的传输器netty4实现类NettyTransporter进行服务绑定:
@Override
public RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException {
实现就是new 一个NettyServer
return new NettyServer(url, handler);
}
NettyServer的构造函数:
public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
// you can customize name and type of client thread pool by THREAD_NAME_KEY and THREADPOOL_KEY in CommonConstants.
// the handler will be wrapped: MultiMessageHandler->HeartbeatHandler->handler
super(ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME), ChannelHandlers.wrap(handler, url));
}
NettyServer的父类AbstractServer的构造函数实现:
public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
super(url, handler);
1、设置当前NettyService实例的localAddress 属性。
localAddress = getUrl().toInetSocketAddress();
2、获取当前dubbo服务进程需要绑定的ip 以及端口port。
String bindIp = getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost());
int bindPort = getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort());
3、判断anyhost配置,如果为true,就将绑定的ip设置为0.0.0.0。
if (url.getParameter(ANYHOST_KEY, false) || NetUtils.isInvalidLocalHost(bindIp)) {
bindIp = ANYHOST_VALUE;
}
4、设置当前的NettyService的绑定地址。
bindAddress = new InetSocketAddress(bindIp, bindPort);
5、设置当前的NettyService能够接受的连接数,根据服务提供者的配置来设置的,不配置默认是0。
this.accepts = url.getParameter(ACCEPTS_KEY, DEFAULT_ACCEPTS);
6、设置当前NettyServer的连接的最大空闲时间,不配置默认是10分钟。
this.idleTimeout = url.getParameter(IDLE_TIMEOUT_KEY, DEFAULT_IDLE_TIMEOUT);
try {
核心中的核心,开启服务进程。
doOpen();
if (logger.isInfoEnabled()) {
logger.info("Start " + getClass().getSimpleName() + " bind " + getBindAddress() + ", export " + getLocalAddress());
}
} catch (Throwable t) {
throw new RemotingException(url.toInetSocketAddress(), null, "Failed to bind " + getClass().getSimpleName()
+ " on " + getLocalAddress() + ", cause: " + t.getMessage(), t);
}
使用当前的服务提供者地址创建一个线程池,并缓存起来,创建线程的规则就是根据服务提供者的配置。
executor = executorRepository.createExecutorIfAbsent(url);
}
第18步:默认的传输器netty4实现类NettyTransporter进行服务进程开启:这个里面就是netty相关的代码了
@Override
protected void doOpen() throws Throwable {
bootstrap = new ServerBootstrap();
bossGroup = NettyEventLoopFactory.eventLoopGroup(1, "NettyServerBoss");
workerGroup = NettyEventLoopFactory.eventLoopGroup(
getUrl().getPositiveParameter(IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),
"NettyServerWorker");
final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this);
channels = nettyServerHandler.getChannels();
bootstrap.group(bossGroup, workerGroup)
.channel(NettyEventLoopFactory.serverSocketChannelClass())
.option(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
.childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// FIXME: should we use getTimeout()?
int idleTimeout = UrlUtils.getIdleTimeout(getUrl());
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
if (getUrl().getParameter(SSL_ENABLED_KEY, false)) {
ch.pipeline().addLast("negotiation",
SslHandlerInitializer.sslServerHandler(getUrl(), nettyServerHandler));
}
ch.pipeline()
.addLast("decoder", adapter.getDecoder())
.addLast("encoder", adapter.getEncoder())
.addLast("server-idle-handler", new IdleStateHandler(0, 0, idleTimeout, MILLISECONDS))
.addLast("handler", nettyServerHandler);
}
});
// bind
ChannelFuture channelFuture = bootstrap.bind(getBindAddress());
channelFuture.syncUninterruptibly();
channel = channelFuture.channel();
}
第19步:提供者服务进程绑定成功,那么接着主流程走,那就是连接注册中心进行服务提供者注册,在RegistryProtocol.export(...)方法中代码片段:
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
...
boolean register = providerUrl.getParameter(REGISTER_KEY, true);
if (register) {
注册服务提供者地址到服务注册中心。
register(registryUrl, registeredProviderUrl);
}
register(URL registryUrl, URL registeredProviderUrl)实现:
private void register(URL registryUrl, URL registeredProviderUrl) {
1、先使用注册中心的地址获取一个服务注册器。
Registry registry = registryFactory.getRegistry(registryUrl);
2、使用注册器去测试服务提供者地址。
registry.register(registeredProviderUrl);
}
获取服务注册器的实现:
@Override
public Registry getRegistry(URL url) {
if (destroyed.get()) {
LOGGER.warn("All registry instances have been destroyed, failed to fetch any instance. " +
"Usually, this means no need to try to do unnecessary redundant resource clearance, all registries has been taken care of.");
return DEFAULT_NOP_REGISTRY;
}
url = URLBuilder.from(url)
.setPath(RegistryService.class.getName())
.addParameter(INTERFACE_KEY, RegistryService.class.getName())
.removeParameters(EXPORT_KEY, REFER_KEY)
.build();
String key = createRegistryCacheKey(url);
// 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();
}
}
我们以ZookeeperRegistryFactory实现来说明:
@Override
public Registry createRegistry(URL url) {
return new ZookeeperRegistry(url, zookeeperTransporter);
}
ZookeeperRegistry构造函数:
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;
使用zookeeperTransporter进行连接。
zkClient = zookeeperTransporter.connect(url);
添加节点状态的监听。
zkClient.addStateListener((state) -> {
if (state == StateListener.RECONNECTED) {
logger.warn("Trying to fetch the latest urls, in case there're provider changes during connection loss.\n" +
" Since ephemeral ZNode will not get deleted for a connection lose, " +
"there's no need to re-register url of this instance.");
ZookeeperRegistry.this.fetchLatestAddresses();
} else if (state == StateListener.NEW_SESSION_CREATED) {
logger.warn("Trying to re-register urls and re-subscribe listeners of this instance to registry...");
try {
ZookeeperRegistry.this.recover();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
} else if (state == StateListener.SESSION_LOST) {
logger.warn("Url of this instance will be deleted from registry soon. " +
"Dubbo client will try to re-register once a new session is created.");
} else if (state == StateListener.SUSPENDED) {
} else if (state == StateListener.CONNECTED) {
}
});
}
AbstractZookeeperTransporter的connect(URL url)方法:
@Override
public ZookeeperClient connect(URL url) {
ZookeeperClient zookeeperClient;
// address format: {[username:password@]address}
List<String> addressList = getURLBackupAddress(url);
// The field define the zookeeper server , including protocol, host, port, username, password
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
synchronized (zookeeperClientMap) {
if ((zookeeperClient = fetchAndUpdateZookeeperClientCache(addressList)) != null && zookeeperClient.isConnected()) {
logger.info("find valid zookeeper client from the cache for address: " + url);
return zookeeperClient;
}
核心在这里,创建一个zookeeper的客户端,根据注册中心的地址。
zookeeperClient = createZookeeperClient(url);
logger.info("No valid zookeeper client found from cache, therefore create a new client for url. " + url);
writeToClientMap(addressList, zookeeperClient);
}
return zookeeperClient;
}
默认是使用CuratorZookeeperTransporter,我们来到CuratorZookeeperTransporter的createZookeeperClient(URL url)方法实现:
@Override
public ZookeeperClient createZookeeperClient(URL url) {
return new CuratorZookeeperClient(url);
}
CuratorZookeeperClient的构造函数:
public CuratorZookeeperClient(URL url) {
super(url);
try {
int timeout = url.getParameter(TIMEOUT_KEY, DEFAULT_CONNECTION_TIMEOUT_MS);
int sessionExpireMs = url.getParameter(ZK_SESSION_EXPIRE_KEY, DEFAULT_SESSION_TIMEOUT_MS);
CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
.connectString(url.getBackupAddress())
.retryPolicy(new RetryNTimes(1, 1000))
.connectionTimeoutMs(timeout)
.sessionTimeoutMs(sessionExpireMs);
String authority = url.getAuthority();
if (authority != null && authority.length() > 0) {
builder = builder.authorization("digest", authority.getBytes());
}
构建一个客户端。
client = builder.build();
添加节点状态监听器。
client.getConnectionStateListenable().addListener(new CuratorConnectionStateListener(url));
启动zookeeper客户端。
client.start();
boolean connected = client.blockUntilConnected(timeout, TimeUnit.MILLISECONDS);
if (!connected) {
throw new IllegalStateException("zookeeper not connected");
}
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
注册器创建好以后进行注册,我们获取到的是ZookeeperRegistry服务注册器,而且是经过ListenerRegistryWrapper包装的,且ZookeeperRegistry实现了抽象类FailbackRegistry(失败重试也就是说注册失败会重试注册),那么执行路径就是:
ListenerRegistryWrapper.register(URL url)
----->FailbackRegistry.(URL url)
----->ZookeeperRegistry.doRegister(URL url);
我们直接来到ZookeeperRegistry.doRegister(URL url)实现:
@Override
public void doRegister(URL url) {
try {
使用zookeeper客户端创建node。
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);
}
}
至此dubbo整个服务发布流程分析结束。