学习了ExtensionLoader 之后,就开始进入服务发布与调用源码,个人先阅读的 服务发布。
一. 服务发布的流程总览
服务发布,实际的执行,是在ServiceBean的父类 ServiceConfig 里面的export()方法. 大体来说,包括以下几个步骤:
1. 检查校验相关配置
public synchronized void export() { checkAndUpdateSubConfigs(); //进行检查、启动配置中心等 if (!shouldExport()) { return; } if (shouldDelay()) { DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS); } else { doExport(); } }
检查更新配置的具体方法:
public void checkAndUpdateSubConfigs() { // Use default configs defined explicitly on global configs completeCompoundConfigs(); // Config Center should always being started first. startConfigCenter(); //启动ConfigCenter checkDefault(); checkProtocol(); checkApplication(); // if protocol is not injvm checkRegistry if (!isOnlyInJvm()) { checkRegistry(); } this.refresh(); checkMetadataReport(); if (StringUtils.isEmpty(interfaceName)) { throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!"); } if (ref instanceof GenericService) { interfaceClass = GenericService.class; if (StringUtils.isEmpty(generic)) { generic = Boolean.TRUE.toString(); } } else { try { interfaceClass = Class.forName(interfaceName, true, Thread.currentThread() .getContextClassLoader()); } catch (ClassNotFoundException e) { throw new IllegalStateException(e.getMessage(), e); } checkInterfaceAndMethods(interfaceClass, methods); checkRef(); generic = Boolean.FALSE.toString(); }
在检查配置的时候,会启动 配置中心ConfigCenter,这个后续单独解析。
在2.7 以上版本,dubbo的configCenter 可以与RegistryCenter 分离,分开存储。
从业务上来说,配置 与 注册 是2个不同的功能,是可以分开的。
从性能上来看,才有zk为注册中心的,将ConfigCenter分离出去,可以降低zk 集群的性能压力。
2. url参数组装
在配置与发布直接,还有一大段冗长的代码,
* 各种url参数的组装
* 服务接口的wrap ,wrap后,进行方法的校验等处理
3. 本地发布
injvm 协议发布, 这个是网络发布的一个简化协议,主要是jvm里面的调用,本地调用的时候,会被filter包装,执行filter的过滤逻辑。
主要步骤为: 构建invoker 的proxy,发布本地服务, 多注册中心,本地服务,只发布一次,多协议,会发布多次。
在实际的场景里面,还没遇到过,既然是同1 个jvm的应用,直接注入服务实现类不是更好?
4. url服务发布
主要过程为:1. 构建 InvokerProxy,2:构建 InvokeChain 3. DubboProctocol 生成exporter 4.启动nettyServer
二、 服务发布
1. doExportUrls() 与 doExportUrlsFor1Protocol()
2个方法共同实现服务发布。伪代码如下:
void doExportUrls{ List registerList = loadRegistries() //加载注册中心列表,多注册中心 for(configProtocol : configProtocolList){ //遍历协议列表, 多协议发布 // 下面代码为doExportUrlsFor1Protocol 方法伪代码 if( ! onlyRemote){ //可发布jvm injvm 协议发布服务 } if(!onlyJVM){ //可发布remote for(registerUrl: registerList){ //遍历注册中心列表 remote 协议&注册中心发布 } } } }
从以上伪代码可以看出, 多协议,多注册中心情况下, 本地jvm发布,仅发布多协议, remote发布,会发布协议与注册中心的笛卡尔积
2. remote 发布:
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())); // 1 DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); //2 Exporter<?> exporter = protocol.export(wrapperInvoker); //3 exporters.add(exporter); //4
以上4行代码,就是源码里面发布remote服务的代码,分行解释。
1. 代码1,ref代表着接口的impl对象, 该行代码,主要是生成实际调用的Invoker, 实际为接口实现的proxy。 默认采用 javassist 实现。 (因历史原因 1.6版本javassist代理效率要高于jdk2倍以上,在1.7及以上版本,网上的测试结果来看,jdk的效率已经超越了javassist)
2. 代码2,对Invoker 进行Delegate封装,主要是在invoker里面放入的metadata 。 具体用处没找到,可能是为后续的扩展或加入其它功能,预留的
3. 代码3, 改行功能发布服务, 因为protocol 进行过非常深的包装,这里面功能非常复杂。这个单独说。
4. 代码4 ,这里比较简单,就是把该exporter,加入集合,可进行生命周期管理。exporter的下架等操作。
三、 protocol.export 方法
接上面的代码3 : Exporter<?> exporter = protocol.export(wrapperInvoker); //3
1. protocol 最开始为 protocol的adaptiveExtension ,是自动生成的。
上图为adaptiveProtocol的代码栈内容,无法单行debug。 fitler 与listener 的顺序在这里不重要。
调用adaptiveProtocol.export(),执行如下:
- 根据传入的invoker的url ,protocol=register ,所以会获取RegisterProtocol, 因为Protocol扩展点有 ProtocolFilterWrapper与ProtocolListenerWrapper 两个wrap 扩展,所以会被包装2次,形成链。
- 调用 adaptiveProtocol 调用export方法时,调用链如下: adaptive.export() ->ProtocolListenerWrapper.export() ->ProtocolFilterWrapper.export()->RegisterProtocol.export() 。
- 因为是registerProtocol,在Listener与Filter里面,是没有执行任何动作,直接进入到RegisterProtocol的export() 方法的。
2. 执行RegisterProtocol.export()
上步执行时,会进入RegisterProtocol.export() 方法。
该方法执行步骤如下:
- 协议转换,url从regiter protocol 转换到具体的发布协议url,如dubboProtocol,redisProtocol 等 ,才有默认的dubbo
- 调用发布协议,执行发布,生成exporter,将exporter包装成 ExporterChangeableWrapper,该类功能主要为 服务关闭unexport操作。
- 在2,完成服务发布后,调用RegistryFactory.adaptive() 动态获取register (注册中心),注册 服务url被ovrride 覆盖的通知监听
- 将2返回的ExporterChangeableWrapper 的exporter包装成 DestroyableExporter ,返回
在步骤2里面,会通过RegistryFactory.adaptive() 动态获取register,例如:NacosRegistryFactory、ZookeeperRegistryFactory ,因为动态获取,所以RegisterProtocol 只有1个实现
3. DubboProtocol的export 。
RegisterProtocol 里面,会调用 protocol.export(),protocol 会被ProtocolListenerWrapper 与ProtocolFilterWrapper 包装,实际调用发生的处理过程如下:
ProtocolFilterWrapper.export()->ProtocolListenerWrapper.export() ->RegisterProtocol.export()
a) ProtocolFilterWrapper.export() 方法,调用 buildInvokerChain() 方法,获取Fitler的activeExtension,组装成FilterChain 后只执行 listener.export(filterChain);
b) ProtocolListenerWrapper.export() 方法,调用DubboProtocol.export()方法, 生成 ListenerExporterWrapper 返回。ListenerExporterWrapper 可监听export、unexport事件。
DubboProtocol.export 方法被 b) 步骤给预先调用, invoker 参数,为 a) 步骤的包装生成的filterChain .
在DubboProtocol 里面,会启动NettyServer,打开端口监听,初始的消息handler 为dubboprotocol 内部的requestHandler。
打开端口监听,这里面的事情还很多,后续单独分析。
四、总结
服务发布,整体流程比较清晰,但是在debug的时候,因为dubbo 类的单一职责,导致太多的包装类、链,过程非常痛苦,一定要有耐心。
RegisterProtocol 因为调用RegisterFactory, 所以只有1个实现,封装的也很好
具体发布协议如DubboProtocol 、HttpProtocol ,里面封装了Server的打开,消息Handler 都是在协议内部生成,设计的很好。
Server 打开,ExchangeServer封装,内部handler 处理,又是一个长串的包装链,这个后续继续总结。