Dubbo 服务导出详解

·  阅读 729

所谓服务导出,其实是包括两方面,一是根据配置将服务注册到注册中心,虽然注册中心并不是必需的,但注册到注册中心确实最佳实践,二是将服务导出到list并且开启服务。两个结合来看就是,要做的就是两件事,假如我们定义一个服务提供者,首先要生成一个可执行的Bean吧,然后服务消费者能通过tcp请求服务提供者暴露的接口吧

Dubbo默认使用Zookeeper作为服务注册中心,使用Netty作为网络通信框架,所以延伸了解一下这两个框架,将会是对阅读源码大有裨益

几个重要概念:

URL,Dubbo URL不是java.net.Url,Dubbo URL代表这一种资源定位,可以表示注册中心,也可以用来表示服务提供者,还可以表示本地资源

Invoker,虽然官网有介绍说,这是个实体域,所有其他模型都向它靠拢,但还是觉得有点抽象,本身也没有一个明确的定义,所以不妨看看DubboInvoker,大概的作用就是提供远程通信交互的能力,也就是说,远程调用基本就是通过Invoker来完成的

Proxy,是对Invoker的代理,目的是为了让用户透明的使用Invoker,以避免Dubbo代码污染用户代码,这两个的获取,都可以在JavassistProxyFactory里找得到

Protocol,代表这一种协议,如果需要注册中心的话,Dubbo就是使用RegistryProtocol

doExport

在前面讲Dubbo与Spring整合时,讲到了服务导出的入口,就是doExport,所以我们直接看这个方法

protected synchronized void doExport() {
	// 省略不重要的代码
	checkApplication();
	checkRegistry();
	checkProtocol();
	appendProperties(this);
	checkStub(interfaceClass);
	checkMock(interfaceClass);
	if (path == null || path.length() == 0) {
		path = interfaceName;
	}
	// 直接执行这个方法导出
	doExportUrls();
	CodecSupport.addProviderSupportedSerialization(getUniqueServiceName(), getExportedUrls());
	ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
	ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
}
复制代码

这个方法其实也没有多复杂,主要是前面做了一些校验,然后直接调用doExportUrls()

doExportUrls

private void doExportUrls() {
        List<URL> registryURLs = loadRegistries(true);
        for (ProtocolConfig protocolConfig : protocols) {
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }
复制代码

这个方法主要是加载注册中心,根据注册中心配置和协议配置,再进行服务导出

loadRegistries

protected List<URL> loadRegistries(boolean provider) {
	// 检查是否有registry配置
	checkRegistry();
	List<URL> registryList = new ArrayList<URL>();
	if (registries != null && !registries.isEmpty()) {
		for (RegistryConfig config : registries) {
			String address = config.getAddress();
			if (address == null || address.length() == 0) {
				address = Constants.ANYHOST_VALUE;
			}
			String sysaddress = System.getProperty("dubbo.registry.address");
			if (sysaddress != null && sysaddress.length() > 0) {
				address = sysaddress;
			}
			if (address.length() > 0 && !RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
				Map<String, String> map = new HashMap<String, String>();
				appendParameters(map, application);
				appendParameters(map, config);
				map.put("path", RegistryService.class.getName());
				map.put("dubbo", 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 (!map.containsKey("protocol")) {
					if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension("remote")) {
						map.put("protocol", "remote");
					} else {
						map.put("protocol", "dubbo");
					}
				}
				// 这里把配置转为URL
				List<URL> urls = UrlUtils.parseURLs(address, map);
				for (URL url : urls) {
					// 因为这个,把协议变成registry,这样就可以在自适应扩展时调用RegistryProtocol
					url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());
					url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
					if ((provider && url.getParameter(Constants.REGISTER_KEY, true))
							|| (!provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) {
						registryList.add(url);
					}
				}
			}
		}
	}
	return registryList;
}
复制代码

doExportUrlsFor1Protocol

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
	// 获取协议名称,会决定使用哪个扩展类Protocol,如果是dubbo,那就是DubboProtocol
	String name = protocolConfig.getName();
	if (name == null || name.length() == 0) {
		name = "dubbo";
	}
	// 这里组装参数,可以忽略
	Map<String, String> map = new HashMap<String, String>();
	map.put(Constants.SIDE_KEY, Constants.PROVIDER_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()));
	}
	appendParameters(map, application);
	appendParameters(map, module);
	appendParameters(map, provider, Constants.DEFAULT_KEY);
	appendParameters(map, protocolConfig);
	appendParameters(map, this);
	
	// 这个if块很长,这个块主要是组装方法属性,比如重试和参数,我觉得不去纠结它是没问题的
	if (methods != null && !methods.isEmpty()) {
		for (MethodConfig method : methods) {
			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 (arguments != null && !arguments.isEmpty()) {
				for (ArgumentConfig argument : arguments) {
					// convert argument type
					if (argument.getType() != null && argument.getType().length() > 0) {
						Method[] methods = interfaceClass.getMethods();
						// visit all methods
						if (methods != null && 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())) {
											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())) {
												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) {
						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
	}

	// 泛型调用参数
	if (ProtocolUtils.isGeneric(generic)) {
		map.put(Constants.GENERIC_KEY, generic);
		map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
	} else {
		// 版本
		String revision = Version.getVersion(interfaceClass, version);
		if (revision != null && revision.length() > 0) {
			map.put("revision", revision);
		}

		String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
		if (methods.length == 0) {
			logger.warn("NO method found in service interface " + interfaceClass.getName());
			map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
		} else {
			map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
		}
	}
	if (!ConfigUtils.isEmpty(token)) {
		if (ConfigUtils.isDefault(token)) {
			map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
		} else {
			map.put(Constants.TOKEN_KEY, token);
		}
	}
	if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {
		protocolConfig.setRegister(false);
		map.put("notify", "false");
	}
	// 服务导出的上下文
	String contextPath = protocolConfig.getContextpath();
	if ((contextPath == null || contextPath.length() == 0) && provider != null) {
		contextPath = provider.getContextpath();
	}

	// 服务暴露的地址和端口
	String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
	Integer port = this.findConfigedPorts(protocolConfig, name, map);
	// 构造URL
	URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);

	if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
			.hasExtension(url.getProtocol())) {
		url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
				.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
	}

	String scope = url.getParameter(Constants.SCOPE_KEY);
	// don't export when none is configured
	if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {

		// 导出到本地
		if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
			exportLocal(url);
		}
		// 导出到远程
		if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
			if (logger.isInfoEnabled()) {
				logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
			}
			if (registryURLs != null && !registryURLs.isEmpty()) {
				for (URL registryURL : registryURLs) {
					url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
					URL monitorUrl = loadMonitor(registryURL);
					if (monitorUrl != null) {
						url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
					}
					if (logger.isInfoEnabled()) {
						logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
					}

					// For providers, this is used to enable custom proxy to generate invoker
					String proxy = url.getParameter(Constants.PROXY_KEY);
					if (StringUtils.isNotEmpty(proxy)) {
						registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
					}

					Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
					DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

					Exporter<?> exporter = protocol.export(wrapperInvoker);
					exporters.add(exporter);
				}
			} else {
				Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
				DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

				Exporter<?> exporter = protocol.export(wrapperInvoker);
				exporters.add(exporter);
			}
		}
	}
	this.urls.add(url);
}
复制代码

这个方法很长,而导出的所有逻辑就在这个方法里了,接下来我们重点关注exportLocal()和protocol.export()

exportLocal

private void exportLocal(URL url) {
	if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
		URL local = URL.valueOf(url.toFullString())
				.setProtocol(Constants.LOCAL_PROTOCOL)
				.setHost(LOCALHOST)
				.setPort(0);
		StaticContext.getContext(Constants.SERVICE_IMPL_CLASS).put(url.getServiceKey(), getServiceClass(ref));
		// 创建Exporter,然后添加到exporters列表
		Exporter<?> exporter = protocol.export(
				proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
		exporters.add(exporter);
		logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
	}
}
复制代码

导出到本地的逻辑是挺简单的,就是根据URL构造Exporter,然后添加到exporters,不用多讲。通过proxyFactory获取Invoker,ProxyFactory是根据自适应扩展创建的,默认实现类是JavassistProxyFactory,主要提供两种方法,获取Proxy和获取Invoker

导出到远程

首先我们来了解一下Wrapper,Dubbo有些组件提供了包装器,用来做功能增强的,比如Protocol有Protocol-FilterWrapper、ProtocolListenerWrapper、QosProtocolWrapper等包装器,包装器设置的条件是实现类有一个带有Protocol参数的构造器,在ExtensionLoader的createExtension时设置包装器,如下

Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
	for (Class<?> wrapperClass : wrapperClasses) {
		instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
	}
}
return instance;
复制代码

关于导出到远程,我们主要看RegistryProtocol,因为一般我们是需要注册中心的,所以看看RegistryProtocol. export()

public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
	// 导出服务,这个originInvoker的协议是dubbo,所以里面导出的逻辑在DubboProtocol里
	final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);

	URL registryUrl = getRegistryUrl(originInvoker);

	// 获取注册中心
	final Registry registry = getRegistry(originInvoker);
	final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker);

	// 判断是否需要将服务注册
	boolean register = registeredProviderUrl.getParameter("register", true);

	ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl);

	if (register) {
		// 注册
		register(registryUrl, registeredProviderUrl);
		ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
	}

	// 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.
	final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl);
	final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
	overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
	registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
	//Ensure that a new exporter instance is returned every time export
	return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl);
}

public void register(URL registryUrl, URL registedProviderUrl) {
	Registry registry = registryFactory.getRegistry(registryUrl);
	registry.register(registedProviderUrl);
}

复制代码

registryFactory返回的是ZookeeperRegistryFactory,所以注册的逻辑在ZookeeperRegistry里,这涉及到Zookeeper的操作,这里就不多讲了,反正操作不会太复杂,进去了解耐心点基本都能看懂

我们继续看看doLocalExport,具体的逻辑

private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) {
	// 从缓存换取,如果为空就执行导出
	String key = getCacheKey(originInvoker);
	ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
	if (exporter == null) {
		synchronized (bounds) {
			exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
			if (exporter == null) {
				// invokerDelegete是个代理Invoker
				final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
				// 这里的protocol是DubboProtocol
				exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);
				bounds.put(key, exporter);
			}
		}
	}
	return exporter;
}
复制代码

这里的逻辑不负责,但是有个地方要注意的是protocol.export(),这个protocol是DubboProtocol,也就是说,在 RegistryProtocol里持有DubboProtocol,确实是,在ServiceConfig的doExportUrls里的loadRegistries()可以看到

public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
	// 暴露接口
	URL url = invoker.getUrl();

	// 把Exporter放到缓存
	String key = serviceKey(url);
	DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
	exporterMap.put(key, exporter);

	//判断是否需要生成存根,和callback回调
	Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT);
	Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
	if (isStubSupportEvent && !isCallbackservice) {
		String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
		if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
			if (logger.isWarnEnabled()) {
				logger.warn(new IllegalStateException("consumer [" + url.getParameter(Constants.INTERFACE_KEY) +
						"], has set stubproxy support event ,but no stub methods founded."));
			}
		} else {
			stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
		}
	}
	// 开启服务
	openServer(url);
	optimizeSerialization(url);
	return exporter;
}
复制代码

这里关键的是openServer,大概就是开启TCP端口,底层是使用Netty进行网络通信,关于Netty挺有意思,有兴趣还是可以多了解一下,这里就不展开了

总结

整体思路并不复杂,关于细节(主要是配置)这里没有细讲,因为Dubbo很多特性都是由配置决定的,所以我觉得还是可以去官网了解一下用户手册

服务导出主要就是定义一个服务提供者,一个可执行的Bean,为消费者请求提供服务;然后再开启socket,提供给消费者连接,进行socket通信,底层默认使用Netty框架

分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改