Dubbo服务暴露(导出)流程

Dubbo作为一个Rpc框架,服务端必须得将自己暴露出去,以便客户端的调用,所以我们来看一下dubbo是如何将服务进行暴露的。


首先我们知道,启动dubbo得进行一些配置,如下图所示的一些dubbo标签(关于spring为什么能识别dubbo标签,可以搜索一下spring的schema机制,这里不做阐述,因为不是重点)

        然后我们可以在下图的文件中找到两个命名空间处理器(因为dubbo是由阿里巴巴捐赠给apache),点进apache的文件里面

        进去之后可以看到就是将配置文件中的信息解析到bean中存放于ioc,那因为我们要暴露service,所以我们看下ServiceBean

public class DubboNamespaceHandler extends NamespaceHandlerSupport {

    static {
        Version.checkDuplicate(DubboNamespaceHandler.class);
    }

    @Override
    public void init() {
        /**
         * 解析配置文件中<dubbo:xxx></dubbo:xxx> 相关的配置,并向容器中注册bean信息
         */
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        registerBeanDefinitionParser("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true));
        registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true));
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.class, true));
        registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
    }

}

在ServiceBean这个类中就可以看到服务暴露的方法,我们一路向里点

/**
     * dubbo服务导出入口,
     * 当容器初始化完成之后,需要处理一些操作,比如一些数据的加载、初始化缓存、特定任务的注册等等。
     * 这个时候我们就可以使用Spring提供的ApplicationListener来进行操作
     * @param event
     */
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (!isExported() && !isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + getInterface());
            }
            /**
             * 导出服务
             */
            export();
        }
    }

一直看到ServiceConfig中的doExportUrl(),上卖弄这个list就是加载我们配置文件中需要注册到的注册中心

/**
     * dubbo服务导出核心
     */
    private void doExportUrls() {
        //加载配置文件中的所有注册中心配置,并且封装为dubbo内部的URL对象列表
        List<URL> registryURLs = loadRegistries(true);
        //循环所有协议配置,根据不同的协议,向注册中心中发起注册 dubbo provider可能提供多种协议服务,默认dubbo协议,还有其他的比如Redis,Thrift等
        for (ProtocolConfig protocolConfig : protocols) {
            String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
            ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
            ApplicationModel.initProviderModel(pathKey, providerModel);
            //核心
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

在方法doExportUrlsFor1Protocol()中可以看到将信息组装成Url的形式进行传输,这url其实就是我们注册中心的信息以及服务信息

// 组装 URL
        URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);

接下来就是为该服务生成一个代理,在进行调用时,先经过代理在调用服务

首先是PROXY_FACTORY进行的一个自适应,可以看到getInvoker()上有个@Adaptive,然后默认选择的是javassist,也就是生成的一个代理

// 为服务提供类(ref)生成 Invoker
                        /**
                         * Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,
                         * 它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现
                         *
                         * 在服务提供方,Invoker用于调用服务提供类。在服务消费方,Invoker用于执行远程调用
                         *
                         * Invoker 是由 ProxyFactory 创建而来,Dubbo 默认的 ProxyFactory 实现类是 JavassistProxyFactory
                         */
                        Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));

 /**
     * 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();

@SPI("javassist")
public interface ProxyFactory {

    /**
     * create proxy.
     *
     * @param invoker
     * @return proxy
     */
    @Adaptive({PROXY_KEY})
    <T> T getProxy(Invoker<T> invoker) throws RpcException;

    /**
     * create proxy.
     *
     * @param invoker
     * @return proxy
     */
    @Adaptive({PROXY_KEY})
    <T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException;

    /**
     * create invoker.
     *
     * @param <T>
     * @param proxy
     * @param type
     * @param url
     * @return invoker
     */
    @Adaptive({PROXY_KEY})
    <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;

}

于是我们选择JavassistProxyFactory中的getInvoker(),可以看到最终执行实例的调用就是用invokeMethod()

    @Override
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
        // 为目标类创建 Wrapper
        /**
         * Wrapper 是一个抽象类,其中 invokeMethod 是一个抽象方法。Dubbo 会在运行时通过 Javassist 框架为 Wrapper 生成实现类,
         * 并实现 invokeMethod 方法,该方法最终会根据调用信息调用具体的服务。以 DemoServiceImpl 为例,Javassist 为其生成的代理类如下。
         *  Wrapper0 是在运行时生成的,大家可使用 Arthas 进行反编译
         *public class Wrapper0 extends Wrapper implements ClassGenerator.DC {
         *
            public static String[] pns;
         *
            public static Map pts;
         *
            public static String[] mns;
         *
            public static String[] dmns;
         *
            public static Class[] mts0;
         *
            public Object invokeMethod(Object object, String string, Class[] arrclass, Object[] arrobject) throws InvocationTargetException {
         *DemoService demoService;
         *try {
         *             // 类型转换
         *      demoService = (DemoService) object;
         *}
         *         catch(Throwable throwable){
         *throw new IllegalArgumentException(throwable);
         *}
         *try {
         *             // 根据方法名调用指定的方法
         *if ("sayHello".equals(string) && arrclass.length == 1) {
         *return demoService.sayHello((String) arrobject[0]);
         *}
         *}
         *         catch(Throwable throwable){
         *throw new InvocationTargetException(throwable);
         *}
                throw new NoSuchMethodException(new StringBuffer().append("Not found method \"").append(string).append("\" in class com.alibaba.dubbo.demo.DemoService.").toString());
         *}
         *
        }
         *
         */
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
        // 创建匿名 Invoker 类对象,并实现 doInvoke 方法。
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable {
                // 调用 Wrapper 的 invokeMethod 方法,invokeMethod 最终会调用目标方法
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }

        然后我们继续回到ServiceConfig中,可以看到用协议进行了导出,其实导出分两个步骤,一个是启动本地服务,然后是像注册中心中去注册该服务,那这里的代码就是根据协议会选择RegistryProtocol中的export

/**
                         * 导出服务,并生成 Exporter 与导出服务到本地相比,导出服务到远程的过程要复杂不少,其包含了服务导出与服务注册两个过程
                         *
                         * debug此处查看应该走哪个protocol的export
                         * 走的是 RegistryProtocol
                         */
                        Exporter<?> exporter = protocol.export(wrapperInvoker);

接着就能看到核心的代码,一个是启动服务,一个是注册,首先先是将url进行了解析然后配置了监听,随后执行了服务的导出,我们点进去看

 接着可以看到根据url的信息找到DubboProtocol,并返回一个Exporter

private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
        // dubbo://192.168.200.10:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bean.name=org.apache.dubbo.demo.DemoService&bind.ip=192.168.200.10&bind.port=20880&deprecated=false&dubbo=2.0.2&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=15804&qos.port=22222&register=true&release=&side=provider&timestamp=1622539267498
        String key = getCacheKey(originInvoker);//访问缓存

        return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
            //创建 Invoker 委托类对象 Delegate
            Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
            //----****重点跟 protocol.export(invokerDelegate) 方法,此处protocol根据SPI机制,根据URL中的参数找 DubboProtocol 实现 即根据协议导出
            return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
        });
    }

随后我们就可以在其中到看到先是拿到url,然后创建了相对应的exporter并存放于缓存之中,随后就是根据url开启了一个服务,可以看到默认采取的是netty启动的

接着就是一个dcl,判断了一下是否服务,是否启动,最后创建并能放入缓存之中

private void openServer(URL url) {
        // find server. key=192.168.200.10:20880
        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) {
            ExchangeServer server = serverMap.get(key); // serverMap根据ip:port缓存Server对象      因为服务端可能在本机不同端口暴露
            if (server == null) {
                synchronized (this) {
                    server = serverMap.get(key);
                    if (server == null) {
                        serverMap.put(key, createServer(url)); // createServer是核心----****重点去关注******
                    }
                }
            } else {
                // server supports reset, use together with override
                server.reset(url);
            }
        }
    }

随后就是在url中添加一系列的参数,如果心跳检测的、编解码参数等,接着就是启动服务

能看到的是获取了一个exchanger并绑定了我们的url以及处理器,exchanger这里也是一个拓展点,所以,默认采取的是HeaderExchanger

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中添加codec参数 dubbo://192.168.200.10:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bean.name=org.apache.dubbo.demo.DemoService&bind.ip=192.168.200.10&bind.port=20880&channel.readonly.sent=true&codec=dubbo&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&heartbeat=60000&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=6052&qos.port=22222&register=true&release=&side=provider&timestamp=1622539986773
        url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
        //getExchanger(url)根据SPI返回的是 HeaderExchanger
        return getExchanger(url).bind(url, handler);
    }

在这个bin的过程中

  1. 创建了HeaderExchangeHandler用于负载处理请求协会结果
  2. 对请求和响应结果做了编解码操作
  3. 最后就是绑定了地址
public class HeaderExchanger implements Exchanger {

    public static final String NAME = "header";

    @Override
    public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
        /**
         * 这里包含了多个调用,分别如下:
         * 1. 创建 HeaderExchangeHandler 对象 参数handler= DubboProtocol.requestHandler  很重要,涉及到发送请求时的处理
         * 2. 创建 DecodeHandler 对象
         * 3. 通过 Transporters 构建 Client 实例
         * 4. 创建 HeaderExchangeClient 对象
         *
         * 重点看:Transporters.connect
         */
        return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
    }

    @Override
    public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
        // 创建 HeaderExchangeServer 实例,该方法包含了多个逻辑,分别如下:
        //   1. new HeaderExchangeHandler(handler)                                                负载处理请求回写结果
        //	 2. new DecodeHandler(new HeaderExchangeHandler(handler))                             对请求数据和响应结果进行解码操作,如何交由后续流程继续处理
        //   3. Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))     重点看这个bind方法,
        return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
    }

}

来到netty的传输层中,看到创建了netty的服务,我们能接着往下点

 最后在doOpen()方法中看到熟悉的netty的代码,跟我们写的几乎一样,在这里呢就是服务的暴露流程

 接着我们回到之前,服务暴露之后我们需要将服务注册到注册中心去

 在这里呢,就是将将服务进行注册,registryFactory不用多说,典型的拓展点,会根据spi机制,先找到FailbackRegistry,在其中的doRegister()找到我们配置的实现--zookeeper的实现类

public void register(URL registryUrl, URL registeredProviderUrl) {
        // zookeeper://192.168.200.129:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F192.168.200.10%3A20880%2Forg.apache.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26bean.name%3Dorg.apache.dubbo.demo.DemoService%26bind.ip%3D192.168.200.10%26bind.port%3D20880%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D12416%26qos.port%3D22222%26register%3Dtrue%26release%3D%26side%3Dprovider%26timestamp%3D1622540622168&pid=12416&qos.port=22222&timestamp=1622540622168
        /**
         *  @Adaptive({"protocol"})
         *  Registry getRegistry(URL url);
         *  根据SPI机制找到  ZookeeperRegistry extends FailbackRegistry  extends AbstractRegistry
         *  看这里:register方法在 FailbackRegistry 中,真正执行注册是在ZookeeperRegistry中的doRegister方法里
         */
        Registry registry = registryFactory.getRegistry(registryUrl);
        registry.register(registeredProviderUrl);
    }
 

 那这里呢,就是做了一些节点的创建,将我们的服务注册到注册中心去


以上呢,就是dubbo的服务端暴露服务的内容。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值