Dubbo重启服务提供者或先启动服务消费者后启动服务提供者,消费者有时候会出现找不到服务的问题及解决

news2024/11/25 11:07:31

文章目录

    • @[toc]
  • 1.环境
  • 2.版本
  • 3.pom依赖
    • 3.1父工程的pom
    • 3.2子模块的pom
  • 4.问题
  • 5.根本原因
    • 5.1根本原因说明
    • 5.2总入口
    • 5.3servletWeb容器初始化
    • 5.4 nacos服务注册监听点
    • 5.5 dubbo启动服务注册监听点
  • 6.解决办法
    • 6.1降低springBoot版本为2.2.x
    • 6.2 修改源码
      • 6.2.1修改源码方式一
      • 6.2.2修改源码方式二
    • 6.3应用启动后,更新一下在注册中心的实例状态
      • 6.3.1方式一
      • 6.3.2方式二
  • 7.dubbo本地调用验证
    • 7.1 dubbo两个服务本地调用配置如下
  • 8.总结

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.总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/922878.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【RPC框架】RPC与Dubbo(让你一文搞懂,超级详细好理解!)

目录 什么是RPC框架&#xff0c;Dubbo又是什么&#xff0c;二者之间有什么联系 是不是说的有些抽象&#xff0c;那我们来说的通俗易懂点吧&#xff0c;这次你一定能听懂 简单举例 实际例子 真实场景demo&#xff08;说了这么多&#xff0c;实际体会一下代码吧&…

(6)(6.3) 自动任务中的相机控制

文章目录 前言 6.3.1 概述 6.3.2 自动任务类型 6.3.3 创建合成图像 前言 本文介绍 ArduPilot 的相机和云台命令&#xff0c;并说明如何在 Mission Planner 中使用这些命令来定义相机勘测任务。这些说明假定已经连接并配置了相机触发器和云台(camera trigger and gimbal ha…

iis服务web页面 localhost可以访问 ip不能访问

1、修改C:\Windows\System32\drivers\etc\下面hosts文件&#xff1b;需要重启电脑查看效果&#xff1b; 2、通过internet选项-》安全-》站点-》添加对应http://127.0.0.1和对应电能IP&#xff1b;

Android SDK 上手指南||第六章 用户交互

第六章 用户交互 在这篇教程中&#xff0c;我们将对之前所添加的Button元素进行设置以实现对用户点击的检测与响应。为了达成这一目标&#xff0c;我们需要在应用程序的主 Activity类中略微涉及Java编程内容。如果大家在Java开发方面的经验不太丰富也没必要担心&#xff0c;只…

Module not found: Error: Can‘t resolve ‘vue-pdf‘ in ‘xxx‘

使用命令npm run serve时vue项目报错&#xff1a; Module not found: Error: Cant resolve vue-pdf in xxx 解决方案&#xff1a; 运行命令&#xff1a; npm install vue-pdf --save --legacy-peer-deps 即可解决。 再次顺利执行npm run serve

032 - 位值类型-BIT

数据BIT类型用于存储位值。一种类型 允许存储-位值。 范围从 1 到 64。 BIT(M)MM 为了指定位值&#xff0c; 可以使用符号。是使用零和一编写的二进制值。例如&#xff0c; 和 分别代表7和128。请参见 第 9.1.5 节“位值文字”。 bvaluevalueb111b10000000 如果将值分配给 长…

ChatGPT提示与技巧分享:如何作出更好的提示2023年8月

​对ChatGPT的一些酷炫技巧感兴趣吗?这里提供了一些可以帮助你充分利用ChatGPT&#xff0c;成为AI工具专家的技巧。 毫无疑问&#xff0c;ChatGPT是目前最广泛使用的人工智能工具之一。它不仅毫不留情地取代了一些特定领域常用的软件小工具&#xff08;如智能对联、经典语录生…

2023.8 - java - Java 异常处理

异常是程序中的一些错误&#xff0c;但并不是所有的错误都是异常&#xff0c;并且错误有时候是可以避免的。 比如说&#xff0c;你的代码少了一个分号&#xff0c;那么运行出来结果是提示是错误 java.lang.Error&#xff1b;如果你用System.out.println(11/0)&#xff0c;那么…

2023年高教社杯数学建模思路 - 复盘:光照强度计算的优化模型

文章目录 0 赛题思路1 问题要求2 假设约定3 符号约定4 建立模型5 模型求解6 实现代码 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 问题要求 现在已知一个教室长为15米&#xff0c;宽为12米&…

缓存穿透、缓存击穿和缓存雪崩

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱发博客的嗯哼&#xff0c;爱好Java的小菜鸟 &#x1f525;如果感觉博主的文章还不错的话&#xff0c;请&#x1f44d;三连支持&#x1f44d;一下博主哦 &#x1f4dd;社区论坛&#xff1a;希望大家能加入社区共同进步…

Linux:LAMP架构与论坛搭建

目录 一、动态资源与语言 二、LAMP 架构的组成 三、CGI和astcgi 3.1CGI​​​​​​ 3.2fastcgi 3.3CGI和fastcgi比较 3.4 PHP 配置 3.5 Opcode语言 四、编译安装Apache http 服务 五、安装论坛 一、动态资源与语言 WEB 资源类型&#xff1a; 静态资源&#xff1a;原…

idea的debug断点的使用

添加断点&#xff08;目前不知道如何添加断点&#xff0c;就给AutoConfigurationImportSelector的每个方法都加上断点&#xff09;&#xff1a; 然后将StockApplication启动类以debug方式运行&#xff0c;然后程序就会停在119行 点击上边的step over让程序往下运行一行&#x…

如何利用IPIDEA代理IP提高运营效率和安全性

Tiktok印尼用户的每月访问时间长达28.7小时&#xff0c;访问时间远远超过其他社交媒体&#xff08;FB15.5小时&#xff0c;INS7.8小时&#xff09;。TikTok Shop在印尼的市场份额跃升&#xff0c;赶超Tokopedia和Lazada。在Tiktok运营中&#xff0c;代理IP可以发挥哪些作用呢&a…

Java“牵手”天猫商品列表数据,关键词搜索天猫商品数据接口,天猫API申请指南

天猫商城是一个网上购物平台&#xff0c;售卖各类商品&#xff0c;包括服装、鞋类、家居用品、美妆产品、电子产品等。要获取天猫商品列表和商品详情页面数据&#xff0c;您可以通过开放平台的接口或者直接访问天猫商城的网页来获取商品详情信息。以下是两种常用方法的介绍&…

基于风险的漏洞管理

基于风险的漏洞管理涉及对即将被利用的漏洞的分类响应&#xff0c;如果被利用&#xff0c;可能会导致严重后果。本文详细介绍了确定漏洞优先级时要考虑的关键风险因素&#xff0c;以及确保基于风险的漏洞管理成功的其他注意事项。 什么是基于风险的漏洞管理对基于风险的漏洞管…

N皇后【回溯】

按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返回所有不同的 n 皇后问题 的解决方案。 每一种…

应用3:VBA中字符串值的比较方案

【分享成果&#xff0c;随喜正能量】活得通透的人 &#xff0c;没有特别想维持的关系 &#xff0c;也没有特别想要的东西 &#xff0c;走近的人不抗拒 &#xff0c;离开的人不强留 &#xff0c;就连吃亏也懒得计较。。 《VBA经典应用69例》&#xff08;10178981&#xff09;&a…

Linux系统安装与配置Anaconda

Linux系统安装与配置Anaconda 下载Linux系统的Anaconda安装包安装Anaconda将Anaconda加入到环境变量测试Anaconda是否安装成功 下载Linux系统的Anaconda安装包 这里提供2种下载方式&#xff1a; 官网下载清华大学开源镜像软件 将下载的Anaconda包保存在Linux系统中。 安装A…

Winter ‘24发布在即,Salesforce Flow中的最热功能不容错过!

Flow Builder作为自动化领域的新秀&#xff0c;它在功能方面已经远远超过Workflow Rules和Process Builder&#xff0c;随着Workflow Rules和Process Builder的退役&#xff0c;目前所有自动化都需要迁移到Flow。 Winter 24发布在即&#xff0c;Flow中的亮点功能不容错过&…

生成折线图【Python思路】

# 第一步&#xff1a;导包 from pyecharts.charts import Line# 第二步&#xff1a;得到折线图对象 line Line()# 第三步&#xff1a;打开数据文件 (数据为JSON格式) f open("D:/text.txt","r",encodingUTF-8) data f.read()# 第四步&#xff1a;根据需…