[dubbo 源码之 ]1. 服务提供方如何发布服务
服务发布
启动流程
1.ServiceConfig#export
服务提供方在启动部署时,dubbo会调用ServiceConfig#export来激活服务发布流程,如下所示:
- Java API:
// 1. 创建ServiceConfig实例
ServiceConfig<IGreetingService> serviceConfig = new ServiceConfig<>();
// 2. 设置应用程序配置
serviceConfig.setApplication(new ApplicationConfig("deep-in-dubbo-first-provider"));
// 3. 设置注册中心
RegistryConfig registryConfig = new RegistryConfig("zookeeper://127.0.0.1:2181/");
serviceConfig.setRegistry(registryConfig);
// 4. 设置接口和实现类
// 5. 设置服务分组和版本
// dubbo中,服务接口+服务分组+服务版本 唯一的确定一个服务,同一个接口可以有不同版本,方便维护升级
serviceConfig.setInterface(IGreetingService.class);
serviceConfig.setRef(new GreetingServiceImpl());
serviceConfig.setVersion("1.0.0");
serviceConfig.setGroup("dubbo-sxzhongf-group");
RpcContext.getContext().setAttachment("age","18");
// 7. 导出服务,启动Netty监听链接请求,并将服务注册到注册中心
serviceConfig.export();
// 8. 挂起线程,避免服务停止
System.out.println("api provider service is started...");
System.in.read();
- XML
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- provider's application name, used for tracing dependency relationship -->
<dubbo:application name="first-xml-provider"/>
<!-- use multicast registry center to export service -->
<dubbo:registry address="zookeeper://127.0.0.1:2181/"/>
<!-- use dubbo protocol to export service on port 20880 -->
<dubbo:protocol name="dubbo" port="20880"/>
<!-- service implementation, as same as regular local bean -->
<bean id="demoService" class="com.sxzhongf.deep.in.dubbo.provider.service.impl.GreetingServiceImpl"/>
<!-- declare the service interface to be exported -->
<dubbo:service interface="com.sxzhongf.deep.in.dubbo.api.service.IGreetingService"
ref="demoService" version="1.0.0" group="dubbo-sxzhongf-group">
<dubbo:method name="sayHello" async="false" timeout="0" retries="3"></dubbo:method>
<dubbo:method name="testGeneric" async="false" timeout="10000" retries="3"></dubbo:method>
</dubbo:service>
</beans>
查看export源码可知,总共有三种服务导出选项:
java public synchronized void export() { //1. 是否导出 if (!shouldExport()) { return; } ... //2.延迟导出 if (shouldDelay()) { DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS); } else { //3.立刻导出 doExport(); } }
2.ServiceConfig#doExport
此方法主要是根据设置的属性进行合法性检查,主要包含是否已被导出,doExportUrls();
3.doExportUrls
4.ConfigValidationUtils#loadRegistries
此方法用来加载所有的服务注册中心对象,在dubbo中,一个service可以被注册到多个注册中心。
通过
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
5.doExportUrlsFor1Protocol
在此方法中会将所有的参数封装成
org.apache.dubbo.common.URL对象,然后执行具体的服务导出。
具体过程分为:
1.解析MethodConfig配置(单独的方法调用参数设置)
2.泛型调用类型设置
3.拼接URL参数
4.导出具体服务
导出又分为四种范围(
scope):SCOPE_NONE = "none",如果设定为none,表示该服务不导出。
SCOPE_LOCAL = "local" ,如果设定为local,表示该服务导出到本地(injvm--伪协议,实现类为:
org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol)- SCOPE_REMOTE = "remote",如果设定为remote,表示该服务导出到远程。
如果有注册中心,发布到注册中心
如果没有注册中心,则表示服务是直连方式
从
dubbo-2.7.0开始,新增加了WritableMetadataService来存储dubbo 服务的元数据,元数据可以存储在远端配置中心和本地,默认是存储在本地,通过设置:METADATA_KEY = "metadata"DEFAULT_METADATA_STORAGE_TYPE = "local"
REMOTE_METADATA_STORAGE_TYPE = "remote"
/**
* @since 2.7.0
* ServiceData Store
*/
WritableMetadataService metadataService = WritableMetadataService.getExtension(url.getParameter(METADATA_KEY, DEFAULT_METADATA_STORAGE_TYPE));
if (metadataService != null) {
metadataService.publishServiceDefinition(url);
}
不设置,导出到本地和远端
最终执行导出的代码如下
// 扩展适配类
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); /**
* A {@link ProxyFactory} implementation that will generate a exported service proxy,the JavassistProxyFactory is its
* default implementation
*/
// 扩展适配类
private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
... Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
由于
protocol和PROXY_FACTORY都是扩展适配类,跟踪代码我们可以发现:执行
PROXY_FACTORY.getInvoker的时候实际上首先执行扩展接口ProxyFactory的适配类ProxyFactory$Adaptive的getInvoker方法,根据URL中参数proxy的设置类型选择具体的代理工厂,默认使用的是javassist,,因此又调用了org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory#getInvoker来获取代理实现类,代码如下:/**
* JavaassistRpcProxyFactory
*/
public class JavassistProxyFactory extends AbstractProxyFactory {
...
@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
// 这里使用javassist动态代理生成serviceImpl实现类的包装类`Wraaper...`
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
...
}
上面代码有2个目的:
inal Wrapper wrapper = Wrapper.getWrapper(...);用来生成具体serviceImpl的包装类,减少反射的性能损耗;return new AbstractProxyInvoker<T>...返回了一个抽象的代理invoker,并且重写了doInvoker方法,重写之后使用包装类中的invokeMethod来调用方法。
经过上述2步,服务提供方就将具体的实现类转换为
Invoker代理。然后,当执行
protocol.export(),实际上也是调用了Protocol$Adaptive#export()方法,同时也分为两种情况- 如果为远程暴露,则执行
RegistryProtocol#export - 如果为本地暴露,则只需
InjvmProtocol#export
由于dubbo的
增强SPI特性支持,injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));,则在调用之前会一层一层调用,ProtocolFilterWrapper->ProtocolListenerWrapper->QosProtocolWrapper,最后会调用export方法,此方法会将Invoker转换为Exporter对象,在org.apache.dubbo.registry.integration.RegistryProtocol#export方法中,org.apache.dubbo.registry.integration.RegistryProtocol#doLocalExport方法启NettyServer来监听服务,org.apache.dubbo.registry.integration.RegistryProtocol#register将当前的服务注册到注册中心。doLocalExport是如何启动NettyServer呢?private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
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);
});
}
此时
URL中的protocol类型为默认的dubbo,因此会执行DubboProtocol#export进行转换,如下:@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl(); // export service.
String key = serviceKey(url);
// invoker->exporter
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."));
} } else {
stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
}
}
//创建server
openServer(url);
//序列化提示
optimizeSerialization(url); return exporter;
}
可以看到代码执行到
openServer,因为key=getAddress()=ip+port,因此,同一台机器只会开启一个NettyServer.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(IS_SERVER_KEY, true);
if (isServer) {
ProtocolServer server = serverMap.get(key);
if (server == null) {
synchronized (this) {
server = serverMap.get(key);
if (server == null) {
serverMap.put(key, createServer(url));
}
}
} else {
// server supports reset, use together with override
server.reset(url);
}
}
}
对于
org.apache.dubbo.remoting.Transporter的适配类选择有三种:MinaTransporter、NettyTransporter、GrizzlyTransporter,关于JavaNIO:Apache Mina、JBoss Netty、Sun Grizzly 框架对比:传送门NettyServer启动之后,回到
org.apache.dubbo.registry.integration.RegistryProtocol#export方法,继续执行将服务注册到注册中心,我们以Zookeeper为例:1.首先查找所有注册中心
final Registry registry = getRegistry(originInvoker);
...
protected Registry getRegistry(final Invoker<?> originInvoker) {
URL registryUrl = getRegistryUrl(originInvoker);
return registryFactory.getRegistry(registryUrl);
}
因为
RegistryFactory是一个SPI扩展接口,代码中设置的为zookeeper,因此这里调用的是ZookeeperRegistryFactory,继承自:org.apache.dubbo.registry.support.AbstractRegistryFactory#getRegistry(org.apache.dubbo.common.URL),在此方法中调用了createRegistry,但是ZookeeperRegistryFactory重写了createRegistry,因此具体调用的是ZookeeperRegistryFactory#createRegistry,该方法返回了一个new ZookeeperRegistry(url, zookeeperTransporter)实例对象。2.开始注册,
RegistryProtocol#register方法执行注册动作,首先获取到我们在上一步找到的注册中心ZookeeperRegistry,ZookeeperRegistry执行父类org.apache.dubbo.registry.support.FailbackRegistry#register,在该方法中会调用抽象方法:doRegister,ZookeeperRegistry重写了改方法,则执行ZookeeperRegistry#doRegister,如下:@Override
public void doRegister(URL url) {
try {
zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
} catch (Throwable e) {
throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
3.
toUrlPath方法会把org.apache.dubbo.common.URL转换格式后存储到zookeeper,如下:dubbo://172.16.44.21:20880/com.sxzhongf.deep.in.dubbo.api.service.IGreetingService?anyhost=true&application=deep-in-dubbo-first-provider&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=dubbo-sxzhongf-group&interface=com.sxzhongf.deep.in.dubbo.api.service.IGreetingService&methods=sayHello,testGeneric&pid=8480&release=2.7.5&revision=1.0.0&side=provider×tamp=1582872610313&version=1.0.0 -----------------------转换------------------------ /dubbo/com.sxzhongf.deep.in.dubbo.api.service.IGreetingService/providers/dubbo%3A%2F%2F172.16.44.21%3A20880%2Fcom.sxzhongf.deep.in.dubbo.api.service.IGreetingService%3Fanyhost%3Dtrue%26application%3Ddeep-in-dubbo-first-provider%26default%3Dtrue%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26group%3Ddubbo-sxzhongf-group%26interface%3Dcom.sxzhongf.deep.in.dubbo.api.service.IGreetingService%26methods%3DsayHello%2CtestGeneric%26pid%3D8480%26release%3D2.7.5%26revision%3D1.0.0%26side%3Dprovider%26timestamp%3D1582872610313%26version%3D1.0.0
转换之后的格式其实就是我们在zookeeper中看到的一样了,不过有几个目录:
- dubbo
- com.sxzhongf.deep.in.dubbo.api.service.IGreetingService
- providers
[zk: localhost:2181(CONNECTED) 2] ls /dubbo/com.sxzhongf.deep.in.dubbo.api.service.IGreetingService/providers
[dubbo%3A%2F%2F172.16.44.21%3A20880%2Fcom.sxzhongf.deep.in.dubbo.api.service.IGreetingService%3Fanyhost%3Dtrue%26application%3Ddeep-in-dubbo-first-provider%26default%3Dtrue%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26group%3Ddubbo-sxzhongf-group%26interface%3Dcom.sxzhongf.deep.in.dubbo.api.service.IGreetingService%26methods%3DsayHello%2CtestGeneric%26pid%3D15716%26release%3D2.7.5%26revision%3D1.0.0%26side%3Dprovider%26timestamp%3D1582872850187%26version%3D1.0.0]
- 如果为远程暴露,则执行
至此,服务消费端就可以从注册中心获取服务提供service进行调用了,下节我们继续来分析,消费端是如何从注册中心拉取service进行处理的。
[dubbo 源码之 ]1. 服务提供方如何发布服务的更多相关文章
- dubbo源码分析3-service bean的创建与发布
dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...
- Dubbo 源码分析 - 服务调用过程
注: 本系列文章已捐赠给 Dubbo 社区,你也可以在 Dubbo 官方文档中阅读本系列文章. 1. 简介 在前面的文章中,我们分析了 Dubbo SPI.服务导出与引入.以及集群容错方面的代码.经过 ...
- dubbo源码分析2-reference bean发起服务方法调用
dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...
- dubbo源码分析6-telnet方式的管理实现
dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...
- dubbo源码分析1-reference bean创建
dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...
- dubbo源码分析4-基于netty的dubbo协议的server
dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...
- dubbo源码分析5-dubbo的扩展点机制
dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...
- dubbo源码—SPI
Java中的SPI SPI,Service Provider Interface,java中提供的一种使程序可扩展的方式,系统定义好接口规范,供其他服务提供方实现,服务提供方将自己jar包META-I ...
- 基于spring-cloud的微服务(2) eureka服务提供方的注册和消费方的消费
启动Eureka注册中心之后,服务提供方就可以注册到Eureka上去(作为一个Eureka的客户端) 我们使用IDEA提供的spring initializer来新建一个springcloud项目 填 ...
随机推荐
- 我终于学会了使用python操作postgresql
一 前言 这篇文章不仅适合pgsql,更适合mysql,思路都是一致的,如果读者学会使用psycopg2操作pgsql,那么使用PyMySQL 操作mysql也是很简单:本篇文章涵盖内容广泛,提供的操 ...
- Qt Installer Framework翻译(3-3)
移除组件 下图说明了删除所有或某些已安装组件的默认工作流程: 本节使用在macOS上运行的Qt 5维护工具为例,来演示用户如何删除所有或部分选定组件. 移除所有组件 用户启动维护工具时,将打开&quo ...
- NOI2019 酱油记
今天是 \(7.18\) ,考完二试炸的很惨-于是我就来写游记了. DAY 0 签到日(7.14) 还没起床,原先定的飞机就被取消了,只好改签. 然而还是很早到的机场,等了好久好久. 到广州咯~下大雨 ...
- Windows 7原版映像中添加usb3.0驱动
最近用软碟通制作了一个win7原版映像,但是在装新系统的时候发现了一个问题,进入安装界面后,显示没有找到驱动器,但是明明是差了U盘的,通过“shift+f12”调出命令行窗口,输入disk list命 ...
- Redhat下如何查看nvidia显卡的工作状况
安装完毕nvidia显卡驱动后,可以使用命令来查看显卡的工作状况,命令如下: nvidia-smi 输入上述命令后,显示界面如下 安装nvidia显卡驱动的步骤,请参照驱动安装cuda和cudnn.
- Windows Server 2016 Active Directory 图文搭建指南
1. 首先打开Manage --> Add Roles and Features 2. 点击Next 3. 不做修改,点击Next 4. 不做修改,点击Next 5. 选择Active Dire ...
- Windos下的一些命令集合
由于在CMD模式下(也就是命令行)有较多的有用的命令.以下是自己平时所记录下来的以帮助平时的任务. 1. 显示计算机的操作系统 wmic os get osarchitecture /value
- SpringBoot整合三大组建(Servlet、Listener、Filter)
>[更多资源和教程请关注公众号:**非科班的科班**.如果觉得我写的还可以请给个赞,谢谢大家,你的鼓励是我创作的动力](https://blog.csdn.net/qq_43255017)## ...
- JDK Proxy和CGLIB Proxy
动态代理在Java中有着广泛的应用,比如Spring AOP,Hibernate数据查询.测试框架的后端mock.RPC,Java注解对象获取等.静态代理的代理关系在编译时就确定了,而动态代理的代理关 ...
- Ubuntu 入门笔记(1)
在阿里云上申请了一个云服务器,开始学习Linux.我选择的是Ubuntu 14.04 ,在登录时就绕了我好长时间,输入用户名是有显示的,但是输入密码就没有反应了,查找了之后才发现原来这是Ubuntu ...