结论
所谓的服务导出,就是服务提供者将本地服务注册到zk集群,并且开启netty,用来接收消费者的请求,我们也可以认为:dubbo服务提供者就是一个netty服务端
对于服务导出,会进行以下几个步骤
1.进行一些校验、参数取值、赋值等(同一个参数配置的覆盖)
2.获取所有的注册中心、获取所有配置的协议
3.组装url对象,由于dubbo是基于url来完成注册的,所以,会先拼接、组装url
4.根据url和registry(注册中心)生成Invoker对象
5.将Invoker对象进行包装,然后进行真正的导出
5.1 首先会根据服务的ip和端口,开启一个netty服务
5.2 然后将url转换成zk的节点,进行注册,其实就是注册到注册中心,比如:zookeeper
5.3 监听路径信息(这里具体监听的路径需要再次确认学习)
所以,我们可以认为服务导出分为两大步
1.参数校验、组装参数
2.服务暴露到远程
在暴露到远程的时候,会分为以下几个步骤
2.1 开启一个netty服务
2.2 注册到zk
导出源码
我学习用的版本还是2.6,所以和2.7的代码会有些差别
dubbo服务导出的源码,可以理解为是从
com.alibaba.dubbo.config.spring.ServiceBean#onApplicationEvent
这行代码开始的,spring容器在启动之后,会发生一个ContextRefreshedEvent事件,dubbo在监听到该事件之后,会开始进行服务导出
具体,dubbo如何利用spring扩展点完成初始化,可以参考这篇博客 --> dubbo如何利用spring扩展点完成初始化
com.alibaba.dubbo.config.ServiceConfig#export
public synchronized void export() {
if (provider != null) {
if (export == null) {
export = provider.getExport();
}
if (delay == null) {
delay = provider.getDelay();
}
}
/**
* 1.判断是否已经导出,如果已经导出,return
* 2.判断是否是延迟导出
* 3.如果是非延迟导出,就进行服务导出
*/
if (export != null && !export) {
return;
}
if (delay != null && delay > 0) {
delayExportExecutor.schedule(new Runnable() {
@Override
public void run() {
doExport();
}
}, delay, TimeUnit.MILLISECONDS);
} else {
doExport();
}
}
在doExport()方法中,会有一些列的参数赋值以及校验,这里我给一张截图
这里的doExportUrls,是去进行服务导出的
private void doExportUrls() {
/**
* 如果是多注册中心,这里会获取到所有配置的注册中心
*/
List<URL> registryURLs = loadRegistries(true);
/**
* 如果是多个协议,这里会循环多个协议
*/
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
doExportUrlsFor1Protocol()在这个方法中,总结而言,完成了两件事情:
1.组装、重拼接url
2.进行真正的服务导出
在这个方法里面,会根据服务提供者配置的scope属性,来判断是导出到本地,还是导出到远程服务
在进行服务导出的时候,会根据服务提供者,生成一个Invoker对象,然后对Invoker对象进行服务导出
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);
在调用protocol.export()方法的时候,会依次经过以下调用链路
org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper#export
org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper#export
org.apache.dubbo.qos.protocol.QosProtocolWrapper#export
org.apache.dubbo.registry.integration.RegistryProtocol#export
这里的调用顺序和dubbo的spi机制有关系,每个export干的事情,后面再讨论
核心的工作,就是在org.apache.dubbo.registry.integration.RegistryProtocol#export方法中
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//export invoker
/**
* 1、导出服务,创建nettyServer
* 这里是调用的是DubboProtocol的export方法来开启netty服务的
*/
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
//获取注册中心的URL, zookeeper://ip:port/xxxxx
URL registryUrl = getRegistryUrl(originInvoker);
//registry provider
final Registry registry = getRegistry(originInvoker);
final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker);
//to judge to delay publish whether or not
boolean register = registeredProviderUrl.getParameter("register", true);
ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl);
if (register) {
/**
* 2、这里是真正的服务注册的逻辑
* registryUrl:是以zookeeper://ip:port
* registeredProviderUrl:是以协议开头的,dubbo://ip:port
*
* 调用FailBackRegistry(这里要看配置的是哪种服务容错策略)
*/
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.
//3、事件监听 待学习补充
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);
}
开启netty服务
doLocalExport(originInvoker)
在这个方法中,会通过spi的扩展机制,调用dubboProtocol的export方法,在export这个方法中,会开启netty服务端
下面这个方法的调用链是这样的:
com.alibaba.dubbo.registry.integration.RegistryProtocol#export
com.alibaba.dubbo.registry.integration.RegistryProtocol#doLocalExport
com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol#export
com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol#openServer
private void openServer(URL url) {
// find server.
String key = url.getAddress();
//client can export a service which's only for server to invoke
boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
if (isServer) {
ExchangeServer server = serverMap.get(key);
if (server == null) {
serverMap.put(key, createServer(url));
} else {
// server supports reset, use together with override
server.reset(url);
}
}
}
createServer()方法后面的逻辑在本篇笔记中,不展开叙述,我们可以认为,createServer就是开启了netty服务端
注册中心注册
在com.alibaba.dubbo.registry.integration.RegistryProtocol#export
方法中,会先调用上面的doLocalExport()方法,去启动netty服务端,接着会去注册中心注册服务
@SPI("dubbo")
public interface RegistryFactory {
@Adaptive({
"protocol"})
Registry getRegistry(URL url);
}
获取注册中心的时候,也是通过SPI机制实现的,默认是dubbo,我们以zk为例,如果我们配置的注册中心是zk,那就会创建ZookeeperRegistry
然后在
com.alibaba.dubbo.registry.integration.RegistryProtocol#export
com.alibaba.dubbo.registry.integration.RegistryProtocol#register
根据registryUrl获取到当前的注册中心工厂,如果是zk,那接着会调用
com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry#doRegister
@Override
protected void doRegister(URL url) {
try {
/**
* 这里的toUrlPath,最终会把路径转换为 dubbo/接口全类名/服务暴露的地址
* 这里true,表示zk创建的是临时节点
*/
zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
} catch (Throwable e) {
throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
这里可以看到,直接调用的zkClient.create()方法
dubbo导出的原理,基本就是这样的,在dubbo官网文档改版之后,提供了服务导出和服务引入的时序图 dubbo官网
所以以上就是dubbo服务导出的核心原理