这篇文章合并两个相关场景:
- 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"
);
}
}
继续检查 Kubernetes Pod YAML,发现启动参数里确实存在:
-Ddubbo.registry.address=zookeeper://old-registry.example:2181
根因与处理
-Ddubbo.registry.address 是 JVM System Property,其优先级高于应用中设置的注册中心地址,导致 Apollo 配置没有成为最终值。
既然注册中心地址已经由 Apollo 明确管理,删除重复的 -Ddubbo.registry.address 即可。配置来源越多,环境差异越难排查;同一个配置项应尽量只保留一个权威来源。
可以参考 Apache 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。
直连只适合开发和临时排障。测试结束后应及时移除映射,避免绕过注册中心的路由、治理和可用性能力。