Skip to content

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 启动,未开启鉴权:

bash
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。

bash
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 侧代码不需要修改:

java
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

根工程统一声明版本:

xml
<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 共用:

java
package nexus.io.tio.dubbo.demo001.service;

public interface HelloService {
  String sayHello(String name);
}
java
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:

xml
<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:

properties
server.listening.enable=false
app.name=service-provider
NACOS_ADDR=127.0.0.1:8848

server.listening.enable=false 关闭 tio-boot 的 HTTP 监听,provider 只提供 RPC 服务。

服务实现类 ​

java
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;
  }
}
java
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 配置 ​

java
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")——客户端选择由协议头决定,不需要再指定。

启动类 ​

java
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 模块:

xml
<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:

properties
server.port=7000
app.name=service-consumer
NACOS_ADDR=127.0.0.1:8848

Dubbo 配置 ​

java
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();
    });
  }
}

控制器类 ​

java
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");
  }
}

启动类 ​

java
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");
  }
}

启动与验证 ​

  1. 启动 provider

    bash
    mvn 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/...&registry=nacos
    [DUBBO] Start NettyServer bind /0.0.0.0:20880
  2. 在 Nacos 中确认服务

    bash
    curl "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:<接口全限定名>。查实例:

    bash
    curl --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 里的接口名、方法和版本。

  3. 启动 consumer 并调用

    bash
    curl http://127.0.0.1:7000/

    返回:

    Hello Tong Li_Hi Tong Li
  4. 验证临时实例自动摘除

    停掉 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:命名空间,不填则使用 public
  • clusterName:集群名
  • accessKey、secretKey:阿里云 ACM 场景的凭据
  • backup、endpoint:备用地址与接入点

例如使用命名空间 dev:

java
registryConfig.setAddress("nacos://127.0.0.1:8848?namespace=dev");

需要注意:这个版本的实现不支持 username / password,因此只适用于未开启鉴权的 Nacos。如果 Nacos 开了鉴权,需要升级 Dubbo 或自行替换注册中心实现。

排查顺序 ​

出现「进程正常但调用失败」时,按下面顺序检查:

  1. provider 日志里两个服务是否都有 Register dubbo service,只有一条通常说明注册时抛了异常
  2. 是否补上了 StringUtils 兼容类,缺失时异常会被异步线程吞掉
  3. classpath 上是否存在两份 nacos-client,存在时优先用 mvn dependency:tree 确认并排除旧版本
  4. Nacos 的 8848 与 9848 是否都可达,新客户端依赖 9848 的 gRPC 通道
  5. Nacos 中的实例是否 healthy: true,服务名是否为 providers:<接口全限定名>

MIT Licensed