• 三、dubbo源码分析-服务发布与注册


    一、概括

    ​ Dubbo 服务发布始于 Spring 容器Refresh刷新事件,接收到事件后,执行服务发布逻辑。整个逻辑大致可分为三个部分:

    ​ 第一部分是前置工作,主要用于检查参数,组装 URL。

    ​ 第二部分是导出服务,包含导出服务到本地 (JVM),和导出服务到远程两个过程。

    ​ 第三部分是向注册中心注册服务,用于服务发现。

    二、Dubbo与Spring的关系

    ​ 服务发布的入口类是一个叫 ServiceBean的类,查看它的引用,是在DubboNamespaceHandler中注册的

    public class DubboNamespaceHandler extends NamespaceHandlerSupport {
    	//略其它属性
        @Override
        public void init() {
            //省略大量其它注册,方便观看
            registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        }
    
    }
    

    没有任何类注解,只是继承自NamespaceHandlerSupport,里面涉及两个新面孔:

    1.NamespaceHandlerSupport

    2.DubboBeanDefinitionParser

    NamespaceHandlerSupport

    Spring 默认会加载 jar 包下的 META-INF/spring.handlers 文件寻找对应的NamespaceHandler

    dubbo包下有这个(侧面也说明dubbo基于spring)

    加载handlers

    Spring提供的可扩展Schema的支持

    DubboBeanDefinitionParser

    实现具体配置文件解析的入口,它重写了 parse 方法,对 spring 的配置进行解析。

    打开parse方法,当else if对应是ProviderConfig时:

    else if (ServiceBean.class.equals(beanClass)) {
            //省略其它
            beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
            }
    else if (ProviderConfig.class.equals(beanClass)) {
                parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);
            }
    

    实际就是解析各个标签中对应的属性

    三、发布入口ServiceBean 的实现

    ServiceBean 这个类,分别实现了六个接口

    InitializingBean,DisposableBean,ApplicationContextAware,

    ApplicationListener,BeanNameAware,ApplicationEventPublisherAware

    InitializingBean

    为 bean 提供初始化方法,只包括 afterPropertiesSet 方法,在初始化 bean 的时候会执行该方法。被重写的方法为 afterPropertiesSet 。dubbo 在这把配置的 application、 registry、 service、 protocol 等信息,加载到对应的 config实体中,便于后续的使用

    DisposableBean

    被重写的方法为 destroy,bean 被销毁的时候, spring 容器会自动执行 destory 方法,比如释放资源

    ApplicationContextAware

    实现了这个接口的 bean,当 spring 容器初始化的时候,会自动的将 ApplicationContext 注入进来

    ApplicationListener

    ApplicationEvent 事件监听, spring 容器启动后会发一个事件通知。被重写的方法为:onApplicationEvent。服务就是在Spring 的上下文刷新后进行发布操作的

    BeanNameAware

    获得自身初始化时,本身的 bean 的 id 属性,被重写的方法为 setBeanName。dubbo获取自己名字

    ApplicationEventPublisherAware

    重写的方法为 setApplicationEventPublisher,在 spring 里面提供了类似于消息队列的异步事件解耦功能。 (典型的观察者模式的应用) ,dubbo这里拿到是为了在发布后自己也发布个”服务已发布“事件

    四、ApplicationListener触发发布ready

    ApplicationListener的onApplicationEvent 是一个事件响应方法,会在收到 Spring 上下文刷新事件后执行你自己重写的逻辑。ServiceBean的onApplicationEvent

    public void onApplicationEvent(ContextRefreshedEvent event) {
        // 是否有延迟发布 && 是否已发布 && 是不是已被取消发布
        if (isDelay() && !isExported() && !isUnexported()) {
            // 发布服务
            export();
        }
    }
    

    继续跟进,分为几个步骤:

    发布前置工作(校验各种

    前置工作主要包含两个部分,分别是配置检查,以及 URL 装配。

    在导出服务之前,Dubbo 需要检查用户的配置是否合理,或者为用户补充缺省配置。

    配置检查完成后,接下来需要根据这些配置组装 URL。

    Dubbo 使用 URL 作为配置载体,所有的拓展点都是通过 URL 获取配置。这一点,官方文档中有所说明。

    采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息。

    跟进到doExportUrls(真逻辑

    private void doExportUrls() {
        // 加载注册中心链接
        List<URL> registryURLs = loadRegistries(true);
        // 遍历 protocols,并在每个协议下导出服务
        for (ProtocolConfig protocolConfig : protocols) {
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }
    

    -1. 记载所有配置的注册中心地址
    -2. 遍历所有配置的协议, protocols
    -3. 针对每种协议发布一个对应协议的服务

    doExportUrlsFor1Protocol (具体协议发布)

    // export service
    String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
    Integer port = this.findConfigedPorts(protocolConfig, name, map);
    URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
    
    
    

    先构建URL,URL 是 Dubbo 配置的载体,通过 URL 可让 Dubbo 的各种配置在各个模块之间传递。

    把解析到的所有数据,组装成一个 URL,大概应该是:
    dubbo://192.168.1.1:20881/com.xxx.dubbo.ISayHelloService

    五、发布

    还是在doExportUrlsFor1Protocol 方法中,后半段:

    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig,//..) { 
            //略去前面说过的各种if else组装URL
             //                  
    		String scope = url.getParameter(SCOPE_KEY);
            // 如果配置不为空
            if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
    
                // injvm 发布到本地
                if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
                    exportLocal(url);
                }
                // 发布远程服务
                if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
                	//略,主逻辑A,太长
                }
            }
    	this.urls.add(url);
    }
    

    Injvm 协议,是一个伪协议,它不开启端口,不发起远程调用,只在 JVM 内直接关联, (通过集合的方式保存了发布的服务信息),但执行 Dubbo 的 Filter 链。

    简单来说,就是你本地的 dubbo 服务调用,都依托于 dubbo 的标准来进行

    发布远程服务,主逻辑A

    根据配置的注册中心进行远程发布。 遍历多个注册中心,进行协议的发布

    if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
        for (URL registryURL : registryURLs) {
             //略部分代码...
            
    		// 1.为服务提供类(ref)生成 Invoker
            Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
           
            // 2.DelegateProviderMetaDataInvoker 用于持有 Invoker 和 ServiceConfig
           DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
    
           // 3.发布服务,并生成 Exporter
           Exporter<?> exporter = protocol.export(wrapperInvoker);
           exporters.add(exporter);
        }
    }
    

    Invoker

    Dubbo 中,Invoker 是一个非常重要的模型。在服务提供端,以及服务引用端均会出现 Invoker。Dubbo 官方文档中引用:

    Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。

    简单说是个 调用执行的抽象体

    本质上是一个代理,经过层层包装最终进行了发布。当消费者发起请求的时候,会获得这个invoker 进行调用。
    最终发布出去的 invoker, 也不是单纯的一个代理,也是经过多层包装InvokerDelegate(DelegateProviderMetaDataInvoker(AbstractProxyInvoker()))

    protocol.export发布执行

    protocol变量是什么?找到定义处发现:

    private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
    

    是一个自适应扩展点 ,打开Protocol定义:

    @SPI("dubbo")
    public interface Protocol {
    	//其它略
        @Adaptive
        <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
    }
    

    export是个自适应方法,根据不同实现生成class代码去执行不同协议export方法,Protocol接口注解默认是dubbo,这里传了invoker参数 里面有URL,debug发现URL是registry开头的。

    找到jar包META-INF.dubbo.internal目录下org.apache.dubbo.rpc.Protocol文件:

    filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
    listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper
    mock=org.apache.dubbo.rpc.support.MockProtocol
    dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
    registry=org.apache.dubbo.registry.integration.RegistryProtocol
    

    那么实现就是registry=org.apache.dubbo.registry.integration.RegistryProtocol(这块上篇文章讲了)

    实现者RegistryProtocol.export

    RegistryProtocol 是用来实现服务注册的,export 逻辑包括:

    1. 调用 doLocalExport 本地发布服务
    2. 向注册中心注册服务
    3. 向注册中心进行订阅 override 数据
    4. 创建并返回 DestroyableExporter
    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        // 导出服务
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
    
        // 获取注册中心 URL,以 zookeeper 注册中心为例,得到的示例 URL 如下:
        // zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F172.17.48.52%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider
        URL registryUrl = getRegistryUrl(originInvoker);
    
        // 根据 URL 加载 Registry 实现类,比如 ZookeeperRegistry
        final Registry registry = getRegistry(originInvoker);
        
        // 获取已注册的服务提供者 URL,比如:
        // dubbo://172.17.48.52:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello
        final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker);
    
        // 获取 register 参数
        boolean register = registeredProviderUrl.getParameter("register", true);
    
        // 向服务提供者与消费者注册表中注册服务提供者
        ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl);
    
        // 根据 register 的值决定是否注册服务
        if (register) {
            // 向注册中心注册服务
            register(registryUrl, registeredProviderUrl);
            ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
        }
    
        // 获取订阅 URL,比如:
        // provider://172.17.48.52:20880/com.alibaba.dubbo.demo.DemoService?category=configurators&check=false&anyhost=true&application=demo-provider&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl);
        // 创建监听器
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
        // 向注册中心进行订阅 override 数据
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
        // 创建并返回 DestroyableExporter
        return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl);
    }
    

    本地发布服务doLocalExport

    通过 doLocalExport 来暴露一个服务,本质上是启动一个通信服务,主要的步骤是将本地 ip 和 20880 端口打开,进行监听

    private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
        String key = getCacheKey(originInvoker);
    
        return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
            //对原有的 invoker,委托给了 InvokerDelegate
            Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
            //将 invoker 转换为 exporter 并启动 netty 服务
            return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
        });
    }
    

    又通过spi委托给了别的 protocol去export,通过key发现已经是dubbo协议了,委托给dubbo协议了

    DubboProtocol.export

    基于SPI动态代理的适配,过渡到了 DubboProtocol ,但实际上会通过 Wrapper 对 Protocol 进行装饰 ,

    装饰器分别为:QosProtocolWrapper/ProtocolListenerWrapper/ProtocolFilterWrapper/DubboProtocol

    具体可以看spi源码,不多说,继续看DubboProtocol.export :

        public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
            URL url = invoker.getUrl();
    
            //获取服务标识,理解成服务坐标也行。由服务组名,服务名,服务版本号以及端口组成。比如
    	    //${group}/copm.gupaoedu.practice.dubbo.ISayHelloService:${version}:20880
            String key = serviceKey(url);
            //创建 DubboExporter
            DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
            // 将 <key, exporter> 键值对放入缓存中
            exporterMap.put(key, exporter);
    
            //省略.....
    		
            //启动服务
            openServer(url);
            optimizeSerialization(url);
    
            return exporter;
        }
    

    发布服务重点看启动服务是咋回事:

    启动服务openServer(url);

    继续跟进发现是个缓存判断,然后跟进到里面的createServer

    创建启动服务createServer

    创建服务,开启心跳检测,默认使用 netty。组装 url

        private ExchangeServer createServer(URL url) {
        	//组装 url,在 url 中添加心跳时间、编解码参数
            url = URLBuilder.from(url)
                    // 当服务关闭以后,发送一个只读的事件,默认是开启状态
                    .addParameterIfAbsent(CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString())
                    // 启动心跳配置
                    .addParameterIfAbsent(HEARTBEAT_KEY, String.valueOf(DEFAULT_HEARTBEAT))
                    .addParameter(CODEC_KEY, DubboCodec.NAME)
                    .build();
            String str = url.getParameter(SERVER_KEY, DEFAULT_REMOTING_SERVER);
    		//略
    		//创建 ExchangeServer
            ExchangeServer server;
            try {
                server = Exchangers.bind(url, requestHandler);
            } catch (RemotingException e) {
                throw new RpcException();
            }
            //略
            return server;
        }
    

    后面就是细节与netty了。

    注意的是,它这里用到了一个 handler 来处理客户端传递过来的请求:nettyServerHandler

    NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this);

    handler 是一个链路,它的正确组成应该是MultiMessageHandler(heartbeatHandler(AllChannelHandler(DecodeHandler(HeaderExchangeHeadler(dubboProtocol
    后续接收到的请求,会一层一层的处理。比较繁琐

    服务注册

    上面讲的是服务发布,RegistryProtocol.export 里还有后面一部分代码是进行服务注册的,如下:

    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        // ${发布服务}
        
        // 省略其他代码
        boolean register = registeredProviderUrl.getParameter("register", true);
        if (register) {
            // 注册服务
            register(registryUrl, registeredProviderUrl);
            ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
        }
        
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl);
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
        // 订阅 override 数据
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
    
        // 省略部分代码
    }
    

    注册RegistryProtocol.register

    public void register(URL registryUrl, URL registedProviderUrl) {
        // 获取 Registry
        Registry registry = registryFactory.getRegistry(registryUrl);
        // 注册服务
        registry.register(registedProviderUrl);
    }
    

    第一步是获取注册中心实例,

    第二步是向注册中心注册服务。

    获取注册中心实例getRegistry

    1. 把 url 转化为对应配置的注册中心的具体协议
    2. 根据具体协议,从 registryFactory 中获得指定的注册中心实现 (registryFactory又是来自spi)

    另外dubbo连zookeeper的话是用的Curator

    在这里插入图片描述

    向注册中心注册服务register

    不断跟进最终会来到ZookeeperRegistry.doRegister

    zookeeper为例,使用了CuratorZookeeperTransport 来实现 zk 的连接 。

    在这里插入图片描述

  • 相关阅读:
    Linux下安装配置jdk
    Linux基础实验(二)
    Linux基础命令(一)
    Linux基础实验(一)
    QT 参考资料
    C++ const_cast用法(转)
    05 内嵌汇编的编程
    构造函数和析构函数可以调用虚函数吗(转)
    C++之迭代器(Iterator)篇 (转)
    链接脚本
  • 原文地址:https://www.cnblogs.com/chz-blogs/p/14255325.html
Copyright © 2020-2023  润新知