这篇文章合并两个相关场景:

  • UAT 环境中的 Dubbo 消费者连接了错误的注册中心。
  • 本地开发时,只让某个接口直连本机提供者,其余接口继续使用注册中心。

示例基于 Dubbo 2.6.x。不同大版本的配置模型有所变化,排查时应以项目实际依赖的源码和对应版本文档为准。

注册中心配置被覆盖

背景

账单缴费服务需要调用收银台 Dubbo API。项目已有 Dubbo 依赖,因此通过 API 方式创建消费者:

compile('com.chaos:cashier-api:6.8.1.0-SNAPSHOT')

核心配置可以简化为:

@Configuration
public class DubboConsumerConfig {
    private final Environment environment;

    public DubboConsumerConfig(Environment environment) {
        this.environment = environment;
    }

    @Bean
    public ApplicationConfig applicationConfig() {
        return new ApplicationConfig(
                environment.getProperty("spring.application.name")
        );
    }

    @Bean
    public RegistryConfig registryConfig() {
        return new RegistryConfig(
                environment.getRequiredProperty("dubbo.registry-address")
        );
    }

    @Bean
    public CashierPaymentFacade cashierPaymentFacade() {
        ConsumerConfig consumer = new ConsumerConfig();
        consumer.setTimeout(20_000);

        ReferenceConfig<CashierPaymentFacade> reference = new ReferenceConfig<>();
        reference.setApplication(applicationConfig());
        reference.setRegistry(registryConfig());
        reference.setInterface(CashierPaymentFacade.class);
        reference.setVersion("1.0.0");
        reference.setGroup("chaos");
        reference.setConsumer(consumer);
        return reference.get();
    }
}

本地开发正常,但部署到 UAT 后,服务启动时报错:

Failed to check the status of the service ...
No provider available for the service ...

错误日志中的 ZooKeeper 地址与 Apollo 中配置的地址不一致。

排查过程

首先打印 Spring Environment 中的配置:

String address =
        environment.getRequiredProperty("dubbo.registry-address");
log.info("dubbo.registry-address={}", address);

输出正确。随后比较创建和注入的 RegistryConfig,也确认使用的是同一个对象。这说明问题不在 Spring Bean 的创建过程,而是在 Dubbo 后续读取并覆盖属性时发生。

调试 Dubbo 2.6.10 的 AbstractConfig#appendProperties,可以看到它会读取 JVM System Property:

if (value == null || value.length() == 0) {
    String propertyName = prefix + property;
    value = System.getProperty(propertyName);
    if (!StringUtils.isBlank(value)) {
        logger.info(
                "Use System Property " + propertyName + " to config dubbo"
        );
    }
}

Dubbo 读取系统属性

继续检查 Kubernetes Pod YAML,发现启动参数里确实存在:

-Ddubbo.registry.address=zookeeper://old-registry.example:2181

Pod 中的 Dubbo JVM 参数

根因与处理

-Ddubbo.registry.address 是 JVM System Property,其优先级高于应用中设置的注册中心地址,导致 Apollo 配置没有成为最终值。

既然注册中心地址已经由 Apollo 明确管理,删除重复的 -Ddubbo.registry.address 即可。配置来源越多,环境差异越难排查;同一个配置项应尽量只保留一个权威来源。

可以参考 Apache Dubbo 配置原理

Dubbo 配置覆盖关系

本地按接口直连提供者

本地调试时,可能只需要让一个接口访问本机提供者,而其他接口仍访问集成环境。Dubbo 提供了接口级服务映射能力。

参考:Dubbo 直连提供者

创建映射文件

默认情况下,可以在用户主目录创建 dubbo-resolve.properties

Windows 示例:

C:\Users\Administrator\dubbo-resolve.properties

在文件中使用“接口全限定名 = 直连 URL”的格式:

com.chaos.cashier.api.CashierConfManageFacade=dubbo://localhost:20485

其中 20485 是提供方的 dubbo.protocol.port

如果不希望使用默认位置,也可以通过 JVM 参数指定文件:

-Ddubbo.resolve.file=C:\dev\dubbo-resolve.properties

配置为何能够生效

Dubbo 2.6.9 的 ReferenceConfig#init 会按以下顺序解析:

  • 读取以接口全限定名为 key 的 System Property。
  • 读取 dubbo.resolve.file 指定的文件。
  • 如果仍未指定,则查找 ${user.home}/dubbo-resolve.properties
  • 用接口全限定名从映射文件读取直连 URL。

核心源码如下:

String resolve = System.getProperty(interfaceName);
String resolveFile = null;

if (resolve == null || resolve.length() == 0) {
    resolveFile = System.getProperty("dubbo.resolve.file");

    if (resolveFile == null || resolveFile.length() == 0) {
        File userResolveFile = new File(
                new File(System.getProperty("user.home")),
                "dubbo-resolve.properties"
        );
        if (userResolveFile.exists()) {
            resolveFile = userResolveFile.getAbsolutePath();
        }
    }

    if (resolveFile != null && resolveFile.length() > 0) {
        Properties properties = new Properties();
        try (FileInputStream input =
                     new FileInputStream(new File(resolveFile))) {
            properties.load(input);
        }
        resolve = properties.getProperty(interfaceName);
    }
}

使用文件映射的好处

  • 多个项目可以共享一份本地直连配置。
  • 注释一行即可恢复注册中心调用,切换速度快。
  • 不必修改项目 XML 或 Java 配置,避免把本地调试地址误提交到 Git。

直连只适合开发和临时排障。测试结束后应及时移除映射,避免绕过注册中心的路由、治理和可用性能力。