1.环境

  springCloudAlibaba+dubbo+nacos的环境,环境搭建或升级请参看这三个的官方文档

2.版本

  JDK版本:1.8

  springBoot的版本:2.3.12.RELEASE

  spring-cloud.version版本:Hoxton.SR9

  spring-cloud-alibaba.version版本:2.2.6.RELEASE

  dubbo.version版本:2.2.6.RELEASE

  nacos服务端2.0.3、nacos客服端1.4.2

  注意:这个客户端还是要跟服务端的版本相匹配的,

  nacos2.x的客户端用的是GRPC调用,而nacos客户端1.4.x使用的是http的方式,这种还是兼容的,但是最好是版本匹配不容易出问题。

3.pom依赖

3.1父工程的pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
        <relativePath/>
    </parent>

    <groupId>com.xxx.xx</groupId>
    <artifactId>xxx-xxx</artifactId>
    <packaging>pom</packaging>
    <version>1.0.0-SNAPSHOT</version>
    <modules>
        <module>xxx-api</module>
        <module>xxx-service</module>
    </modules>

    <distributionManagement>
        <repository>
            <id>nexus-snapshots</id>
            <name>Nexus snapshots</name>
            <url>xxxxxxx</url>
        </repository>
    </distributionManagement>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
        <spring-cloud-alibaba.version>2.2.6.RELEASE</spring-cloud-alibaba.version>
        <dubbo.version>2.2.6.RELEASE</dubbo.version>
        <mybatisplus.version>3.5.1</mybatisplus.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

</project>

3.2子模块的pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>xxx-xxxx</artifactId>
        <groupId>com.xxx.xxx</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>xxxx-service</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-dubbo</artifactId>
            <version>${dubbo.version}</version>
        </dependency>
        <dependency>
            <groupId>com.xxx.xxxx</groupId>
            <artifactId>xxxx-api</artifactId>
            <version>1.2.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.8</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

4.问题

org.apache.dubbo.rpc.RpcException: No provider available from registry localhost:9090 for service xxxx on consumer 192.168.20.92.1 use dubbo version 2.7.8, please check status of providers(disabled, not registered or in blacklist).
at org.apache.dubbo.registry.integration.RegistryDirectory.doList(RegistryDirectory.java:599)
	at org.apache.dubbo.rpc.cluster.directory.AbstractDirectory.list(AbstractDirectory.java:74)
	at org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker.list(AbstractClusterInvoker.java:292)
	at org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(AbstractClusterInvoker.java:257)
	at org.apache.dubbo.rpc.cluster.interceptor.ClusterInterceptor.intercept(ClusterInterceptor.java:47)
	at org.apache.dubbo.rpc.cluster.support.wrapper.AbstractCluster$InterceptorInvokerNode.invoke(AbstractCluster.java:92)
	at org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker.invoke(MockClusterInvoker.java:88)
	at org.apache.dubbo.rpc.proxy.InvokerInvocationHandler.invoke(InvokerInvocationHandler.java:74)

  调用服务提供者时,消费者的dubbo的服务目录 org.apache.dubbo.registry.integration.RegistryDirectoryforbidden 属性 为 true

图片

5.根本原因

5.1根本原因说明

  在springBoot2.3.x版本中nacos的服务注册是监听了ServletWebServerInitializedEvent事件,该事件是servletWeb容器初始化完成后会发这个事件,而dubbo的服务注册时机是在SpringBoot容器完成刷新的时候会发ContextRefreshedEvent这个事件,是nacos的服务注册时间早于dubbo的服务注册的时间,这种会导致nacos服务端在处理了服务提供者的注册请求后向订阅者下发了实例变更通知,而在这个过程中提供者自身的dubbo服务暴露有可能还没有完成,最直接的表现就是服务提供者的 com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepositoryallExportedURLs 属性中还没有对应的dubbo服务的URL。

  因为spring cloud alibaba + dubbo 中dubbo的服务是暴露在本地的com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository中的 allExportedURLs 属性中,不会传到注册中心服务端。所以最终暴露完成以后,nacos服务端无法感知到dubbo服务是否已准备妥当,也无法通知订阅者。这种情况下,提供者发起调用时通过泛化调用DubboMetadataService接口获取提供者暴露的服务时,从 allExportedURLs 中获取到的就是一个空的 List。然后消费者就会以为是没有提供者,于是在自己本地的dubbo服务目录 RegistryDirectory 中 把禁用属性 forbidden 的值更新为了 true

  在spring boot 2.2.xServletWebServerInitializedEvent事件的发布是在ContextRefreshedEvent事件之后,源码分析如下(以下是springBoot2.3.x的版本):

5.2总入口

public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);//这里就是总入口
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

5.3servletWeb容器初始化

  AbstractApplicationContext#onRefresh

图片

  ServletWebServerApplicationContext#onRefresh

图片

  ServletWebServerApplicationContext#createWebServer

图片

  WebServerStartStopLifecycle#start

图片

5.4 nacos服务注册监听点

  nacos的服务注册的上层抽象定义是在spring-cloud-commons2.2.6.RELEASE的  AbstractAutoServiceRegistration#onApplicationEvent#bind

图片

	public void start() {
		if (!isEnabled()) {
			if (logger.isDebugEnabled()) {
				logger.debug("Discovery Lifecycle disabled. Not starting");
			}
			return;
		}

		// only initialize if nonSecurePort is greater than 0 and it isn't already running
		// because of containerPortInitializer below
		if (!this.running.get()) {
			this.context.publishEvent(
					new InstancePreRegisteredEvent(this, getRegistration()));
			register();//这里是注册的上层接口定义
			if (shouldRegisterManagement()) {
				registerManagement();
			}
			this.context.publishEvent(
					new InstanceRegisteredEvent<>(this, getConfiguration()));
			this.running.compareAndSet(false, true);
		}

	}

  NacosServiceRegistry#register

public void register(Registration registration) {
        if (StringUtils.isEmpty(registration.getServiceId())) {
            log.warn("No service to register for nacos client...");
        } else {
            NamingService namingService = this.namingService();
            String serviceId = registration.getServiceId();
            String group = this.nacosDiscoveryProperties.getGroup();
            Instance instance = this.getNacosInstanceFromRegistration(registration);

            try {
                namingService.registerInstance(serviceId, group, instance);
                log.info("nacos registry, {} {} {}:{} register finished", new Object[]{group, serviceId, instance.getIp(), instance.getPort()});
            } catch (Exception var7) {
                if (this.nacosDiscoveryProperties.isFailFast()) {
                    log.error("nacos registry, {} register failed...{},", new Object[]{serviceId, registration.toString(), var7});
                    ReflectionUtils.rethrowRuntimeException(var7);
                } else {
                    log.warn("Failfast is false. {} register failed...{},", new Object[]{serviceId, registration.toString(), var7});
                }
            }

        }
    }

5.5 dubbo启动服务注册监听点

  DubboBootstrapApplicationListener#onApplicationContextEvent#onContextRefreshedEvent#dubboBootstrap.start

图片

  DubboBootstrap#start

public DubboBootstrap start() {
        if (started.compareAndSet(false, true)) {
            ready.set(false);
            initialize();
            if (logger.isInfoEnabled()) {
                logger.info(NAME + " is starting...");
            }
            // 1. export Dubbo Services //暴露dubbo服务
            exportServices();

            // Not only provider register
            if (!isOnlyRegisterProvider() || hasExportedServices()) {
                // 2. export MetadataService //暴露dubbo服务元数据服务
                exportMetadataService();
                //3. Register the local ServiceInstance if required
                registerServiceInstance(); //注册dubbo服务实例
            }

            referServices();
            if (asyncExportingFutures.size() > 0) {
                new Thread(() -> {
                    try {
                        this.awaitFinish();
                    } catch (Exception e) {
                        logger.warn(NAME + " exportAsync occurred an exception.");
                    }
                    ready.set(true);
                    if (logger.isInfoEnabled()) {
                        logger.info(NAME + " is ready.");
                    }
                }).start();
            } else {
                ready.set(true);
                if (logger.isInfoEnabled()) {
                    logger.info(NAME + " is ready.");
                }
            }
            if (logger.isInfoEnabled()) {
                logger.info(NAME + " has started.");
            }
        }
        return this;
    }

6.解决办法

6.1降低springBoot版本为2.2.x

  该方法有可能不适配,需要去调整适配到相对应的版本上才可以的,具体可以去尝试下

6.2 修改源码

  DubboServiceRegistrationAutoConfiguration是dubbo的子动装配类

package com.alibaba.cloud.dubbo.autoconfigure;
.........
import static com.alibaba.cloud.dubbo.autoconfigure.DubboServiceRegistrationAutoConfiguration.CONSUL_AUTO_SERVICE_AUTO_CONFIGURATION_CLASS_NAME;
import static com.alibaba.cloud.dubbo.autoconfigure.DubboServiceRegistrationAutoConfiguration.EUREKA_CLIENT_AUTO_CONFIGURATION_CLASS_NAME;
import static com.alibaba.cloud.dubbo.registry.SpringCloudRegistryFactory.ADDRESS;
import static com.alibaba.cloud.dubbo.registry.SpringCloudRegistryFactory.PROTOCOL;
import static org.springframework.util.ObjectUtils.isEmpty;

/**
 * Dubbo Service Registration Auto-{@link Configuration}.
 *
 * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
 * @author <a href="mailto:chenxilzx1@gmail.com">theonefx</a>
 */
@Configuration(proxyBeanMethods = false)
@Import({ DubboServiceRegistrationEventPublishingAspect.class,
		DubboBootstrapStartCommandLineRunner.class })
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled",
		matchIfMissing = true)
@AutoConfigureAfter(name = { EUREKA_CLIENT_AUTO_CONFIGURATION_CLASS_NAME,
		CONSUL_AUTO_SERVICE_AUTO_CONFIGURATION_CLASS_NAME,
		"org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration" },
		value = { DubboMetadataAutoConfiguration.class })
public class DubboServiceRegistrationAutoConfiguration {

		/**
	 * EurekaClientAutoConfiguration.
	 */
	public static final String EUREKA_CLIENT_AUTO_CONFIGURATION_CLASS_NAME = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration";

	/**
	 * ConsulAutoServiceRegistrationAutoConfiguration.
	 */
	public static final String CONSUL_AUTO_SERVICE_AUTO_CONFIGURATION_CLASS_NAME = "org.springframework.cloud.consul.serviceregistry.ConsulAutoServiceRegistrationAutoConfiguration";

	/**
	 * ConsulAutoRegistration.
	 */
	public static final String CONSUL_AUTO_SERVICE_AUTO_REGISTRATION_CLASS_NAME = "org.springframework.cloud.consul.serviceregistry.ConsulAutoRegistration";

	/**
	 * ZookeeperAutoServiceRegistrationAutoConfiguration.
	 */
	public static final String ZOOKEEPER_AUTO_SERVICE_AUTO_CONFIGURATION_CLASS_NAME = "org.springframework.cloud.zookeeper.serviceregistry.ZookeeperAutoServiceRegistrationAutoConfiguration";

	private static final Logger logger = LoggerFactory
			.getLogger(DubboServiceRegistrationAutoConfiguration.class);

	@Autowired
	private DubboServiceMetadataRepository dubboServiceMetadataRepository;

	@Bean
	@Conditional({ MissingSpringCloudRegistryConfigPropertyCondition.class })
	public RegistryConfig defaultSpringCloudRegistryConfig() {
		return new RegistryConfig(ADDRESS, PROTOCOL);
	}

	private Map<ServiceRegistry<Registration>, Set<Registration>> registrations = new ConcurrentHashMap<>();

	@EventListener(DubboBootstrapStartedEvent.class)
	public void onDubboBootstrapStarted(DubboBootstrapStartedEvent event) {
		if (!event.getSource().isReady()) {
			return;
		}
		registrations.forEach(
				(registry, registrations) -> registrations.forEach(registration -> {
					attachDubboMetadataServiceMetadata(registration);
					registry.register(registration);
				}));
	}

	@EventListener(ServiceInstancePreRegisteredEvent.class)
	public void onServiceInstancePreRegistered(ServiceInstancePreRegisteredEvent event) {
		Registration registration = event.getSource();
		if (!DubboBootstrap.getInstance().isReady()
				|| !DubboBootstrap.getInstance().isStarted()) {
			ServiceRegistry<Registration> registry = event.getRegistry();
			synchronized (registry) {
				registrations.putIfAbsent(registry, new HashSet<>());
				registrations.get(registry).add(registration);
			}
		}
		else {
			attachDubboMetadataServiceMetadata(registration);
		}

	}

	@EventListener(ServiceInstancePreDeregisteredEvent.class)
	public void onServiceInstancePreDeregistered(
			ServiceInstancePreDeregisteredEvent event) {
		ServiceRegistry<Registration> registry = event.getRegistry();
		registrations.remove(registry);
	}

	private void attachDubboMetadataServiceMetadata(Registration registration) {
		if (registration == null) {
			return;
		}
		synchronized (registration) {
			Map<String, String> metadata = registration.getMetadata();
			attachDubboMetadataServiceMetadata(metadata);
		}
	}

	private void attachDubboMetadataServiceMetadata(Map<String, String> metadata) {
		Map<String, String> serviceMetadata = dubboServiceMetadataRepository
				.getDubboMetadataServiceMetadata();
		if (!isEmpty(serviceMetadata)) {
			metadata.putAll(serviceMetadata);
		}
	}
    ......................................

}

  DubboBootstrapStartCommandLineRunner在dubbo服务启动注册完成会发一个DubboBootstrapStartedEvent事件

@Component
public class DubboBootstrapStartCommandLineRunner
		implements CommandLineRunner, ApplicationEventPublisherAware {

	private ApplicationEventPublisher applicationEventPublisher;

	@Override
	public void setApplicationEventPublisher(
			ApplicationEventPublisher applicationEventPublisher) {
		this.applicationEventPublisher = applicationEventPublisher;
	}

	@Override
	public void run(String... args) {
		applicationEventPublisher.publishEvent(
				new DubboBootstrapStartedEvent(DubboBootstrapWrapper.getInstance()));
	}

}

  该事件会被DubboServiceRegistrationAutoConfiguration监听到启动后触发dubbo服务注册

@EventListener(DubboBootstrapStartedEvent.class)
public void onDubboBootstrapStarted(DubboBootstrapStartedEvent event) {
		if (!event.getSource().isReady()) {
			return;
		}
		registrations.forEach(
				(registry, registrations) -> registrations.forEach(registration -> {
					attachDubboMetadataServiceMetadata(registration);
					registry.register(registration);
				}));
}

  ServiceInstancePreRegisteredEvent事件是服务实例预注册时间,它的触发是在  DubboServiceRegistrationEventPublishingAspect切面类里面,

  DubboServiceRegistrationEventPublishingAspect该切面会拦截上面的NacosServiceRegistry#register方法执行前做一些处理

@Aspect
public class DubboServiceRegistrationEventPublishingAspect
		implements ApplicationEventPublisherAware {

	/**
	 * The pointcut expression for {@link ServiceRegistry#register(Registration)}.
	 */
	public static final String REGISTER_POINTCUT_EXPRESSION = "execution(* org.springframework.cloud.client.serviceregistry.ServiceRegistry.register(*)) && target(registry) && args(registration)";

	/**
	 * The pointcut expression for {@link ServiceRegistry#deregister(Registration)}.
	 */
	public static final String DEREGISTER_POINTCUT_EXPRESSION = "execution(* org.springframework.cloud.client.serviceregistry.ServiceRegistry.deregister(*)) && target(registry) && args(registration)";

	private ApplicationEventPublisher applicationEventPublisher;

    //服务预注册事件的发布
	@Before(value = REGISTER_POINTCUT_EXPRESSION, argNames = "registry, registration")
	public void beforeRegister(ServiceRegistry registry, Registration registration) {
		applicationEventPublisher.publishEvent(
				new ServiceInstancePreRegisteredEvent(registry, registration));
	}

    //服务注销事件的发布
	@Before(value = DEREGISTER_POINTCUT_EXPRESSION, argNames = "registry, registration")
	public void beforeDeregister(ServiceRegistry registry, Registration registration) {
		applicationEventPublisher.publishEvent(
				new ServiceInstancePreDeregisteredEvent(registry, registration));
	}
    
    //服务册事件完成后的事件发布
	@After(value = REGISTER_POINTCUT_EXPRESSION, argNames = "registry, registration")
	public void afterRegister(ServiceRegistry registry, Registration registration) {
		applicationEventPublisher
				.publishEvent(new ServiceInstanceRegisteredEvent(registration));
	}

	@Override
	public void setApplicationEventPublisher(
			ApplicationEventPublisher applicationEventPublisher) {
		this.applicationEventPublisher = applicationEventPublisher;
	}

}

  下面的源码修改正式利用了上面这几个地方。

6.2.1修改源码方式一

  DubboServiceRegistrationEventPublishingAspect该切面会拦截上面的NacosServiceRegistry#register方法执行前做一些处理

  将这类DubboServiceRegistrationEventPublishingAspect提出来修改

图片

package com.alibaba.cloud.dubbo.registry;
@Aspect
public class DubboServiceRegistrationEventPublishingAspect
        implements ApplicationEventPublisherAware {

    /**
     * The pointcut expression for {@link ServiceRegistry#register(Registration)}.
     */
    public static final String REGISTER_POINTCUT_EXPRESSION = "execution(* org.springframework.cloud.client.serviceregistry.ServiceRegistry.register(*)) && target(registry) && args(registration)";

    /**
     * The pointcut expression for {@link ServiceRegistry#deregister(Registration)}.
     */
    public static final String DEREGISTER_POINTCUT_EXPRESSION = "execution(* org.springframework.cloud.client.serviceregistry.ServiceRegistry.deregister(*)) && target(registry) && args(registration)";

    private ApplicationEventPublisher applicationEventPublisher;

    private static DubboBootstrap dubboBootstrap;//这里是新增的代码

    static {
        //这里是新增的代码
        dubboBootstrap = DubboBootstrap.getInstance();
    }

    @Before(value = REGISTER_POINTCUT_EXPRESSION, argNames = "registry, registration")
    public void beforeRegister(ServiceRegistry registry, Registration registration) {
        dubboBootstrap.start();//这里是新增的代码
        applicationEventPublisher.publishEvent(
                new ServiceInstancePreRegisteredEvent(registry, registration));
    }

    @Before(value = DEREGISTER_POINTCUT_EXPRESSION, argNames = "registry, registration")
    public void beforeDeregister(ServiceRegistry registry, Registration registration) {
        dubboBootstrap.stop();//这里是新增的代码
        applicationEventPublisher.publishEvent(
                new ServiceInstancePreDeregisteredEvent(registry, registration));
    }

    @After(value = REGISTER_POINTCUT_EXPRESSION, argNames = "registry, registration")
    public void afterRegister(ServiceRegistry registry, Registration registration) {
        applicationEventPublisher
                .publishEvent(new ServiceInstanceRegisteredEvent(registration));
    }

    @Override
    public void setApplicationEventPublisher(
            ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

}

  将DubboBootstrapApplicationListener类提出来修改:
图片

  DubboBootstrapApplicationListener修改如下:

public class DubboBootstrapApplicationListener extends OneTimeExecutionApplicationContextEventListener
        implements Ordered {

    /**
     * The bean name of {@link DubboBootstrapApplicationListener}
     *
     * @since 2.7.6
     */
    public static final String BEAN_NAME = "dubboBootstrapApplicationListener";

    private final DubboBootstrap dubboBootstrap;

    public DubboBootstrapApplicationListener() {
        this.dubboBootstrap = DubboBootstrap.getInstance();
    }

    @Override
    public void onApplicationContextEvent(ApplicationContextEvent event) {
        if (event instanceof ContextRefreshedEvent) {
            //onContextRefreshedEvent((ContextRefreshedEvent) event); 注释这一行,这个逻辑已经放到上面的DubboServiceRegistrationEventPublishingAspect里面来触发了
        } else if (event instanceof ContextClosedEvent) {
            onContextClosedEvent((ContextClosedEvent) event);
        }
    }

    private void onContextRefreshedEvent(ContextRefreshedEvent event) {
        dubboBootstrap.start();
    }

    private void onContextClosedEvent(ContextClosedEvent event) {
        dubboBootstrap.stop();
    }

    @Override
    public int getOrder() {
        return LOWEST_PRECEDENCE;
    }
}

6.2.2修改源码方式二

  将DubboBootstrapApplicationListener的监听父级事件放宽松,修改监听触发事件改为InstancePreRegisteredEvent事件,这个InstancePreRegisteredEvent是在nacos注册前会发这个时间的,上面nacos的注册点有这个代码的,可以去欣赏下的,需要重新如下几个类:

图片

  DubboBootstrapApplicationListener类修改入下:

public class DubboBootstrapApplicationListener extends OneTimeExecutionApplicationContextEventListener
        implements Ordered {

    /**
     * The bean name of {@link DubboBootstrapApplicationListener}
     *
     * @since 2.7.6
     */
    public static final String BEAN_NAME = "dubboBootstrapApplicationListener";

    private final DubboBootstrap dubboBootstrap;

    public DubboBootstrapApplicationListener() {
        this.dubboBootstrap = DubboBootstrap.getInstance();
    }


    /*@Override
    public void onApplicationContextEvent(ApplicationContextEvent event) {
        if (event instanceof ContextRefreshedEvent) {
            onContextRefreshedEvent((ContextRefreshedEvent) event);
        } else if (event instanceof ContextClosedEvent) {
            onContextClosedEvent((ContextClosedEvent) event);
        }
    }*/

    @Override
    public void onApplicationContextEvent(ApplicationEvent event) {
        if (event instanceof InstancePreRegisteredEvent) {
            onContextRefreshedEvent((InstancePreRegisteredEvent) event);
        } else if (event instanceof ContextClosedEvent) {
            onContextClosedEvent((ContextClosedEvent) event);
        }
    }

    /*private void onContextRefreshedEvent(ContextRefreshedEvent event) {
        dubboBootstrap.start();
    }*/

    private void onContextRefreshedEvent(InstancePreRegisteredEvent event) {
        dubboBootstrap.start();
    }

    /*private void onContextClosedEvent(ContextClosedEvent event) {
        dubboBootstrap.stop();
    }*/

    private void onContextClosedEvent(ContextClosedEvent event) {
        dubboBootstrap.stop();
    }

    @Override
    public int getOrder() {
        return LOWEST_PRECEDENCE;
    }
}

  DubboLifecycleComponentApplicationListener类修改如下:

public class DubboLifecycleComponentApplicationListener extends OneTimeExecutionApplicationContextEventListener {

    /**
     * The bean name of {@link DubboLifecycleComponentApplicationListener}
     *
     * @since 2.7.6
     */
    public static final String BEAN_NAME = "dubboLifecycleComponentApplicationListener";

    private List<Lifecycle> lifecycleComponents = emptyList();

   /* @Override
    protected void onApplicationContextEvent(ApplicationContextEvent event) {
        if (event instanceof ContextRefreshedEvent) {
            onContextRefreshedEvent((ContextRefreshedEvent) event);
        } else if (event instanceof ContextClosedEvent) {
            onContextClosedEvent((ContextClosedEvent) event);
        }
    }*/


    @Override
    protected void onApplicationContextEvent(ApplicationEvent event) {
        if (event instanceof InstancePreRegisteredEvent) {
            onContextRefreshedEvent((InstancePreRegisteredEvent) event);
        } else if (event instanceof ContextClosedEvent) {
            onContextClosedEvent((ContextClosedEvent) event);
        }
    }

  /*  protected void onContextRefreshedEvent(ContextRefreshedEvent event) {
        initLifecycleComponents(event);
        startLifecycleComponents();
    }*/

    protected void onContextRefreshedEvent(InstancePreRegisteredEvent event) {
        initLifecycleComponents(event);
        startLifecycleComponents();
    }

    protected void onContextClosedEvent(ContextClosedEvent event) {
        destroyLifecycleComponents();
    }

    /*private void initLifecycleComponents(ContextRefreshedEvent event) {
        ApplicationContext context = event.getApplicationContext();
        ClassLoader classLoader = context.getClassLoader();
        lifecycleComponents = new LinkedList<>();
        // load the Beans of Lifecycle from ApplicationContext
        loadLifecycleComponents(lifecycleComponents, context);
    }*/

    private void initLifecycleComponents(InstancePreRegisteredEvent event) {
        ApplicationContext context = SpringUtils.getApplicationContext();
        ClassLoader classLoader = context.getClassLoader();
        lifecycleComponents = new LinkedList<>();
        // load the Beans of Lifecycle from ApplicationContext
        loadLifecycleComponents(lifecycleComponents, context);
    }

    private void loadLifecycleComponents(List<Lifecycle> lifecycleComponents, ApplicationContext context) {
        lifecycleComponents.addAll(beansOfTypeIncludingAncestors(context, Lifecycle.class).values());
    }

    private void startLifecycleComponents() {
        lifecycleComponents.forEach(Lifecycle::start);
    }

    private void destroyLifecycleComponents() {
        lifecycleComponents.forEach(Lifecycle::destroy);
    }
}

  OneTimeExecutionApplicationContextEventListener类修改如下:

abstract class OneTimeExecutionApplicationContextEventListener implements ApplicationListener, ApplicationContextAware {

    private ApplicationContext applicationContext;

   /* public final void onApplicationEvent(ApplicationEvent event) {
        if (isOriginalEventSource(event) && event instanceof ApplicationContextEvent) {
            onApplicationContextEvent((ApplicationContextEvent) event);
        }
    }*/

    public final void onApplicationEvent(ApplicationEvent event) {
        if (isOriginalEventSource(event) && event instanceof InstancePreRegisteredEvent) {
            onApplicationContextEvent((ApplicationContextEvent) event);
        }
    }


    /**
     * The subclass overrides this method to handle {@link ApplicationContextEvent}
     *
     * @param event {@link ApplicationContextEvent}
     */
    //protected abstract void onApplicationContextEvent(ApplicationContextEvent event);
    protected abstract void onApplicationContextEvent(ApplicationEvent event);

    /**
     * Is original {@link ApplicationContext} as the event source
     *
     * @param event {@link ApplicationEvent}
     * @return
     */
    /*private boolean isOriginalEventSource(ApplicationEvent event) {
        return (applicationContext == null) // Current ApplicationListener is not a Spring Bean, just was added
                // into Spring's ConfigurableApplicationContext
                || Objects.equals(applicationContext, event.getSource());
    }*/
    
    private boolean isOriginalEventSource(ApplicationEvent event) {
        return (applicationContext == null) // Current ApplicationListener is not a Spring Bean, just was added
                // into Spring's ConfigurableApplicationContext
                || Objects.equals(applicationContext, event.getSource());
    }

    @Override
    public final void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }
}

  将dubbo服务启动注册监听点提前到nacos的服务注册点之前执行,会发送一个ServiceInstancePreRegisteredEvent事件会被dubbo自动装配监听后将dubbo服务注册的服务信息放到registrations中:

	@EventListener(ServiceInstancePreRegisteredEvent.class)
	public void onServiceInstancePreRegistered(ServiceInstancePreRegisteredEvent event) {
		Registration registration = event.getSource();
		if (!DubboBootstrap.getInstance().isReady()
				|| !DubboBootstrap.getInstance().isStarted()) {
			ServiceRegistry<Registration> registry = event.getRegistry();
			synchronized (registry) {
				registrations.putIfAbsent(registry, new HashSet<>());
				registrations.get(registry).add(registration);
			}
		}
		else {
			attachDubboMetadataServiceMetadata(registration);
		}

	}

  当dubbo服务启动后会发一个DubboBootstrapStartedEvent事件dubbo的自动自动装配监听到这个事件将自动装配中监听的ServiceInstancePreRegisteredEvent事件提前注册的dubbo服务注册实例在去遍历注册到nacos上:

@EventListener(DubboBootstrapStartedEvent.class)
	public void onDubboBootstrapStarted(DubboBootstrapStartedEvent event) {
		if (!event.getSource().isReady()) {
			return;
		}
		registrations.forEach(
				(registry, registrations) -> registrations.forEach(registration -> {
					attachDubboMetadataServiceMetadata(registration);
					registry.register(registration);
				}));
	}

   dubboBootstrap.start();的启动和注册只会被重复一次,方法里面使用的是CAS机制保证只启动注册一次,有兴趣的小伙伴可以去参看源码。

6.3应用启动后,更新一下在注册中心的实例状态

  在应用启动后,在 ApplicationRunner接口的run方法中,调用 springCloudAlibaba框架中的NacosServiceRegistry类的setStatus方法,更新一下在注册中心的实例状态,这两种方式的本质都是在应用启动都开了两个线程,当服务启动的时候会有一个线程不断的周期性的去上报务实例的相关信息,当服务停止的时候也是会上报状态给nacos服务端,然后nacos服务端收到服务上线(上线会关闭检测的线程池)或下线会发一个服务实例变更通知给所有的client,然后各个client会收到这个服务实例变更的通知,然后更新本地的服务缓存列表,这样不管是消费者先启动,服务提供者后启动,消费者都可以感知到服务的变更,然后正确的调用到所需的服务实例。

6.3.1方式一

import com.alibaba.cloud.nacos.registry.NacosRegistration;
import com.alibaba.cloud.nacos.registry.NacosServiceRegistry;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.common.lifecycle.Closeable;
import com.alibaba.nacos.common.utils.ThreadUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
 
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
 
@Component
public class NacosServiceInstanceUpAndDownOperator implements ApplicationRunner, Closeable {
    protected Logger logger = LoggerFactory.getLogger(this.getClass());
    /**
     * nacos服务实例上线
     */
    private static final String OPERATOR_UP = "UP";
    /**
     * nacos服务实例下线
     */
    private static final String OPERATOR_DOWN = "DOWN";
 
    @Resource
    NacosServiceRegistry nacosServiceRegistry;
 
    @Resource
    NacosRegistration nacosRegistration;
 
    private ScheduledExecutorService executorService;
 
 
    @PostConstruct
    public void init() {
        int poolSize = 1;
        this.executorService = new ScheduledThreadPoolExecutor(poolSize, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setDaemon(true);
                thread.setName("NacosServiceInstanceUpAndDownOperator");
                return thread;
            }
        });
    }
 
 
    @Override
    public void run(ApplicationArguments args) throws Exception {
        long delayDown = 5000L;  //下线任务延迟
        long delayUp = 10000L;   // 上线任务延迟
        this.executorService.schedule(new InstanceDownAndUpTask(nacosServiceRegistry, nacosRegistration, OPERATOR_DOWN), delayDown, TimeUnit.MILLISECONDS);
        this.executorService.schedule(new InstanceDownAndUpTask(nacosServiceRegistry, nacosRegistration, OPERATOR_UP), delayUp, TimeUnit.MILLISECONDS);
    }
 
    @Override
    public void shutdown() throws NacosException {
        ThreadUtils.shutdownThreadPool(executorService, logger);
    }
 
    /**
     * 服务实例上下线任务
     */
    class InstanceDownAndUpTask implements Runnable {
        private NacosServiceRegistry nacosServiceRegistry;
        private NacosRegistration nacosRegistration;
        //更新服务实例的状态 :UP 、DOWN
        private String nacosServiceInstanceOperator;
 
        InstanceDownAndUpTask(NacosServiceRegistry nacosServiceRegistry, NacosRegistration nacosRegistration, String nacosServiceInstanceOperator) {
            this.nacosServiceRegistry = nacosServiceRegistry;
            this.nacosRegistration = nacosRegistration;
            this.nacosServiceInstanceOperator = nacosServiceInstanceOperator;
        }
 
        @Override
        public void run() {
            logger.info("===更新nacos服务实例的状态to:{}===start=", nacosServiceInstanceOperator);
            this.nacosServiceRegistry.setStatus(nacosRegistration, nacosServiceInstanceOperator);
            logger.info("===更新nacos服务实例的状态to:{}===end=", nacosServiceInstanceOperator);
 
            //上线后,关闭线程池
            if (NacosServiceInstanceUpAndDownOperator.OPERATOR_UP.equals(nacosServiceInstanceOperator)) {
                ThreadUtils.shutdownThreadPool(NacosServiceInstanceUpAndDownOperator.this.executorService, NacosServiceInstanceUpAndDownOperator.this.logger);
            }
        }
    }
}

6.3.2方式二

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.registry.NacosRegistration;
import com.alibaba.cloud.nacos.registry.NacosServiceRegistry;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.common.lifecycle.Closeable;
import com.alibaba.nacos.common.utils.ThreadUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.stereotype.Component;
 
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Properties;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
 
@Component
public class NacosUpDown implements ApplicationRunner, Closeable {
    private static final Logger logger = LoggerFactory.getLogger(NacosUpDown.class);
    /**
     * nacos服务实例上线
     */
    private static final String OPERATOR_UP = "UP";
    /**
     * nacos服务实例下线
     */
    private static final String OPERATOR_DOWN = "DOWN";
 
    @Resource
    NacosServiceRegistry nacosServiceRegistry;
 
    @Resource
    NacosRegistration nacosRegistration;
 
    @Resource
    private NacosServiceManager nacosServiceManager;
    @Resource
    private NacosDiscoveryProperties nacosDiscoveryProperties;
 
    private ScheduledExecutorService executorService;
 
 
    @PostConstruct
    public void init() {
        int poolSize = 1;
        this.executorService = new ScheduledThreadPoolExecutor(poolSize, r -> {
            Thread thread = new Thread(r);
            thread.setDaemon(true);
            thread.setName("NacosUpAndDown");
            return thread;
        });
    }
 
 
    @Override
    public void run(ApplicationArguments args){
        //下线任务延迟
        long delayDown = 15000L;
        // 上线任务延迟
        long delayUp = 21000L;
        this.executorService.schedule(new InstanceDownAndUpTask(nacosServiceRegistry, nacosRegistration, OPERATOR_DOWN), delayDown, TimeUnit.MILLISECONDS);
        this.executorService.schedule(new InstanceDownAndUpTask(nacosServiceRegistry, nacosRegistration, OPERATOR_UP), delayUp, TimeUnit.MILLISECONDS);
    }
 
    @Override
    public void shutdown() {
        ThreadUtils.shutdownThreadPool(executorService, logger);
    }
 
    /**
     * 服务实例上下线任务
     */
    class InstanceDownAndUpTask implements Runnable {
        private final NacosServiceRegistry nacosServiceRegistry;
        private final NacosRegistration nacosRegistration;
        //更新服务实例的状态 :UP 、DOWN
        private final String nacosServiceInstanceOperator;
 
        InstanceDownAndUpTask(NacosServiceRegistry nacosServiceRegistry, NacosRegistration nacosRegistration, String nacosServiceInstanceOperator) {
            this.nacosServiceRegistry = nacosServiceRegistry;
            this.nacosRegistration = nacosRegistration;
            this.nacosServiceInstanceOperator = nacosServiceInstanceOperator;
        }
 
        @Override
        public void run() {
            logger.info("===更新nacos服务实例的状态to:{}===start=", nacosServiceInstanceOperator);
            setStatus(nacosRegistration, nacosServiceInstanceOperator);
            logger.info("===更新nacos服务实例的状态to:{}===end=", nacosServiceInstanceOperator);
 
            //上线后,关闭线程池
            if (NacosUpDown.OPERATOR_UP.equals(nacosServiceInstanceOperator)) {
                ThreadUtils.shutdownThreadPool(NacosUpDown.this.executorService, logger);
            }
        }
    }
 
 
    public void setStatus(Registration registration, String status) {
        if (!status.equalsIgnoreCase(OPERATOR_UP) && !status.equalsIgnoreCase(OPERATOR_DOWN)) {
        } else {
            String serviceId = registration.getServiceId();
            Instance instance = this.getNacosInstanceFromRegistration(registration);
            if (status.equalsIgnoreCase(OPERATOR_DOWN)) {
                instance.setEnabled(false);
            } else {
                instance.setEnabled(true);
            }
 
            try {
                Properties nacosProperties = this.nacosDiscoveryProperties.getNacosProperties();
                this.nacosServiceManager.getNamingMaintainService(nacosProperties).updateInstance(serviceId,nacosProperties.getProperty("group"), instance);
            } catch (Exception var6) {
                throw new RuntimeException("update nacos instance status fail", var6);
            }
        }
    }
    private Instance getNacosInstanceFromRegistration(Registration registration) {
        Instance instance = new Instance();
        instance.setIp(registration.getHost());
        instance.setPort(registration.getPort());
        instance.setWeight(this.nacosDiscoveryProperties.getWeight());
        instance.setClusterName(this.nacosDiscoveryProperties.getClusterName());
        instance.setEnabled(this.nacosDiscoveryProperties.isInstanceEnabled());
        instance.setMetadata(registration.getMetadata());
        instance.setEphemeral(this.nacosDiscoveryProperties.isEphemeral());
        return instance;
    }
}

  这个问题可以升级版本看看,在高匹配版本上官方有没有把这个bug修复了。

7.dubbo本地调用验证

7.1 dubbo两个服务本地调用配置如下

  服务提供者yml配置:

server:
  address: 192.168.20.2 # 本机ip
  port: 8081
dubbo:
  provider:
    host: 192.168.20.2
spring:
  cloud:
    nacos:
      config:
        server-addr: ${nacos.addr}
        group: ${nacos.group}
        namespace: ${nacos.ns}
        file-extension: yaml
      discovery:
        server-addr: ${nacos.addr}
        namespace: ${nacos.ns}
        ip: 192.168.20.2 # 服务提供者注册指定ip注册

nacos:
  addr: xxxx:8848
  group: xxxxx
  ns: xxxxx

  服务消费者yml配置和消费者的配置基本大同小异,都要加上上面那几个本地的ip配置

  服务提供者的nacos的公共配置:

server:
  port: 12188
dubbo:
  provider:
    filter: -validation
  consumer:
    check: false
  cloud:
    subscribed-services: ''
  scan:
    base-packages: com.dy.member.service.dubbo
  protocol:
    name: dubbo
    port: -1 # 这里设置为-1就会导致每次服务提供者重启后,服务提供者的服务端口会变,这里在本地两个服务相互调用的时候需要注意
  registry:
    address: spring-cloud://localhost # nacos://xxxx:8848 这种方式是直接注册到nacos上不注册到本地的目录中,每次都去nacos上拉取最新的,就不至于会等实例变更通知后,客户端没有及时去拉取nacos上的服务实例信息缓存到本地,在从本地调用
  application:
    version: 1.0.0

  服务消费者调用服务提供者的dubbo接口代码姿势如下:

@RestController
@RequestMapping("xxx")
@Slf4j
public class xxxxController {

    @DubboReference(version = "${dubbo.application.version}",url = "dubbo://192.168.20.2:20880")
    UserClient userClient; //这里是服务提供这的dubbo服务,需要设置url为本地注册到nacos的服务的url信息,该信息可以从nacos的服务那里查看,如果测试环境,不配置这个url,可以将测试环境上的其它这个服务提供者注册上去的服务下线,只保留你自己本地启动注册上去的服务,这种就可以不用加这个url了,因为nacos上只有你的你本地的服务,就可以直接调用到这个服务的,若果加了这个url,需要去nacos上查看服务提供者本地注册上去的dubbo服务的地址和端口
    
    @PostMapping("getUserInfo")
    public RestResponse<?> getUserInfo() {
        Long uid = customThreadLocal.getCustomInfo().getMemberId();
        MemberVOV2 memberVOV2 = userClient.queryMemberByUid(uid);
    }
}

  nacos服务提供者查看服务信息和下线服务提供者的非本地的服务

图片

  本机验证,先把服务提供者的nacos的测试环境的其它服务提供实例下线,然后启动服务消费者,然后启动服务提供者后,在使用postman调用服务消费的getUserInfo接口发现是可以立马调用到接口的,这个问题由于是一个偶现的问题,所以还得去测试环境或者生产环境具体的验证的,所以采用那种方式就至关重要了,如果改源码的方式,风险会有点大的,因为你不知道不这种改会不会还有其它的bug问题出现的,所以推荐6.3应用启动后,更新一下在注册中心的实例状态的这种方式,如果要使用改源码的方法是是可以的,6.2和6.3一起使用,双保险的,改源码的方式经过上面的源码分析,评估是可以行的,不会有太大的影响,但是还是得小心谨慎哦,充分验证后在决定使用啥方式来搞。

8.总结

  这个问题也是一个同事问我的,当时我也很懵,后面经过一番查阅源码分析后总结得出以上的方法,希望对大家有所帮助,请一键三连,么么哒!

更多推荐