浅析dubbo服务暴露

前言

敖丙是我非常喜欢的技术博主,2020年开始关注的,慢慢的看着他成为2020年度博客之星Top1,对比2020年的自己,还是那个小菜鸡,所以开始学这些技术大牛,写写博客总结经验,平时有空反复打磨,也欢迎各位看官挑挑刺,互相学习

服务暴露

先贴一张Dubbo官方的架构图,我们今天涉及的内容是第0步和第1步
在这里插入图片描述
服务启动后,会调用spring容器的refresh(),后续会出文章讲讲refresh里的13个方法

步骤1:spring容器启动,解析dubbo自定义标签,扫描dubbo.xsd文件,
步骤2:将Dubbo的ServiceBean注册到Spring容器中,ServiceBean实现了ApplicationListener接口
步骤3:spring容器刷新完,触发事件监听器,执行onApplicationEvent里的重写方法,执行export()——今天的主角
在这里插入图片描述
因为dubbo的源码中很多地方都涉及到spi机制,所以可以先看一下敖丙的阿里面试真题:Dubbo的SPI机制,理解了dubbo的spi机制之后,回来看服务暴露的源码才不至于一头雾水

大致的流程图

在这里插入图片描述
⚓️代码1:doExportUrlsFor1Protocol
⚓️代码2:判断scope
⚓️代码3:injvm协议
⚓️代码4:判断注册中心
⚓️代码5:openServer
⚓️代码6:createServer
⚓️代码7:Exchanger
⚓️代码8:Transport
⚓️代码9:NettyServer

上图根据色块,主要分成了三个大步骤

  1. 检查配置,组装URL
  2. 根据协议和注册中心暴露服务
  3. 如果有注册中心则注册服务

我会在每个小节后面加一个指向上面这个流程图的链接,方便跳回来看看自己走到哪一步了,不至于迷失在源码的世界里

代码1:doExportUrlsFor1Protocol

  1. URL是dubbo中比较重要的一个概念,URL拼接参数,这些参数在之后的dubbo spi调用中起到很重要的作用,根据参数调用具体的实现类
  2. 配置文件中的服务提供者信息 protocol://username:password@host:port/path?key=value&key=value
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
    String name = protocolConfig.getName();
    if (name == null || name.length() == 0) {
        name = "dubbo";
    }

    //处理host
    //处理port
    //设置参数到map,组装成URL
    // 导出服务
    String contextPath = protocolConfig.getContextpath();
    if ((contextPath == null || contextPath.length() == 0) && provider != null) {
        contextPath = provider.getContextpath();
    }
    URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);

    if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
            .hasExtension(url.getProtocol())) {
        url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
    }

    //此处省略:服务暴露(详见代码2)
    this.urls.add(url);
}

大致的流程图

代码2:判断scope

  1. scope=none时,不暴露
  2. scope为null或者scope!=remote时,本地暴露
  3. scope为null或者scope=remote时,远程暴露
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
    //代码1,见上
    String scope = url.getParameter(Constants.SCOPE_KEY);
    // 当配置了scope = none时不暴露
    if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
        // 如果不是remote则本地暴露 (只有remote才会进行远程暴露)
        if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
            //代码3:injvm
            exportLocal(url);
        }
        // export to remote if the config is not local (export to local only when config is local)
        if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
            ... ...
        }
    }
}

大致的流程图

代码3:injvm协议

本地暴露时,采用的是injvm协议,之所以有本地暴露,是因为有些服务会存在自己调用自己的情况,有了本地暴露,就不需要走网络通信那一part了

private void exportLocal(URL url) {
	//injvm
    if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
        URL local = URL.valueOf(url.toFullString())
                .setProtocol(Constants.LOCAL_PROTOCOL)
                .setHost(LOCALHOST)
                .setPort(0);
        StaticContext.getContext(Constants.SERVICE_IMPL_CLASS).put(url.getServiceKey(), 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");
    }
}

最主要的是这一行代码,由于配置了protocol等于injvm,所以dubbo的spi机制会选InjvmProtocol,调用export生成一个InjvmExporter,放入Map中。

Exporter<?> exporter = protocol.export(
                    proxyFactory.getInvoker(ref, (Class) interfaceClass, local));

大致的流程图

代码4:判断注册中心

if(registryURLs!=null&&!registryURLs.isEmpty()){
    //如果一个服务有多个注册中心,则生成多个Exporter
    for(URL registryURL:registryURLs){
    	... ...
    }
}else{
    //没有注册中心,则默认采用dubbo协议,进入DubboProtocol
    Invoker<?> invoker=proxyFactory.getInvoker(ref,(Class)interfaceClass,url);
    DelegateProviderMetaDataInvoker wrapperInvoker=new DelegateProviderMetaDataInvoker(invoker,this);
    
    Exporter<?> exporter=protocol.export(wrapperInvoker);
    exporters.add(exporter);
}

没有注册中心,则默认采用dubbo协议,进入DubboProtocol
在这里插入图片描述
大致的流程图

代码5:openServer

代码4中的protocol.export(),最终会进入到DubboProtocol的export方法中,

public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    ... ...
    openServer(url);
    optimizeSerialization(url);
    return exporter;
}

private void openServer(URL url) {
    // 寻找server
    String key = url.getAddress();
    //client也可以暴露一个只有server可以调用的服务
    boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
    if (isServer) {
        ExchangeServer server = serverMap.get(key);
        if (server == null) {
            serverMap.put(key, createServer(url));
        } else {
            //server支持reset,配合override功能使用
            server.reset(url);
        }
    }
}

大致的流程图

代码6:createServer

远程暴露的时候,需要创建Server进行网络请求的处理,默认是创建NettyServer

private ExchangeServer createServer(URL url) {
    //默认开启server关闭时发送readonly事件
    
	//拼接URL
    ... ...

    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 {
        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;
}

大致的流程图

代码7:Exchanger

代码6中涉及的Exchangers.bind(url, requestHandler),同样也采用了dubbo的spi机制,默认使用HeaderExchanger,封装请求响应模式,同步转异步(这里涉及另一个知识点,下次再单独出一篇说明)

@SPI(HeaderExchanger.NAME)
public interface Exchanger {
	... ... 
}

public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
    return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}

大致的流程图

代码8:Transport

getTransporter也是使用dubbo的spi机制,最后会调用到NettyTransporter.bind(),返回NettyServer

public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
    if (url == null) {
        throw new IllegalArgumentException("url == null");
    }
    if (handlers == null || handlers.length == 0) {
        throw new IllegalArgumentException("handlers == null");
    }
    ChannelHandler handler;
    if (handlers.length == 1) {
        handler = handlers[0];
    } else {
        handler = new ChannelHandlerDispatcher(handlers);
    }
    return getTransporter().bind(url, handler);
}

public Server bind(URL url, ChannelHandler listener) throws RemotingException {
    return new NettyServer(url, listener);
}

大致的流程图

代码9:NettyServer

基于远程协议的暴露,需要NettyServer进行连接处理,也需要开启服务监听,处理其他进程发来的rpc请求

public Server bind(URL url, ChannelHandler listener) throws RemotingException {
    return new NettyServer(url, listener);
}

public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
    super(url, handler);
    localAddress = getUrl().toInetSocketAddress();

    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 {
        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()));
}

大致的流程图

总结

在这里插入图片描述

参考资料

Dubbo之服务暴露源码分析
源码讲解Dubbo服务暴露过程
阿里面试官:你知道Dubbo的服务暴露机制么?

结语

学习就应该经常做减法,这样学起来才不至于被劝退,至于细节方面,肯定是要更多地接触后,才能理解的透彻。有些知识点可能没有讲透,我自己也是在摸索阶段,等之后有了更深地理解后,再回来补充吧~

写博客只是为了方便自己复盘的同时,也分享下自己的学习成果,让后来人少走点弯路

如若文中部分段落、图片涉嫌侵权,还请联系本人删除~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值