Dubbo之服务引用
服务引用
-
服务引用分为两步
- 通过持有远程服务实例生成Invoker,这个Invoker在客户端是核心的远程代理对象
- 将Invoker通过动态代理转换为实现用户接口的动态代理引用
-
服务引用的入口在
ReferenceBean#getObject
@Override public Object getObject() throws Exception { return get(); } public synchronized T get() { if (destroyed) { throw new IllegalStateException("Already destroyed!"); } if (ref == null) { init(); } return ref; }
-
ReferenceConfig#init
服务引用初始化的核心步骤- 配置检查与封装
- 泛化调用,对接口类赋值。非泛化调用,尝试加载接口类
- 获取是否有直连配置(从系统变量和文件中加载)
- 消费主机IP设置,优先从系统变量中读取
- 创建远程代理对象和转换为Invoker
private void init() { //避免重复初始化 if (initialized) { return; } initialized = true; if (interfaceName == null || interfaceName.length() == 0) { throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!"); } // get consumer's global configuration 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); } // 从系统变量中获取与接口名对应的属性值,用于直连,配置类似java -Dcom.alibaba.xxx.XxxService=dubbo://localhost:20890 String resolve = System.getProperty(interfaceName); String resolveFile = null; if (resolve == null || resolve.length() == 0) { // 从系统属性中获取解析文件路径,java -Ddubbo.resolve.file=xxx.properties 优先级高 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)); // 从文件中加载配置(用于直连),映射文件的格式类似com.alibaba.xxx.XxxService=dubbo://localhost:20890 properties.load(fis); } catch (IOException e) { throw new IllegalStateException("Unload " + resolveFile + ", cause: " + e.getMessage(), e); } 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) { //赋值给直连的URL地址,类似dubbo://172.16.117.33:20882/ url = resolve; if (logger.isWarnEnabled()) { if (resolveFile != null) { logger.warn("Using default dubbo resolve file " + resolveFile + " replace " + interfaceName + "" + resolve + " to p2p invoke remote service."); } else { logger.warn("Using -D" + interfaceName + "=" + resolve + " to p2p invoke remote service."); } } } if (consumer != null) { if (application == null) { 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(); } } checkApplication(); checkStub(interfaceClass); checkMock(interfaceClass); Map<String, String> map = new HashMap<String, String>(); Map<Object, Object> attributes = new HashMap<Object, Object>(); 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); } //com.alibaba.dubbo.common.bytecode.Wrapper0 这个的意义是什么? //直接通过接口的interfaceClass.getMethods()获取不行吗? //猜测意义:1. 提前生成代理类,会缓存在内存中。 // 2. wrapper可以对interface的方法做过滤。后面ConsumerModel又使用interfaceClass.getMethods(),感觉这个猜测可能不成立 String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames(); if (methods.length == 0) { logger.warn("NO method found in service interface " + interfaceClass.getName()); map.put("methods", Constants.ANY_VALUE); } else { map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ",")); } } map.put(Constants.INTERFACE_KEY, interfaceName); appendParameters(map, application); appendParameters(map, module); appendParameters(map, consumer, Constants.DEFAULT_KEY); appendParameters(map, this); String prefix = StringUtils.getServiceKey(map); //method配置解析 <dubbo:method name="sayHello" retries="1" onreturn="xx" onthrow=xx/> if (methods != null && !methods.isEmpty()) { for (MethodConfig method : methods) { appendParameters(map, method, method.getName()); //方法名.retry String retryKey = method.getName() + ".retry"; if (map.containsKey(retryKey)) { String retryValue = map.remove(retryKey); if ("false".equals(retryValue)) { map.put(method.getName() + ".retries", "0"); } } appendAttributes(attributes, method, prefix + "." + method.getName()); checkAndConvertImplicitConfig(method, map, attributes); } } // 以系统环境变量(DUBBO_IP_TO_REGISTRY)的值作为服务消费者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:" + Constants.DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry); } map.put(Constants.REGISTER_IP_KEY, hostToRegistry); //attributes are stored by system context. StaticContext.getSystemContext().putAll(attributes); //创建代理类 com.alibaba.dubbo.common.bytecode.proxy0 //Dubbo支持多注册中心同时消费,如果有多个注册中心,则合并成一个invoker ref = createProxy(map); ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods()); ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel); }
- 配置检查与封装
-
ReferenceConfig#createProxy
:完成了远程代理对象的创建及代理对象的转换等工作- 如果是JVM内部引用(injvm="true"或者scope="local"),则直接从内存中获取(
InjvmProtocol=>InjvmInvoker
) - 如果是点对点直连调用,则合并URL,移除服务提供者的一些配置。
DubboProtocol => DubboInvoker
- 如果是一个注册中心,则启动并拉取元数据。如果是多个注册中心,则合并invoker。
RegistryProtocol => DubboInvoker
@SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) 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) { // if a url is specified, don't do local reference isJvmRefer = false; // 根据 url 的协议、scope 以及 injvm 等参数检测是否需要本地引用 // 比如如果用户显式配置了 scope=local,此时 isInjvmRefer 返回 true } else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) { // by default, reference local service if there is isJvmRefer = true; } else { isJvmRefer = false; } } else { // 获取 injvm 配置值 , <dubbo:reference injvm="true" /> 这种配置时,已过时(现在使用scope="local") isJvmRefer = isInjvm().booleanValue(); } // 本地引用 if (isJvmRefer) { //设置protocol为injvm,此时protocol对应的是InjvmProtocol,invoker对应的是InjvmInvoker URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map); //直接从内存中获取实例inJvm invoker = refprotocol.refer(interfaceClass, url); if (logger.isInfoEnabled()) { logger.info("Using injvm service " + interfaceClass.getName()); } } else { // 远程引用,url 不为空(比如dubbo://172.16.117.33:20882,多个使用分号分隔),表明想进行点对点调用 if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address. //使用分号分割多个 String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url); // 当需要配置多个 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,若是,表明用户想使用指定的注册中心(直连模式使用注册中心) // xml中的url配置类似如下格式registry://dubbo-zookeeper:2181/cn.jannal.dubbo.facade.DemoService/?registry=zookeeper 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 { // assemble URL from register center's configuration // 加载注册中心 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 " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config."); } } // 单个注册中心或服务提供者(服务直连,下同) if (urls.size() == 1) { // 直连调用是直接调用DubboProtocol 的 refer 构建 Invoker 实例 //DubboProtocol => DubboInvoker 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 实例(RegistryProtocol),并调用实例的 refer 方法 invokers.add(refprotocol.refer(interfaceClass, url)); if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) { registryURL = url; // use last registry url } } if (registryURL != null) { // registry url is available // use AvailableCluster only when register's cluster is available // 如果注册中心链接不为空,则将使用 AvailableCluster URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME); invoker = cluster.join(new StaticDirectory(u, invokers)); } else { // not a registry url // 无注册中心(直连),创建 StaticDirectory 实例,并由 Cluster 对多个 Invoker 进行合并 invoker = cluster.join(new StaticDirectory(invokers)); } } } Boolean c = check; if (c == null && consumer != null) { c = consumer.isCheck(); } if (c == null) { c = true; // default true } // invoker 可用性检查 if (c && !invoker.isAvailable()) { // make it possible for consumer to retry later if provider is temporarily unavailable initialized = false; //可能会OOM,2.6.11已经修复https://github.com/apache/dubbo/issues/8515 //RegistryProtocol调用了 ProviderConsumerRegTable.registerConsumer //所以如果如果出现错误,需要清除ProviderConsumerRegTable中的consumerInvoker,避免内存泄露 throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion()); } if (logger.isInfoEnabled()) { logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl()); } // create service proxy // 生成代理类 return (T) proxyFactory.getProxy(invoker); }
- 如果是JVM内部引用(injvm="true"或者scope="local"),则直接从内存中获取(
-
服务引用大致流程图
-
服务引用活动图
InvjmProtocol引用部分
-
InvjmProtocol.refer就是创建一个InjvmInvoker
//exporterMap是在本地export时在InjvmExporter的构造方法中赋值的 @Override public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException { return new InjvmInvoker<T>(serviceType, url, url.getServiceKey(), exporterMap); }
DubboProtocol引用部分
-
DubboProtocol.refer是创建一个DubboInvoker
@Override public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException { optimizeSerialization(url); // create rpc invoker. DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers); invokers.add(invoker); return invoker; }
RegistryProtocol引用部分
-
RegistryProtocol.refer
@Override @SuppressWarnings("unchecked") public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException { //设置注册中心协议 url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY); //创建具体注册中心实例 Registry registry = registryFactory.getRegistry(url); if (RegistryService.class.equals(type)) { return proxyFactory.getInvoker((T) registry, type, url); } // group="a,b" or group="*" 多分组结果聚合 Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY)); 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); } } //处理订阅数据并通过Cluster合并多个Invoker return doRefer(cluster, registry, type, url); } 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); // all attributes of REFER_KEY Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters()); // 生成服务消费者链接 URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters); // 注册服务消费者,在 consumers 目录下新节点 if (!Constants.ANY_VALUE.equals(url.getServiceInterface()) && url.getParameter(Constants.REGISTER_KEY, true)) { URL registeredConsumerUrl = getRegisteredConsumerUrl(subscribeUrl, url); //注册消费信息到注册中心 registry.register(registeredConsumerUrl); directory.setRegisteredConsumerUrl(registeredConsumerUrl); } //订阅服务提供者、路由和动态配置, providers、configurators、routers 等节点数据 directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY, Constants.PROVIDERS_CATEGORY + "," + Constants.CONFIGURATORS_CATEGORY + "," + Constants.ROUTERS_CATEGORY)); //通过Cluster合并invokers,一个注册中心可能有多个服务提供者,需要将多个服务提供者合并为一个 Invoker invoker = cluster.join(directory); ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory); return invoker; }