Dubbo源码之服务暴露

722 阅读8分钟

服务注册

服务暴露入口

  1. Spring应用启动的时候,在ServiceAnnotationBeanPostProcessor#postProcessBeanDefinitionRegistry方法中会为每一个带有@service注解的类创建一个BeanDefinition,beanName为ServiceBean:实现类全类名这种格式; 除此之后,ServiceAnnotationBeanPostProcessor#postProcessBeanDefinitionRegistry方法中还会为所有带有@service注解的原始的类,如:org.apache.dubbo.demo.provider.DemoServiceImpl生成BeanDefinition信息(DubboClassPathBeanDefinitionScanner.scan); 即:每个带有@service注解的类在Spring容器中会对应两个Bean,ServiceBean是实现provider的关键;

  2. ServiceBean实现了InitializingBean接口,在ServiceBean#afterPropertiesSet注册provider;

  3. ServiceBean#afterPropertiesSet方法调用了ServiceConfig#exportServiceConfig#export是实provider注册的关键;

  4. ServiceConfig#export=>启动配置中心;ServiceConfig#doExport=>ServiceConfig#doExportUrls=>ServiceConfig#doExportUrlsFor1Protocol=>创建provider对应的Exporter对象;publish服务url到元数据中心;

代理对象创建

默认通过javassist创建代理服务,ProxyFactory对应的JavassistProxyFactory

  1. ServiceConfig#doExportUrlsFor1Protocol方法中,首次涉及到动态代理的创建时这行代码,貌似是为例获取到该接口的方法,不太清楚这里为什么要创建一个动态代理对象:
// 返回 ["sayHello"]
String[] methods = Wrapper.getWrapper(org.apache.dubbo.demo.DemoService.class).getMethodNames();

//通过 javassist 动态生成的代码如下

package org.apache.dubbo.common.bytecode;

import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import org.apache.dubbo.common.bytecode.ClassGenerator.DC;
import org.apache.dubbo.demo.DemoService;

public class Wrapper0 extends Wrapper implements DC {
    public static String[] pns;
    public static Map pts;
    public static String[] mns;
    public static String[] dmns;
    public static Class[] mts0;

    public String[] getPropertyNames() {
        return pns;
    }

    public boolean hasProperty(String var1) {
        return pts.containsKey(var1);
    }

    public Class getPropertyType(String var1) {
        return (Class)pts.get(var1);
    }

    public String[] getMethodNames() {
        return mns;
    }

    public String[] getDeclaredMethodNames() {
        return dmns;
    }

    public void setPropertyValue(Object var1, String var2, Object var3) {
        try {
            DemoService var4 = (DemoService)var1;
        } catch (Throwable var6) {
            throw new IllegalArgumentException(var6);
        }
        throw new NoSuchPropertyException("Not found property \"" + var2 + "\" field or setter method in class org.apache.dubbo.demo.DemoService.");
    }

    public Object getPropertyValue(Object var1, String var2) {
        try {
            DemoService var3 = (DemoService)var1;
        } catch (Throwable var5) {
            throw new IllegalArgumentException(var5);
        }
        throw new NoSuchPropertyException("Not found property \"" + var2 + "\" field or setter method in class org.apache.dubbo.demo.DemoService.");
    }

    public Object invokeMethod(Object var1, String var2, Class[] var3, Object[] var4) throws InvocationTargetException {
        DemoService var5;
        try {
            var5 = (DemoService)var1;
        } catch (Throwable var8) {
            throw new IllegalArgumentException(var8);
        }
        try {
            if ("sayHello".equals(var2) && var3.length == 1) {
                return var5.sayHello((String)var4[0]);
            }
        } catch (Throwable var9) {
            throw new InvocationTargetException(var9);
        }
        throw new NoSuchMethodException("Not found method \"" + var2 + "\" in class org.apache.dubbo.demo.DemoService.");
    }
    public Wrapper0() {
    }
}
  1. 在接下来的本地服务暴露或者远程服务暴露过程中,会根据实现类 org.apache.dubbo.demo.DemoServiceImpl 再创建一个代理对象,还是在ServiceConfig#doExportUrlsFor1Protocol方法中,主要就是通过SPI拿到JavassistProxyFactory,然后再执行JavassistProxyFactory#getInvoker方法,先创建动态代理对象,最终返回一个AbstractProxyInvoker。而根据下面的代码可以看出,调用Invoker.doInvoke方法的时候,最终还是调用了代理对象的invokeMethod方法
proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));

// JavassistProxyFactory#getInvoker
@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
    // wrapper里面包含了代理对象,创建过的会被缓存起来
    final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
    return new AbstractProxyInvoker<T>(proxy, type, url) {
        @Override
        protected Object doInvoke(T proxy, String methodName,Class<?>[] parameterTypes,Object[] arguments) throws Throwable {
            return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
        }
    };
}

有关于javassist创建代理对象的过程就不展开了,最后生成的代理对象如下:

package org.apache.dubbo.common.bytecode;

import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import org.apache.dubbo.common.bytecode.ClassGenerator.DC;
import org.apache.dubbo.demo.provider.DemoServiceImpl;

public class Wrapper1 extends Wrapper implements DC {
    public static String[] pns;
    public static Map pts;
    public static String[] mns;
    public static String[] dmns;
    public static Class[] mts0;

    public String[] getPropertyNames() {
        return pns;
    }

    public boolean hasProperty(String var1) {
        return pts.containsKey(var1);
    }

    public Class getPropertyType(String var1) {
        return (Class)pts.get(var1);
    }

    public String[] getMethodNames() {
        return mns;
    }

    public String[] getDeclaredMethodNames() {
        return dmns;
    }

    public void setPropertyValue(Object var1, String var2, Object var3) {
        try {
            DemoServiceImpl var4 = (DemoServiceImpl)var1;
        } catch (Throwable var6) {
            throw new IllegalArgumentException(var6);
        }
        throw new NoSuchPropertyException("Not found property \"" + var2 + "\" field or setter method in class org.apache.dubbo.demo.provider.DemoServiceImpl.");
    }

    public Object getPropertyValue(Object var1, String var2) {
        try {
            DemoServiceImpl var3 = (DemoServiceImpl)var1;
        } catch (Throwable var5) {
            throw new IllegalArgumentException(var5);
        }
        throw new NoSuchPropertyException("Not found property \"" + var2 + "\" field or setter method in class org.apache.dubbo.demo.provider.DemoServiceImpl.");
    }

    public Object invokeMethod(Object var1, String var2, Class[] var3, Object[] var4) throws InvocationTargetException {
        DemoServiceImpl var5;
        try {
            var5 = (DemoServiceImpl)var1;
        } catch (Throwable var8) {
            throw new IllegalArgumentException(var8);
        }
        try {
            if ("sayHello".equals(var2) && var3.length == 1) {
                return var5.sayHello((String)var4[0]);
            }
        } catch (Throwable var9) {
            throw new InvocationTargetException(var9);
        }
        throw new NoSuchMethodException("Not found method \"" + var2 + "\" in class org.apache.dubbo.demo.provider.DemoServiceImpl.");
    }
    
    public Wrapper1() {
    }
}

本地服务暴露

有关于暴露本地服务还是远程服务,是通过scope属性进行控制

  1. scope=none:既不暴露本地服务,也不暴露远程服务;
  2. scope=remote:只暴露远程服务;
  3. scope=local:只暴露本地服务;
  4. scope未配置:即暴露本地服务,又暴露远程服务;

默认情况下,scope是没有配置的,所以会先暴露本地服务,然后暴露远程服务,同样是在ServiceConfig#doExportUrlsFor1Protocol方法中。暴露本地服务调用的是exportLocal方法:

// ServiceConfig#exportLocal
private void exportLocal(URL url) {
    URL local = URLBuilder.from(url)
            .setProtocol(LOCAL_PROTOCOL)
            .setHost(LOCALHOST_VALUE)
            .setPort(0)
            .build();
    Exporter<?> exporter = protocol.export(
            proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
    exporters.add(exporter);
    logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local);
}

通过上一小节的代理服务创建已经知道,proxyFactory#getInvoker方法将返回一个AbstractProxyInvoker对象,接下来将以这个AbstractProxyInvoker对象创建一个Exporter对象;

根据SPI机制可以知道,此时的protocol对应的是InjvmProtocol,所以暴露本地服务的时候,将执行InjvmProtocol#export方法。该方法比较简单,没有涉及到注册服务到注册中心以及打开服务端口等复杂操作,仅仅是返回一个InjvmExporter对象,在服务引用的时候会用到。

// InjvmProtocol#export
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}

远程服务暴露

同样是在ServiceConfig#doExportUrlsFor1Protocol方法中,以下这行代码将完成远程服务暴露操作:

Exporter<?> exporter = protocol.export(wrapperInvoker);

根据SPI机制,此处protocol对应的是RegistryProtocol,所以这里将执行RegistryProtocol#export方法

@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
    URL registryUrl = getRegistryUrl(originInvoker);
    // url to export locally
    URL providerUrl = getProviderUrl(originInvoker);

    final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
    final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
    overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);

    providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
    //暴露服务 通过netty开一个端口
    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);

    // url to registry
    final Registry registry = getRegistry(originInvoker);
    final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);
    ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
            registryUrl, registeredProviderUrl);
    //to judge if we need to delay publish
    boolean register = registeredProviderUrl.getParameter("register", true);
    if (register) {
        // 注册服务
        register(registryUrl, registeredProviderUrl);
        providerInvokerWrapper.setReg(true);
    }

    // 订阅override节点 /dubbo/service/configurations
    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);

    exporter.setRegisterUrl(registeredProviderUrl);
    exporter.setSubscribeUrl(overrideSubscribeUrl);
    //Ensure that a new exporter instance is returned every time export
    return new DestroyableExporter<>(exporter);
}

所谓的服务注册,核心就是Exporter对象的生成过程,而在生成Exporter对象的过程中,还涉及到动态配置、路由规则的监听工作服务端暴露工作

动态配置监听

dubbo2.7之前,这些配置都是放在/dubbo/service/routers/dubbo/service/configurations节点下面,从dubbo2.7开始,可以将这些信息放在/dubbo/config/service/xxx下面。

那么在创建Exporter对象过程中,有关于节点监听这部分工作是怎么样的流程? 创建Exporter对象主要在RegistryProtocol#export方法中进行的;

  1. 通过RegistryProtocol#register注册provider的节点,以:org.apache.dubbo.demo.provider.DemoService服务为例,这个方法就是在zookeeper生成/dubbo/org.apache.dubbo.demo.provider.DemoService节点;

  2. 以provider的URL信息创建OverrideListener

  3. OverrideListener为入参创建ChildListener

  4. ChildListener为入参创建CuratorWatcherImplCuratorWatcherImpl继承了CuratorWatcher

  5. CuratorFramework添加监听器CuratorWatcherImplCuratorZookeeperClient持有CuratorFramework实例。如果节点发生变更,执行ChildListener逻辑,即执行ZookeeperRegistry#notify方法,如果有必要重新导出Exporter

  6. 执行notify方法;如果此时/dubb/service/configurations下面有数据,拿到这些数据,封装成Configurator,根据这些Configurator,重新生成服务端的URL,然后重新生成服务端对应的Exporter

  7. 如果/dubb/service/configurations节点的内容发生变更,它的触发流程是这样的: CuratorWatcherImpl#process=>ChildListener#childChanged=>ZookeeperRegistry#notify=>ZookeeperRegistry#doNotify=>OverrideListener#notify=>OverrideListener#doOverrideIfNecessary;

服务端暴露

这部分工作在RegistryProtocol#doLocalExport方法中

private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
    String key = getCacheKey(originInvoker);
    return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
        Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
        return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
    });
}

此处的protocol是通过SPI的依赖注入功能生成的,默认情况下对应DubboProtocol,所以这里将执行DubboProtocol#export方法。这部分涉及到的代码非常多,目前并不是太懂,就以时序图的方式介绍吧: DubboProtocol#export => DubboProtocol#openServer => DubboProtocol#createServer => Exchangers#bind => Transporters#bind => new NettyServer => NettyServer#doOpen

  1. Exchangers#bind方法用于返回一个ExchangeServer对象,SPI机制默认会使用HeaderExchanger,所以默认执行HeaderExchanger#bind;从以下代码中可以发现,默认返回一个HeaderExchangeServer
@Override
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
    return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}
  1. Transporters#bind方法用于返回一个org.apache.dubbo.remoting.Server对象,SPI机制默认会使用NettyTransporter,所以默认执行NettyTransporter#bind;从以下代码中可以发现,默认返回一个NettyServer
@Override
public Server bind(URL url, ChannelHandler listener) throws RemotingException {
    return new NettyServer(url, listener);
}
  1. 在创建NettyServer的时候会调用NettyServer#doOpen方法开启一个netty服务端;

元数据中心

元数据中心从dubbo2.7开始支持

  1. ServiceConfig#doExportUrlsFor1Protocol方法中,export服务之后,如果设置了元数据中心地址,就执行MetadataReportService#publishProvider方法,该方法将provider相关的元信息写入元数据中心;
  2. ReferenceConfig#createProxy方法中,在为consumer创建代理服务之前,如果设置了元数据中心地址,就执行MetadataReportService#publishConsumer方法,该方法将consumer相关的元信息写入元数据中心;

配置中心

配置中心从dubbo2.7开始支持

  1. ServiceConfig#checkAndUpdateSubConfigs方法中,startConfigCenter方法会判断是否需要启动配置中心,如果设置了配置中心地址,就会启动配置中心;

  2. ReferenceConfig#checkAndUpdateSubConfigs方法中,startConfigCenter方法会判断是否需要启动配置中心,如果设置了配置中心地址,就会启动配置中心;

  3. 以zookeeper配置中心为例,在startConfigCenter方法中创建ZookeeperDynamicConfiguration的时候,会创建CuratorZookeeperClient,然后通过AbstractZookeeperClient#addDataListenerCuratorZookeeperClient添加DataListener

  4. 新的配置中心节点监听是通过TreeCacheListener#childEvent进行监听的,对应DataListenerTreeCache通过ZookeeperDynamicConfiguration中定义的Executor执行监听逻辑; 老的/dubbo/service/configurations是通过CuratorWatcher#process进行监听的,对应ChildListener; 一个application中,只需要创建一个'ZookeeperDynamicConfiguration'即可,然后会为每个服务创建对应ConfigurationListener;

  5. 新老配置中心触发监听逻辑的流程如下:

    CuratorWatcherImpl#process=>ChildListener#childChanged=>ZookeeperRegistry#notify=>ZookeeperRegistry#doNotify=>OverrideListener#notify=>OverrideListener#doOverrideIfNecessary;

    CuratorWatcherImpl#childEvent=>DataListener#dataChanged=>ConfigurationListener#process=>AbstractConfiguratorListener#process;AbstractConfiguratorListener#genConfiguratorsFromRawRule;=>OverrideListener#doOverrideIfNecessary;

  6. OverrideListener#doOverrideIfNecessary方法会根据配置中心的配置信息重新为Provider生成Exporter对象;那么这和配置中心有什么关系?AbstractConfiguratorListener#genConfiguratorsFromRawRule方法会根据监听到的数据动动态刷新AbstractConfiguratorListener中的configurators,在OverrideListener#doOverrideIfNecessary方法中会获取AbstractConfiguratorListener中的configurators的值,然后Merge出一个新的URL,然后用这个新的URL老的URL对比,如果不一致,就会为Provider重新生成一个Exporter对象;