服务导出
Dubbo服务导出大体流程
- Dubbo的每个Service注解都对应一个ServiceBean对象,每个ServiceBean对象都实现了Spring的ApplicationListener接口,当Spring项目启动结束后,会触发一个上下文刷新事件ContextRefreshEvent事件, 触发的方法是onApplicationEvent方法, ServiceBean的onApplicationEvent方法中,会调用ServiceBean#export(), 再去调用父类ServiceConfig#export()方法进行服务导出
- 会确定每个ServiceBean的配置参数,像timeout, protocol等, 这些参数的配置可以在以下的地方进行配置;
2.1 JVM环境运行参数, 通过-D的方式来指定参数 2.2 配置中心可以配置 项目的全局配置和应用配置, 相当于一个分布式共享的Dubbo.properties文件; 2.3 @Service注解可以指定参数; 2.4 项目中, 指定的dubbo.properties配置文件; 这么多个地方可以指定配置参数, 在服务导出时,就需要确定每个Service使用的是哪些配置参数,即确定优先级最高的参数; - 加载确定注册中心信息;
- 根据配置的Protocol协议启动容器, Dubbo协议对应的Netty, http对应的是tomcat/jetty, 并在接收请求过程中,根据我们的配置接收请求, 如超时事件, 最大连接数等;
- 容器启动完后,每个ServiceBean的服务配置会注册到注册中心,同时会注册监听服务配置,监听配置的变更;
主要就是这几个过程, 更简单点 确定服务配置参数 => 启动容器 => 注册到注册中心;
public synchronized void export() {
checkAndUpdateSubConfigs();
if (!shouldExport()) {
return;
}
if (shouldDelay()) {
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
doExport();
}
}
服务导出思路
服务导出要做的几件事情:
- 确定服务的参数
- 确定服务支持的协议
- 构造服务最终的URL
- 根据服务支持的不同协议,启动不同的Server,用来接收和处理请求
- 服务URL注册到注册中心去
- 因为Dubbo支持动态配置服务参数,所以服务导出时还需要绑定一个监听器Listener来监听服务的参数是否有修改,如果发现有修改,则需要重新进行导出
确定服务的参数
上篇博客单独写了一篇,不重复描述了。 简单概述一下流程:
- 补全参数;
- 刷新配置参数;
2.1 获取配置中心的配置参数,例如配置中心地址;并刷新配置中心的参数 2.2 获取配置中心的全局配置和应用配置,放入Environment的appExternalConfiuration和externalConfiguration两种Map属性中; 2.3 刷新所有Dubbo配置类的参数; ==>ApplicationConfig, MonitorConfig,ModuleConfig,ProtocolConfig, RegistryConfig, ProviderConfig, ConsumerConfig (注意:在刷新参数过程中,指定服务参数的优先级别, JVM环境配置 > 配置中心App应用配置 > 配置中心全局配置 > 注解参数配置 > dubbo.properties文件配置) - 创建设置provider配置类,由ConfigManager管理
- 创建设置Protocol配置类, 由Configmanager管理
- 创建设置Application, 由ConfigManager管理;
- @Service注解代表的ServiceConfig参数的刷新,这时候设置的配置参数值全都是最高优先级的。
- 一些Mock, Stub等的配置;
处理注册中心配置
项目可能会配置多个注册中心, Dubbo会将这些注册中心配置转换为一个个的RegistryConfig。 在确定服务配置参数阶段,每个RegistryConfig获取了最高优先级别的参数; 目的: 处理RegistryConfig, 将每个注册中心配置解析为一个个的URL, 最后返回一个URL集合;
private void doExportUrls() {
List<URL> registryURLs = loadRegistries(true);
}
loadRegistries(true)
工作:
- 遍历注册中心配置类;
- 注册中心配置类若没有设置地址address, 则使用匿名地址0.0.0.0
- 注册中心配置类的地址若为 “N/A”, 则跳出循环, 执行下一个配置类;
- 注册中心配置类的地址不为"N/A",则
4.1 获取ApplicationConfig的参数, 放入map中; (通过遍历Method方法) 4.2 获取RegistryConfig的参数, 放入map中; (通过遍历Method方法) 注册中心配置类里面 包装了用户的姓名username, 密码password 都会放入map; 4.3 添加path的值,固定为RegistryService.class.getName(); 4.4 获取Dubbo版本信息, 放入map中; 4.5 构造URL,通过地址 + 参数(UrlUtils.parseURLs(address, map)); 4.6 传入protocol参数值, 以及URL的协议名称,构建最终URL; 4.7 从URL中获取provider的register的值, true注册,false反之; 消费者,判断subscribe的值,true订阅,false不订阅注册中心的煮熟;
protected List<URL> loadRegistries(boolean provider) {
List<URL> registryList = new ArrayList<URL>();
if (CollectionUtils.isNotEmpty(registries)) {
for (RegistryConfig config : registries) {
String address = config.getAddress();
if (StringUtils.isEmpty(address)) {
address = ANYHOST_VALUE;
}
if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
Map<String, String> map = new HashMap<String, String>();
appendParameters(map, application);
appendParameters(map, config);
map.put(PATH_KEY, RegistryService.class.getName());
appendRuntimeParameters(map);
if (!map.containsKey(PROTOCOL_KEY)) {
map.put(PROTOCOL_KEY, DUBBO_PROTOCOL);
}
List<URL> urls = UrlUtils.parseURLs(address, map);
for (URL url : urls) {
url = URLBuilder.from(url)
.addParameter(REGISTRY_KEY, url.getProtocol())
.setProtocol(REGISTRY_PROTOCOL)
.build();
if ((provider && url.getParameter(REGISTER_KEY, true))
|| (!provider && url.getParameter(SUBSCRIBE_KEY, true))) {
registryList.add(url);
}
}
}
}
}
return registryList;
}
UrlUtils.parseURLs(address, map)
目的: 根据address 和 参数 初步创建一个URL; 如果address 格式 如: “192.168.2.110;192.168.2.111”,则会解析为两个URL;
public static List<URL> parseURLs(String address, Map<String, String> defaults) {
if (address == null || address.length() == 0) {
return null;
}
String[] addresses = REGISTRY_SPLIT_PATTERN.split(address);
if (addresses == null || addresses.length == 0) {
return null;
}
List<URL> registries = new ArrayList<URL>();
for (String addr : addresses) {
registries.add(parseURL(addr, defaults));
}
return registries;
}
遍历ProtocolConfig协议
- 获取注册中心的URL集合;
- 遍历ProtocolConfig协议;
- 获取PathKey, 格式: group / contextPath / path :version; contextPath 为应用名, path为全路径名;
- 创建ProvicerModel实例, 包装了路径pathkey, 服务实现类, 接口类信息;ApplicationModel表示应用中服务接口以及接口实现类 提供了 一种服务;
- 以pathKey为 key, providerModel实例为value,放入缓存PROVIDER_SERVICES的Map中
- 每种协议导出一个单独的服务,注册到各个注册中心,一个服务对应一个或者多个协议;
private List<ProtocolConfiguration> protocols;
private void doExportUrls() {
List<URL> registryURLs = loadRegistries(true);
for (ProtocolConfig protocolConfig : protocols) {
String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
ApplicationModel.initProviderModel(pathKey, providerModel);
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
浅谈构造器模式与使用
大体概念:建造者模式 是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们, 用户不需要知道内部的具体构建细节。
url = URLBuilder.from(url).addParameter(REGISTRY_KEY, url.getProtocol()).setProtocol(REGISTRY_PROTOCOL).build();
public static URLBuilder from(URL url) {
String protocol = url.getProtocol();
String username = url.getUsername();
String password = url.getPassword();
String host = url.getHost();
int port = url.getPort();
String path = url.getPath();
Map<String, String> parameters = new HashMap<>(url.getParameters());
return new URLBuilder(
protocol,
username,
password,
host,
port,
path,
parameters);
}
public URLBuilder addParameter(String key, String value) {
if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value)) {
return this;
}
if (value.equals(parameters.get(key))) {
return this;
}
parameters.put(key, value);
return this;
}
public URLBuilder setProtocol(String protocol) {
this.protocol = protocol;
return this;
}
public URL build() {
port = port < 0 ? 0 : port;
int firstNonSlash = 0;
return new URL(protocol, username, password, host, port, path, parameters);
}
- 像业务中,new Object(), 然后一个个的set, 代码量非常之多,而且很多地方都会用到创建复杂对象,
- 数据库表多了一个字段,entity对象也多了一个字段,那就麻烦了,到处都得改,非常不方便, 因此,这个URLBuidler构造器值得借鉴。
- 例如URL 需要增加一个字段,那么只需要在Builder构造器里面修改即可,不需要去修改我们的业务代码。减少代码改动量,
符合开闭原则,单一职责原则; 处理注册中心配置 : 根据RegistryConfig转换成一个一个的URL对象,最终返回一个URL集合;
|