dubbo-2-6-6 使用nacos作为注册中心
前面的示例用 ZooKeeper 作注册中心。本篇把注册中心换成 Nacos,业务代码与 Dubbo 配置结构都不变,只调整依赖和注册地址。
示例源码位于 https://github.com/litongjava/java-ee-tio-boot-study 的 tio-boot-latest-study/tio-boot-dubbo-2-6-6-nacos-demo01 目录。
准备 Nacos
需要一台可访问的 Nacos 服务。本篇在本地用 Docker 启动,未开启鉴权:
docker run -d --name nacos -p 8848:8848 -p 9848:9848 -e MODE=standalone nacos/nacos-server:latest启动后确认两个端口都通:8848 是 HTTP 控制台与 OpenAPI,9848 是 gRPC 端口,客户端从 2.x 起走 gRPC。
curl "http://127.0.0.1:8848/nacos/v1/ns/service/list?pageNo=1&pageSize=20"刚启动时返回 {"count":0,"doms":[]}。
Dubbo 如何选择注册中心
Dubbo 依据 RegistryConfig 地址的协议头选择注册中心实现:zookeeper:// 对应 ZooKeeper,nacos:// 对应 Nacos。因此从 ZooKeeper 换成 Nacos,本质只是换地址和补上 Nacos 对应的注册中心依赖,RPC 调用本身完全不受影响。
nacos:// 对应的实现来自 dubbo-registry-nacos,它通过 SPI 注册为 nacos=NacosRegistryFactory。
关于 tio-cloud
tio-cloud 的 Nacos 模块提供的是「配置中心 + tio-boot 自己的服务发现」,服务于应用之间的 HTTP 调用,与 Dubbo 的 RPC 注册是两条独立通道,不能替代 Dubbo 的注册中心实现。
本例只从它借鉴两点:使用同一版本的 nacos-client,以及沿用 nacos.server-addr 这类参数命名习惯,方便同一个团队的项目保持一致。
版本选择
dubbo-registry-nacos:2.6.6 默认依赖的 nacos-client 是 0.6.2,这个版本无法与 Nacos 3.x 服务端通信,因此必须替换成较新的客户端。示例使用 3.1.1。
替换后有一个兼容问题需要处理:该注册中心实现调用了 com.alibaba.nacos.client.naming.utils.StringUtils.isNotEmpty,这个类在 1.1.x 之后被移到 com.alibaba.nacos.common.utils.StringUtils,旧位置不再存在。解决办法是在项目中补一个同名兼容类,Dubbo 侧代码不需要修改:
package com.alibaba.nacos.client.naming.utils;
public final class StringUtils {
private StringUtils() {
}
public static boolean isNotEmpty(String str) {
return str != null && str.length() > 0;
}
}不加这个类的话,注册时会抛 NoClassDefFoundError: com/alibaba/nacos/client/naming/utils/StringUtils。又因为 DubboProvider.export() 在异步线程中执行,异常会被线程池吞掉,表现为「进程正常、端口也绑上了,但只导出第一个服务」。
创建工程
三个模块与 ZooKeeper 版本一致,只把坐标换成 2-6-6-nacos-demo01:
tio-boot-dubbo-2-6-6-nacos-demo01-api
tio-boot-dubbo-2-6-6-nacos-demo01-provider
tio-boot-dubbo-2-6-6-nacos-demo01-consumer根工程统一声明版本:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<tio-boot.version></tio-boot.version>
<jfinal-aop.version></jfinal-aop.version>
<dubbo.version>2.6.6</dubbo.version>
<nacos-client.version>3.1.1</nacos-client.version>
</properties>
<modules>
<module>tio-boot-dubbo-2-6-6-nacos-demo01-api</module>
<module>tio-boot-dubbo-2-6-6-nacos-demo01-provider</module>
<module>tio-boot-dubbo-2-6-6-nacos-demo01-consumer</module>
</modules>tio-boot-dubbo-2-6-6-nacos-demo01-api
接口定义,由 provider 与 consumer 共用:
package nexus.io.tio.dubbo.demo001.service;
public interface HelloService {
String sayHello(String name);
}package nexus.io.tio.dubbo.demo001.service;
public interface HiService {
String sayHi(String name);
}tio-boot-dubbo-2-6-6-nacos-demo01-provider
添加依赖
与 ZooKeeper 版本相比,移除 zkclient,换成 Nacos 的注册中心实现。
zkclient 并不是 Dubbo 的通用依赖,它只是 ZooKeeper 注册中心的一种客户端实现。ZooKeeper 版本的工程之所以要引入它,是因为 Dubbo 2.6.6 的 ZookeeperTransporter SPI 里有 zkclient 和 curator 两个实现,而这两个客户端库都不由 Dubbo 自带,必须由应用显式提供。Nacos 注册中心走 HTTP 与 gRPC,不经过 ZookeeperTransporter,所以本项目既不依赖 zkclient,也不需要 curator:
<dependency>
<groupId>nexus.io</groupId>
<artifactId>tio-boot-dubbo-2-6-6-nacos-demo01-api</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>nexus.io</groupId>
<artifactId>tio-boot</artifactId>
<version>${tio-boot.version}</version>
</dependency>
<dependency>
<groupId>nexus.io</groupId>
<artifactId>jfinal-aop</artifactId>
<version>${jfinal-aop.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.42.Final</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>${nacos-client.version}</version>
</dependency>排除 dubbo-registry-nacos 自带的 nacos-client 是关键一步。不排除会在 classpath 上留下 0.6.2 和 3.1.1 两份客户端,加载顺序一旦偏向前者就会连不上 Nacos 3.x。
netty-all 仍然是必需的:2.6.6 默认传输层是 netty4。
配置文件
src/main/resources/app.properties:
server.listening.enable=false
app.name=service-provider
NACOS_ADDR=127.0.0.1:8848server.listening.enable=false 关闭 tio-boot 的 HTTP 监听,provider 只提供 RPC 服务。
服务实现类
package nexus.io.tio.dubbo.demo001.service.impl;
import nexus.io.annotation.AService;
import nexus.io.tio.dubbo.demo001.service.HelloService;
@AService
public class HelloServiceImpl implements HelloService {
public String sayHello(String name) {
return "Hello " + name;
}
}package nexus.io.tio.dubbo.demo001.service.impl;
import com.alibaba.dubbo.config.annotation.Service;
import nexus.io.tio.dubbo.demo001.service.HiService;
@Service
public class HiServiceImpl implements HiService {
@Override
public String sayHi(String name) {
return "Hi " + name;
}
}Dubbo 配置
package nexus.io.tio.dubbo.demo001.config;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ProviderConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import nexus.io.annotation.AConfiguration;
import nexus.io.annotation.Initialization;
import nexus.io.constants.ServerConfigKeys;
import nexus.io.hook.HookCan;
import nexus.io.jfinal.aop.Aop;
import nexus.io.jfinal.dubbo.DubboProvider;
import nexus.io.tio.dubbo.demo001.service.HelloService;
import nexus.io.tio.dubbo.demo001.service.HiService;
import nexus.io.tio.dubbo.demo001.service.impl.HelloServiceImpl;
import nexus.io.tio.dubbo.demo001.service.impl.HiServiceImpl;
import nexus.io.tio.utils.environment.EnvUtils;
import nexus.io.tio.utils.thread.TioThreadUtils;
@AConfiguration
public class DubboProviderConfig {
@Initialization
public void config() {
// 使用 slf4j 输出日志
System.setProperty("dubbo.application.logger", "slf4j");
// 创建应用配置
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName(EnvUtils.get(ServerConfigKeys.APP_NAME));
applicationConfig.setQosEnable(false);
// 创建注册中心配置,使用nacos作为注册中心
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("nacos://" + EnvUtils.get("NACOS_ADDR"));
// 创建服务提供者配置
ProviderConfig providerConfig = new ProviderConfig();
// dubbo端口设置为随机可用端口
providerConfig.setPort(-1);
// init
DubboProvider.init(applicationConfig, registryConfig, providerConfig);
// add
DubboProvider.add(HelloService.class, Aop.get(HelloServiceImpl.class));
DubboProvider.add(HiService.class, Aop.get(HiServiceImpl.class));
// export
TioThreadUtils.submit(() -> {
DubboProvider.export();
});
// unexport
HookCan.me().addDestroyMethod(() -> {
DubboProvider.unexport();
});
}
}相比 ZooKeeper 版本,这里只有两处变化:地址改成 nacos:// 前缀,以及删掉了 registryConfig.setClient("zkclient")——客户端选择由协议头决定,不需要再指定。
启动类
package nexus.io.tio.dubbo.demo001;
import nexus.io.annotation.AComponentScan;
import nexus.io.tio.boot.TioApplication;
@AComponentScan
public class ProviderApp {
public static void main(String[] args) {
long start = System.currentTimeMillis();
TioApplication.run(ProviderApp.class, args);
long end = System.currentTimeMillis();
System.out.println((end - start) + "ms");
}
}tio-boot-dubbo-2-6-6-nacos-demo01-consumer
添加依赖
与 provider 相同的依赖集合,外加 api 模块:
<dependency>
<groupId>nexus.io</groupId>
<artifactId>tio-boot-dubbo-2-6-6-nacos-demo01-api</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.42.Final</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>${nacos-client.version}</version>
</dependency>消费者同样需要 netty-all(RPC 客户端走 NettyClient),以及上面那个 StringUtils 兼容类——订阅服务时同样会走到 NacosRegistryFactory。
配置文件
src/main/resources/app.properties:
server.port=7000
app.name=service-consumer
NACOS_ADDR=127.0.0.1:8848Dubbo 配置
package nexus.io.tio.web.hello.config;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import nexus.io.annotation.AConfiguration;
import nexus.io.annotation.Initialization;
import nexus.io.constants.ServerConfigKeys;
import nexus.io.hook.HookCan;
import nexus.io.jfinal.dubbo.Dubbo;
import nexus.io.tio.dubbo.demo001.service.HelloService;
import nexus.io.tio.dubbo.demo001.service.HiService;
import nexus.io.tio.utils.environment.EnvUtils;
@AConfiguration
public class DubboConsumerConfig {
@Initialization
public void config() {
int dubboTimeout = EnvUtils.getInt("dubbo.timeout", 3000); // 设置默认超时时间为3000毫秒
// 配置日志系统
System.setProperty("dubbo.application.logger", "slf4j");
// 创建应用配置
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName(EnvUtils.get(ServerConfigKeys.APP_NAME));
applicationConfig.setQosEnable(false);
// 创建注册中心配置,使用nacos作为注册中心
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("nacos://" + EnvUtils.get("NACOS_ADDR"));
// 设置dubbo
Dubbo.initialize(applicationConfig, registryConfig, dubboTimeout);
// 获取服务器类
Dubbo.get(HelloService.class);
Dubbo.get(HiService.class);
HookCan.me().addDestroyMethod(() -> {
Dubbo.clear();
});
}
}控制器类
package nexus.io.tio.web.hello.controller;
import nexus.io.annotation.RequestPath;
import nexus.io.jfinal.aop.Aop;
import nexus.io.tio.dubbo.demo001.service.HelloService;
import nexus.io.tio.dubbo.demo001.service.HiService;
@RequestPath("/")
public class IndexController {
@RequestPath()
public String index() {
HelloService helloService = Aop.get(HelloService.class);
HiService hiService = Aop.get(HiService.class);
return helloService.sayHello("Tong Li") + "_" + hiService.sayHi("Tong Li");
}
}启动类
package nexus.io.tio.web.hello;
import nexus.io.annotation.AComponentScan;
import nexus.io.tio.boot.TioApplication;
@AComponentScan
public class ConsumerApp {
public static void main(String[] args) {
long start = System.currentTimeMillis();
TioApplication.run(ConsumerApp.class, args);
long end = System.currentTimeMillis();
System.out.println((end - start) + "ms");
}
}启动与验证
启动 provider
bashmvn spring-boot:run日志中能看到两个服务都被导出并注册:
[DUBBO] Export dubbo service nexus.io.tio.dubbo.demo001.service.HelloService to url dubbo://192.168.31.151:20880/... [DUBBO] Register dubbo service nexus.io.tio.dubbo.demo001.service.HelloService url dubbo://... to registry registry://127.0.0.1:8848/...®istry=nacos [DUBBO] Start NettyServer bind /0.0.0.0:20880在 Nacos 中确认服务
bashcurl "http://127.0.0.1:8848/nacos/v1/ns/service/list?pageNo=1&pageSize=20"返回:
json{"count":2,"doms":["providers:nexus.io.tio.dubbo.demo001.service.HelloService", "providers:nexus.io.tio.dubbo.demo001.service.HiService"]}Dubbo 在 Nacos 中注册的服务名格式是
providers:<接口全限定名>,消费者则注册为consumers:<接口全限定名>。查实例:bashcurl --get --data-urlencode "serviceName=providers:nexus.io.tio.dubbo.demo001.service.HelloService" \ "http://127.0.0.1:8848/nacos/v1/ns/instance/list"能看到
ip、port、healthy: true,以及metadata里的接口名、方法和版本。启动 consumer 并调用
bashcurl http://127.0.0.1:7000/返回:
Hello Tong Li_Hi Tong Li验证临时实例自动摘除
停掉 provider 后再次查询实例列表,
hosts变为空数组,服务节点本身保留。此时再访问 consumer 的接口会返回 500,因为已经没有可用的 provider。
与 ZooKeeper 版本的差异
换成 Nacos 只涉及三处:
- 依赖:移除 ZooKeeper 客户端
zkclient,改为dubbo-registry-nacos,并显式引入新版本nacos-client。zkclient只是 ZooKeeper 注册中心的客户端实现,换成 Nacos 后不再需要,也不需要curator - 注册地址:
zookeeper://host:2181换成nacos://host:8848 - 配置类:删掉
registryConfig.setClient("zkclient"),客户端由协议头决定
业务接口、实现类、注入写法、控制器、启动类全部不变。
支持的连接参数
dubbo-registry-nacos:2.6.6 从注册地址的查询串里读取以下参数:
namespace:命名空间,不填则使用publicclusterName:集群名accessKey、secretKey:阿里云 ACM 场景的凭据backup、endpoint:备用地址与接入点
例如使用命名空间 dev:
registryConfig.setAddress("nacos://127.0.0.1:8848?namespace=dev");需要注意:这个版本的实现不支持 username / password,因此只适用于未开启鉴权的 Nacos。如果 Nacos 开了鉴权,需要升级 Dubbo 或自行替换注册中心实现。
排查顺序
出现「进程正常但调用失败」时,按下面顺序检查:
- provider 日志里两个服务是否都有
Register dubbo service,只有一条通常说明注册时抛了异常 - 是否补上了
StringUtils兼容类,缺失时异常会被异步线程吞掉 - classpath 上是否存在两份
nacos-client,存在时优先用mvn dependency:tree确认并排除旧版本 - Nacos 的 8848 与 9848 是否都可达,新客户端依赖 9848 的 gRPC 通道
- Nacos 中的实例是否
healthy: true,服务名是否为providers:<接口全限定名>
