dubbo源码浅析(三)-服务提供者初始化

时间:2021-09-13 19:37:39

dubbo服务提供者由dubbo:service来定义,从前面可以看到,Spring把dubbo:service解析成一个ServiceBean,ServiceBean实现了ApplicationListener和InitializingBean接口,ServiceBean有个核心方法export,在这个方法中初始化服务提供者并且暴露远程服务。这个方法在bean初始化或容器中所有bean刷新完毕时被调用,根据provider的延迟设置决定,如果设置了延迟(delay属性)在bean初始化结束之后调用否则容易刷新事件中被调用,默认会延迟export,即在所有bean的刷新结束时间中被调用。
com.alibaba.dubbo.config.spring.ServiceBean.onApplicationEvent(ApplicationEvent)
dubbo源码浅析(三)-服务提供者初始化
在export方法中,总体上export方法暴露服务时主要做了下列这些步骤:
1. 选用服务端口
2. 生成URL对象
3. 生成本地服务代理(如果需要)
4. 生成远程服务代理
5. 启用服务监听
6. 服务注册到注册中心

选用服务端口

服务需要一个端口来进行服务调用侦听,框架默认会从20880开始选定一个未被占用的端口来提供服务,也可以在配置中的port参数指定服务的端口:
com.alibaba.dubbo.config.ServiceConfig.doExportUrlsFor1Protocol(ProtocolConfig, List)

final int defaultPort = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort();
if (port == null || port == 0) {
port = defaultPort;
}
if (port == null || port <= 0) {
port = getRandomPort(name);
if (port == null || port < 0) {
port = NetUtils.getAvailablePort(defaultPort);
putRandomPort(name, port);
}
logger.warn("Use random available port(" + port + ") for protocol " + name);
}

生成URL对象

URL对象是dubbo框架中的核心模型,它携带了调用链路中注册中心、消费端配置、提供端配置等全部信息。无论是消费端代理还是提供端代理都需持有一个URL对象携带服务调用时的上下文信息。
在服务初始化时需要对注册中心进行访问,所以需要一个URL来跟注册中心通信,根据应用的注册中心配置来生成这个URL,URL协议是registry,包含了注册中心地址、端口,path部分是com.alibaba.dubbo.registry.RegistryService类名,其中还有dubbo版本、应用名称等信息放在URL的参数中,格式化之后是这种形式:registry://10.165.124.205:2181/com.alibaba.dubbo.registry.RegistryService?application=…
除了注册中心URL,还需要生成一个URL来携带服务本身的信息,协议由应用侧指定,在我们的示例配置中是dubbo协议,host是本机ip,端口是上面选中的随机或配置中指定的端口,path是服务对应的接口class全名(含包路径),添加side参数provider,dubbo版本号、服务方法名称、group等等,格式化之后是这种形式:
dubbo://10.240.176.159:20880/com.netease.haitao.mykaola.generic.api.InternalStaffServiceFacade?…&createInternalStaffOperDetail.timeout=10000&…&default.group=hz*yun1&…&dubbo=3.0.0&…&methods=…&…&side=provider&…
这个url会被设置到registry URL的export属性中,这点很重要,后面的初始化过程是围绕registry URL,需要从这个export属性中拿到提供者服务URL。

生成本地服务代理

如果scope属性没有被设置成remote,服务同时会在本地暴露,生成一个本地服务代理对象,这里会生成一个新的URL,协议是代表本地服务的injvm,host是127.0.0.1端口是0,生成一个InjvmExporter,这时本地调用dubbo接口时直接调用本地代理不走网络请求。生成Exporter的过程和生成远程Exporter的过程类似,在后面详细描述。

com.alibaba.dubbo.config.ServiceConfig.exportLocal(URL)

private void exportLocal(URL url) {
if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
URL local = URL.valueOf(url.toFullString())
.setProtocol(Constants.LOCAL_PROTOCOL)
.setHost(NetUtils.LOCALHOST)
.setPort(0);
Exporter<?> exporter = protocol.export(
proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() +" to local registry");
}
}

生成远程服务代理

如果scope属性没有被设置成local,生成远程服务代理,框架提供了ProxyFactory生成服务代理(dubbo提供JDK动态代理和Javaassist代理,默认使用Javaassist,使用者也可以替换成其他的代理,这里不展开),它会生成一个Invoker,该Invoker是服务ref的一个代理包含了携带服务信息的URL对象。这里的ref就是在dubbo:service中配置的ref属性,指定服务的具体实现类,Invoker的invoke方法被调用时,最终会调用到ref指定的服务实现。这里的ProxyFactory使用ExtensionLoader的自适应扩展点加载,如应用侧没有特别指定,默认的是JavassistProxyFactory。

com.alibaba.dubbo.config.ServiceConfig.doExportUrlsFor1Protocol(ProtocolConfig, List)
dubbo源码浅析(三)-服务提供者初始化
dubbo源码浅析(三)-服务提供者初始化
从上面的代码截图里面看到,创建Invoker之后接下来就是创建一个Exporter,由Protocol组件来创建。这里的Protocol扩展点也是自适应加载,而当前的Url的协议是registry,自适应protocol最终会调用ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(“registry”), Protocol定义了ProtocolListenerWrapper和ListenerExporterWrapper两个Wrapper扩展点,而当前的Url的协议是registry,根据ExtensionLoader的加载规则,它会返回ProtocolListenerWrapper->ListenerExporterWrapper->RegistryProtocol对象链,对于registry协议,两个Wrapper都不会做任何处理,会直接调到RegistryProtocol.export方法。

com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
dubbo源码浅析(三)-服务提供者初始化
com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
dubbo源码浅析(三)-服务提供者初始化
RegistryProtocol.export会调用doLocalExport方法,执行服务暴露逻辑:
com.alibaba.dubbo.registry.integration.RegistryProtocol.doLocalExport(Invoker)
dubbo源码浅析(三)-服务提供者初始化
接着调用protocol.export,这时候url发生了变化不再是registryUrl了,而是存放在registryUrl的export参数中的providerUrl,根据不同协议会定位到不同的扩展,示例配置是dubbo协议,对应的实现是DubboProtocol,同样地根据Wrapper扩展点加载机制会加载出ProtocolListenerWrapper和 ListenerExporterWrapper两个Wrapper,然后依次调用ProtocolListenerWrapper->ListenerExporterWrapper->DubboProtocol的export方法。从上面的代码截图可以看到,ProtocolListenerWrapper.export会创建一个ListenerExporterWrapper实例,并添加所有激活的ExporterListener到ListenerExporterWrapper实例中,再经过ProtocolFilterWrapper处理,加载所有激活的Filter,并且构建Invoker-Filter链包装成新的Invoker。接着会调用DubboProtocol.export方法生成一个DubboExporter对象,Exporter中持有上面包装Filter链后的Invoker对象引用和url对应的key(key的生成规则在com.alibaba.dubbo.rpc.support.ProtocolUtils.serviceKey(URL)方法中),该Exporter对象会注册到DubboProtocol的exporterMap中,服务器监听到服务调用之后会在这个map中查找Exporter,并封装的Invoker。
com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
dubbo源码浅析(三)-服务提供者初始化

启动服务监听

上面已经生成了服务代理,为了能收到消费端的服务请求,还要在上面选中的端口上监听消费端的请求,调用DubboProtocol的openServer方法,dubbo使用了Netty、grizzly、Mina三种网络框架,框架默认使用Netty,示例配置没有特殊配置,主要看一下Netty。启动服务时组件的调用顺序:Exchanger.bind->Transporter.bind->Server,Exchanger和Transporter也是通过ExtensionLoader来加载,会加载到NettyTransporter实现,它会创建生成一个NettyServer实例,在构造NettyServer实例时调用doOpen打开端口监听, 并添加三个IO事件处理器,NettyCodecAdapter负责请求解码、响应编码,nettyHandler处理请求,把NettyCodecAdapter.decoder->NettyCodecAdapter.encoder->NettyHandler IO事件处理器链到Netty框架中,服务调用消息由多层框架内部的Handler转发最终会转发到DubboProcotol的requestHandler中处理,服务的调用流程后面再详细分析。

com.alibaba.dubbo.remoting.transport.netty.NettyServer.doOpen()
dubbo源码浅析(三)-服务提供者初始化
ps:在这里建议先了解一些Netty框架的使用,Netty框架封装了NIO复杂的编程模型,使用者只需实现多个ChannelHandler接口注册到Netty框架中实现业务逻辑无需关注NIO的复杂性,像NettyCodecAdapter中的两个编解码处理器encoder和decoder都是ChannelHandler的实现。

服务注册到注册中心
服务起好之后,接下来是把提供者注册到注册中心,和注册中心建立联系。回到RegistryProtocol.export方法,框架由RegistryFactory加载出一个Registry组件来处理注册中心相关的逻辑。
dubbo源码浅析(三)-服务提供者初始化
registryFactory是啥时候设置进来的?dubbo框架在创建组件实例的时候,它会扫描所有的set方法,如果set方法的参数类型是可扩展的组件类型,会加载对应的扩展点实例并设置进去。
com.alibaba.dubbo.common.extension.ExtensionLoader
dubbo源码浅析(三)-服务提供者初始化
Dubbo内置支持zookeeper、redis、广播注册中心。实例配置的zookeeper注册中心,对应ZookeeperRegistry。提供者会在zookeeper注册中心生成一个节点,节点路径为/dubbo/ interfaceClass/providers/ {providerUrl},存储了服务提供方ip、端口、group、接口名称、版本、应用名称(redis注册中心通过set命令把这些信息缓存到redis服务器)。

com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry.doRegister(URL)
dubbo源码浅析(三)-服务提供者初始化
为了感知注册中心的一些配置变化,提供者会监听注册中心路径/dubbo/${interfaceClass}/configurators的节点,监听该节点在注册中心的一些配置信息变更。zookeeper注册中心通过zookeeper框架的监听回调接口进行监听(redis注册中心通过订阅命令(subscribe)监听),服务器缓存注册中心的配置,当配置发生变更时,服务会刷新本地缓存,代码在ZookeeperRegistry的doSubscribe方法。
dubbo源码浅析(三)-服务提供者初始化
上面是服务提供者在初始化时做的一些主要动作,其实框架做的事远比这多了,先暂时先忽略一些细节,专项功能放到后面再逐个击破。