Davids原理探究:Dubbo服务暴露原理

13 篇文章 0 订阅
11 篇文章 1 订阅

Dubbo服务暴露原理

在这里插入图片描述

关注可以查看更多粉丝专享blog~

前面已经讲过Dubbo配置解析的原理,今天分析一下Dubbo服务暴露原理,Dubbo服务的暴露和消费原理离不开配置解析。

Dubbo服务暴露机制
整体上看,Dubbo框架实现服务暴露分为两大部分:

  1. 将服务持有的实例通过代理转换成Invoker。
  2. 将Invoker通过具体的协议(如:Dubbo)转换成Exporter。

这里的Invoker可以简单理解成一个真实的服务对象实例,是Dubbo框架实体域,所有的模型都会向它靠拢,可向它发起invoke调用,它可能是一个本地的实现,也可能是一个远程的实现,还可能是一个集群的实现。

服务暴露的核心类ServiceConfig

  1. 通过反射获取配置对象并放到map中用于后续构造URL参数。
  2. 区分全局配置,默认在属性前面添加default.前缀,当框架获取URL中的参数时,如果不存在则会自动尝试获取default.前缀对应的值。
  3. 处理本地JVM协议暴露。
  4. 追加监控上报地址,框架会在拦截器中执行数据上报。
  5. 通过动态代理创建Invoker对象,在服务端生成的是AbstractProxyInvoker实例,所有真实的方法调用都会委托给代理,然后代理转发给服务ref调用。目前的两种代理:
    1. JavassistProxyFactory:创建Wrapper子类,在子类中实现invokeMethod方法,方法体内会为每个ref方法都做方法名和方法参数匹配校验,如果匹配则直接调用即可,相比JdkProxyFactory省去了反射调用的开销。
    2. JdkProxyFactory:通过反射获取真是对象的方法,然后调用即可。
  6. 暴露服务(端口打开等),然后进行服务元数据注册。
  7. 最后处理没有注册中心的场景,直接进行服务暴露,不需要元数据注册,因为直接暴露的URL信息是以具体的RPC协议开头的,并不是以注册中心协议开头的。
// 注册中心URL示例
registry://host:port/com.alibaba.dubbo.registry.RegistryService?protocol=zookeeper&export=dubbo://ip:port/xxx?...

// 直接暴露URL示例
dubbo://ip:port/xxx?timeout=1000&...

框架真正进行服务暴露的入口点在ServiceConfig#doExport中,无论是XML还是注解,都会转换成ServiceBean,它继承自ServiceConfig,主要处理思路就是遍历所有方法,如果没有值则尝试从-D选项中获取,如果还没有则自动从配置文件dubbo.properties中读取。

ServiceConfig#doExportUrls(服务暴露入口)
// ServiceConfig#doExportUrls()
private void doExportUrls() {
	// 获取当前服务对应注册中心实例
   	List<URL> registryURLs = loadRegistries(true);
   	// 如果服务指定暴露多个协议(Dubbo、REST),则依次暴露服务
    for (ProtocolConfig protocolConfig : protocols) {
        doExportUrlsFor1Protocol(protocolConfig, registryURLs);
    }
}
ServiceConfig#doExportUrlsFor1Protocol(真实服务暴露实现逻辑)
// ServiceConfig#doExportUrlsFor1Protocol
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
	// 如果协议名称为空则默认使用dubbo协议
    String name = protocolConfig.getName();
    if (name == null || name.length() == 0) {
        name = "dubbo";
    }
	// 构造配置信息
    Map<String, String> map = new HashMap<String, String>();
    // 设置side=provider
    map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
    // 设置version
    map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
    // 设置时间戳
    map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
    if (ConfigUtils.getPid() > 0) {
        map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
    }
    // 读其他配置信息到map,用于后续构造
    appendParameters(map, application);
    appendParameters(map, module);
    // 读取全局配置信息会自动添加前缀
    appendParameters(map, provider, Constants.DEFAULT_KEY);
    appendParameters(map, protocolConfig);
    appendParameters(map, this);
    
    ...
    
    URL url = new URL(name, host, port, path, map);
    
    ...
    
    String scope = url.getParameter(Constants.SCOPE_KEY);
    // 没有配置时不要导出
    if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {

        // 如果配置不是远程的,则导出为本地(仅当配置是远程的时候才导出为远程)
        if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
            exportLocal(url);
        }
        // 如果配置不是本地的就导出到远程(只有当配置是本地的时候才导出到本地)
        if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
            if (logger.isInfoEnabled()) {
                logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
            }
            if (registryURLs != null && !registryURLs.isEmpty()) {
            	// 有注册中心的场景,直接注册到注册中心
                for (URL registryURL : registryURLs) {
                    url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
                    // 如果配置了监控中心,则服务调用信息会上报
                    URL monitorUrl = loadMonitor(registryURL);
                    if (monitorUrl != null) {
                        url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
                    }
                    // 对于providers,用于启用自定义代理来生成invoker
                    String proxy = url.getParameter(Constants.PROXY_KEY);
                    if (StringUtils.isNotEmpty(proxy)) {
                        registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
                    }
                    // 通过动态代理转换成Invoker,registryURL存储的是注册中心地址,使用export作为key追加服务元数据信息
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
                    // 服务暴露后向注册中心注册服务信息
                    Exporter<?> exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
            } else {
            	// 没有注册中心的场景,直接暴露服务
                Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
                DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
                // 直接暴露服务
                Exporter<?> exporter = protocol.export(wrapperInvoker);
                exporters.add(exporter);
            }
        }
    }
    this.urls.add(url);
}
ServiceConfig#exportLocal(暴露本地服务(injvm))
// ServiceConfig#exportLocal
private void exportLocal(URL url) {
    if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
    	// 将协议设置为injvm进行导出
        URL local = URL.valueOf(url.toFullString())
                .setProtocol(Constants.LOCAL_PROTOCOL)
                .setHost(LOCALHOST)
                .setPort(0);
        ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref));
        Exporter<?> exporter = protocol.export(proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
        // 缓存到exporters,unexport可以使用
        exporters.add(exporter);
        logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
    }
}

注册中心暴露服务

在将服务实例ref转换成Invoker之后,如果有注册中心时,则会通过RegistryProtocol#export进行更细粒度的控制,比如进行服务暴露再注册服务元数据。注册中心暴露服务的时候依次作了以下5件事情:

  1. 委托具体协议(Dubbo)进行服务暴露,创建NettyServer监听端口和保存服务实例。
  2. 创建注册中心对象,与注册中心建立TCP连接。
  3. 注册服务元数据到注册中心。
  4. 订阅configurators节点,监听服务动态属性变更事件。
  5. 服务销毁收尾工作,比如关闭端口、反注册服务信息等。
// RegistryProtocol#export
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
    // 打开端口,把服务实例储存到map
    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
    URL registryUrl = getRegistryUrl(originInvoker);
    // 创建注册中心实例,暴露服务
    final Registry registry = getRegistry(originInvoker);
    final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker);
	boolean register = registeredProviderUrl.getParameter("register", true);
    ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl);
    // 服务暴露之后,注册元数据
    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);
    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
    // 确保每次导出时都返回一个新的export实例,内部监听configurators节点,以及unexport之后的操作
    return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl);
}

拦截器初始化

在进行服务暴露前,框架会做拦截器初始化,Dubbo在加载protocol扩展点时会自动注入ProtocolListenerWrapper和ProtocolFilterWrapper。在ProtocolListenerWrapper实现中,在对服务提供者进行暴露时回调对应的监听器方法。ProtocolFilterWrapper会调用下一级ProtocolListenerWrapper#export方法,在该方法内部会触发buildInvokerChain进行拦截器构造。

// ProtocolFilterWrapper---> ProtocolListenerWrapper ---> DubboProtocol
// 协议拦截器扩展 ProtocolFilterWrapper#export
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
        return protocol.export(invoker);
    }
    // 先构造拦截器(会过滤provider端分组),然后触发Dubbo协议暴露
    return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}

private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
    Invoker<T> last = invoker;
    // 获取url中(key:service.filter)指定的filter扩展类 (使用Dubbo SPI)
    List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
    if (!filters.isEmpty()) {
        for (int i = filters.size() - 1; i >= 0; i--) {
            final Filter filter = filters.get(i);
            // 将真实的Invoker(服务对象ref,放到拦截器末尾)
            final Invoker<T> next = last;
            // 为每一个filter生成一个exporter依次串起来
            last = new Invoker<T>() {

			...
			
                @Override
                public Result invoke(Invocation invocation) throws RpcException {
                	// 每次调用都会传递给下一个拦截器
                    return filter.invoke(next, invocation);
                }
            
            ...
            
            };
        }
    }
    return last;
}

// 协议监听器扩展 ProtocolListenerWrapper#export
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
        return protocol.export(invoker);
    }
    // 获取url中(key:exporter.listener)指定的扩展类(使用Dubbo SPI)
    return new ListenerExporterWrapper<T>(protocol.export(invoker),
            Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
                    .getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));
}

// Dubbo协议暴露 DubboProtocol#export
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    URL url = invoker.getUrl();

    // 根据服务分组、版本、接口和端口构造key
    String key = serviceKey(url);
    DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
    // 把exporter储存到单例DubboProtocol中
    exporterMap.put(key, exporter);

	...
	
	// 服务初次暴露会创建监听服务器
    openServer(url);
    optimizeSerialization(url);
    return exporter;
}

// DubboProtocol#openServer
private void openServer(URL url) {
    // 查找服务
    String key = url.getAddress();
    // 客户端可以导出仅供服务器调用的服务
    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(url);
        }
    }
}

// DubboProtocol#createServer
private ExchangeServer createServer(URL url) {
   	
   	...
   	
    ExchangeServer server;
    try {
    	// 创建NettyServer并初始化Handler
        server = Exchangers.bind(url, requestHandler);
    } catch (RemotingException e) {
        throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
    }
    
    ...
    
    return server;
}

相关文章:
Davids原理探究:Dubbo源码编译(2.7.8)
Davids原理探究:Dubbo SPI和Java SPI实现原理
Davids原理探究:Dubbo注册中心(ZooKeeper、Redis)实现原理
Davids原理探究:Dubbo配置解析原理
Davids原理探究:Dubbo服务暴露原理
Davids原理探究:Dubbo服务消费原理
Davids原理探究:Dubbo优雅停机原理
Davids原理探究:Dubbo调用流程图
Davids原理探究:Dubbo路由实现原理
Davids原理探究:Dubbo负载均衡实现原理
Davids原理探究:Dubbo过滤器原理

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值