Ohhnews

分类导航

$ cd ..
Baeldung原文

阿里巴巴Nacos简介

#nacos#服务发现#配置管理#分布式锁#微服务

1. 引言

在大数据和众多潜在参与方并存的场景下,分布式至关重要。为此,既有简单的解决方案,也有复杂的解决方案。

在本教程中,我们将了解 Alibaba Nacos。首先,我们看看它是什么。接着,我们介绍配置和使用 Nacos 的不同方式。最后,我们演示该平台能实现哪些功能。

2. 什么是 Nacos?

Nacos 是一个提供多种工具的平台:

  • 动态服务发现
  • 配置管理
  • 分布式锁

它主要用于构建高度分布式的系统

Nacos 的核心是提供服务发现系统,使我们能够动态启停不同服务的实例,同时让其他服务仍然可以访问到它们。这在微服务应用中尤其有价值,因为微服务应用中可能包含大量应用,而每个应用又可能有数量不等的运行实例。

Nacos 还提供管理应用配置的工具,允许在单一位置配置所有内容,并让运行中的实例集群自动反映这些配置。此外,它还提供分布式锁工具,使分布式应用能够无冲突地共享有限资源。

当然,Nacos 还引入了一些用于 AI Agent 的新特性——包括 MCP 服务器、技能、提示词和 AgentCard 发现。但这些不在本文讨论范围之内。

3. 运行 Nacos

快速上手 Nacos 最简单的方式是使用 Docker

具体来说,我们可以通过一个相当简单的 docker-compose.yml 文件来启动一个单机 Nacos 实例

$ config
services:
  nacos:
    image: nacos/nacos-server:latest
    environment:
      - MODE=standalone
      - NACOS_AUTH_TOKEN=U2VjdXJlTmFjb3NBdXRoVG9rZW5Gb3JEZW1vUHVycG9zZXMxMjM=
      - NACOS_AUTH_IDENTITY_KEY=serverIdentity
      - NACOS_AUTH_IDENTITY_VALUE=nacos-demo-node
    ports:
      - "8080:8080"
      - "8848:8848"
      - "9848:9848"

NACOS_AUTH_TOKEN 变量必须包含一个至少 32 个字符的字符串,然后进行 base64 编码。而 NACOS_AUTH_IDENTITY_KEYNACOS_AUTH_IDENTITY_VALUE 可以包含任何标识当前 Nacos 实例的值。

现在,我们可以运行该配置:

$ shell
$ docker compose up
.....
nacos-1  | 2026-04-27 14:20:53,304 INFO Nacos Console started successfully in 311 ms

启动后,我们可以通过 http://localhost:8080 访问控制台。

首次访问时,我们需要为 nacos 用户配置密码[LOADING...]

现在,让我们登录管理控制台: [LOADING...]

以这种方式运行 Nacos,我们会得到一组默认数据供使用,但我们也可以使用此 UI 来配置自定义环境。

4. 使用 Java SDK 操作 Nacos

现在我们已经启动了 Nacos,便可以在应用中使用它。

为此,我们首先需要将 客户端依赖 添加到项目中。撰写本文时,最新版本是 3.2.1

如果使用 Maven,可以在 pom.xml 文件中包含此依赖:

$ xml
<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-client</artifactId>
    <version>3.2.1</version>
</dependency>

为了在应用中使用 Nacos,我们需要知道所操作的 Nacos 服务器的地址、要使用的命名空间(如果不是默认命名空间),以及该服务的其他配置属性:

$ java
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.SERVER_ADDR, "localhost:8848");
properties.setProperty(PropertyKeyConst.NAMESPACE, "public");

然后,我们可以为所需的每个 Nacos 服务创建一个客户端:

$ java
ConfigService configService = NacosFactory.createConfigService(properties);
NamingService namingService = NacosFactory.createNamingService(properties);
LockService lockService = NacosFactory.createLockService(properties);

这些服务客户端即可供我们使用。

5. 配置管理

Nacos 可以在一个中心位置管理配置设置,使服务能够自动使用它们。

5.1. 管理配置

具体来说,我们可以通过管理控制台来管理配置设置:选择侧边栏中的“配置管理”条目即可: [LOADING...]

该部分显示了系统中当前的所有配置条目,并允许我们添加新的条目。

使用顶部的“新建配置”按钮可以添加新条目: [LOADING...]

在此界面中,我们为每个配置元素输入值:

  • 数据 ID:配置条目的 ID,通常采用与完全限定类名相同的格式
  • 分组:配置条目所属的分组,通常代表该配置对应的产品
  • 格式:配置条目的格式,通常是 TextJSONXMLYAMLHTMLPropertiesTOML 之一
  • 内容:实际的配置条目,以指定格式书写

此外,我们还可以指定描述、应用和标签。但这些只是条目的元数据,不会影响其使用。

方便的是,我们可以查看配置的历史版本: [LOADING...]

因此,我们可以编辑或删除配置设置,查看条目的详细信息以及所有更改的历史记录。

5.2. 获取配置

配置设置好后,我们需要能够使用它。为此,我们通过之前创建的 ConfigService 来设置配置

具体来说,最简单的方式可能是直接调用 getConfig() 方法,传入数据 ID、分组以及超时时间(毫秒):

$ java
String config = configService.getConfig("com.baeldung.nacos.Example", "DEFAULT_GROUP", 1000);

这样,我们就能立即获得该配置设置的值。如果配置不存在或无法连接到 Nacos,则返回 null

但是,这只能得到原始值。通常我们需要预先知道它的类型,才能知道如何处理。为避免这种情况,我们可以改用 getConfigWithResult() 方法,返回一个更丰富的对象,其中包含配置设置的类型(text、JSON 等),从而知道如何处理它

$ java
ConfigQueryResult config = configService.getConfigWithResult("com.baeldung.nacos.Example", "DEFAULT_GROUP", 1000);
String configType = config.getConfigType();
String configValue = config.getContent();

这样,即使配置设置不存在或 Nacos 服务器不可达,我们也总能获得结果。在这些情况下,我们会得到一个有效的 ConfigQueryResult 对象,其内容和配置类型均为 null

5.3. 监听配置变更

除了在某一时刻获取配置值之外,我们还可以注册一个回调,用于在值发生变化时触发。这样,我们就能对配置变更做出响应,而无需手动轮询值。

具体来说,我们可以使用 addListener() 方法为特定的配置设置注册一个监听器

$ java
configService.addListener("com.baeldung.nacos.Example", "DEFAULT_GROUP", new AbstractListener() {
    @Override
    public void receiveConfigInfo(String configInfo) {
        LOG.info("Received config info: {}", configInfo);
    }
});

每当该配置设置发生变化时,此代码块就会运行,并传入新值。但遗憾的是,只有在值发生变化时才会触发,而不会立即执行。

另一种方式是使用 getConfigAndSignListener() 方法,它既能获取当前配置,又能立即注册一个监听器

$ java
String config = configService.getConfigAndSignListener("com.baeldung.nacos.Example", "DEFAULT_GROUP", 1000, new AbstractListener() {
    @Override
    public void receiveConfigInfo(String configInfo) {
        LOG.info("Received config info: {}", configInfo);
    }
});

这样,我们既能立即获得当前配置使用,又能在配置发生变化时做出响应。从某种意义上说,这相当于将 getConfig()addListener() 合并为一次调用。

6. 服务发现

除配置外,Nacos 还提供分布式服务的服务发现功能。其工作原理是:服务将其地址和端口注册到一个众所周知的名称下,随后客户端可以通过该名称查找这些服务。

6.1. 注册服务

首先,我们使用之前构建的 NamingService 来注册服务。我们使用 registerInstance() 方法,传入服务名称和地址

$ java
namingService.registerInstance("BaeldungTest", "localhost", 8848);

或者,我们可以传入一个包含更多服务详细信息的 Instance 类:

$ java
Instance instance = new Instance();
instance.setIp("localhost");
instance.setPort(8848);
instance.setEnabled(true);
instance.setHealthy(true);
Map<String, String> metadata = new HashMap<>();
metadata.put("Example", "value");
instance.setMetadata(metadata);
namingService.registerInstance("BaeldungTest", instance);

需要注意的是,如果这样做,我们必须确保 enabledhealthy 标志已设置,因为它们默认为 false,因此服务将无法用于发现。

如果需要更新服务注册信息——例如,更改服务是否健康——我们可以简单地使用新的详细信息重新注册相同的服务名称和地址。Nacos 会检测到这些是相同的详细信息,从而进行更新而不是创建新的注册。

6.2. 注销服务

重要的是,NamingService 客户端在关闭时会自动注销它所注册的所有服务。这意味着我们不需要做任何特殊处理,一旦服务关闭,Nacos 应该会自动移除它。但这还取决于心跳和临时实例等其他因素。

然而,有时我们需要在不关闭整个服务的情况下自行注销。为此,我们可以使用 deregisterInstance() 方法:

$ java
namingService.deregisterInstance("BaeldungTest", "localhost", 8848);

该方法接受服务名称以及地址和端口,或者 Instance 类(与 registerInstance() 类似)。有了这些信息,deregisterInstance() 会立即从 Nacos 服务器中移除该地址,从而阻止发现。

6.3. 发现服务实例

服务注册后,我们需要能够发现它们,以便实际利用它们提供的功能。Nacos 提供了多种实现方式。

服务发现中最有用的方法可能是 selectOneHealthyInstance()。该方法接受服务名称,并随机返回一个健康的已注册实例:

$ java
Instance baeldungTest = namingService.selectOneHealthyInstance("BaeldungTest");
String address = baeldungTest.getIp();
int port = baeldungTest.getPort();

如果给定的服务名称没有注册任何实例,代码会抛出 IllegalStateException 以指示该情况,因为这意味着当前没有可用的服务。

或者,selectInstances()getAllInstances() 可以返回所有已注册的实例,而不仅仅是一个

$ java
List<Instance> allInstances = namingService.getAllInstances("BaeldungTest");
List<Instance> allHealthyInstances = namingService.selectInstances("BaeldungTest", true);

需要注意的是,selectInstances()selectOneHealthyInstance() 只返回标记为 enabledhealthy 标志符合预期的服务getAllInstances() 则返回所有实例,不论这些标志如何。

6.4. 监听服务发现变更

除了在需要时选择一个实例外,我们还可以注册一个回调,当某个服务名称的实例发生变更时触发:

$ java
namingService.subscribe("BaeldungTest", (event) -> {
    NamingChangeEvent namingChangeEvent = (NamingChangeEvent) event;
    LOG.info("Added Instances: {}", namingChangeEvent.getAddedInstances());
    LOG.info("Removed Instances: {}", namingChangeEvent.getRemovedInstances());
    LOG.info("Modified Instances: {}", namingChangeEvent.getModifiedInstances());
});

我们可以利用这些信息做任何想做的事情——例如,维护一个实例的本地缓存,这样就不需要每次查找实例时都调用 Nacos。

7. 分布式锁

Nacos 的另一个重要功能是支持资源的分布式锁。具体来说,Nacos 支持对资源的访问控制,使得不同服务或线程无法同时访问同一资源

为了获取锁,我们使用之前创建的 LockService,并调用 lock() 方法,提供我们想要获取的锁的定义

$ java
Boolean result = lockService.lock(new NLock("Baeldung", 5000L));

在获取锁时,我们需要提供资源的标识符和锁的最大持续时间(以毫秒为单位)。关键的是,我们会收到一个结果,表明是否成功获取了锁。

锁可以在指定持续时间后自动释放,也可以通过显式调用 unlock() 方法来释放:

$ java
lockService.unlock(new NLock("Baeldung", 5000L));

与服务注册不同,当客户端断开连接时,锁不会自动移除,因此我们需要确保合理地管理锁。## 8. 总结

在本文中,我们快速了解了阿里巴巴 Nacos,看到了它是什么以及如何在应用程序中使用它。

总而言之,Nacos 是一个通过提供服务发现、集中配置和协调能力来管理分布式系统的平台。

像往常一样,本文中的所有示例都可以在 GitHub 上找到。