dubbo源码(一)-- 服务注册

摘要:大部分互联网公司都会使用dubbo作为微服务架构的中间件选择,这篇博客从一次故障说起,延伸到dubbo源码分析,以供他人借鉴。

在某次服务迁移中,需要把服务从服务器集群A迁移到服务器集群B,为了保证服务平滑迁移,所以存在一个时期,是服务器集群A和B同时存在的,我的服务provider要向集群A和B同时注册服务、提供服务。配置如下

    <dubbo:registry address="A ip address" />
    <dubbo:registry address="B ip address"/>

某日,集群A到期,已经彻底不可用,此时provider注册失败,产生大量warn日志
次日,日常发布时,dubbo服务发布失败,到致服务全部挂掉。

查看日志时,发现dubbo服务在提供服务时不断的向集群A注册服务,由于集群A已经不可用,所以注册全部失败。后来将dubbo配置中A的registry拿掉即解决问题。

但是,为了分析深层原因,我对dubbo注册服务相关的源码进行了分析。

在常规的使用中,dubbo往往和spring bean和spring context模块结合起来使用,下面是一个最简单的dubbo和spring结合的例子

public class Application {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-provider.xml");
        context.start();
        System.in.read();
    }
}

provider的xml配置文件如下

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    
    <dubbo:application name="demo-provider"/>

    <dubbo:registry address="multicast://224.5.6.7:1234" />

    
    <dubbo:protocol name="dubbo"/>

    
    <bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl"/>

    
    <dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService"/>

beans>

其中的dubbo:service标签会被spring注册为ServiceBean对象,其中的原理就是spring提供了创建自定义标签的能力,dubbo的xsd文件实现了对标签的解析并且生成bean。

我们看一下ServiceBean源码

public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
        ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware,
        ApplicationEventPublisherAware {

    // ..省略了一些不重要的代码
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        SpringExtensionFactory.addApplicationContext(applicationContext);
        supportedApplicationListener = addApplicationListener(applicationContext, this);
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (!isExported() && !isUnexported()) {
            // 在spring初始化完成所有bean时,会发布事件。这个方法监听此事件,并进行export操作
            export();
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        // 在service bean的各个属性被赋值之后,这里会根据全局、或自定义的dubbo配置,将bean的一些空白属性给填补上。
    }


    @Override
    public void export() {
        super.export();// 这里进入ServiceConfig的export方法
        // Publish ServiceBeanExportedEvent
        publishExportEvent();
    }

    /**
     * @since 2.6.5
     */
    private void publishExportEvent() {
        ServiceBeanExportedEvent exportEvent = new ServiceBeanExportedEvent(this);
        applicationEventPublisher.publishEvent(exportEvent);
    }

}

在export方法中进入了ServiceConfig的export方法

class ServiceConfig {
 public synchronized void export() {
        checkAndUpdateSubConfigs();

        if (provider != null) {
            if (export == null) {
                export = provider.getExport();
            }
            if (delay == null) {
                delay = provider.getDelay();
            }
        }
        if (export != null && !export) {
            return;
        }
		//在这里会判断如果设置了延迟注册,就会在delay时间后,进行注册
        if (delay != null && delay > 0) {
            delayExportExecutor.schedule(this::doExport, delay, TimeUnit.MILLISECONDS);
        } else {
            doExport();
        }
    }
}

进入doExport方法–>进入doExportUrls方法

private void doExportUrls() {
		// 获取配置的多个注册中心
        List<URL> registryURLs = loadRegistries(true);
        for (ProtocolConfig protocolConfig : protocols) {
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }
    
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        
        // export service
        String contextPath = protocolConfig.getContextpath();
        String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
        Integer port = this.findConfigedPorts(protocolConfig, name, map);
        URL url = new URL(name, host, port, (StringUtils.isEmpty(contextPath) ? "" : contextPath + "/") + path, map);

        String scope = url.getParameter(Constants.SCOPE_KEY);
        // don't export when none is configured
        if (!Constants.SCOPE_NONE.equalsIgnoreCase(scope)) {

            // export to local if the config is not remote (export to remote only when config is remote)
            if (!Constants.SCOPE_REMOTE.equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
            // export to remote if the config is not local (export to local only when config is local)
            if (!Constants.SCOPE_LOCAL.equalsIgnoreCase(scope)) {
                if (CollectionUtils.isNotEmpty(registryURLs)) {
                    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());
                        }
                        // For providers, this is used to enable custom proxy to generate invoker
                        String proxy = url.getParameter(Constants.PROXY_KEY);
                        if (StringUtils.isNotEmpty(proxy)) {
                            registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
                        }

                        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);
                }
                /**
                 * @since 2.7.0
                 * ServiceData Store
                 */
                MetadataReportService metadataReportService = null;
                if ((metadataReportService = getMetadataReportService()) != null) {
                    metadataReportService.publishProvider(url);
                }
            }
        }
        this.urls.add(url);
    }

Invoker invoker = proxyFactory.getInvoker;
Exporter exporter = protocol.export(wrapperInvoker);
这两句是最核心的代码了
Invoker是服务provider向外提供服务的一个代理类,一般由JavassistProxyFactory 实现,JavassistProxyFactory 很简单

 @Override
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
        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);
            }
        };
    }

他首先将你的类使用Wrapper类包装一次,然后调用wrapper类的invokeMethod方法。
Wrapper包装的过程也很简单,通过反射拿到你的类的属性,方法,根据这些,生成一模一样的方法属性,简单来说,可以认为Wrapper类是你的类的子类。
下一步便是 Exporter exporter = protocol.export(wrapperInvoker);这里将使用你配置的协议暴露服务,如果你使用的是dubbo服务,将进入DubboProtocol的export方法
后面是一些连接注册中心,订阅消息的操作,服务注册到这里基本结束。

回到本文的最前面,如果有两个注册中心,一个注册中心不可用,服务注册时会发生什么?
在dubbo官方版本中,如果一个注册中心3秒内连不上,整个程序会停掉,并关闭所有注册中心的连接

[24/09/19 08:24:42:628 CST] main-SendThread(172.x.x.x:x)  INFO zookeeper.ClientCnxn: Opening socket connection to server 172.x.x.x/172.x.x.x:x. Will not attempt to authenticate using SASL (unknown error)
Exception in thread "main" java.lang.IllegalStateException: Failed to connect to config center (zookeeper): 172.x.x.x:x in 30000ms.
	at org.apache.dubbo.configcenter.support.zookeeper.ZookeeperDynamicConfiguration.<init>(ZookeeperDynamicConfiguration.java:75)
	at org.apache.dubbo.configcenter.support.zookeeper.ZookeeperDynamicConfigurationFactory.createDynamicConfiguration(ZookeeperDynamicConfigurationFa

然而在我们使用的版本中,dubbo会不断重试,导致已经注册到正常注册中心的连接不会关掉,而后面要注册到zk的连接则不断重试。由于已经正常注册到zk的服务正是用来检查服务状态的服务,所以在服务正确性检查时未发现dubbo服务启动失败。

你可能感兴趣的:(源码,dubbo)