一、概述

Nacos 官网地址:https://nacos.io

nacos-1

我们先进入它的官网看看,官网说的很清楚了Nacos的2个核心能力

  • 服务发现
  • 配置管理

所以Nacos可以作为:

  • 服务注册中心
    • Nacos 支持基于 DNS 和基于 RPC 的服务发现
    • 服务提供者使用原生SDK、OpenApi注册 Service 后,服务消费者可以使用DNS TODO或HTTP&API查找和发现服务
    • Nacos提供对服务的健康监测,阻止向不健康的服务或者实例发送请求
  • 配置中心
    • 动态配置服务可以让使用者以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置
    • 动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷
    • 配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易

其他:

Nacos支持AP和CP模式的切换

C是所有节点在同一时间看到的数据是一致的;而A的定义是所有的请求都会收到响应。

何时选择使用何种模式?

—般来说,如果不需要存储服务级别的信息且服务实例是通过nacos-client注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如Spring cloud和Dubbo服务,都适用于AP模式,AP模式为了服务的可能性而减弱了一致性,因此AP模式下只支持注册临时实例。

如果需要在服务级别编辑或者存储配置信息,那么CP是必须,K8S服务和DNS服务则适用于CP模式。CP模式下则支持注册持久化实例,此时则是以Raft协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误。

切换命令:

1
curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP

二、安装Nacos-server

2.1 下载编译后压缩包

下载地址: 最新稳定版本 下载nacos-server-$version.zip

2.2 启动服务

1
2
# 进入nacos下的bin目录
cd nacos/bin

Nacos支持三种服务部署方式

  1. 单机模式:用户测试和单机使用
  2. 集群模式:生产环境使用,保证服务的高可用
  3. 多集群模式: 用于多数据中心场景

这里就使用单机模式了

Linux/Unix/Mac

启动命令(standalone代表着单机模式运行,非集群模式):

1
sh startup.sh -m standalone

如果您使用的是ubuntu系统,或者运行脚本报错提示[[符号找不到,可尝试如下运行:

1
bash startup.sh -m standalone

Windows

启动命令(standalone代表着单机模式运行,非集群模式):

1
startup.cmd -m standalone

双击 startup.cmd 文件也行

2.3 访问Nacos-server

浏览器访问 127.0.0.1:8848 就能看到Nacos的Web页面了,默认的账号密码都是 nacos

三、nacos的注册中心

3.1 创建服务提供者模块

新建module:nacos-provider

Nacos-1

pom.xml文件如下(注意spring-boot-starter-web也一定要引入,因为有被使用):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<groupId>com.apps</groupId>
<artifactId>nacos-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>nacos-provider</name>

<properties>
<java.version>1.8</java.version>
<nacos.version>2.2.6.RELEASE</nacos.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Web启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Nacos依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>${nacos.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

application.yml文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 7001

spring:
application:
# 微服务名称
name: service-provider
cloud:
nacos:
discovery:
# nacos服务发现地址, 多个地址使用逗号隔开, 注意: 不用添加http(s)://
server-addr: 192.168.6.206:8848

主启动类:

(@EnableDiscoveryClient) SpringCloud的服务注册发现标签,让注册中心能够发现,扫描到该服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.apps;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class NacosDemoApplication {

public static void main(String[] args) {
SpringApplication.run(NacosDemoApplication.class, args);
}

}

编写控制器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.apps.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @author Blue 2113438464@qq.com
* @ClassName ProviderController
* @Description 作用
* @date 2022/6/13 9:49
* @Version 1.0
*/
@RestController
@RequestMapping("/provider")
public class ProviderController {

@Value("${server.port}")
private String port;

@GetMapping("/info")
public String getPayment(){
return "nacos注册表,服务器端口:" + port ;
}

}

然后启动该模块,在nacos控制台就能看到该实例已经注册到nacos:

img

3.2 创建服务消费者模块

新建module:nacos-consumer

Nacos-3

pom.xml文件如下(注意spring-boot-starter-web也一定要引入,因为有被使用):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<groupId>com.apps</groupId>
<artifactId>nacos-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>nacos-consumer</name>

<properties>
<java.version>1.8</java.version>
<nacos.version>2.2.6.RELEASE</nacos.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Web启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Nacos依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>${nacos.version}</version>
</dependency>
</dependencies>

<build>
<plugins>
</plugins>
</build>

</project>

application.yml文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 10083

spring:
application:
name: service-consumer
cloud:
nacos:
discovery:
server-addr: 192.168.6.206:8848

# 消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
nacos-user-service: http://service-provider

主启动类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.apps;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class NacosConsumerApplication {

public static void main(String[] args) {
SpringApplication.run(NacosConsumerApplication.class, args);
}

}

配置类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.apps.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;


/**
* @author Blue 2113438464@qq.com
* @ClassName JavaConfig
* @Description 作用
* @date 2022/6/13 10:50
* @Version 1.0
*/
@Component
public class JavaConfig {

// @LoadBalanced这个注解是让 RestTemplate 开启负载均衡的能力
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}

}

控制器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.apps.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
* @author Blue 2113438464@qq.com
* @ClassName controller
* @Description 作用
* @date 2022/6/13 10:56
* @Version 1.0
*/
@RestController
@RequestMapping("consumer")
public class ConsumerController {

@Value("${service-url.nacos-user-service}")
private String serviceUrl;

@Autowired
private RestTemplate restTemplate;

@GetMapping(value = "/info")
public String getClientServerResult(){
return restTemplate.getForObject(serviceUrl + "/provider/info", String.class);
}

}

然后启动消费者模块,在nacos控制台可以看到服务列表

img

然后启动消费者模块,在nacos控制台可以看到服务列表,便可以使用服务了,当前我们可以启动多个服务名相同服务实例来使用Nacos的负载均衡。

3.3 Nacos的负载均衡

nacos支持负载均衡是因为spring-cloud-starter-alibaba-nacos-discovery内含netflix-ribbon包(这里要感谢技术大牛浩哥的指导)。

为了省事,我们可以直接copy一个provider进行启动

img

修改名字,好区别。并且指定服务端口,这里指定权重更高,所以优先VM的参数

img

copy完成后,启动。就有两个相同服务名的实例了

img

然后通过consumer的AIP访问provider的接口,通过打印可以知道是轮询访问10001/10002

四、nacos作为配置中心

4.1 创建配置模块

新建module:nacos-config

Nacos-4

pom.xml文件如下(注意这里引入的是spring-cloud-starter-alibaba-nacos-config):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<groupId>com.apps</groupId>
<artifactId>nacos-config</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>nacos-config</name>
<description>nacos-config</description>

<properties>
<java.version>1.8</java.version>
<nacos.version>2.2.6.RELEASE</nacos.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!--nacos-config-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>${nacos.version}</version>
</dependency>
<!-- Web启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

</project>

nacos和springcloud-config一样,在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置之后,才能保证项目的正常启动

springboot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application。所以我们拉取配置需要写在bootstrap.yml文件中,启动完bootstrap.yml把相关配置拉取下来后才能正常启动项目。

配置bootstrap.yml文件

1
2
3
4
5
6
7
8
9
10
11
server:
port: 3377
spring:
application:
name: nacos-config
cloud:
nacos:
config:
server-addr: 192.168.6.206:8848 # nacos本地服务器地址
file-extension: yaml # 配置文件的格式,默认properties
group: DEFAULT_GROUP # 分组,默认DEFAULT_GROUP

配置application.yml文件

1
2
3
spring:
profiles:
active: dev

4.2 在nacos控制台增加配置信息

前面我们在bootstrap.yml文件中配置了spring.application.name,是因为它是构成 Nacos 配置管理 dataId字段的一部分(是什么dataId等一些技术名词我会在文章后面讲解)。

在Nacos Spring Cloud 中,dataId 的完整格式如下:

1
${prefix}-${spring.profiles.active}.${file-extension}
  • prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix来配置。
  • spring.profiles.active 即为当前环境对应的 profile,详情可以参考 Spring Boot文档注意:当 spring.profiles.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension}
  • file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。目前只支持 propertiesyaml 类型。

最后公式:

1
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

img

接下来在nacos控制台新增配置

img

img

保存后,创建控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.apps.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @author Blue 2113438464@qq.com
* @ClassName NacosConfigController
* @Description 作用
* @date 2022/6/13 16:58
* @Version 1.0
*/
@RefreshScope
@RestController
public class NacosConfigController {

@Value("${user.name}")
private String name;
@Value("${user.age}")
private String name;

@RequestMapping(value = "config/info")
public String getConfigValue() {
System.err.println("user name :" +name+"; age: "+age);
return this.name + this.age;
}

}

启动项目,访问请求,控制台打印

img

nacos控制台修改yml参数后(也可以发送Open API修改),又访问请求,配置信息就修改了。

五、名词理念

5.1 namespace和group和dataId三者的关系

三个东西是为了解多项目多环境的问题。

实际开发中,通常一个系统会准备

  • dev开发环境
  • test测试环境
  • prod生产环境

如何保证指定环境启动时服务能正确读取到Nacos上相应环境的配置文件呢?

一个大型分布式微服务系统会有很多微服务子项目,每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境…那怎么对这些微服务配置进行管理呢?


nacos默认的namespace是public,默认的group是DEFAULT_GROUP

img

img


类似Java里面的package名和类名,最外层的namespace是可以用于区分部署环境的,Group和DatalD逻辑上区分两个目标对象。

img

  • Nacos默认的Namespace是public,Namespace主要用来实现隔离。

    比方说我们现在有三个环境:开发、测试、生产环境,我们就可以创建三个Namespace,不同的Namespace之间是隔离的。

  • Group默认是DEFAULT_GROUP,Group可以把不同的微服务划分到同一个分组里面去

  • Service就是微服务:一个Service可以包含多个Cluster (集群),Cluster是对指定微服务的一个虚拟划分。
    比方说为了容灾,将Service微服务分别部署在了杭州机房和广州机房,这时就可以给杭州机房的Service微服务起一个集群名称(HZ) ,给广州机房的Service微服务起一个集群名称(GZ),还可以尽量让同一个机房的微服务互相调用,以提升性能。

  • 最后是Instance,就是微服务的实例


我们可以在nacos控制台创建namespace和group。

创建dev和test的namespace

img


回到配置列表,就能看到我们创建的namespace

img


分别在test和dev的namespace里新建配置文件

img

img

img


在bootstrap.yml中指定group和namespace就可以获取不同的配置文件了。(记住dataId的生成规则)

1
2
3
4
5
6
7
8
9
10
11
12
spring:
application:
name: cloud-nacos-config
cloud:
nacos:
config:
server-addr: localhost:8848 # nacos本地服务器地址
file-extension: yaml # 配置文件的格式,默认properties
group: TEST_GROUP # 分组,默认DEFAULT_GROUP
# group: DEV_GROUP
namespace: 155a7fd7-6834-4787-80f7-35f56dd9f8fb
# namespace: 2f42a525-6d50-45dc-8ed1-d474fff7ce42

5.2 Nacos配置管理模型

对于Nacos配置管理,通过Namespace、group、Data ID能够定位到一个配置集

配置集(Data ID)

在系统中,一个配置文件通常就是一个配置集,一个配置集可以包含了系统的各种配置信息,例如一个配置集可能包含了数据源、线程池、日志级别等配置项。每个配置集都可以定义一个有意义的名称,就是配置集的ID即Data ID


配置项

就是我们的配置内容

配置集中包含的一个个配置内容就是配置项。它代表一个具体的可配置的参数与其值域,通常以key=value的形式存在。例如我们常配置系统的日志输出级别( logLevel=INFO |WARN|ERROR)就是一个配置项


配置分组(Group)

配置分组是对配置集进行分组,通过一个有意义的字符串(如Buy或Trade ) 来表示

**不同的配置分组下可以有相同的配置集( DataID)**。当您在Nacos上创建一个配置时,如果未填写配置分组的名称,则配置分组的名称默认采用DEFAULT_GROUP。配置分组的常见场景:可用于区分不同的项目或应用,例如学生管理系统的配置集可以定义一个group为:STUDENT_GROUP


命名空间(Namespace)

命名空间( namespace )可用于进行不同环境的配置隔离

例如可以隔离开发环境、测试环境和生产环境,因为它们的配置可能各不相同,或者是隔离不同的用户,不同的开发人员使用同一个nacos管理各自的配置,可通过namespace隔离。

不同的命名空间下,可以存在相同名称的配置分组(Group)或配置集。


获取某配置集的代码:需要指定

  1. nacos服务地址,必须指定

  2. namespace,不指定默认为public

  3. group,不指定默认为DEFAULT_GROUP

  4. dateId,必须指定

5.3 服务发现概念简介

分布式系统中,包含多个独立的服务,服务之间存在需要调用的需求,应该如何调用?

服务调用时必须知道目标服务实例的 IP、端口、API 接口。

其中 API 信息可以通过服务接口文档中获知,但 IP、端口 都是动态的,每次部署服务实例的时候可能都会变。

知道了目标服务实例的地址,就相当于发现了对方。具体怎么发现目标服务?这就是服务发现机制。

在这里插入图片描述

如上图,在服务实例数量少的时候,每个服务实例可以自己维护相关服务实例的地址信息,也就是自己维护一个通讯录。

例如通过配置文件来记录,相关服务实例地址发生变更的时候就修改一下,有点麻烦,但还可以忍受。

但是,当服务实例数量变大之后,就无法自己维护了,相关服务数量多、地址变化频繁。

此时必须有一种更优的发现机制,就出现了服务注册中心。

在这里插入图片描述

如上图,每个服务实例启动之后,都主动向注册中心登记自己的地址信息,这样注册中心便拥有了所有服务实例的记录,类似于查号台。

当某个服务实例停止运行的时候,主动让注册中心销毁自己的信息。

如果服务实例不是主动停止,而是因为故障等原因死掉的,如何处理?需要注册中心能够主动清理。

注册中心要能够实时知道各个服务实例的状态,通过心跳机制来实现,实例定时向注册中心发送请求,表明自己还活着,如果心跳没了,注册中心就可以对其清理。

在这里插入图片描述

一个服务实例在调用另一个服务时,可以根据服务名称从注册中心获取此服务的实例信息列表,从中选取一个实例进行调用。

以上就是注册中心这种服务发现机制的工作方式。

5.4 分布式配置概念简介

在这里插入图片描述

每个服务都会有自己的配置信息,例如 SpringBoot 项目中的配置文件,服务运行时会读取配置文件中的配置项。

一个服务通常会启动多个实例,来提供其可靠性,那么每个实例中就都会包含这个配置文件,一个服务的各个实例中配置都是相同的。

如果要改某个配置项的值,怎么办?

修改配置文件,然后重新部署此服务的所有实例。麻烦低效。

在这里插入图片描述

如果把服务的配置信息提出来,不放在自己的配置文件中,而是放到一个第三方的配置中心,服务实例从配置中心读取属于自己的配置,而且,在配置中心里面修改配置项之后,所有相关实例都可以立即拿到最新值,不用重新部署了,这样就方便很多。

所以分布式配置有2大好处:

  • 方便维护,集中维护优于分散式维护每个服务的配置文件。
  • 动态更新,配置修改后直接生效,不用重新部署。

此外,Nacos 还可以做配置的版本管理,轻松实现历史版本的回滚。