先看一段XML下的Dubbo服务配置
<!-- 指定dubbo项目名称 -->
<dubbo:application name="application_provider" />
<!-- 使用zookeeper做注册中心暴露服务地址 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880" />
<!-- 声明需要暴露的服务接口 ref指向接口实现类 首字母小写 -->
<dubbo:service interface="com.dubbo.demo.Provider"
ref="demoService" />
其他标签的配置说明,具体参考官网SCHEMA配置参考手册
Dubbo这种标签定义Bean的方法,实际上是有一套自己的配置文件标签规范,Spring也允许开发者定义自己的XML SCHEMA,只是Spring有如下约定
META-INF/spring.schemas
中指定,dubbo中的配置文件XML模板为dubbo.xsd文件,有兴趣的可以去看看xsd文件规范,这里不展开说明。配置文件有了,还得有一个类来解析配置文件。META-INF/spring.handlers
中指定xsd文件的解析类,dubbo中指定解析类如下,DubboNamespaceHandler
即为Dubbo配置文件的解析类本文重点是服务暴露,因此重点关注service标签的解析,可以看到其XML中的Service最终会转化为一个ServiceBean
类
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
ServiceBean
也是分析Dubbo服务导出的入口
Dubbo的启动依赖于Spring的启动,Dubbo服务在接收到Spring容器的刷新事件后,会执行服务暴露、导出工作,看下ServiceBean是如何与Spring整合在一起的。
看下ServiceBean的类定义
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware {
//省略其他变量、方法
private static transient ApplicationContext SPRING_CONTEXT;
private transient String beanName;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (isDelay() && !isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
}
明确如下几点
ServiceConfig
,因此ServiceBean中保留有Dubbo服务相关的配置,如导出的接口interfaceName
、要导出的方法methods
等ApplicationContextAware
、BeanNameAware
接口,因此能获取到Spring的上下文对象ApplicationContext
以及bean的beanName
。如图所示InitializingBean
, DisposableBean
因此能在Dubbo服务对应的bean初始化完成时执行延迟导出工作,!isDelay()
标识延迟导出,即delay>0时,isDelay返回false,在afterPropertiesSet方法中进行服务导出,表示延迟到Spring容器初始化完成时暴露服务ApplicationListener
,使得ServiceBean能够接收到Spring容器的刷新事件,在监听到刷新事件时进行服务导出至此已经清晰了服务导出的入口实在ServiceBean的export方法中
在从代码层面上分析服务暴露过程之前,先上一张Dubbo官方的暴露服务时序图,大家后面好对照着时序图来跟踪、分析源码
先看下ServiceBean#export
方法的实现,逻辑简单,分为三步
private ProviderConfig provider;
private static final ScheduledExecutorService delayExportExecutor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("DubboServiceDelayExporter", true));
public synchronized void export() {
if (provider != null) {
if (export == null) {
export = provider.getExport();
}
if (delay == null) {
delay = provider.getDelay();
}
}
if (export != null && !export) {
return;
}
if (delay != null && delay > 0) {
delayExportExecutor.schedule(new Runnable() {
@Override
public void run() {
doExport();
}
}, delay, TimeUnit.MILLISECONDS);
} else {
doExport();
}
}
进到ServiceConfig#doExport
方法,其主要是做了配置检查操作
protected synchronized void doExport() {
if (unexported) {
throw new IllegalStateException("Already unexported!");
}
if (exported) {
return;
}
exported = true;
//步骤一:检测interfaceName合法性
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException(" interface not allow null!");
}
checkDefault();
//步骤二:从providerConfig、moduleConfig、applicationConfig中获取参数
if (provider != null) {
if (application == null) {
application = provider.getApplication();
}
if (module == null) {
module = provider.getModule();
}
if (registries == null) {
registries = provider.getRegistries();
}
if (monitor == null) {
monitor = provider.getMonitor();
}
if (protocols == null) {
protocols = provider.getProtocols();
}
}
if (module != null) {
if (registries == null) {
registries = module.getRegistries();
}
if (monitor == null) {
monitor = module.getMonitor();
}
}
if (application != null) {
if (registries == null) {
registries = application.getRegistries();
}
if (monitor == null) {
monitor = application.getMonitor();
}
}
//步骤三:判断是否是泛化接口
if (ref instanceof GenericService) {
interfaceClass = GenericService.class;
if (StringUtils.isEmpty(generic)) {
generic = Boolean.TRUE.toString();
}
} else {
try {
//反射生成Class,判断interfaceName是否正确
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
//校验methods中方法名称合法性
checkInterfaceAndMethods(interfaceClass, methods);
//判断指定的实现类ref是否实现了接口interfaceName
checkRef();
generic = Boolean.FALSE.toString();
}
//步骤四:判断本地存根类的合法性
if (local != null) {
if ("true".equals(local)) {
local = interfaceName + "Local";
}
Class<?> localClass;
try {
localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if (!interfaceClass.isAssignableFrom(localClass)) {
throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
}
}
if (stub != null) {
if ("true".equals(stub)) {
stub = interfaceName + "Stub";
}
Class<?> stubClass;
try {
stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if (!interfaceClass.isAssignableFrom(stubClass)) {
throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
}
}
//步骤五:配置如果仍然为空,生成配置、获取抛出异常
checkApplication();
checkRegistry();
checkProtocol();
appendProperties(this);
checkStubAndMock(interfaceClass);
if (path == null || path.length() == 0) {
path = interfaceName;
}
//步骤六
doExportUrls();
ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
}
代码比较长,分为六个步骤来说明
<dubbo:service interface="com.dubbo.demo.DemoService"
ref="demoService" />
GenericService
,如果不是则调用反射加载Class,如果报错了说明interfaceName
写的不对,同时也会检查配置的Methods名称是否正确,指定的ref实现类是否实现了interface接口<dubbo:service interface="com.foo.DemoService" stub="com.foo.DemoServiceStub" />
doExportUrls
中进行实际按协议导出服务doExportUrls中方法实现如下
private void doExportUrls() {
List<URL> registryURLs = loadRegistries(true);
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
registry://XXX.XXX.XXX.XXX:2181/com.alibaba.dubbo.registry.RegistryService?application=dubbo-demo-producer&dubbo=2.6.2&pid=60667®istry=zookeeper×tamp=1573543584310
再就进入到了单个协议的导出方法doExportUrlsFor1Protocol
中,这个方法主要将方法相关的参数拼装成一个要导出的URL,然后进行本地导出和服务导出,
URL信息组装的代码就不贴了,总得来说就是利用一个Map存放Dubbo版本、时间戳等信息,然后从applicationConfig、moduleConfig、providerConfig中获取相关参数
然后根据Map生成一个URL,看下Map里都有哪些参数,生成的URL又是怎么样的
参数Map,可以看到包含了side(provider)、application项目名称、导出的方法methods、dubbo版本、进程号pid、接口名称、dubbo端口20880、时间戳等信息
根据参数生成的URL如下
dubbo://192.168.202.83:20880/dubbo.demo.api.IDemoService?anyhost=true&application=dubbo-demo-producer&bind.ip=192.168.202.83&bind.port=20880&dubbo=2.6.2&generic=false&interface=dubbo.demo.api.IDemoService&methods=sayHello&pid=60667&side=provider×tamp=1573545047968
Dubbo中, URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息
有了URL后就要进行服务导出、有的时候服务提供者本身也是消费者,因此将服务暴露到本地能够节省网络IO的时间,提高了方法在本地进行调用时的性能,故首先进行的是本地服务暴露,相关代码如下
如果没有指定配置scope=“remote”,即没有指定服务只暴露到远程时,则进行本地暴露
本地服务暴露代码如下
private void exportLocal(URL url) {
//如果protocol不等于injvm,修改协议、ip、端口
if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
URL local = URL.valueOf(url.toFullString())
.setProtocol(Constants.LOCAL_PROTOCOL)
.setHost(LOCALHOST)
.setPort(0);
ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref));
Exporter<?> exporter = protocol.export(
proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
}
}
步骤比较简单
ProtocolListenerWrapper
,其内部持有一个ProtocolFilterWrapper
的引用,ProtocolFilterWrapper
中最终持有InJvmProtocol的引用,也就是URL种指定的本地协议(InJVM)的实现,name为什么要在最终的InJvmProtocol实现类包装几层呢?下面具体分析先看下ProtocolListenerWrapper的export方法
最终还是调用ProtocolFilterWrapper#export方法进行导出,进到ProtocolFilterWrapper的export方法
可以看到ProtocolFilterWrapper中进行导出时,组装了一个Filter责任链,这个Filter就是Dubbo中的过滤器,看下buildInvokerChain的实现
说明下过滤器责任链组装的思路
本地服务导出后,还剩下远程导出。Dubbo的本质也是为了远程调用,这块是重点。
远程暴露代码如下
//如果scope!=local
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);
}
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);
}
}
与本地服务暴露类似,先调用来生成一个Invoker,注意这里用了registryURL来生成Invoker
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
在调用protocol.export进行服务暴露,因为这边的Protocol协议是registry,因此根据SPI,会进入到RegistryProtocol#export
方法中,RegistryProtocol是在dubbo、rmi等协议上的封装,加入了注册服务相关逻辑,其代码如下(篇幅原因注册相关代码先不分析)
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//对服务进行暴露
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
//获取注册中心URL
URL registryUrl = getRegistryUrl(originInvoker);
//根据 URL 加载 Registry 实现类,比如 ZookeeperRegistry
final Registry registry = getRegistry(originInvoker);
//获取已经注册过的provider的URL
final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
//获取register参数 为true时立马注册
boolean register = registedProviderUrl.getParameter("register", true);
ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);
//进行服务注册
if (register) {
register(registryUrl, registedProviderUrl);
ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
}
// 获取订阅 URL
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
//创建监听器
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
//监听注册中中心Proviver下数据变化
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
//Ensure that a new exporter instance is returned every time export
return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registedProviderUrl);
}
注册相关代码先不展开,只看服务导出的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) {
final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);
bounds.put(key, exporter);
}
}
}
return exporter;
}
此处针对originInvoker生成委托类,originInvoker的协议是我们在XML中配置的协议,如Dubbo协议,而不是registry协议
因此Protocol.export方法会进入到DubboProtocol#export
中,看下具体实现
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
//省略相关代码
....
openServer(url);
optimizeSerialization(url);
return exporter;
}
其核心就是调用了openServer方法开启一个NettyServer,这里的key是服务提供者的ip+端口
private void openServer(URL url) {
// 服务提供者的ip端口 如192.168.202.83:20880
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实例
ExchangeServer server = serverMap.get(key);
if (server == null) {
//没有就创建一个ExchangeServer
serverMap.put(key, createServer(url));
} else {
// server supports reset, use together with override
server.reset(url);
}
}
}
进到createServer方法中
private ExchangeServer createServer(URL url) {
// send readonly event when server closes, it's enabled by default
url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
// 设置心跳检测时间间隔
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
//获取默认的 底层server参数,默认是netty
String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);
//判断SPI中是否有对应server的实现类
if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))
throw new RpcException("Unsupported server type: " + str + ", url: " + url);
url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
ExchangeServer server;
try {
// 创建 ExchangeServer
server = Exchangers.bind(url, requestHandler);
} catch (RemotingException e) {
throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
}
str = url.getParameter(Constants.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 server;
}
再看下Exchangers.bind方法,最终调用的是Exchanger#bind
方法
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");
}
url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
return getExchanger(url).bind(url, handler);
}
默认实现是HeaderExchanger
public class HeaderExchanger implements Exchanger {
public static final String NAME = "header";
@Override
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}
}
其内部又调用了Transporters#bind(URL url, ChannelHandler… handlers)
public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
//省略
return getTransporter().bind(url, handler);
}
调用的是Transporter#bind方法,其默认实现为NettyTransporter,跟到NettyTransporter中
public class NettyTransporter implements Transporter {
public static final String NAME = "netty";
@Override
public Server bind(URL url, ChannelHandler listener) throws RemotingException {
return new NettyServer(url, listener);
}
}
大家可能会比较懵,这又是Exchanger,又是Transpoter。这俩个类分别是干嘛的?
Exchanger顾名思义是交换层,其封装了请求与响应相关内容。
Transporter的意思是传输层,封装了Grizzly和Mina和Netty的相关实现
简单粗暴,直接new了一个NettyServer,再跟到NettyServer中
其构造函数如下,调用了AbstractServer的父类构造方法
public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
}
AbstractServer的构造方法如下,从URL中获取了ip、端口等基本信息,最终还是调用子类的doOpen模板方法来真正的开启服务
public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
super(url, handler);
localAddress = getUrl().toInetSocketAddress();
//绑定的ip
String bindIp = getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost());
//绑定的端口
int bindPort = getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort());
if (url.getParameter(Constants.ANYHOST_KEY, false) || NetUtils.isInvalidLocalHost(bindIp)) {
bindIp = NetUtils.ANYHOST;
}
bindAddress = new InetSocketAddress(bindIp, bindPort);
this.accepts = url.getParameter(Constants.ACCEPTS_KEY, Constants.DEFAULT_ACCEPTS);
this.idleTimeout = url.getParameter(Constants.IDLE_TIMEOUT_KEY, Constants.DEFAULT_IDLE_TIMEOUT);
try {
//开启Server
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);
}
//fixme replace this with better method
DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
executor = (ExecutorService) dataStore.get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY, Integer.toString(url.getPort()));
}
NettyServer中的doOpen实现如下,都在注释中,基本都是NIO相关知识
protected void doOpen() throws Throwable {
NettyHelper.setNettyLoggerFactory();
//创建boss线程池
ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true));
//创建worker线程池
ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true));
ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));
//创建 ServerBootstrap
bootstrap = new ServerBootstrap(channelFactory);
final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
channels = nettyHandler.getChannels();
// 设置 PipelineFactory
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
@Override
public ChannelPipeline getPipeline() {
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
ChannelPipeline pipeline = Channels.pipeline();
/*int idleTimeout = getIdleTimeout();
if (idleTimeout > 10000) {
pipeline.addLast("timer", new IdleStateHandler(timer, idleTimeout / 1000, 0, 0));
}*/
pipeline.addLast("decoder", adapter.getDecoder());
pipeline.addLast("encoder", adapter.getEncoder());
pipeline.addLast("handler", nettyHandler);
return pipeline;
}
});
// 绑定到指定ip
channel = bootstrap.bind(getBindAddress());
}