当前位置: 首页 > article >正文

Dubbo源码解析-服务导出(四)

一、服务导出

当我们在某个接口的实现类上加上@DubboService后,就表示定义了一个Dubbo服务,应用启动时Dubbo只要扫描到了@DubboService,就会解析对应的类,得到服务相关的配置信息,比如:

1. 服务的类型,也就是接口,接口名就是服务名
2. 服务的具体实现类,也就是当前类
3. 服务的version、timeout等信息,就是@DubboService中所定义的各种配置


解析完服务的配置信息后,就会把这些配置信息封装成为一个ServiceConfig对象,并调用其export()方法进行服务导出,此时一个ServiceConfig对象就表示一个Dubbo服务。而所谓的服务导出,主要就是完成三件事情:

1. 确定服务的最终参数配置
2. 按不同协议启动对应的Server(服务暴露)
3. 将服务注册到注册中心(服务注册)

二、确定服务参数

一个Dubbo服务,除开服务的名字,也就是接口名,还会有很多其他的属性,比如超时时间、版本
号、服务所属应用名、所支持的协议及绑定的端口等众多信息。但是,通常这些信息并不会全部在@DubboService中进行定义,比如,一个Dubbo服务肯定是属于某个应用的,而一个应用下可以有多个Dubbo服务,所以我们可以在应用级别定义一些通用的配置,比如协议。

我们在application.properties中定义:

表示当前应用下所有的Dubbo服务都支持通过tri协议进行访问,并且访问端口为20880,所以在进行某个服务的服务导出时,就需要将应用中的这些配置信息合并到当前服务的配置信息中。另外,除开可以通过@DubboService来配置服务,我们也可以在配置中心对服务进行配置,比如在
配置中心中配置:

dubbo.service.org.apache.dubbo.samples.api.DemoService.timeout=5000

表示当前服务的超时时间为5s。所以在服务导出时,也需要从配置中心获取当前服务的配置,如果在@DubboService中也定义了timeout,那么就用配置中心的覆盖掉,配置中心的配置优先级更高。最终确定出服务的各种参数。

三、服务暴漏

服务暴露就是根据不同的协议启动不同的Server,比如dubbo协议启动的都是Netty,我们也主要是讲解dubbo协议。

protected synchronized void doExport() {
        if (unexported) {
            throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
        }
        if (exported) {
            return;
        }

        if (StringUtils.isEmpty(path)) {
            path = interfaceName;
        }
        doExportUrls();
        exported();
 }

继续进入到doExportUrls()方法里面,for循环ProtocolConfig进行服务的暴漏

for (ProtocolConfig protocolConfig : protocols) {
	String pathKey = URL.buildKey(getContextPath(protocolConfig)
			.map(p -> p + "/" + path)
			.orElse(path), group, version);
	// In case user specified path, register service one more time to map it to path.
	repository.registerService(pathKey, interfaceClass);
	doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}

例如暴漏的协议可以是dubbo,tri,http等,本节我们只讨论dubbo协议的暴漏,也是生产上用的最多的。下图就是当ProtocolConfig为dubbo协议时的信息

在服务暴漏的方法中有一个比较关键的入参,即url,通过buildUrl(protocolConfig, map)方法创建,其实就是提取一些配置信息得到,那么在后续exportUrl暴漏的过程中,会通过dubbo的SPI机制获取到url上面的信息,从而获取到真正的实现类完成服务的暴漏,注册等过程,这种做法在dubbo中屡见不鲜。

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
	Map<String, String> map = buildAttributes(protocolConfig);

	// remove null key and null value
	map.keySet().removeIf(key -> key == null || map.get(key) == null);
	// init serviceMetadata attachments
	serviceMetadata.getAttachments().putAll(map);

	URL url = buildUrl(protocolConfig, map);//20881这里赋值

	exportUrl(url, registryURLs);
}

可以看下url具体长什么样:

一、本地暴漏

继续执行方法exportUrl,这里会进行exportLocal,也就是本地暴漏,这里又重新生成了一个injvm协议的url,通过这种方式的暴漏,允许在同一个 JVM 中进行直连调用,而不需要通过网络。

举个栗子:DemoService定义如下

@Service
public class DemoServiceImpl implements DemoService {
    @Override
    public String sayHello(String name) {
        return "Hello " + name;
    }
}

如果同一个jvm内部服务消费者希望调用这个方法,服务消费者通过 protocol=“injvm”配置引用本地的 InJvm 服务。

@Reference(protocol = "injvm")
private DemoService demoService;

使用 InJvm 协议暴露服务在 Dubbo 中具有多种用途,包括提高性能、简化测试、方便应用内部模块间调用等。通过 InJvm 协议,可以在同一个 JVM 内快速高效地调用服务,无需网络开销,适用场景广泛且灵活。

二、远程暴漏

我们重点讲解下远程暴漏,也就是依据dubbo协议,在exportRemote方法中;最终来到最最核心的方法,这里的ref就是我们的原始service类,可以看到这里是通过动态代理的方式将其最终包装了一个Invoker,可想而知最终执行业务逻辑的还是ref,dubbo的一些强大的功能扩展就是在Invoker进行再次封装,Invoker也非常的重要。

private void doExportUrl(URL url, boolean withMetaData) {
	Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
	if (withMetaData) {
		invoker = new DelegateProviderMetaDataInvoker(invoker, this);
	}
	Exporter<?> exporter = protocolSPI.export(invoker);
	exporters.add(exporter);
}

这行代码中的protocolSPI通过名字可以看出来他会动态的读取invoker属性中的url获取具体的类执行export方法,所以我们要时刻查看invoker中的url属性,

Exporter<?> exporter = protocolSPI.export(invoker);

当前也即表示目前是registry协议的暴漏,意思就是要将服务在注册中心,就是会将服务的url注册到zk上。

很明显下面判断UrlUtils.isRegistry(invoker.getUrl())会等于true,最终来到RegistryProtocol类的export方法中

public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
	if (UrlUtils.isRegistry(invoker.getUrl())) {
		return protocol.export(invoker);
	}
	FilterChainBuilder builder = getFilterChainBuilder(invoker.getUrl());
	return protocol.export(builder.buildInvokerChain(invoker, SERVICE_FILTER_KEY, CommonConstants.PROVIDER));
}

这个方法中代码比较多,我们只看几个关键步骤

1、doLocalExport,当执行到这个方法的时候,注意到此时url上的协议已经是dubbo了,也就是说接下来会根据providerUrl上面的协议去进行服务暴露了。

2、Registry registry = getRegistry(registryUrl)获取到注册中心实现类

3、register(registry, registeredProviderUrl)最终将url写到注册中心,供服务消费者订阅使用

关于服务注册部分,下面会进行详细解释,我们继续看dubbo协议的服务暴漏,那么就会来到DubboProtocol类中的export方法,这里将invoker传递给了DubboExporter然后返回,服务暴露的返回对象就是这个exporter。

下面还有一行openServer,这里就是去启动netty,因为要供消费者进行网络调用,所以netty启动后作为服务端能够接受消费端的传过来的信息,最终这些信息会传递到exporter中的invoker方法,由invoker方法中的ref完成业务逻辑最终再通过netty发送给消费端。

public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
	checkDestroyed();
	URL url = invoker.getUrl();

	// export service.
	String key = serviceKey(url);
	DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);

	openServer(url);
	optimizeSerialization(url);

	return exporter;
}

最终operServer会执行到下面的doOpen()方法,可以看到这里是标准的netty启动流程 

protected void doOpen() throws Throwable {
	bootstrap = new ServerBootstrap();

	bossGroup = NettyEventLoopFactory.eventLoopGroup(1, EVENT_LOOP_BOSS_POOL_NAME);
	workerGroup = NettyEventLoopFactory.eventLoopGroup(
			getUrl().getPositiveParameter(IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),
		EVENT_LOOP_WORKER_POOL_NAME);

	final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this);
	channels = nettyServerHandler.getChannels();

	boolean keepalive = getUrl().getParameter(KEEP_ALIVE_KEY, Boolean.FALSE);

	bootstrap.group(bossGroup, workerGroup)
			.channel(NettyEventLoopFactory.serverSocketChannelClass())
			.option(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
			.childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
			.childOption(ChannelOption.SO_KEEPALIVE, keepalive)
			.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
			.childHandler(new ChannelInitializer<SocketChannel>() {
				@Override
				protected void initChannel(SocketChannel ch) throws Exception {
	ChannelFuture channelFuture = bootstrap.bind(getBindAddress());
	channelFuture.syncUninterruptibly();
	channel = channelFuture.channel();

}

至此服务暴漏过程结束。

四、服务注册(下个章节继续解析服务注册)


http://www.kler.cn/a/401444.html

相关文章:

  • JVM垃圾回收详解(重点)
  • Java 核心技术卷 I 学习记录九
  • Redis配置主从架构、集群架构模式 redis主从架构配置 redis主从配置 redis主从架构 redis集群配置
  • 4-7-1.C# 数据容器 - LinkedList(LinkedList 的定义、LinkedList 结点的遍历、LinkedList 的常用方法)
  • DataOps for LLM 的数据工程技术架构实践
  • 区块链中的wasm合约是什么?
  • chatGPT是如何使用tensrFlow训练模型的?
  • 【数据分享】1981-2024年我国逐日最低气温栅格数据(免费获取)
  • CSS3中的弹性布局之侧轴的对齐方式
  • 梧桐数据库深入探索递归查询的强大功能
  • Excel——宏教程(精简版)
  • EMall实践DDD模拟电商系统总结
  • 学习整理在php中一个二维数组按另一个一维数组顺序排序
  • 【OpenCV】Could NOT find TIFF (missing: TIFF_LIBRARY TIFF_INCLUDE_DIR)
  • C++ 对函数的详细记录 【雨露均沾】
  • 【UGUI】Unity 游戏开发:背包系统初始化克隆道具
  • CFD 应用于分离过程:旋风分离器(第 2 部分)
  • 服务器上Cuda+Pytorch兼容性的那些问题
  • 鸿蒙学习生态应用开发能力全景图-鸿蒙开发套件(2)续集 OS 开放能力集
  • 跨平台WPF框架Avalonia教程 二
  • Java学习笔记--数组常见算法:数组翻转,冒泡排序,二分查找
  • 【leetcode 02】27. 移除元素
  • Three.js PBR材质
  • iOS无人直播虚拟视频实用版
  • Windows系统编程 - 进程遍历
  • 力扣hot100-->二分查找