服务导出(服务注册)
服务导出(服务注册)
本文主要记录学习Dubbo 整合 Spring 的源码笔记。记录服务导出(注册源码)的学习笔记。
服务导出(服务注册)的核心方法是ServiceBean对象中监听器里的export();调用时机是在Spring容器启动完成之后发布的完成事件,
ServiceBean通过监听该事件然后去进行服务导出(服务注册)。
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 当前服务没有被导出并且没有卸载,才导出服务
if (!isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
// 服务导出(服务注册)
export();
}
}
大体流程
主要做三件事
- 读取配置
读取配置其实就是要获取dubbo服务在启动时的一些配置信息,例如@Service注解、config文件等。
其实这些在服务注册前就已经有一些配置解析好保存到ServiceBean中了,而读取配置真正意义上来说是读取当前服务
最新(优先级最高的)、最全的配置。 - 服务注册(导出)
获取注册中心地址,生成服务提供者服务地址,注将服务地址注册到注册中心
- 启动Netty/Tomcat
如果是dubbo协议,启动的是Netty,Rest启动的是Tomcat
- 服务提供者监听配置更新
服务提供者会监听配置中心配置的变更,修改本地配置
checkAndUpdateSubConfigs()
- 补全配置,ServiceConfig中的某些属性如果是空的,那么就从ProviderConfig、ModuleConfig、ApplicationConfig中获取并补全
- 连接配置中心
- 获取配置中心数据,包括全局和应用配置,放到externalConfigurationMap和appExternalConfigurationMap中
- 刷新所有的xxxConfig中的属性(除了ServiceCOnfig),即覆盖掉对象里面的属性值
- 进行一系列的检查
- 判断protocol是不是只有injvm协议,不是即服务调用不只是在本机jvm里调用,需要用到注册中心,然后会校验是否有配置了注册中心的地址
- 刷新ServiceConfig,刷新某个服务的配置
- 创建一个Configuration列表,用compositeConfiguration进行封装。用来存各个位置的配置
- (JVM环境变量、操作系统环境变量、配置中心APP配置、配置中心Global配置、dubbo中的properties文件配置)
- 校验configCenterFirst配置,是否配置中心配置优先,默认true,根据这个来处理配置优先级
- true :SystemConfiguration -> AppExternalConfiguration -> ExternalConfiguration -> AbstractConfig ->
PropertiesConfiguration - false :SystemConfiguration -> AbstractConfig -> AppExternalConfiguration -> ExternalConfiguration ->
PropertiesConfiguration - 按照以上顺序存到Configuration列表中
- true :SystemConfiguration -> AppExternalConfiguration -> ExternalConfiguration -> AbstractConfig ->
- 覆盖ServiceBean中的配置
- 便利ServiceBean中的方法
- 是set方法
- 截取方法名,获取属性名
- 通过属性名从compositeConfiguration按优先级获取,获取到覆盖ServiceBean中的属性值并返回
- 是setParameters方法
- 获取parameter配置项的value
- 覆盖ServiceBean中的属性值并返回
- 创建一个Configuration列表,用compositeConfiguration进行封装。用来存各个位置的配置
刷新ServiceConfig配置的源码
public void refresh() {
try {
CompositeConfiguration compositeConfiguration = Environment.getInstance().getConfiguration(getPrefix(), getId());
// 表示XxConfig对象本身- AbstractConfig
Configuration config = new ConfigConfigurationAdapter(this); // ServiceConfig
if (Environment.getInstance().isConfigCenterFirst()) {
// The sequence would be: SystemConfiguration -> AppExternalConfiguration -> ExternalConfiguration -> AbstractConfig -> PropertiesConfiguration
compositeConfiguration.addConfiguration(4, config);
} else {
// The sequence would be: SystemConfiguration -> AbstractConfig -> AppExternalConfiguration -> ExternalConfiguration -> PropertiesConfiguration
compositeConfiguration.addConfiguration(2, config);
}
// loop methods, get override value and set the new value back to method
Method[] methods = getClass().getMethods(); //ServiceBean
for (Method method : methods) {
// 是不是setXX()方法
if (MethodUtils.isSetter(method)) {
// 获取xx配置项的value
String value = StringUtils.trim(compositeConfiguration.getString(extractPropertyName(getClass(), method)));
// isTypeMatch() is called to avoid duplicate and incorrect update, for example, we have two 'setGeneric' methods in ReferenceConfig.
if (StringUtils.isNotEmpty(value) && ClassUtils.isTypeMatch(method.getParameterTypes()[0], value)) {
method.invoke(this, ClassUtils.convertPrimitive(method.getParameterTypes()[0], value));
}
// 是不是setParameters()方法
} else if (isParametersSetter(method)) {
// 获取parameter配置项的value
String value = StringUtils.trim(compositeConfiguration.getString(extractPropertyName(getClass(), method)));
if (StringUtils.isNotEmpty(value)) {
Map<String, String> map = invokeGetParameters(getClass(), this);
map = map == null ? new HashMap<>() : map;
map.putAll(convert(StringUtils.parseParameters(value), ""));
invokeSetParameters(getClass(), this, map);
}
}
}
} catch (Exception e) {
logger.error("Failed to override ", e);
}
}
export
校验是否执行服务导出,通过属性可进行配置,默认true
delay
校验是否延迟导出,通过delay属性可进行配置,默认0不延迟(毫秒)
doExport()
调用doExport方法进行服务导出
loadRegistries()
loadRegistries方法主要做的事情就是获取配置用户配置的注册中心,最终会返回一个URL列表,一个URL就代表一个注册中心地址,URL的内容如下:
registry://1.12.242.126:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-provider1-application&dubbo=2.0.2&logger=log4j&pid=93947®istry=zookeeper&release=2.7.0&timeout=3000×tamp=1684854503882
此时URL并没有标识是使用zookeeper或者nacos,而是先试用registry进行标识
doExportUrlsFor1Protocol()
这个方法主要做的事情就是为每一个协议,都导出一个服务。例如提供者提供了一个UserService服务,但是我配置了两个协议,那此时就会生成两个UserService服务地址,并且都会注册到所有的注册中心上。
具体实现流程
- 获取协议名称,没有默认就是dubbo
- 构建一个map,用来存服务url的参数,并往map中填值
- 构建Token,Token是为了防止服务被消费者直接调用(伪造http请求)
- token生成规则:如果没有配则没有,配置true或者default自动通过uuid生成,否者使用配置的字符做token
- 构建服务URL
- 生成一个当前服务接口的代理对象,使用代理对象生成一个Invoker,Invoker表示服务提供者的代理,可以使用Invoker的invoke方法执行服务
- 封装DelegateProviderMetaDataInvoker,包括了Invoker和服务的配置
- 导出服务&服务注册
- 从invoker种获取注册中心URL(registerUrl)和服务提供者URL(providerUrl)
- 在providerUrl的基础上生成overrideSubscribeUrl,这个是老版本的动态配置监听url,表示了需要监听的服务以及监听的类型(configurators,
这是老版本上的动态配置)s - 创建OverrideListener监听器,用来监听overrideSubscribeUrl配置变更(老版本)
- 添加两个监听器(新版本的监听器)
- providerConfigurationListener:表示应用级别的动态配置监听器,属于RegistyProtocol的一个属性
- serviceConfigurationListener:表示服务级别的动态配置监听器,每暴露一个服务时就会生成一个
- 通过两个监听器,重写providerUrl
- 使用重写过后的providerUrl,调用doLocalExport()进行服务导出
- 获取注册中心(通过SPI扩展点机制)
- 简化服务URL并注册到注册中心
doLocalExport()
此方法就是真正去执行服务导出的逻辑,里面会调用protocol.export(invokerDelegate)去导出服务
具体实现
- 获取服务URL
- 通过URL生成一个key
- 构造一个Exporter进行服务导出
- 存到exporterMap中
- 调用openServer启动Netty/Tomcat
服务提供者监听配置更新
服务提供者主要监听动态配置,在服务运行是时可以动态修改服务配置,会监听三个节点,一个是老版本的节点以及两个新节点
补充
当dubbo的某一个服务导出完了之后,会发布一个Spring的时间,如果想知道某个服务是否已经导出完毕,可以监听ServiceBeanExportedEvent实现。