dubbo系列之服务发布(十)

553 阅读10分钟

欢迎关注公众号【sharedCode】致力于主流中间件的源码分析, 可以直接与我联系

博主个人网站:www.shared-code.com

前言

前文中 我们介绍了ServiceBean的初始化,每个暴露出去的服务都会对应一个ServiceBean实例,在这个类中有个方法,就是用来暴露服务的。

本文的源码解析会非常深入,从服务的参数解析,到如何连接zookeeper,最后在开启20880端口,开启dubbo的服务。所有的源码分析都在这里,本文适合对dubbo的源码想有深刻理解的人,如果只是想了解个大概,那么这篇文章可能不太适合。

暴露入口

@Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
      	// 服务没有延迟加载 && 服务没有发布 && 服务没有下线过    。 满足这三个条件,则进行服务暴露
        if (isDelay() && !isExported() && !isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + getInterface());
            }
          	// 服务暴露
            export();
        }
    }

在服务没有延迟加载的时候,会调用export方法进行服务暴露, 该方法是其父类的方法,ServiceConfig中的方法。

ServiceConfig#export

public synchronized void export() {
  		// 服务提供信息不为空,
        if (provider != null) {
            if (export == null) {
              	// 设置发布信息
                export = provider.getExport();
            }
            if (delay == null) {
              	// 获取延迟信息
                delay = provider.getDelay();
            }
        }
  		//export为false , 那就不发布,直接返回。
        if (export != null && !export) {
            return;
        }
		// 延迟暴露的设置大于0 
        if (delay != null && delay > 0) {
          	// 开启一个定时线程, 在delay时间过后,执行doExport方法。
            delayExportExecutor.schedule(new Runnable() {
                @Override
                public void run() {
                    // 服务暴露
                    doExport();
                }
            }, delay, TimeUnit.MILLISECONDS);
        } else {
          	// 服务暴露
            doExport();
        }
    }

说明:

通过上面的的代码,我们有根据的得出结论

1.通过设置delay属性,可以进行服务延迟暴露,delay的单位为毫秒。

2.没有设置delay属性,或者delay的值= -1 , 服务会在spring上下文刷新的时候进行服务暴露。

doExport

protected synchronized void doExport() {
  		// unexported 表示服务已经下线。
        if (unexported) {
            throw new IllegalStateException("Already unexported!");
        }
        // exported = true   , 表示服务以及暴露,无需再次发布
        if (exported) {
            return;
        }
        exported = true; // 设置暴露属性为true,
  		// 需要暴露的服务接口不能为空。
        if (interfaceName == null || interfaceName.length() == 0) {
            throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
        }
  		// 检查默认的配置,provider的配置。
        checkDefault();
  		// provider属性检查
        if (provider != null) {
            if (application == null) {
                application = provider.getApplication();
            }
            if (module == null) {
                module = provider.getModule();
            }
            if (registries == null) {
                registries = provider.getRegistries();
            }
            if (monitor == null) {
                monitor = provider.getMonitor();
            }
            if (protocols == null) {
                protocols = provider.getProtocols();
            }
        }
 		// 模块属性检查
        if (module != null) {
            if (registries == null) {
                registries = module.getRegistries();
            }
            if (monitor == null) {
                monitor = module.getMonitor();
            }
        }
  		// 全局application应用属性检查
        if (application != null) {
            if (registries == null) {
                registries = application.getRegistries();
            }
            if (monitor == null) {
                monitor = application.getMonitor();
            }
        }
  		//ref 是服务暴露的实现类
  		// GenericService 是dubbo泛化调用的实现。
  		//泛接口实现方式主要用于服务器端没有API接口及模型类元的情况,参数及返回值中的所有POJO均用Map表示,通常用于框架集成,
		//比如:实现一个通用的远程服务Mock框架,可通过实现GenericService接口处理所有服务请求。
        if (ref instanceof GenericService) {
            interfaceClass = GenericService.class;
            if (StringUtils.isEmpty(generic)) {
                generic = Boolean.TRUE.toString();
            }
        } else {
            try {
              	// 通过反射,实例化暴露的服务接口实例
                interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
          	// 检查方法
            checkInterfaceAndMethods(interfaceClass, methods);
          	// 检查服务的实现类是否和当前的服务一致
            checkRef();
            generic = Boolean.FALSE.toString();
        }
        //设为true,表示使用缺省代理类名,即:接口名 + Local后缀,服务接口客户端本地代理类名,用于在客户端执行本地逻辑,如本地缓存等,该本地代理类的构		  // 造函数必须允许传入远程代理对象,构造函数如:public XxxServiceLocal(XxxService xxxService)
        if (local != null) {
            if ("true".equals(local)) {
                local = interfaceName + "Local";
            }
            Class<?> localClass;
            try {
                localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            if (!interfaceClass.isAssignableFrom(localClass)) {
                throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
            }
        }
  		// dubbo的本地存根配置 。 http://dubbo.apache.org/zh-cn/docs/user/demos/local-stub.html
        if (stub != null) {
            if ("true".equals(stub)) {
                stub = interfaceName + "Stub";
            }
            Class<?> stubClass;
            try {
              	// 获取本地存根类的实例
                stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
          	// 本地存根类检查
            if (!interfaceClass.isAssignableFrom(stubClass)) {
                throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
            }
        }
  		// 检查配置,下面四个check都是用于配置检查。
        checkApplication();
        checkRegistry();
        checkProtocol();
        appendProperties(this);
        checkStubAndMock(interfaceClass);
        if (path == null || path.length() == 0) {
            path = interfaceName;
        }
  		// 执行服务暴露。下面的流程主要是走这个方法
        doExportUrls();
        ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
        ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
    }

步骤说明:

1.配置检查

2.检查local,stub,mock等模式。

3.执行doExportUrls方法暴露服务。

上面的这个方法,更主要的作用就在于配置的检查。 服务暴露的主要逻辑不在这里。

doExportUrls

private void doExportUrls() {
  		// 加载注册地址,dubbo是可以支持多个注册中心的
        List<URL> registryURLs = loadRegistries(true);
  		// dubbo也是可以支持多个协议的,这里直接循环Protocols
        for (ProtocolConfig protocolConfig : protocols) {
          	// 暴露服务
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

loadRegisteries 这个方法返回的注册地址如下:

registry://localhost:2181/com.alibaba.dubbo.registry.RegistryService?application=dubbo-service&dubbo=2.6.2&pid=20748&registry=zookeeper&timestamp=1537240877173
##应用名&版本号&应用PID&注册类型&时间戳

如果有多个注册地址,那么就会返回多个。

doExportUrlsFor1Protocol

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
  		// 获取协议的名称,
        String name = protocolConfig.getName();
        if (name == null || name.length() == 0) {
          	// 如果没有设置,则默认为dubbo
            name = "dubbo";
        }

        Map<String, String> map = new HashMap<String, String>();
  		// side , provider
        map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
  		// dubbo , 版本号
        map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
  		// timestamp , 当前时间戳
        map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
        if (ConfigUtils.getPid() > 0) { // 获取应用PID,
          	// 设置
            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);
        if (methods != null && !methods.isEmpty()) {
         	// methods 处理 ,省略。。。。
        }

        if (ProtocolUtils.isGeneric(generic)) {
            map.put(Constants.GENERIC_KEY, generic);
            map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
        } else {
          	// dubbo的版本
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put("revision", revision);
            }
			// 获取服务接口的方法
            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if (methods.length == 0) {
              	// 方法数量为空
                logger.warn("NO method found in service interface " + interfaceClass.getName());
                map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
            } else {
              	// 方法数量大于0
                map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
            }
        }
  		// 令牌不为空
        if (!ConfigUtils.isEmpty(token)) {
            if (ConfigUtils.isDefault(token)) {
                map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
            } else {
                map.put(Constants.TOKEN_KEY, token);
            }
        }
  		// 协议名称不等于injvm ,如果等于这个,表示就只是本地暴露,不会远程暴露
        if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {
          	// 设置注册属性为false 
            protocolConfig.setRegister(false);
            map.put("notify", "false");
        }
        // export service
        String contextPath = protocolConfig.getContextpath();
        if ((contextPath == null || contextPath.length() == 0) && provider != null) {
            contextPath = provider.getContextpath();
        }
		// 获取IP
        String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
  		// 获取端口
        Integer port = this.findConfigedPorts(protocolConfig, name, map);
  		//-----------------------------------------------------------------------------------------------------
  		// 上面的代码,都是为了组装map参数,用来拼接url/
        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);
        }
		// 服务的发布范围
        String scope = url.getParameter(Constants.SCOPE_KEY);
        // 如果是配置了none, 那么服务就不会发布
        if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {

            // 如果scope不等于remote, 那么就会进行本地发布
            if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
              	// 本地暴露
                exportLocal(url);
            }
            // 如果scope不等于local , 那么就会进行远程发布
            if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
                if (logger.isInfoEnabled()) {
                    logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + 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());
                        }
                        if (logger.isInfoEnabled()) {
                            logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                        }
                      	// 获取proxfactory 生成invoker
                        Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                      	// 服务暴露出去的invoker
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
					 	// 调用Protocol进行服务发布
                        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);
    }

上面的代码比较长,总结一下。

在代码中,我使用中杠分割开来了两部分代码,中杠上面的那一部分,是用来收集参数的,参数被放置在map中,最后通过map生成url,

url如下:

dubbo://192.168.59.3:20880/com.dubbo.service.user.UserFacadeService?anyhost=true&application=dubbo-service&bind.ip=192.168.59.3&bind.port=20880&dubbo=2.6.2&generic=false&interface=com.dubbo.service.user.UserFacadeService&methods=getUser&pid=20748&side=provider&stub=com.dubbo.service.user.UserFacadeServiceStub&timestamp=1537240877178

包含了这个服务的所有参数。 长杠下面的,就是dubbo要开始进行服务发布了。

上面有一个比较重要的属性,**scope ** , 这个属性一共有下面三种设置。

local : 仅进行本地暴露
remote : 仅进行远程暴露
none : 不进行服务暴露

上面的代码中,在实际情形中,scope的值为null, 所以他会进行本地暴露和远程暴露,二者都会进行。

本地暴露在本文中暂时不讲了,重点在于远程暴露,不过有一点需要注意:

**本地暴露在同一个JVM中会被优先选择,比如消费者和生产者都跑在同一个Tomcat中,那么会优先选择调用本地暴露出来的服务 **

服务暴露最重要的三行代码

// 获取proxfactory 生成invoker
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded
                                             (Constants.EXPORT_KEY, url.toFullString()));
// 服务暴露出去的invoker
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
// 调用Protocol进行服务发布
Exporter<?> exporter = protocol.export(wrapperInvoker);

使用registryURL 生成Invoker ,registryURL如下:

registry://localhost:2181/com.alibaba.dubbo.registry.RegistryService?application=dubbo-service&dubbo=2.6.2&pid=10588&registry=zookeeper&timestamp=1537255304679

这个地方,就奠定了Invoker的协议类型,Invoker中的协议类型就是register,而不是dubbo。因为这是用于发布服务的

proxyFactory是dubbo使用SPI拓展机制生成的扩展实现类,默认采用的实现是javassist , 生成Invoker之后,将Invoker和ServiceConfig绑定起来,生成DelegateProviderMetaDataInvoker , 加强了一下。

调用Protocol的export方法进行服务发布。

protocol是通过SPI生成的扩展代理类,export中代码如下

public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
  		// 获取当前的协议类型,是dubbo还是其他的,比如这个地方服务发布,传入的是register,用于注册。
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) 
                                            name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader
                                            (com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }

这里返回的extension的实例结构如下: ProtocolListenerWrapper > ProtocolFilterWrapper > QosProtocolWrapper > RegistryProtocol

Protocol的扩展实现类在之前的文章中详细分析过,Protocol的扩展实现类首先会经过ProtocolListenerWrapper ProtocolFilterWrapper QosProtocolWrapper 这三个类,而且这三个类的**顺序是不一致的 **。

在我这次调试中,Protocol的装饰器类的执行顺序如下:

QosProtocolWrapper > ProtocolFilterWrapper >QosProtocolWrapper >RegistryProtocol

QosProtocolWrapper

给注册的操作,开启dubbo的qos-server在线运维平台,默认端口:22222

@Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
      	// 判断是否是注册操作。
        if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
          	// 开启dubbo的在线运维平台,默认占用端口
            startQosServer(invoker.getUrl());
            return protocol.export(invoker);
        }
        return protocol.export(invoker);
    }

ProtocolListenerWrapper

除去注册操作,给Invoker添加监听器

@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
  // 判断是否是注册操作。
  if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
    return protocol.export(invoker);
  }
  // 
  return new ListenerExporterWrapper<T>(protocol.export(invoker),
                                        Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
                                                                     .getActivateExtension(invoker.getUrl(), 	Constants.EXPORTER_LISTENER_KEY)));
}

ProtocolFilterWrapper

除去注册操作,给Invoker添加过滤器

@Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
      	// 判断是否是注册操作。
        if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
            return protocol.export(invoker);
        }
      	// buildInvokerChain建立过滤器链。
        return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
    }

RegistryProtocol

@Override
    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        //暴露服务,开启dubbo的
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
		// 获取zookeeper的地址
        URL registryUrl = getRegistryUrl(originInvoker);

        // 连接zookeeper,建立长连接
        final Registry registry = getRegistry(originInvoker);
        final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);

        //获取register属性,判断是否需要马上注册
        boolean register = registedProviderUrl.getParameter("register", true);

        ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);

        if (register) {
          	// 注册
            register(registryUrl, registedProviderUrl);
            ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
        }

        // Subscribe the override data
        // FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call the same service. Because the subscribed is cached key with the name of the service, it causes the subscription information to cover.
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
        //Ensure that a new exporter instance is returned every time export
        return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registedProviderUrl);
    }

上面的方法,看上去就比较纯粹了,除了doLocalExport方法,其他的代码都是跟zookeeper交互,注册URL,订阅资源等等。

我们知道,dubbo的消费者调用生产者的方法,默认是通过netty来通信的,并且默认端口是20880,生产者内部会启动一个netty服务。这个操作就是在doLocalExport方法来完成的。

private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) {
  		// key = dubbo协议的URL
        String key = getCacheKey(originInvoker);
  		// 获取dubbo的协议的Export的装饰类
        ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
  		// 等于空,说明dubbo协议所代表的服务并没有开启。
        if (exporter == null) {
            synchronized (bounds) { // 同步锁
                exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
                if (exporter == null) { // 双重检查
                    final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
                  	// 这里又开始使用protocol , 不过需要注意的是,这个地方invokerDelegete
                    exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);
                    bounds.put(key, exporter);
                }
            }
        }
        return exporter;
    }

这里的invokerDelegete中的协议类型是dubbo, 不是之前注册所需要的register。

因此protocol.export()的执行链如下:ProtocolListenerWrapper > ProtocolFilterWrapper > QosProtocolWrapper > DubboProtocol

前面三个的顺序不确定。 最终都会执行到DubboProtocol 。

DubboProtocol

@Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
      	// 获取当前服务的URL
        URL url = invoker.getUrl();

        // 获取服务的key , key = com.dubbo.service.user.UserFacadeService:20880
        String key = serviceKey(url);
        DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
        exporterMap.put(key, exporter);

        //export an stub service for dispatching event
        Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT);
        Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
        if (isStubSupportEvent && !isCallbackservice) {
            String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
            if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
                if (logger.isWarnEnabled()) {
                    logger.warn(new IllegalStateException("consumer [" + url.getParameter(Constants.INTERFACE_KEY) +
                            "], has set stubproxy support event ,but no stub methods founded."));
                }
            } else {
                stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
            }
        }
		// 开启netty服务端口,就是dubbo那个20880的那个,表示准备可以接收请求了。
        openServer(url);
        optimizeSerialization(url);
        return exporter;
    }

说明:

服务的URL格式如下:

dubbo://192.168.59.3:20880/com.dubbo.service.user.UserFacadeService?anyhost=true&application=dubbo-service&bind.ip=192.168.59.3&bind.port=20880&dubbo=2.6.2&generic=false&interface=com.dubbo.service.user.UserFacadeService&methods=getUser&pid=20748&side=provider&stub=com.dubbo.service.user.UserFacadeServiceStub&timestamp=1537240877178

dubbo协议服务暴露流程图:

dubbo服务暴露的整理执行链:

zookeeper目录:

欢迎关注公众号【sharedCode】致力于主流中间件的源码分析, 可以直接与我联系

博主个人网站:www.shared-code.com