服务发布的源码分析(七)
在说dubbo的发布源码中插入了五、六两章节的扩展点说明,下面接着第四篇幅的doExportUrlsFor1Protocol()方法说明;
1、doExportUrlsFor1Protocol根据不同协议暴露服务
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) { String name = protocolConfig.getName(); if (StringUtils.isEmpty(name)) { name = DUBBO; } Map<String, String> map = new HashMap<String, String>(); map.put(SIDE_KEY, PROVIDER_SIDE); ServiceConfig.appendRuntimeParameters(map); AbstractConfig.appendParameters(map, getMetrics()); AbstractConfig.appendParameters(map, getApplication()); AbstractConfig.appendParameters(map, getModule()); // remove 'default.' prefix for configs from ProviderConfig // appendParameters(map, provider, Constants.DEFAULT_KEY); AbstractConfig.appendParameters(map, provider); AbstractConfig.appendParameters(map, protocolConfig); AbstractConfig.appendParameters(map, this); MetadataReportConfig metadataReportConfig = getMetadataReportConfig(); if (metadataReportConfig != null && metadataReportConfig.isValid()) { map.putIfAbsent(METADATA_KEY, REMOTE_METADATA_STORAGE_TYPE); } if (CollectionUtils.isNotEmpty(getMethods())) { for (MethodConfig method : getMethods()) { AbstractConfig.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 (CollectionUtils.isNotEmpty(arguments)) { for (ArgumentConfig argument : arguments) { // convert argument type if (argument.getType() != null && argument.getType().length() > 0) { Method[] methods = interfaceClass.getMethods(); // visit all methods if (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())) { AbstractConfig.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())) { AbstractConfig.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) { AbstractConfig.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(GENERIC_KEY, generic); map.put(METHODS_KEY, ANY_VALUE); } else { String revision = Version.getVersion(interfaceClass, version); if (revision != null && revision.length() > 0) { map.put(REVISION_KEY, revision); } String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames(); if (methods.length == 0) { logger.warn("No method found in service interface " + interfaceClass.getName()); map.put(METHODS_KEY, ANY_VALUE); } else { map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ",")); } } /** * Here the token value configured by the provider is used to assign the value to ServiceConfig#token */ if(ConfigUtils.isEmpty(token) && provider != null) { token = provider.getToken(); } if (!ConfigUtils.isEmpty(token)) { if (ConfigUtils.isDefault(token)) { map.put(TOKEN_KEY, UUID.randomUUID().toString()); } else { map.put(TOKEN_KEY, token); } } //init serviceMetadata attachments serviceMetadata.getAttachments().putAll(map); // export service // 获取dubbo暴露的地址端口 String host = findConfigedHosts(protocolConfig, registryURLs, map); Integer port = findConfigedPorts(protocolConfig, name, map); //拼接url URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map); // You can customize Configurator to append extra parameters // SPI处理,可以自定义url拼接规则 if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class) .hasExtension(url.getProtocol())) { url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class) .getExtension(url.getProtocol()).getConfigurator(url).configure(url); } //scope表示这个服务是发布本地还是远程,可以点SCOPE_KEY看下他的参数 String scope = url.getParameter(SCOPE_KEY); // don't export when none is configured //配置为none不暴露 if (!SCOPE_NONE.equalsIgnoreCase(scope)) { // export to local if the config is not remote (export to remote only when config is remote) //配置不是remote的情况下做本地暴露 (配置为remote,则表示只暴露远程服务) if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) { // 本地服务导出 exportLocal(url); } // export to remote if the config is not local (export to local only when config is local) //如果配置不是local则暴露为远程服务.(配置为local,则表示只暴露远程服务) if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) { // 需要判断服务注册中心是否存在,如果注册url地址存在,例如申明了注册的zk地址 if (CollectionUtils.isNotEmpty(registryURLs)) { // 注册的zk地址可能是集群或多个,那么需要遍历这些地址一一进行注册 for (URL registryURL : registryURLs) { //if protocol is only injvm ,not register // //如果设置的protocol是injvm,跳过 if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) { continue; } // 是否动态,该字段标识是有自动管理服务提供者的上线和下线,若为false 则人工管理 url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY)); // 监控中心配置 URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL); if (monitorUrl != null) { url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString()); } if (logger.isInfoEnabled()) { if (url.getParameter(REGISTER_KEY, true)) { logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL); } else { logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url); } } // 是否采用自定义的动态代理机制的扩展,默认是javassist // For providers, this is used to enable custom proxy to generate invoker String proxy = url.getParameter(PROXY_KEY); if (StringUtils.isNotEmpty(proxy)) { //registy://ip:port registryURL = registryURL.addParameter(PROXY_KEY, proxy); } // 远程服务导出,把实现类包装成invoker Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())); DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); // 暴露服务,并生成 Exporter,注册到注册中心 Exporter<?> exporter = PROTOCOL.export(wrapperInvoker); exporters.add(exporter); } } else { if (logger.isInfoEnabled()) { logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url); } // 如果没有注册中心,则采用直连方式,即没有服务注册和监听的过程 Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url); DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); //暴露服务,注册到注册中心 Exporter<?> exporter = PROTOCOL.export(wrapperInvoker); exporters.add(exporter); } /** * @since 2.7.0 * ServiceData Store */ WritableMetadataService metadataService = WritableMetadataService.getExtension(url.getParameter(METADATA_KEY, DEFAULT_METADATA_STORAGE_TYPE)); if (metadataService != null) { // 元数据存储 metadataService.publishServiceDefinition(url); } } } this.urls.add(url); }
方法比较长,值得关注的有两个方法:
- 获取invoker:Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
- 服务暴露:Exporter<?> exporter = PROTOCOL.export(wrapperInvoker)
2、获取invoker
在 Dubbo 中,Invoker 是一个非常重要的模型。在服务提供端,以及服务引用端均会出现 Invoker。Dubbo 官方文档中对Invoker 进行了说明,这里引用一下。 Invoker 是实体域,它是 Dubbo的核心模型,其它模型都向它靠扰,或转换成它,
它代表一个可执行体,可向它发起 invoke调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
其中这里面最难的逻辑可能就是Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);它返回的Invoker对象是什么了,说实话如果不懂自适应扩展点的看这玩意是看不懂的;
其实这里的PROXY_FACTORY是在ServiceConfig中定义的静态常量,赋值后无法修改:
private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
看到这里的朋友肯定就明白了,接下来其实要走的逻辑其实就是自定义扩展点的逻辑了;proxyFactory通过ExtensionLoader拓展机制进行加载。查看ProxyFactory接口源码如下:
package org.apache.dubbo.rpc; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.Adaptive; import org.apache.dubbo.common.extension.SPI; import static org.apache.dubbo.rpc.Constants.PROXY_KEY; /** * ProxyFactory. (API/SPI, Singleton, ThreadSafe) */ @SPI("javassist") public interface ProxyFactory { /** * create proxy. * * @param invoker * @return proxy */ @Adaptive({PROXY_KEY}) <T> T getProxy(Invoker<T> invoker) throws RpcException; /** * create proxy. * * @param invoker * @return proxy */ @Adaptive({PROXY_KEY}) <T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException; /** * create invoker. * * @param <T> * @param proxy * @param type * @param url * @return invoker */ @Adaptive({PROXY_KEY}) <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException; }
ProxyFactory接口有三个实现类,分别为JavassistProxyFactory、JdkProxyFactory、StubProxyFactoryWrapper。其中JavassistProxyFactory、JdkProxyFactory作为代理工厂,StubProxyFactoryWrapper实现了对代理工厂进行装饰的功能。在Dubbo中通过SPI配置默认的代理工厂为JavassistProxyFactory
接下来我们重点来看JavassistProxyFactory 代理工厂:
由上面的说明可以知道主要是通过ProxyFactory接口去获取invoker,默认的实现类是JavassistProxyFactory
public class JavassistProxyFactory extends AbstractProxyFactory { @Override @SuppressWarnings("unchecked") public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) { return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker)); } @Override public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) { // TODO Wrapper cannot handle this scenario correctly: the classname contains '$' // 为目标类创建 Wrapper final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type); // 创建匿名 Invoker 类对象,并实现 doInvoke 方法。 return new AbstractProxyInvoker<T>(proxy, type, url) { @Override protected Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable { // 调用 Wrapper 的 invokeMethod 方法,invokeMethod 最终会调用目标方法 return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); } }; } }
可以看到,AbstractProxyInvoker通过invoker方法调用了 Wrapper 的 invokeMethod 方法,invokeMethod 最终会调用目标方法,并返回了wapper后的Invoker对象,而且可以看到最后返回的是一个包装后的CompletableFuture对象,可以进行异步处理:
public abstract class AbstractProxyInvoker<T> implements Invoker<T> { /** * invoke 执行方法 * @param invocation * @return * @throws RpcException */ @Override public Result invoke(Invocation invocation) throws RpcException { try { // wrapper的invokeMethod方法 Object value = doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments()); CompletableFuture<Object> future = wrapWithFuture(value); CompletableFuture<AppResponse> appResponseFuture = future.handle((obj, t) -> { AppResponse result = new AppResponse(invocation); if (t != null) { if (t instanceof CompletionException) { result.setException(t.getCause()); } else { result.setException(t); } } else { result.setValue(obj); } return result; }); return new AsyncRpcResult(appResponseFuture, invocation); } catch (InvocationTargetException e) { if (RpcContext.getContext().isAsyncStarted() && !RpcContext.getContext().stopAsync()) { logger.error("Provider async started, but got an exception from the original method, cannot write the exception back to consumer because an async result may have returned the new thread.", e); } return AsyncRpcResult.newDefaultAsyncResult(null, e.getTargetException(), invocation); } catch (Throwable e) { throw new RpcException("Failed to invoke remote proxy method " + invocation.getMethodName() + " to " + getUrl() + ", cause: " + e.getMessage(), e); } } }
3、远程服务暴露
xporter<?> exporter = PROTOCOL.export(wrapperInvoker);
获取到上一步获取的wapper后,就可以开始进行远程服务的暴露,PROTOCOL.export(wrapperInvoker)会调用到Protocol接口的export方法。首先会调用到RegistryProtocol.export()
/** * 暴露服务 * 1、先发布协议服务 * 2、服务注册 */ @Override public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException { //注册中心url以 zookeeper 注册中心为例,得到的示例 URL 如下: // zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F172.17.48.52%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider URL registryUrl = getRegistryUrl(originInvoker); // url to export locally //导出服务url URL providerUrl = getProviderUrl(originInvoker); // 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. // 获取订阅 URL,比如: // provider://172.17.48.52:20880/com.alibaba.dubbo.demo.DemoService?category=configurators&check=false&anyhost=true&application=demo-provider&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl); // 创建监听器 final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker); overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener); providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener); //export invoker // 启动netty服务导出服务(启动一个NetServer,发布Dubbo协议服务) final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl); // url to registry //获取注册中心实现类,如ZookeeperRegistry final Registry registry = getRegistry(originInvoker); // 获取已注册的服务提供者 URL,比如: // dubbo://172.17.48.52:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl); // decide if we need to delay publish // 标注是否需要延期发布 boolean register = providerUrl.getParameter(REGISTER_KEY, true); if (register) { //注册服务 register(registryUrl, registeredProviderUrl); } // register stated url on provider model registerStatedUrl(registryUrl, registeredProviderUrl, register); exporter.setRegisterUrl(registeredProviderUrl); // 向注册中心进行订阅 override 数据 exporter.setSubscribeUrl(overrideSubscribeUrl); // Deprecated! Subscribe to override rules in 2.6.x or before. // 注册中心订阅 registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener); //监听器通知,可以通过spi自定义 notifyExport(exporter); //Ensure that a new exporter instance is returned every time export return new DestroyableExporter<>(exporter); }
doLocalExport(originInvoker, providerUrl)
在RegistryProtocol.export中,有两个核心流程,
- 调用 doLocalExport 启动本地服务,也就是netty server
- 调用 register 方法进行服务地址的注册
我们按照代码的编写顺序来分析服务的发布过程,在调用 doLocalExport 这个方法中,传递了两个参数,第一个参数先暂时不说,它实际上是一个动态代理对象,它实现了服务提供者的访问代理,把这个代理类发布到网络上,使得客户端调用时可以直接出发路由。
/** * 1 从invoker的URL中的Map<String, String> parameters中获取key为export的地址providerUrl, * 该地址将是服务注册在zk上的节点 * 2 从 Map<String, ExporterChangeableWrapper<?>> bounds 缓存中获取key为上述providerUrl的exporter, * 如果有,直接返回,如果没有,创建并返回 */ private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) { //从缓存中拿到 key String key = getCacheKey(originInvoker); return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> { Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl); return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker); }); }
bounds是以key为指定服务的协议,value表示这个服务发布的地址构建的一个ConcurrentHashMap集合,protocol.export(invokerDelegate) 调用指定协议发布服务
protocol.export(invokerDelegate)
protocol是通过依赖注入来初始化的一个协议扩展点,并且我们可以看到这个protocol.export()方法上增加了@Adaptive注解,表示它是一个动态适配的扩展点,意味着最终的执行链路应该是ProtocolListenerWrapper ->QosProtocolWrapper ->ProtocolFilterWrapper-DubboProtocol
@Override public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { URL url = invoker.getUrl(); /*********** 1. 前置工作 **********/ // export service. // 获取需要暴露的服务的key,结构为 协议/服务接口:版本:端口号 // 如这里解析出来的key为 : dubbo/api.DemoService:1.0.0:20880 String key = serviceKey(url); // 封装成 Export。exporterMap 中保存的是本机暴露的服务接口列表, // 在服务调用 Exporter#unexport 时会将服务从exporterMap 中移除。 DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap); exporterMap.put(key, exporter); //export an stub service for dispatching event //是否是本地存根事件 Boolean isStubSupportEvent = url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT); //是否配置了参数回调机制 Boolean isCallbackservice = url.getParameter(IS_CALLBACK_SERVICE, false); if (isStubSupportEvent && !isCallbackservice) { String stubServiceMethods = url.getParameter(STUB_EVENT_METHODS_KEY); if (stubServiceMethods == null || stubServiceMethods.length() == 0) { if (logger.isWarnEnabled()) { logger.warn(new IllegalStateException("consumer [" + url.getParameter(INTERFACE_KEY) + "], has set stubproxy support event ,but no stub methods founded.")); } } } /*********** 2. 开启服务 **********/ // 开启服务,这里默认的是 Netty。 同一个机器的不同服务导出只会开启一个NettyServer openServer(url); /*********** 3. 序列化优化 **********/ // 对序列化进行优化,可以通过 optimizer 参数指定优化器类的全路径类名 optimizeSerialization(url); return exporter; }
a.前置工作
前置工作并没有做什么复杂工作,主要就做了两件事:
- 将将要暴露的服务保存到 exporterMap 中。我们这里需要注意 exporterMap,其声明如下:
protected final Map<String, Exporter<?>> exporterMap = new ConcurrentHashMap<String, Exporter<?>>();
exporterMap key 为 服务唯一key(服务接口 + 服务分组 + 服务版本号确定唯一服务),value 为 服务Exporter。即 exporterMap 缓存了当前机器上暴露的服务信息。而此时对于 一个 Exporter 来说,其结构应为 :
当服务消费者进行调用时, 提供者会根据调用服务信息获取serviceKey 通过 exporterMap 来获取 服务Exporter,再通过 Exporter 获取 Invoker 来进行方法调用。这些我们在讲到消费者的调用过程时会详细分析。
2.本地存根和和回调服务的配置检查,并将其保存到 stubServiceMethodsMap 中。
b.开启服务
这里通过 DubboProtocol#openServer 方法开启了服务。默认情况下,如果服务提供者机器尚未开启过服务,该方法会在服务提供者机器上开启Netty 服务。需要注意的是,服务并不会重复开启,如果 服务提供者的 ip:port 已经创建了服务,则不会重复创建。DubboProtocol#openServer 的 具体实现如下:
// 开启服务, // org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#openServer private void openServer(URL url) { // find server. // 获取服务提供者机器的 ip:port, 并将其作为服务器实例的 key,用于标识当前的服务器实例。 // 这里的端口是 dubbo 服务 端口 String key = url.getAddress(); //client can export a service which's only for server to invoke // 判断是否是服务提供者,服务提供者才会启动监听 boolean isServer = url.getParameter(IS_SERVER_KEY, true); if (isServer) { // 从缓存中获取服务,如果 服务提供者的 ip:port 已经创建了服务,则不会重复创建 ProtocolServer server = serverMap.get(key); if (server == null) { synchronized (this) { server = serverMap.get(key); if (server == null) { // 通过 createServer(url) 创建服务 serverMap.put(key, createServer(url)); } } } else { // server supports reset, use together with override server.reset(url); } } }
可以看到,关键的操作在 createServer(URL url)
中。
createServer(URL url)
DubboProtocol#createServer 完成了服务的创建过程,在服务提供者上开启了端口监听。
DubboProtocol#createServer的返回值实例类型为 HeaderExchangeServer,内部包含一个NettyServer,NettyServer 包含属性 ChannelHandler,用来处理服务的具体消息,ChannelHandler 在传递过程中层层包裹,最终如下图中所示(部分委托类并未画出):
下面我们来看一下 DubboProtocol#createServer 的具体实现
private ProtocolServer createServer(URL url) { //组装url,在url中添加心跳时间、编解码参数 url = URLBuilder.from(url) /******* 1. 参数设置 ********/ // 1.1 当服务关闭以后,发送一个只读的事件,默认是开启状态 // send readonly event when server closes, it's enabled by default .addParameterIfAbsent(CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString()) // enable heartbeat by default // 1.2 默认启用心跳 .addParameterIfAbsent(HEARTBEAT_KEY, String.valueOf(DEFAULT_HEARTBEAT)) // 1.3 获取传输协议,默认为 Netty .addParameter(CODEC_KEY, DubboCodec.NAME) .build(); String str = url.getParameter(SERVER_KEY, DEFAULT_REMOTING_SERVER); //通过 SPI 检测是否存在 server 参数所代表的 Transporter 拓展,不存在则抛出异常 if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) { throw new RpcException("Unsupported server type: " + str + ", url: " + url); } /******* 2. 开启服务端口 ********/ //创建ExchangeServer. ExchangeServer server; try { // 2.1 绑定ip端口,开启服务,这里server 默认是NettyServer, // 需要注意的是这里传入的requestHandler 是最终处理消息的处理器 server = Exchangers.bind(url, requestHandler); } catch (RemotingException e) { throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e); } // 2.2 校验客户端的传输协议 str = url.getParameter(CLIENT_KEY); if (str != null && str.length() > 0) { Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(); if (!supportedTypes.contains(str)) { throw new RpcException("Unsupported client type: " + str); } } return new DubboProtocolServer(server); }
绑定服务端口
Dubbo 通过 Exchangers.bind(url, requestHandler) 来绑定Dubbo 服务端口,我们这里需要注意一下这里的 requestHandler 是 DubboProtocol 中的匿名 ExchangeHandlerAdapter 实现类,调用消息最终会交由该类来处理。
Exchangers.bind(url, requestHandler) 代码调用顺序较为复杂,具体如下:
public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException { if (url == null) { throw new IllegalArgumentException("url == null"); } if (handler == null) { throw new IllegalArgumentException("handler == null"); } // 如果 codec 没有默认值,则添加 exchange。但是在基础参数设置中我们已经指定了编码器为 dubbo url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange"); // 1. getExchanger(url) : 获取url 中的 exchanger 属性来获取到 Exchanger,默认是Header // 2. bind(url, handler):第一步中默认是HeaderExchanger,所以这里实际上是 HeaderExchanger#bind(url, handler) return getExchanger(url).bind(url, handler); }
Exchangers.bind(url, requestHandler) 实现如下:getExchanger(url) : 通过SPI 获取 Exchanger,默认实现是 HeaderExchanger。所以这里bind(url, handler) 调用的是 HeaderExchanger#bind
// Exchangers#getExchanger 实现如下: public static Exchanger getExchanger(URL url) { String type = url.getParameter(Constants.EXCHANGER_KEY, Constants.DEFAULT_EXCHANGER); return getExchanger(type); } // 通过SPI 获取Exchange public static Exchanger getExchanger(String type) { return ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type); }
HeaderExchanger#bind 实现如下:
// HeaderExchanger#bind @Override public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException { return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))); }
其中 Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))
实现如下:
public static RemotingServer bind(URL url, ChannelHandler... handlers) throws RemotingException { if (url == null) { throw new IllegalArgumentException("url == null"); } if (handlers == null || handlers.length == 0) { throw new IllegalArgumentException("handlers == null"); } ChannelHandler handler; // 如果有多个 Handlers ,则使用 ChannelHandlerDispatcher 来分发处理。 if (handlers.length == 1) { handler = handlers[0]; } else { handler = new ChannelHandlerDispatcher(handlers); } // 1. getTransporter() 通过SPI 获取到了默认的 Transporter实现 NettyTransporter。 // 2. .bind(url, handler) 的实现是 NettyTransporter.bind(url, handler),完成了绑定了当前服务.所以此处调用的是NettyTransporter#bind return getTransporter().bind(url, handler); }
传输协议 server 的值为 netty。 getTransporter() 通过SPI 获取到了默认的 Transporter实现 NettyTransporter,所以此处调用的是NettyTransporter#bind,其详细实现如下:
@Override public RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException { return new NettyServer(url, handler); }
而在 Dubbo2.7 中提供了Netty3.x 和Netty4.x版本的实现,实现上流程大体相同。
我们这里看的是Netty4版本 即 org.apache.dubbo.remoting.transport.netty4.NettyServer
public class NettyTransporter implements Transporter { public static final String NAME = "netty"; // 服务发布流程,本文分析的方法,创建了 Netty服务 @Override public RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException { return new NettyServer(url, handler); } // 服务调用流程,消费者调用提供者时会进行连接。该方法我们在分析到服务消费者的时候会进行具体分析 @Override public Client connect(URL url, ChannelHandler handler) throws RemotingException { return new NettyClient(url, handler); } }
实际上 在 NettyTransporter 中不仅创建了提供者的 NettyServer,还创建了消费者的 NettyClient。关于消费者的内容,之后的文章中会说;NettyServer 的构造函数如下 :
public NettyServer(URL url, ChannelHandler handler) throws RemotingException { // you can customize name and type of client thread pool by THREAD_NAME_KEY and THREADPOOL_KEY in CommonConstants. // the handler will be wrapped: MultiMessageHandler->HeartbeatHandler->handler // 1. super 调用 NettyServer 父类 AbstractServer#AbstractServer 的构造函数 // 2. ChannelHandlers.wrap 完成了Dubbo线程策略的应用 super(ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME), ChannelHandlers.wrap(handler, url)); }
NettyServer 的构造函数虽然只有一句,但是其中工作还是挺多的,如下:
- 线程模型的应用 : 在构造函数中通过 ChannelHandlers.wrap 包装 Handler,而ChannelHandlers#wrap 中应用了 Dubbo 线程策略。
- Netty 服务开启 :NettyServer 构造函数中使用 super 调用了 父类构造函数AbstractServer#AbstractServer,而 AbstractServer#AbstractServer 中开启了 Netty 服务。
4.线程模型的应用
ChannelHandlers#wrap 中会对 当前 Handler 按照指定的线程模型进行包装。
4.1.Dubbo 线程模型
Dubbo 默认的底层网络通信使用的是Netty,服务提供方NettyServer 使用两级线程池,其中boss线程池主要用来接受客户端的链接请求,并把完成Tcp三次握手的连接分发给 worker 来处理,我们把 boss 和worker线程组称为 I/O 线程。
在实际调用时,如果服务提供方的逻辑处理能够迅速完成,并且不会发起新的IO 请求,那么直接在 IO 线程上处理会更快,因为这样减少了线程池调度与上下文切换的开销。
但是如果处理逻辑较慢,或者需要发起新的IO请求(比如需要查询数据库),则IO线程必须派发请求到新的线程池进行处理,否则 IO 线程被阻塞,导致不能接收其他请求。
在Dubbo 中,线程模型的扩展接口为 org.apache.dubbo.remoting.Dispatcher,all为默认的线程模型,即由AllChannelHandler 来处理Netty 事件。
根据请求的消息是否被 IO线程处理还是被业务线程处理,Dubbo提供了下面几种线程模型:
all=org.apache.dubbo.remoting.transport.dispatcher.all.AllDispatcher direct=org.apache.dubbo.remoting.transport.dispatcher.direct.DirectDispatcher message=org.apache.dubbo.remoting.transport.dispatcher.message.MessageOnlyDispatcher execution=org.apache.dubbo.remoting.transport.dispatcher.execution.ExecutionDispatcher connection=org.apache.dubbo.remoting.transport.dispatcher.connection.ConnectionOrderedDispatcher
all (AllDispatcher) : 所有的消息都派发到业务线程池,这些消息包括请求、响应、连接事件、断开事件、心跳事件等(话虽如此,但是在看AllChannelHandler 实现时发现sent 方法仍是由I/O线程处理,暂时不明),如下图,其中NettyServerBoss 、NettyServerWorker 为I/O 线程。
direct : 所有的消息都不派发到业务线程池,全都在 IO 线程上直接执行,如下
message :只有请求响应消息派发到业务线程池,其他的消息如连接消息、断开连接、心跳事件等直接在 IO 线程上执行
execution :只把请求类消息派发到业务线程池,但是响应、连接、断开、心跳等消息直接在 IO上执行
connection :在 IO 线程上将连接、断开消息放入队列,有序逐个执行,其他消息派发到业务线程池处理。
这里举个例子来帮助理解:
我们这里把 Boss 线程池理解为医院挂号护士, Workder线程池理解为医院问诊医生。传统的 Netty 就是靠这两个线程池完成工作,由 护士分发患者找不同的医生问诊 (boss 线程池分发新连接给 worker 线程池处理)。而 Dubbo 的线程模型相当于给 问诊医生 添加了很多助理医生(业务线程池)。这时候问诊医生在处理一些耗时操作时可以交由助理医生来帮助完成,问诊医生可以继续接诊下一个病人,防止后面的病人等不及(worker 线程池接收到请求后可以交由 业务线程池处理,自身便可继续接受下一个请求,防止请求堆积阻塞)。而我们通过 Dubbo 线程模型 来规定助理医生可以帮助处理哪些事情。如 : AllDispatcher 则表示所有的事件都会交由助理医生 (业务线程池)处理;DirectDispatcher 表示所有的事情都不交给助理医生(业务线程池)处理 等。
4.2 ChannelHandlers#wrap
上面介绍完了Dubbo线程模型基础概念,下面我们来看一下ChannelHandlers#wrap 的具体实现,如下:
public class ChannelHandlers { // 保存 ChannelHandlers 实例 private static ChannelHandlers INSTANCE = new ChannelHandlers(); protected ChannelHandlers() { } // 进行包装 public static ChannelHandler wrap(ChannelHandler handler, URL url) { return ChannelHandlers.getInstance().wrapInternal(handler, url); } // 获取自身的实例,INSTANCE 是 单例 protected static ChannelHandlers getInstance() { return INSTANCE; } static void setTestingChannelHandlers(ChannelHandlers instance) { INSTANCE = instance; } // 进行线程模型的包装 protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) { // 我们按照如下方式拆分一下 // 1. MultiMessageHandler // 2. HeartbeatHandler // 3. ExtensionLoader.getExtensionLoader(Dispatcher.class).getAdaptiveExtension().dispatch(handler, url)) return new MultiMessageHandler(new HeartbeatHandler(ExtensionLoader.getExtensionLoader(Dispatcher.class) .getAdaptiveExtension().dispatch(handler, url))); } }
很显然,这里的关键逻辑在 所以这里的关键方法在于 ChannelHandlers#wrapInternal 中。ChannelHandlers#wrapInternal 方法使用了装饰器模式 对 Handler进行了装饰。我们这里拆解一下ChannelHandlers#wrapInternal 方法的实现 :
- MultiMessageHandler :使用 MultiMessageHandler 装饰 HeartbeatHandler ,其目的是使其可以处理多个请求事件。如下:
public class MultiMessageHandler extends AbstractChannelHandlerDelegate { public MultiMessageHandler(ChannelHandler handler) { super(handler); } @SuppressWarnings("unchecked") @Override public void received(Channel channel, Object message) throws RemotingException { // 如果 message 类型是 MultiMessage,则Message由多个消息构成,遍历消息分别接收 if (message instanceof MultiMessage) { MultiMessage list = (MultiMessage) message; for (Object obj : list) { handler.received(channel, obj); } } else { handler.received(channel, message); } } }
2.HeartbeatHandler
:心跳Handler,其主要功能是处理心跳返回与心跳请求,直接在IO线程中执行,每次收到信息,更新通道的读事件戳,每次发送数据时,记录通道的写事件戳。
public class HeartbeatHandler extends AbstractChannelHandlerDelegate { ... @Override public void connected(Channel channel) throws RemotingException { // 记录读写时间戳 setReadTimestamp(channel); setWriteTimestamp(channel); // 消息透传给下一层 Handler handler.connected(channel); } @Override public void disconnected(Channel channel) throws RemotingException { // 记录读写时间戳 clearReadTimestamp(channel); clearWriteTimestamp(channel); // 消息透传给下一层 Handler handler.disconnected(channel); } @Override public void sent(Channel channel, Object message) throws RemotingException { // 记录写时间戳 setWriteTimestamp(channel); // 消息透传给下一层 Handler handler.sent(channel, message); } @Override public void received(Channel channel, Object message) throws RemotingException { // 记录读时间戳 setReadTimestamp(channel); // 如果是心跳请求 if (isHeartbeatRequest(message)) { Request req = (Request) message; // 心跳请求需要回复 if (req.isTwoWay()) { // 回复心跳消息 Response res = new Response(req.getId(), req.getVersion()); res.setEvent(Response.HEARTBEAT_EVENT); channel.send(res); ... 打印日志 } return; } // 如果是心跳响应 if (isHeartbeatResponse(message)) { ... 打印日志 return; } // 不是心跳消息则透传给下一层 handler handler.received(channel, message); } .... }
3.ExtensionLoader.getExtensionLoader(Dispatcher.class).getAdaptiveExtension().dispatch(handler, url) : 这里通过 SPI 机制,返回合适的Dispatcher 实现类,完成基于Dubbo线程模型的事件派发。ExtensionLoader.getExtensionLoader(Dispatcher.class).getAdaptiveExtension() 获取一个 Dispatcher 适配器,随后调用 Dispatcher#dispatch 返回一个线程模型的 ChannelHandler
@SPI(AllDispatcher.NAME) public interface Dispatcher { @Adaptive({Constants.DISPATCHER_KEY, "dispather", "channel.handler"}) // The last two parameters are reserved for compatibility with the old configuration ChannelHandler dispatch(ChannelHandler handler, URL url); }
这里可以看到,整个 Dubbo线程模型的引用就是一个大型的包装过程, 当 原始 的 Handler 经过 ChannelHandlers#wrap 包装后返回的 Handler 结构如下:
MultiMessageHandler -> HeartbeatHandler -> Dubbo线程模型指定的 Handler -> 原始的 Handler
当有消息到达时,流转如下:
- MultiMessageHandler#received 判断消息类型后透传给 HeartbeatHandler#received
- HeartbeatHandler#received 处理心跳消息,如果不是心跳消息则透传给 线程模型指定的 Handler,我们这里假设是默认的 AllChannelHandler 。
- AllChannelHandler#received 中会获取业务线程池中线程,在线程中执行 handler#received 方法。
5.Netty服务开启
接着上面源码说,NettyServer 的父类 AbstractServer 在构造函数中开启了Netty 服务。其实现如下:(需要注意,此时构造函数中的 handler 是 经历过Dubbo线程模型包装的 MultiMessageHandler。 )
public AbstractServer(URL url, ChannelHandler handler) throws RemotingException { /************** 1. 准备工作 ***********/ // 获取部分属性和编解码器 super(url, handler); // 获取指定的到本地地址(ip:port) 如: /192.168.131.10:20880 localAddress = getUrl().toInetSocketAddress(); // 获取绑定的ip和端口, Netty 服务启动后会绑定 bind.ip 的 bind.port端口, // 监听消息。因为可能存在多网卡,此时则需要指定暴露哪个ip。 // 如果配置了< dubbo:parameter key = “bind.ip” value = “”/> 与 // < dubbo:parameter key = “bind.port” />,则用该IP与端口创建bindAddress,通常用于多网卡, // 如果未配置,bindAddress与 localAddress绑定的IP与端口一样 String bindIp = getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost()); int bindPort = getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort()); if (url.getParameter(ANYHOST_KEY, false) || NetUtils.isInvalidLocalHost(bindIp)) { bindIp = ANYHOST_VALUE; } // Netty 将要绑定的地址 bindAddress = new InetSocketAddress(bindIp, bindPort); // 获取最大可接受连接数,默认为 0,不限制 this.accepts = url.getParameter(ACCEPTS_KEY, DEFAULT_ACCEPTS); // 获取空闲时间, 默认 600s this.idleTimeout = url.getParameter(IDLE_TIMEOUT_KEY, DEFAULT_IDLE_TIMEOUT); /************** 2. 开启服务 ***********/ try { // 正式在相应端口建立网络监听 doOpen(); //... 日志打印 if (logger.isInfoEnabled()) { logger.info("Start " + getClass().getSimpleName() + " bind " + getBindAddress() + ", export " + getLocalAddress()); } } catch (Throwable t) { //... 日志打印 throw new RemotingException(url.toInetSocketAddress(), null, "Failed to bind " + getClass().getSimpleName() + " on " + getLocalAddress() + ", cause: " + t.getMessage(), t); } executor = executorRepository.createExecutorIfAbsent(url); }
我们按照注释顺序:
准备工作:包括暴露 Ip、Port 、最大连接数的确认等操作。
开启服务: 这里可以看到 开启服务是通过 doOpen 方法,而 doOpen 方法在 AbstractServer 并没有实现,而是交由子类实现。所以该方法实现在 org.apache.dubbo.remoting.transport.netty4.NettyServer 中。
在 org.apache.dubbo.remoting.transport.netty4.NettyServer#doOpen
中, Dubbo完成了服务的建立,实现过程是常规的Netty流程,其实现如下:
@Override protected void doOpen() throws Throwable { bootstrap = new ServerBootstrap(); // 创建 boss 和 worker 线程池 bossGroup = NettyEventLoopFactory.eventLoopGroup(1, "NettyServerBoss"); workerGroup = NettyEventLoopFactory.eventLoopGroup( getUrl().getPositiveParameter(IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS), "NettyServerWorker"); // 业务 handler,这里将Netty 的各种请求分发给NettyServer 的方法。 final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this); channels = nettyServerHandler.getChannels(); bootstrap.group(bossGroup, workerGroup) .channel(NettyEventLoopFactory.serverSocketChannelClass()) .option(ChannelOption.SO_REUSEADDR, Boolean.TRUE) .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE) .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { // FIXME: should we use getTimeout()? int idleTimeout = UrlUtils.getIdleTimeout(getUrl()); NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this); if (getUrl().getParameter(SSL_ENABLED_KEY, false)) { ch.pipeline().addLast("negotiation", SslHandlerInitializer.sslServerHandler(getUrl(), nettyServerHandler)); } ch.pipeline() // 添加解码器 handler .addLast("decoder", adapter.getDecoder()) // 添加编码器 handler .addLast("encoder", adapter.getEncoder()) .addLast("server-idle-handler", new IdleStateHandler(0, 0, idleTimeout, MILLISECONDS)) // 添加业务handler .addLast("handler", nettyServerHandler); } }); // bind // 绑定ip端口,启用netty服务 ChannelFuture channelFuture = bootstrap.bind(getBindAddress()); channelFuture.syncUninterruptibly(); channel = channelFuture.channel(); }
服务的建立是常规的 Netty 流程。我们这里主要关注添加到管道的三个处理器
- decoder : adapter.getDecoder() 是解码器,当服务接收到消息后使用该Handler对数据进行解码。
- encoder :adapter.getEncoder() 是编码器,当服务发送消息时使用该Handler对数据进行编码
- handler :nettyServerHandler 是业务处理器,是真正处理业务的地方。解码器解码后的数据会交由 nettyServerHandler 处理, nettyServerHandler 写回通道的数据会被编码器编码后写入信道。
看到这里,整个服务的启动过程就完成了,完成了启动过程接下来就是注册逻辑了,返回RegistyProtocol方法的export()方法
/** * 暴露服务 * 1、先发布协议服务 * 2、服务注册 */ @Override public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException { //注册中心url以 zookeeper 注册中心为例,得到的示例 URL 如下: // zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F172.17.48.52%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider URL registryUrl = getRegistryUrl(originInvoker); // url to export locally //导出服务url URL providerUrl = getProviderUrl(originInvoker); // 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. // 获取订阅 URL,比如: // provider://172.17.48.52:20880/com.alibaba.dubbo.demo.DemoService?category=configurators&check=false&anyhost=true&application=demo-provider&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl); // 创建监听器 final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker); overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener); providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener); //export invoker // 启动netty服务导出服务(启动一个NetServer,发布Dubbo协议服务) final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl); // url to registry //获取注册中心实现类,如ZookeeperRegistry final Registry registry = getRegistry(originInvoker); // 获取已注册的服务提供者 URL,比如: // dubbo://172.17.48.52:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl); // decide if we need to delay publish // 标注是否需要延期发布 boolean register = providerUrl.getParameter(REGISTER_KEY, true); if (register) { //注册服务 register(registryUrl, registeredProviderUrl); } // register stated url on provider model registerStatedUrl(registryUrl, registeredProviderUrl, register); exporter.setRegisterUrl(registeredProviderUrl); // 向注册中心进行订阅 override 数据 exporter.setSubscribeUrl(overrideSubscribeUrl); // Deprecated! Subscribe to override rules in 2.6.x or before. // 注册中心订阅 registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener); //监听器通知,可以通过spi自定义 notifyExport(exporter); //Ensure that a new exporter instance is returned every time export return new DestroyableExporter<>(exporter); }