上一篇文章详细分析了服务导出的过程,本篇文章我们趁热打铁,继续分析服务引用过程。在 Dubbo 中,我们可以通过两种方式引用远程服务。第一种是使用服务直连的方式引用服务,第二种方式是基于注册中心进行引用。服务直连的方式仅适合在调试或测试服务的场景下使用,不适合在线上环境使用。因此,本文我将重点分析通过注册中心引用服务的过程。从注册中心中获取服务配置只是服务引用过程中的一环
服务引用原理
服务引用关键配置demo:
<dubbo:reference id="userApi" interface="com.czj.rpc.api.UserApi" ></dubbo:reference>
在服务发布源码中已经说了dubbo如何集成spring的原理, 我们现在主要看是哪个解析类负责解析reference标签即可
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
@Override
public void init() {
/**
往spring注册不同的标签解析,DubboBeanDefinitionParser是dubbo通用的
标签解析类, 里面主要的工作原理为:
1、生成 RootBeanDefinition , 把入参的class赋值到beanClass属性中
2、使用parserContext.getRegistry().registerBeanDefinition()
往IOC容器注册创建的BeanDefinition
3、根据入参的Class类型为RootBeanDefinition赋值propertyValues
**/
//省略相关代码...
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
}
}
Dubbo 服务引用的时机有两个,第一个是在 Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务,第二个是在 ReferenceBean 对应的服务被注入到其他类中时引用。这两个引用服务的时机区别在于,第一个是饿汉式的,第二个是懒汉式的。默认情况下,Dubbo 使用懒汉式引用服务。如果需要使用饿汉式,可通过配置 dubbo:reference 的 init 属性开启。下面我们按照 Dubbo 默认配置进行分析,整个分析过程从 ReferenceBean 的 getObject 方法开始。当我们的服务被注入到其他类中时,Spring 会第一时间调用 getObject 方法,并由该方法执行服务引用逻辑。按照惯例,在进行具体工作之前,需先进行配置检查与收集工作。接着根据收集到的信息决定服务用的方式,有三种,第一种是引用本地 (JVM) 服务,第二是通过直连方式引用远程服务,第三是通过注册中心引用远程服务。不管是哪种引用方式,最后都会得到一个 Invoker 实例。如果有多个注册中心,多个服务提供者,这个时候会得到一组 Invoker 实例,此时需要通过集群管理类 Cluster 将多个 Invoker 合并成一个实例。合并后的 Invoker 实例已经具备调用本地或远程服务的能力了,但并不能将此实例暴露给用户使用,这会对用户业务代码造成侵入。此时框架还需要通过代理工厂类 (ProxyFactory) 为服务接口生成代理类,并让代理类去调用 Invoker 逻辑。避免了 Dubbo 框架代码对业务代码的侵入,同时也让框架更容易使用。
我们先看ReferenceBean 的 afterPropertiesSet ,Spring在创建ReferenceBean 后, 会先执行该方法
public void afterPropertiesSet() throws Exception {
//检查 ConsumerConfig consumer 如果为空,从IOC容器中获取 ConsumerConfig 类型的bean对象,并赋值给当前对象的 consumer 属性
if (getConsumer() == null) {
Map<String, ConsumerConfig> consumerConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ConsumerConfig.class, false, false);
if (consumerConfigMap != null && consumerConfigMap.size() > 0) {
ConsumerConfig consumerConfig = null;
for (ConsumerConfig config : consumerConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
if (consumerConfig != null) {
throw new IllegalStateException("Duplicate consumer configs: " + consumerConfig + " and " + config);
}
consumerConfig = config;
}
}
if (consumerConfig != null) {
setConsumer(consumerConfig);
}
}
}
//检查 ApplicationConfig application 如果为空,从IOC容器中获取 ApplicationConfig 类型的bean对象,并赋值给当前对象的 application 属性
if (getApplication() == null
&& (getConsumer() == null || getConsumer().getApplication() == null)) {
Map<String, ApplicationConfig> applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ApplicationConfig.class, false, false);
if (applicationConfigMap != null && applicationConfigMap.size() > 0) {
ApplicationConfig applicationConfig = null;
for (ApplicationConfig config : applicationConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
if (applicationConfig != null) {
throw new IllegalStateException("Duplicate application configs: " + applicationConfig + " and " + config);
}
applicationConfig = config;
}
}
if (applicationConfig != null) {
setApplication(applicationConfig);
}
}
}
/**
检测 ModuleConfig module 是否为空, 为空的话将从IOC容器从获取
ModuleConfig 类型的bean对象,并赋值给当前对象的 module 属性
**/
if (getModule() == null
&& (getConsumer() == null || getConsumer().getModule() == null)) {
Map<String, ModuleConfig> moduleConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ModuleConfig.class, false, false);
if (moduleConfigMap != null && moduleConfigMap.size() > 0) {
ModuleConfig moduleConfig = null;
for (ModuleConfig config : moduleConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
if (moduleConfig != null) {
throw new IllegalStateException("Duplicate module configs: " + moduleConfig + " and " + config);
}
moduleConfig = config;
}
}
if (moduleConfig != null) {
setModule(moduleConfig);
}
}
}
/**
重要:获取注册中心的配置bean
检测 List<RegistryConfig> registries 是否为空, 为空的话将从IOC容器从获取
RegistryConfig 类型的bean对象,并赋值给当前对象的 registries 属性
**/
if ((getRegistries() == null || getRegistries().isEmpty())
&& (getConsumer() == null || getConsumer().getRegistries() == null || getConsumer().getRegistries().isEmpty())
&& (getApplication() == null || getApplication().getRegistries() == null || getApplication().getRegistries().isEmpty())) {
Map<String, RegistryConfig> registryConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, RegistryConfig.class, false, false);
if (registryConfigMap != null && registryConfigMap.size() > 0) {
List<RegistryConfig> registryConfigs = new ArrayList<RegistryConfig>();
for (RegistryConfig config : registryConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
registryConfigs.add(config);
}
}
if (registryConfigs != null && !registryConfigs.isEmpty()) {
super.setRegistries(registryConfigs);
}
}
}
/**
检测 MonitorConfig monitor 是否为空, 为空的话将从IOC容器从获取
MonitorConfig 类型的bean对象,并赋值给当前对象的 monitor 属性
**/
if (getMonitor() == null
&& (getConsumer() == null || getConsumer().getMonitor() == null)
&& (getApplication() == null || getApplication().getMonitor() == null)) {
Map<String, MonitorConfig> monitorConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, MonitorConfig.class, false, false);
if (monitorConfigMap != null && monitorConfigMap.size() > 0) {
MonitorConfig monitorConfig = null;
for (MonitorConfig config : monitorConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
if (monitorConfig != null) {
throw new IllegalStateException("Duplicate monitor configs: " + monitorConfig + " and " + config);
}
monitorConfig = config;
}
}
if (monitorConfig != null) {
setMonitor(monitorConfig);
}
}
}
//检查 <dubbo:reference init="true|false" > init属性是否为true,如果为ture证明是饿汉式,会在此处做服务引用
Boolean b = isInit();
if (b == null && getConsumer() != null) {
b = getConsumer().isInit();
}
if (b != null && b.booleanValue()) {
getObject();
}
}
Dubbo 选择在afterPropertiesSet 里做其他配置项的依赖注入和校验,并根据init属性判断是否需要提前服务引用
服务引用的入口方法为 ReferenceBean 的 getObject 方法,该方法定义在 Spring 的 FactoryBean 接口中,ReferenceBean 实现了这个方法。实现代码如下:
public Object getObject() throws Exception {
return get();
}
public synchronized T get() {
if (destroyed) {
throw new IllegalStateException("Already destroyed!");
}
// 检测 ref 是否为空,为空则通过 init 方法创建
if (ref == null) {
// init 方法主要用于处理配置,以及调用 createProxy 生成代理类
init();
}
return ref;
}
检查配置
Dubbo 提供了丰富的配置,用于调整和优化框架行为,性能等。Dubbo 在引用或导出服务时,首先会对这些配置进行检查和处理,以保证配置的正确性。配置解析逻辑封装在 ReferenceConfig 的 init 方法中,下面进行分析。
private void init() {
// 避免重复初始化
if (initialized) {
return;
}
initialized = true;
// 检测接口名合法性
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException("interface not allow null!");
}
// 检测 consumer 变量是否为空,为空则创建
checkDefault();
appendProperties(this);
if (getGeneric() == null && getConsumer() != null) {
// 设置 generic
setGeneric(getConsumer().getGeneric());
}
// 检测是否为泛化接口
if (ProtocolUtils.isGeneric(getGeneric())) {
interfaceClass = GenericService.class;
} else {
try {
// 加载类
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
checkInterfaceAndMethods(interfaceClass, methods);
}
// -------------------------------✨ 分割线1 ✨------------------------------
// 从系统变量中获取与接口名对应的属性值
String resolve = System.getProperty(interfaceName);
String resolveFile = null;
if (resolve == null || resolve.length() == 0) {
// 从系统属性中获取解析文件路径
resolveFile = System.getProperty("dubbo.resolve.file");
if (resolveFile == null || resolveFile.length() == 0) {
// 从指定位置加载配置文件
File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties");
if (userResolveFile.exists()) {
// 获取文件绝对路径
resolveFile = userResolveFile.getAbsolutePath();
}
}
if (resolveFile != null && resolveFile.length() > 0) {
Properties properties = new Properties();
FileInputStream fis = null;
try {
fis = new FileInputStream(new File(resolveFile));
// 从文件中加载配置
properties.load(fis);
} catch (IOException e) {
throw new IllegalStateException("Unload ..., cause:...");
} finally {
try {
if (null != fis) fis.close();
} catch (IOException e) {
logger.warn(e.getMessage(), e);
}
}
// 获取与接口名对应的配置
resolve = properties.getProperty(interfaceName);
}
}
if (resolve != null && resolve.length() > 0) {
// 将 resolve 赋值给 url
url = resolve;
}
// -------------------------------✨ 分割线2 ✨------------------------------
if (consumer != null) {
if (application == null) {
// 从 consumer 中获取 Application 实例,下同
application = consumer.getApplication();
}
if (module == null) {
module = consumer.getModule();
}
if (registries == null) {
registries = consumer.getRegistries();
}
if (monitor == null) {
monitor = consumer.getMonitor();
}
}
if (module != null) {
if (registries == null) {
registries = module.getRegistries();
}
if (monitor == null) {
monitor = module.getMonitor();
}
}
if (application != null) {
if (registries == null) {
registries = application.getRegistries();
}
if (monitor == null) {
monitor = application.getMonitor();
}
}
// 检测 Application 合法性
checkApplication();
// 检测本地存根配置合法性
checkStubAndMock(interfaceClass);
// -------------------------------✨ 分割线3 ✨------------------------------
Map<String, String> map = new HashMap<String, String>();
Map<Object, Object> attributes = new HashMap<Object, Object>();
// 添加 side、协议版本信息、时间戳和进程号等信息到 map 中
map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
// 非泛化服务
if (!isGeneric()) {
// 获取版本
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put("revision", revision);
}
// 获取接口方法列表,并添加到 map 中
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
if (methods.length == 0) {
map.put("methods", Constants.ANY_VALUE);
} else {
map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
}
map.put(Constants.INTERFACE_KEY, interfaceName);
// 将 ApplicationConfig、ConsumerConfig、ReferenceConfig 等对象的字段信息添加到 map 中
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, consumer, Constants.DEFAULT_KEY);
appendParameters(map, this);
// -------------------------------✨ 分割线4 ✨------------------------------
String prefix = StringUtils.getServiceKey(map);
if (methods != null && !methods.isEmpty()) {
// 遍历 MethodConfig 列表
for (MethodConfig method : methods) {
appendParameters(map, method, method.getName());
String retryKey = method.getName() + ".retry";
// 检测 map 是否包含 methodName.retry
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
if ("false".equals(retryValue)) {
// 添加重试次数配置 methodName.retries
map.put(method.getName() + ".retries", "0");
}
}
// 添加 MethodConfig 中的“属性”字段到 attributes
// 比如 onreturn、onthrow、oninvoke 等
appendAttributes(attributes, method, prefix + "." + method.getName());
checkAndConvertImplicitConfig(method, map, attributes);
}
}
// -------------------------------✨ 分割线5 ✨------------------------------
// 获取服务消费者 ip 地址
String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY);
if (hostToRegistry == null || hostToRegistry.length() == 0) {
hostToRegistry = NetUtils.getLocalHost();
} else if (isInvalidLocalHost(hostToRegistry)) {
throw new IllegalArgumentException("Specified invalid registry ip from property..." );
}
map.put(Constants.REGISTER_IP_KEY, hostToRegistry);
// 存储 attributes 到系统上下文中
StaticContext.getSystemContext().putAll(attributes);
// 创建代理类
ref = createProxy(map);
// 根据服务名,ReferenceConfig,代理类构建 ConsumerModel,
// 并将 ConsumerModel 存入到 ApplicationModel 中
ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());
ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);
}
上面这段代码主要做了以下事情:
- 检查是否重复初始化 与 接口的合法性
- 对必须的配置项进行校验
- 构造一个Map 遍历 application module consumer methods 自身(ReferenceBean) 所有的get is方法,并且返回值是基本数据类型,并且不为空,则放入map中, 该Map是构造服务代理的顶级配置项
- 调用createProxy(map) 创建代理对象
接下来我们重点查看createProxy(map) 如何创建代理对象
创建代理对象
private T createProxy(Map<String, String> map) {
URL tmpUrl = new URL("temp", "localhost", 0, map);
final boolean isJvmRefer;
if (isInjvm() == null) {
// url 配置被指定,则不做本地引用
if (url != null && url.length() > 0) {
isJvmRefer = false;
// 根据 url 的协议、scope 以及 injvm 等参数检测是否需要本地引用
// 比如如果用户显式配置了 scope=local,此时 isInjvmRefer 返回 true
} else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
isJvmRefer = true;
} else {
isJvmRefer = false;
}
} else {
// 获取 injvm 配置值
isJvmRefer = isInjvm().booleanValue();
}
// 本地引用
if (isJvmRefer) {
// 生成本地引用 URL,协议为 injvm
URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
// 调用 refer 方法构建 InjvmInvoker 实例
invoker = refprotocol.refer(interfaceClass, url);
// 远程引用
} else {
// url 不为空,表明用户可能想进行点对点调用
if (url != null && url.length() > 0) {
// 当需要配置多个 url 时,可用分号进行分割,这里会进行切分
String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);
if (us != null && us.length > 0) {
for (String u : us) {
URL url = URL.valueOf(u);
if (url.getPath() == null || url.getPath().length() == 0) {
// 设置接口全限定名为 url 路径
url = url.setPath(interfaceName);
}
// 检测 url 协议是否为 registry,若是,表明用户想使用指定的注册中心
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
// 将 map 转换为查询字符串,并作为 refer 参数的值添加到 url 中
urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
} else {
// 合并 url,移除服务提供者的一些配置(这些配置来源于用户配置的 url 属性),
// 比如线程池相关配置。并保留服务提供者的部分配置,比如版本,group,时间戳等
// 最后将合并后的配置设置为 url 查询字符串中。
urls.add(ClusterUtils.mergeUrl(url, map));
}
}
}
} else {
// 加载注册中心 url
List<URL> us = loadRegistries(false);
if (us != null && !us.isEmpty()) {
for (URL u : us) {
URL monitorUrl = loadMonitor(u);
if (monitorUrl != null) {
map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
// 添加 refer 参数到 url 中,并将 url 添加到 urls 中
urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
}
}
// 未配置注册中心,抛出异常
if (urls.isEmpty()) {
throw new IllegalStateException("No such any registry to reference...");
}
}
// 单个注册中心或服务提供者(服务直连,下同)
if (urls.size() == 1) {
// 调用 RegistryProtocol 的 refer 构建 Invoker 实例
invoker = refprotocol.refer(interfaceClass, urls.get(0));
// 多个注册中心或多个服务提供者,或者两者混合
} else {
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
// 获取所有的 Invoker
for (URL url : urls) {
// 通过 refprotocol 调用 refer 构建 Invoker,refprotocol 会在运行时
// 根据 url 协议头加载指定的 Protocol 实例,并调用实例的 refer 方法
invokers.add(refprotocol.refer(interfaceClass, url));
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
registryURL = url;
}
}
if (registryURL != null) {
// 如果注册中心链接不为空,则将使用 AvailableCluster
URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
// 创建 StaticDirectory 实例,并由 Cluster 对多个 Invoker 进行合并
invoker = cluster.join(new StaticDirectory(u, invokers));
} else {
invoker = cluster.join(new StaticDirectory(invokers));
}
}
}
Boolean c = check;
if (c == null && consumer != null) {
c = consumer.isCheck();
}
if (c == null) {
c = true;
}
// invoker 可用性检查
if (c && !invoker.isAvailable()) {
throw new IllegalStateException("No provider available for the service...");
}
// 生成代理类
return (T) proxyFactory.getProxy(invoker);
}
上面代码的逻辑比较清晰,主要判断是否本地服务引用或者远程服务引用:
一、判断本地服务引用逻辑
- 检查是否有配置inJvm=true或者配置的scope=local|true
- 构造一个URL 协议为 injvm:// , 并委派给 Protocol$Adaptive 自适应扩展类去寻找扩展点为 injvm 的 InjvmProtocol.refer() 实例创建Invoker
二、判断远程服务引用逻辑
- 检查是否配置url属性,不为空证明用户想绕过注册中心进行点对点的直连调用 或者 指定注册中心
- 将注册中心配置列表转为URL 列表, 并把入参map转为xx=xx&bb=bb的字符串并进行URL编码添加到URL参数中,参数名为refer,
若url列表等于1,则直接根据URL的协议 委派给 Protocol$Adaptive 自适应扩展类实例创建Invoker,
若 urls 列表数量大于1,即存在多个注册中心或服务直连 url,此时先根据 url 构建 Invoker。然后再通过 Cluster 将多个 Invoker 合并成一个集群Invoker,
最后调用 ProxyFactory 生成代理类
创建Invoker
我们在这里只研究Url列表等于1其使用注册中心发现服务的情况,url内容如下:
registry://10.0.0.77:2181/com.alibaba.dubbo.registry.RegistryService?application=hello-world-consumer&dubbo=2.0.2&pid=17708&refer=application%3Dhello-world-consumer%26dubbo%3D2.0.2%26interface%3Dcom.czj.api.UserApi%26methods%3Dget%26pid%3D17708%26register.ip%3D192.168.233.1%26side%3Dconsumer%26timestamp%3D1580353582836®istry=zookeeper×tamp=1580353582863
协议为 registry , 我们找到 扩展点为 registry 的 Protocol的实例 RegistryProtocol
public class RegistryProtocol implements Protocol {
@Override
@SuppressWarnings("unchecked")
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
//重新设置url的协议值为registry参数, 此处值应该为 zookeeper
url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
//寻找扩展点为zookeeper的RegistryFactory实例, ZookeeperRegistryFactory
Registry registry = registryFactory.getRegistry(url);
//若
if (RegistryService.class.equals(type)) {
return proxyFactory.getInvoker((T) registry, type, url);
}
// 将 url 的refer参数值转为 Map
Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
//获取 group 配置,处理相同服务名不同组,此处不研究
String group = qs.get(Constants.GROUP_KEY);
if (group != null && group.length() > 0) {
if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
|| "*".equals(group)) {
return doRefer(getMergeableCluster(), registry, type, url);
}
}
//创建Invoker
return doRefer(cluster, registry, type, url);
}
}
上面的代码主要重新构造Url的协议值,并动态获取RegistryFactory实例对象,真正创建Invoker的在doRefer代码块中
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
//构造注册中心服务目录对象
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
directory.setRegistry(registry);
directory.setProtocol(protocol);
// 把refer查询字符串转为map
Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
/**
重新构造一个协议为comsumer://,参数为refer的URL 示例内容:
consumer://192.168.233.1/com.czj.api.UserApi?application=hello-world-consumer&dubbo=2.0.2&interface=com.czj.api.UserApi&methods=get&pid=17708&side=consumer×tamp=1580353582836
**/
//
URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);
//检测是否往/dubbo/${服务名}/consumers/consumers 注册,主要用于监控全局服务引用情况
if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
&& url.getParameter(Constants.REGISTER_KEY, true)) {
registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
Constants.CHECK_KEY, String.valueOf(false)));
}
/**
该方法做了以下几个事情:
1、为subscribeUrl追加一个参数:category=providers,configurators,routers
2、并监听下面几个节点下的内容变化
/dubbo/com.czj.api.UserApi/providers
/dubbo/com.czj.api.UserApi/configurators
/dubbo/com.czj.api.UserApi/routers
3、第一次监听会得到监听节点下的所有子节点列表,把节点列表转为URL,并委派给 RegistryDirectory.notify(List<URL> urls) 生成最终的Invoker实例列表
**/
directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
Constants.PROVIDERS_CATEGORY
+ "," + Constants.CONFIGURATORS_CATEGORY
+ "," + Constants.ROUTERS_CATEGORY));
Invoker invoker = cluster.join(directory);
ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
return invoker;
}
public class RegistryDirectory<T> extends AbstractDirectory<T> implements NotifyListener {
public void subscribe(URL url) {
setConsumerUrl(url);
//委派给 ZookeeperRegistry 去监听, 第二个参数是监听事件,因为RegistryDirectory 实现了 NotifyListener,所以传递自身进去
registry.subscribe(url, this);
}
}
//ZookeeperRegistry 继承于 FailbackRegistry,所以我们进到FailbackRegistry 查看
public abstract class FailbackRegistry extends AbstractRegistry {
@Override
public void subscribe(URL url, NotifyListener listener) {
super.subscribe(url, listener);
removeFailedSubscribed(url, listener);
try {
// 委派给子类实现
doSubscribe(url, listener);
} catch (Exception e) {
//...省略异常出出力
}
}
}
public class ZookeeperRegistry extends FailbackRegistry{
@Override
protected void doSubscribe(final URL url, final NotifyListener listener) {
try {
if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
//... 省略无关代码
} else {
//远程服务的url列表, 里面装着的都是dubbo://打头的
List<URL> urls = new ArrayList<URL>();
/**
获取要监听的路径列表,路径获取规则为: 得到category参数,并进行逗号分隔,进行遍历
${服务名}/${catgoryItem}
以服务名com.czj.api.UserApi为例:
/dubbo/com.czj.api.UserApi/providers
/dubbo/com.czj.api.UserApi/configurators
/dubbo/com.czj.api.UserApi/routers
**/
for (String path : toCategoriesPath(url)) {
//根据服务引用URL为key得到一个Map<业务监听事件,zookeeper子节点变化监听事件>
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
//使用putIfAbsent解决并发问题
if (listeners == null) {
zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
listeners = zkListeners.get(url);
}
//查看map是否有缓存
ChildListener zkListener = listeners.get(listener);
if (zkListener == null) {
/**
创建一个zookeeper子节点监听事件并加入缓存,监听事件主要做的事:
1、把子节点内容转为provider URL列表,并校验provider Url对应的接口名、版本号、分组、版本号是否与入参consumer Url是否一致
2、把consumer Url、父节点路径、provider URL列表 委派给ZookeeperRegistry做节点变更事件
**/
listeners.putIfAbsent(listener, new ChildListener() {
@Override
public void childChanged(String parentPath, List<String> currentChilds) {
ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
}
});
zkListener = listeners.get(listener);
}
//根据路径创建持久化节点
zkClient.create(path, false);
//为节点添加字节点变化事件,并返回已存在的子节点路径列表
List<String> children = zkClient.addChildListener(path, zkListener);
if (children != null) {
//把子节点内容转为provider URL列表,并校验provider Url对应的接口名、版本号、分组、版本号是否与入参consumer Url是否一致
urls.addAll(toUrlsWithEmpty(url, path, children));
}
}
//第一次初始化,需要手动执行节点变更事件
notify(url, listener, urls);
}
} catch (Throwable e) {
throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
}
上面主要对URL进行解析,得到需要监听事件的节点,并进行监听事件的注册,第一次监听会得到已存在的子节点路径,并会手动执行节点变更事件,接下来我们来看看
变更事件notify()做了什么事情
protected void notify(URL url, NotifyListener listener, List<URL> urls) {
if (url == null) {
throw new IllegalArgumentException("notify url == null");
}
if (listener == null) {
throw new IllegalArgumentException("notify listener == null");
}
if ((urls == null || urls.isEmpty())
&& !Constants.ANY_VALUE.equals(url.getServiceInterface())) {
logger.warn("Ignore empty notify urls for subscribe url " + url);
return;
}
if (logger.isInfoEnabled()) {
logger.info("Notify urls for subscribe url " + url + ", urls: " + urls);
}
//对不同类型的URL进行分组
Map<String, List<URL>> result = new HashMap<String, List<URL>>();
for (URL u : urls) {
//再次检测子节点URL 与 消费URL的对应的接口名、版本号、分组、版本号是否一致
if (UrlUtils.isMatch(url, u)) {
//获取URL的category的参数,如果为空取providers
String category = u.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
//加入缓存
List<URL> categoryList = result.get(category);
if (categoryList == null) {
categoryList = new ArrayList<URL>();
result.put(category, categoryList);
}
categoryList.add(u);
}
}
if (result.size() == 0) {
return;
}
Map<String, List<URL>> categoryNotified = notified.get(url);
if (categoryNotified == null) {
notified.putIfAbsent(url, new ConcurrentHashMap<String, List<URL>>());
categoryNotified = notified.get(url);
}
//对不同类型的URL进行分批触发事件
for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
String category = entry.getKey();
List<URL> categoryList = entry.getValue();
categoryNotified.put(category, categoryList);
saveProperties(url);
//这里是创建远程调用Invoker的入口,原理是远程节点可能有多个,当有新增或删减时,将会触发远程Invoker的重构事件
listener.notify(categoryList);
}
}
上面主要对远程节点URL进行分组并触发Dubbo NotifyListener 事件,我们入参的NotifyListener是 RegistryDirectory , 我们回到RegistryDirectory查看在里面做了什么
public synchronized void notify(List<URL> urls) {
//服务节点URL列表缓存
List<URL> invokerUrls = new ArrayList<URL>();
//服务路由URL列表缓存
List<URL> routerUrls = new ArrayList<URL>();
//服务配置URL列表缓存
List<URL> configuratorUrls = new ArrayList<URL>();
//判断入参URL的所属类型,并加入所属的缓存
for (URL url : urls) {
String protocol = url.getProtocol();
String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
if (Constants.ROUTERS_CATEGORY.equals(category)
|| Constants.ROUTE_PROTOCOL.equals(protocol)) {
routerUrls.add(url);
} else if (Constants.CONFIGURATORS_CATEGORY.equals(category)
|| Constants.OVERRIDE_PROTOCOL.equals(protocol)) {
configuratorUrls.add(url);
} else if (Constants.PROVIDERS_CATEGORY.equals(category)) {
invokerUrls.add(url);
} else {
logger.warn("Unsupported category " + category + " in notified url: " + url + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost());
}
}
// 处理服务配置
if (configuratorUrls != null && !configuratorUrls.isEmpty()) {
this.configurators = toConfigurators(configuratorUrls);
}
// 处理服务路由
if (routerUrls != null && !routerUrls.isEmpty()) {
List<Router> routers = toRouters(routerUrls);
if (routers != null) { // null - do nothing
setRouters(routers);
}
}
List<Configurator> localConfigurators = this.configurators; // local reference
// merge override parameters
this.overrideDirectoryUrl = directoryUrl;
if (localConfigurators != null && !localConfigurators.isEmpty()) {
for (Configurator configurator : localConfigurators) {
this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl);
}
}
// 刷新远程Invoker
refreshInvoker(invokerUrls);
}
上面根据入参的URL的类型,分别追加到对应类型的List, 再进行对应处理, 我们在这里只查看服务节点URL的处理 refreshInvoker(invokerUrls)
private void refreshInvoker(List<URL> invokerUrls) {
//如果Url的数量等于1,并且协议值为empty,则证明无远程服务节点提供服务,需要删除缓存中的所有远程Invoker
if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null
&& Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
//fobidden标示为ture
this.forbidden = true;
//清空缓存
this.methodInvokerMap = null;
//销毁Invoker
destroyAllInvokers();
} else {
this.forbidden = false; // Allow to access
Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference
if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) {
invokerUrls.addAll(this.cachedInvokerUrls);
} else {
this.cachedInvokerUrls = new HashSet<URL>();
this.cachedInvokerUrls.addAll(invokerUrls);//Cached invoker urls, convenient for comparison
}
if (invokerUrls.isEmpty()) {
return;
}
//将URL转为远程Invoker
Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map
//将远程Invoker提供的方法列表转为Map<方法名,List<Invoker>>
Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // Change method name to map Invoker Map
// state change
// If the calculation is wrong, it is not processed.
if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) {
logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls.toString()));
return;
}
this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
this.urlInvokerMap = newUrlInvokerMap;
try {
//销毁不可用Invoker,如果新节点中的Invoker在历史缓存中不存在,则销毁历史缓存中的Invoker
destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
} catch (Exception e) {
logger.warn("destroyUnusedInvokers error. ", e);
}
}
}
上面主要做了将Url转为远程Invoker,并销毁缓存中不可用的Invoker,下面我们看看如何将URL转为远程Invoker :toInvokers(invokerUrls)
private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<String, Invoker<T>>();
if (urls == null || urls.isEmpty()) {
return newUrlInvokerMap;
}
Set<String> keys = new HashSet<String>();
//得到消费者URL的协议值
String queryProtocols = this.queryMap.get(Constants.PROTOCOL_KEY);
for (URL providerUrl : urls) {
// 如果消费者自定义协议,则需要检查provider的协议值是否支持消费者定义的协议值
if (queryProtocols != null && queryProtocols.length() > 0) {
boolean accept = false;
String[] acceptProtocols = queryProtocols.split(",");
//检查是否匹配协议
for (String acceptProtocol : acceptProtocols) {
if (providerUrl.getProtocol().equals(acceptProtocol)) {
accept = true;
break;
}
}
//不匹配则忽略
if (!accept) {
continue;
}
}
if (Constants.EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) {
continue;
}
//检查消费端是否支持provider 协议的 Protocol 扩展点
if (!ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(providerUrl.getProtocol())) {
logger.error(new IllegalStateException("Unsupported protocol " + providerUrl.getProtocol() + " in notified url: " + providerUrl + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost()
+ ", supported protocol: " + ExtensionLoader.getExtensionLoader(Protocol.class).getSupportedExtensions()));
continue;
}
//把消费端的设置覆盖掉服务端的参数设置, 例如服务端设置的retries=3 ,消费端设置了retries=0,将优先使用消费端的配置
URL url = mergeUrl(providerUrl);
//得到唯一标示
String key = url.toFullString(); // The parameter urls are sorted
//检查是否有重复值
if (keys.contains(key)) { // Repeated url
continue;
}
keys.add(key);
//得到本地缓存
Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference
//查看本地缓存是否已存在
Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key);
//缓存不存在,则需要创建
if (invoker == null) {
try {
//查看远程服务是否禁用参数标示
boolean enabled = true;
if (url.hasParameter(Constants.DISABLED_KEY)) {
enabled = !url.getParameter(Constants.DISABLED_KEY, false);
} else {
enabled = url.getParameter(Constants.ENABLED_KEY, true);
}
if (enabled) {
/**
开始创建远程Invoker, 以dubbo协议URL为例:
dubbo://192.168.251.37:20880/com.czj.rpc.api.UserApi?anyhost=true&application=hello-world-app
&bind.ip=192.168.251.37&bind.port=20880&dubbo=2.0.2&generic=false
&interface=com.czj.rpc.api.UserApi&methods=getName,get,save&pid=41080&side=provider×tamp=1578635962790
将会调用 DubboProtocol.refer
**/
invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);
}
} catch (Throwable t) {
logger.error("Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t);
}
//加入缓存
if (invoker != null) {
newUrlInvokerMap.put(key, invoker);
}
} else {
newUrlInvokerMap.put(key, invoker);
}
}
keys.clear();
return newUrlInvokerMap;
}
上面的代码逻辑比较清晰 , 我们直接跟进DubboProtocol.refer查看
private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<String, Invoker<T>>();
if (urls == null || urls.isEmpty()) {
return newUrlInvokerMap;
}
Set<String> keys = new HashSet<String>();
//得到消费者URL的协议值
String queryProtocols = this.queryMap.get(Constants.PROTOCOL_KEY);
for (URL providerUrl : urls) {
// 如果消费者自定义协议,则需要检查provider的协议值是否支持消费者定义的协议值
if (queryProtocols != null && queryProtocols.length() > 0) {
boolean accept = false;
String[] acceptProtocols = queryProtocols.split(",");
//检查是否匹配协议
for (String acceptProtocol : acceptProtocols) {
if (providerUrl.getProtocol().equals(acceptProtocol)) {
accept = true;
break;
}
}
//不匹配则忽略
if (!accept) {
continue;
}
}
if (Constants.EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) {
continue;
}
//检查消费端是否支持provider 协议的 Protocol 扩展点
if (!ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(providerUrl.getProtocol())) {
logger.error(new IllegalStateException("Unsupported protocol " + providerUrl.getProtocol() + " in notified url: " + providerUrl + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost()
+ ", supported protocol: " + ExtensionLoader.getExtensionLoader(Protocol.class).getSupportedExtensions()));
continue;
}
//把消费端的设置覆盖掉服务端的参数设置, 例如服务端设置的retries=3 ,消费端设置了retries=0,将优先使用消费端的配置
URL url = mergeUrl(providerUrl);
//得到唯一标示
String key = url.toFullString(); // The parameter urls are sorted
//检查是否有重复值
if (keys.contains(key)) { // Repeated url
continue;
}
keys.add(key);
//得到本地缓存
Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference
//查看本地缓存是否已存在
Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key);
//缓存不存在,则需要创建
if (invoker == null) {
try {
//查看远程服务是否禁用参数标示
boolean enabled = true;
if (url.hasParameter(Constants.DISABLED_KEY)) {
enabled = !url.getParameter(Constants.DISABLED_KEY, false);
} else {
enabled = url.getParameter(Constants.ENABLED_KEY, true);
}
if (enabled) {
/**
开始创建远程Invoker, 以dubbo协议URL为例:
dubbo://192.168.251.37:20880/com.czj.rpc.api.UserApi?anyhost=true&application=hello-world-app
&bind.ip=192.168.251.37&bind.port=20880&dubbo=2.0.2&generic=false
&interface=com.czj.rpc.api.UserApi&methods=getName,get,save&pid=41080&side=provider×tamp=1578635962790
将会调用 DubboProtocol.refer
**/
invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);
}
} catch (Throwable t) {
logger.error("Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t);
}
//加入缓存
if (invoker != null) {
newUrlInvokerMap.put(key, invoker);
}
} else {
newUrlInvokerMap.put(key, invoker);
}
}
keys.clear();
return newUrlInvokerMap;
}
上面的代码逻辑比较清晰 , 我们直接跟进DubboProtocol.refer查看
public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
optimizeSerialization(url);
// 创建 DubboInvoker
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}
上面方法看起来比较简单,不过这里有一个调用需要我们注意一下,即 getClients。这个方法用于获取客户端实例,实例类型为 ExchangeClient。ExchangeClient 实际上并不具备通信能力,它需要基于更底层的客户端实例进行通信。比如 NettyClient、MinaClient 等,默认情况下,Dubbo 使用 NettyClient 进行通信。接下来,我们简单看一下 getClients 方法的逻辑。
private ExchangeClient[] getClients(URL url) {
// 是否共享连接
boolean service_share_connect = false;
// 获取连接数,默认为0,表示未配置
int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
// 如果未配置 connections,则共享连接
if (connections == 0) {
service_share_connect = true;
connections = 1;
}
ExchangeClient[] clients = new ExchangeClient[connections];
for (int i = 0; i < clients.length; i++) {
if (service_share_connect) {
// 获取共享客户端
clients[i] = getSharedClient(url);
} else {
// 初始化新的客户端
clients[i] = initClient(url);
}
}
return clients;
}
这里根据 connections 数量决定是获取共享客户端还是创建新的客户端实例,默认情况下,使用共享客户端实例。getSharedClient 方法中也会调用 initClient 方法,因此下面我们一起看一下这两个方法。
private ExchangeClient getSharedClient(URL url) {
String key = url.getAddress();
// 获取带有“引用计数”功能的 ExchangeClient
ReferenceCountExchangeClient client = referenceClientMap.get(key);
if (client != null) {
if (!client.isClosed()) {
// 增加引用计数
client.incrementAndGetCount();
return client;
} else {
referenceClientMap.remove(key);
}
}
locks.putIfAbsent(key, new Object());
synchronized (locks.get(key)) {
if (referenceClientMap.containsKey(key)) {
return referenceClientMap.get(key);
}
// 创建 ExchangeClient 客户端
ExchangeClient exchangeClient = initClient(url);
// 将 ExchangeClient 实例传给 ReferenceCountExchangeClient,这里使用了装饰模式
client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap);
referenceClientMap.put(key, client);
ghostClientMap.remove(key);
locks.remove(key);
return client;
}
}
上面方法先访问缓存,若缓存未命中,则通过 initClient 方法创建新的 ExchangeClient 实例,并将该实例传给 ReferenceCountExchangeClient 构造方法创建一个带有引用计数功能的 ExchangeClient 实例。ReferenceCountExchangeClient 内部实现比较简单,就不分析了。下面我们再来看一下 initClient 方法的代码。
private ExchangeClient initClient(URL url) {
// 获取客户端类型,默认为 netty
String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));
// 添加编解码和心跳包参数到 url 中
url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
// 检测客户端类型是否存在,不存在则抛出异常
if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
throw new RpcException("Unsupported client type: ...");
}
ExchangeClient client;
try {
// 获取 lazy 配置,并根据配置值决定创建的客户端类型
if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
// 创建懒加载 ExchangeClient 实例
client = new LazyConnectExchangeClient(url, requestHandler);
} else {
// 创建普通 ExchangeClient 实例
client = Exchangers.connect(url, requestHandler);
}
} catch (RemotingException e) {
throw new RpcException("Fail to create remoting client for service...");
}
return client;
}