一文读懂新版Nacos的使用方式

  • 所有提供者将自己提供服务的名称以及自己的主机详情(IP,端口,版本等)写入到作为服务注册中心的主机中的一个列表中,而该表称为服务注册表
  • 所有消费者需要调用微服务时,会从注册中心首先将服务注册表下载到本地,然后根据消费者本地设置好的负载均衡策略算法选择一个服务提供者进行调用,这个过程称为服务发现
  • 可以作为 SpringCloud 服务注册中心的服务器有很多,Zookeeper,Eureka,Consul 等,Spring Cloud Alibaba 中使用的注册中心是 Nacos
  • 服务在启动后,当发生调用时会自动从 Nacos 注册中心下载并缓存注册表到本地。所以,即使 Nacos 发生宕机,会发现消费者仍然是可以调用到提供者的。只不过此时已经不能再有服务进行注册了,服务中缓存的注册列表信息无法更新。"

什么是 Nacos

官网

  • 云原生应用简单来说就是 SaaS,跑在 IaaS,PaaS 上的 SaaS
  • 开发应用时只需要考虑如何充分利用云端的弹性,容错能力分布式能力,有异于本地化开发
  • 简而言之云原生=DevOps+CI/CD+容器化

Nacos 架构

  • Java 语言本身集成 Nacos 使用的是 Nacos Client
  • Nacos 为其它语言的集成提供了 Nacos Sidecar(多语言异构模块)
    • 为其它语言使用 SpringCloud Alibaba 提供可能
  • NameService
    • 支持dns,vip(虚拟ip),address-server
  • 通过客户端调用 OpenApi支持,http,dns,udp,tls就能使用 ConfigService 和 NamingService(服务发现) 两大核心功能
  • Nacos 集群遵循数据一致性协议 Consistency Protocol
    • priv-raft,sync renew,rdbms based
    • ConfigService 使用关系型数据库 rdbms based
    • NamingService 使用 priv-raft 算法

Nacos 的本地启动

  • 下载 nacos2.2.1,解压进入 conf 文件夹
  • 进入 application.properties 文件
  • 鉴权配置帮助文档
代码语言:javascript
复制
nacos.core.auth.enabled=true #开启鉴权
nacos.core.auth.server.identity.key=nacosIdKey			#设key值
nacos.core.auth.server.identity.value=nacosIdValue 	#设value值
### The default token (Base64 String):
nacos.core.auth.plugin.nacos.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789
  • 进入 nacos,bin 目录下执行 startup.cmd -m standalone指定单机启动,默认是集群启动
  • 通过启动日志输出的后台地址进入 nacos 管理后台,默认账号密码均为 nacos
  • 进入后台初始页面配置列表默认都是空的此处由于小李在之前进行过配置,作为配置中心进行的配置会持久化存储到 database 中
  • 与配置中心不同,作为注册中心,nacos 中注册的服务默认为临时实例,不会进行持久化存储

构建提供者 provider-nacos-8081

搭建环境

  • 创建一个父工程,利用 maven 依赖的传递性,在父工程的 pom 文件中指定 springboot 父工程版本以及 springcloud,springcloudalibaba 版本
代码语言:javascript
复制
<?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>3.0.7</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.cheese</groupId>
  <artifactId>springcloudalibaba</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>pom</packaging>
  <name>springcloudalibaba</name>
  <description>springcloudalibaba</description>
  <modules>
    <module>01-provider-nacos-8081</module>
    <module>02-consumer-nacos-8080</module>
    <module>03-provider-config-8081</module>
    <module>04-consumer-openfegin-8080</module>
  </modules>

<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.0</spring-cloud.version>
<spring-cloud-alibaba.version>2022.0.0.0-RC2</spring-cloud-alibaba.version>
</properties>
<!--管理子工程的springcloudalibaba版本-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

</dependencies>

</project>

  • 在 provider-nacos-8081 和 consumer-nacos-8080 中只需要指定父工程的 gav 信息即可自动获取父工程的依赖,剩下的只需要引入自身所需要的依赖即可,另外新引入的版本也在父工程的 spring-boot-parent 中进行默认配置,子工程中引入的依赖可默认继承版本配置,也可自定义依赖版本
代码语言:javascript
复制
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--指定父工程gav-->
<parent>
<groupId>com.cheese</groupId>
<artifactId>springcloudalibaba</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>

&lt;artifactId&gt;01-provider-nacos-8081&lt;/artifactId&gt;
&lt;packaging&gt;jar&lt;/packaging&gt;

&lt;name&gt;01-provider-nacos-8081&lt;/name&gt;
&lt;url&gt;http://maven.apache.org&lt;/url&gt;

&lt;properties&gt;
    &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
&lt;/properties&gt;

&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;com.alibaba.cloud&lt;/groupId&gt;
        &lt;artifactId&gt;spring-cloud-starter-alibaba-nacos-discovery&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.data&lt;/groupId&gt;
        &lt;artifactId&gt;spring-data-jpa&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;com.mysql&lt;/groupId&gt;
        &lt;artifactId&gt;mysql-connector-j&lt;/artifactId&gt;
        &lt;scope&gt;runtime&lt;/scope&gt;
    &lt;/dependency&gt;
    &lt;!-- https://mvnrepository.com/artifact/com.alibaba/druid --&gt;
  &lt;!--外部依赖需要指定version--&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;com.alibaba&lt;/groupId&gt;
        &lt;artifactId&gt;druid&lt;/artifactId&gt;
        &lt;version&gt;1.2.23&lt;/version&gt;
    &lt;/dependency&gt;

    &lt;dependency&gt;
        &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
        &lt;artifactId&gt;lombok&lt;/artifactId&gt;
        &lt;optional&gt;true&lt;/optional&gt;
    &lt;/dependency&gt;

    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-data-jpa&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-actuator&lt;/artifactId&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;

</project>

编写配置文件 application.yaml

  • 指定端口号
代码语言:javascript
复制
server:
port: 8081
  • 配置微服务名称
代码语言:javascript
复制
spring:
#微服务名称
application:
name: depart-provider
  • 配置 spring-data-jpa
代码语言:javascript
复制
spring:
jpa:
generate-ddl: true #指定spring容器启动时创建表,默认为false
show-sql: true #sql是否在控制台显示sql语句,默认为fasle
hibernate:
ddl-auto: none #设置应用重启时不更新表

构建数据库

代码语言:javascript
复制
-- auto-generated definition
create schema alibaba2024 collate utf8mb4_0900_ai_ci;
use alibaba2024;
-- auto-generated definition
create table depart_entity
(
id int not null
primary key,
name varchar(255) null
);

编写业务

实体类
代码语言:javascript
复制
@Data
@Entity
@JsonIgnoreProperties({"hibernateLazyInitializer","handler","fieldHandler"})
/**

  • JPA查询对象,默认延迟加载,忽略延迟加载
    /
    public class DepartEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)//自增ID
    public Integer id;
    public String name;
    }

控制器类
代码语言:javascript
复制
@RequestMapping("/provider/depart")
@RestController
public class DepartController {
@Autowired
private DepartService departService;
/
*

  • 一般生产环境中会采用同一个接口名称,CRUD通请求方式进行区分
  • @param depart
  • @return
    */
    //新增
    @PostMapping("/save")
    public Boolean addDepart(@RequestBody DepartEntity depart) {
    return departService.addDepart(depart);
    }
    //删除
    @DeleteMapping("/del/{id}")
    public Boolean deleteDepart(@PathVariable("id") Integer id) {
    return departService.deleteDepartById(id);
    }

//修改
@PutMapping("/update")
public Boolean updateDepart(@RequestBody DepartEntity depart) {
return departService.updateDepart(depart);
}

//获取
@GetMapping("/get/{id}")
public DepartEntity getDepart(@PathVariable("id") Integer id) {
return departService.getDepartById(id);
}

//列表
@GetMapping("/list")
public List<DepartEntity> getDepartList() {
return departService.getAllDepart();
}

}

逻辑层与数据层接口
  • controller 调取 service 接口
代码语言:javascript
复制
public interface DepartService {
//insert
boolean addDepart(DepartEntity depart);
//update
boolean updateDepart(DepartEntity depart);
//delete
boolean deleteDepartById(Integer id);
//get
DepartEntity getDepartById(Integer id);
//Query
List<DepartEntity> getAllDepart();
}
  • service 调取 repostitory
代码语言:javascript
复制
@Service
public class DepartServiceImpl implements DepartService {

@Resource
private DepartRepository departRepository;


@Override
public boolean addDepart(DepartEntity depart) {
    return departRepository.save(depart) != null;
}

@Override
public boolean updateDepart(DepartEntity depart) {
    return addDepart(depart);//JPA底层使用了saveOrUpdate方法区别在于传入的对象ID属性是否null
}

@Override
public boolean deleteDepartById(Integer id) {
    if (departRepository.existsById(id)) {
        departRepository.deleteById(id);
        return true;
    }
    return false;
}

@Override
public DepartEntity getDepartById(Integer id) {
    if (departRepository.existsById(id)) {
        return departRepository.getReferenceById(id);
    }
    DepartEntity depart = new DepartEntity();
    depart.setName(&#34;not exist this depart&#34;);
    return depart;
}

@Override
public List&lt;DepartEntity&gt; getAllDepart() {
    return departRepository.findAll();
}

}

  • repostitory 接口调用了 spring-data-jpa 中的底层方法完成数据的读写操作
代码语言:javascript
复制
public interface DepartRepository extends JpaRepository<DepartEntity, Integer> {
}

模块结构

构建消费者 consumer-nacos-8080

搭建环境

  • 外除去父工程依赖于 provider-nacos-8081一致外 consumer 作为消费者所需要的环境依赖和功能只会更加简单,因此此处不做展开

编写 yaml 文件配置

代码语言:javascript
复制
# 端口号
server:
  port: 8080
spring:
  #微服务名称
  application:
    name: depart-consumer

编写业务

  • 实体类与 provicer-nacos-8081 保持一致即可,消费者不需要 service 和 repostitory
  • 采用 spring 自带的 RestTemplate 进行远程访问
编写配置类
代码语言:javascript
复制
@Configuration
public class DepartConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
编写 Controller
代码语言:javascript
复制
@RestController

@RequestMapping("/consumer/depart")
public class DeportController {
@Resource
private RestTemplate restTemplate;
//直连方式
public static final String SERVICE_PROVIDER = "http://localhost:8081/provider/depart";

//新增
@PostMapping(&#34;/&#34;)
public Boolean addDepart(@RequestBody DepartEntity depart) {
    return restTemplate.postForObject(SERVICE_PROVIDER + &#34;/save&#34;, depart, Boolean.class);
}

//修改
@PutMapping(&#34;/&#34;)
public void modifyDepart(@RequestBody DepartEntity depart) {
    restTemplate.put(SERVICE_PROVIDER + &#34;/update&#34;, depart);
}

//删除
@DeleteMapping(&#34;/{id}&#34;)
public void deleteDepart(@PathVariable Integer id) {
    restTemplate.delete(SERVICE_PROVIDER + &#34;/del/&#34; + id);
}
//查询
@GetMapping(&#34;/{id}&#34;)
public DepartEntity getDepart(@PathVariable Integer id) {
    return restTemplate.getForObject(SERVICE_PROVIDER + &#34;/get/&#34; + id, DepartEntity.class);
}
//列表
@GetMapping(&#34;/list&#34;)
public List&lt;DepartEntity&gt; getDepartList() {
   return restTemplate.getForObject(SERVICE_PROVIDER + &#34;/list&#34;, List.class);
}

}

启动服务进行进行测试

启动 provider 和 consumer 进行测试

  • 测试 provider
    • 新增人资部,财务部
  • 查询列表
  • 查询财务部
  • 修改为研发部
  • 删除研发部
  • 测试 consumer 类似上述方式进行,此处仅测试一次
    • 获取人资部

将 provider 和 consumer 注入 nacos

  • 在 provider-nacos-8081 ,consumer-nacos-8080的 yaml 文件中新增如下配置
代码语言:javascript
复制
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848 #nacos注册中心地址
username: nacos
password: nacos
  • 将 consumer 中的 DepartConterller 就行如下修改
代码语言:javascript
复制
    //直连方式
//public static final String SERVICE_PROVIDER = "http://localhost:8081/provider/depart";
//微服务调用方式
public static final String SERVICE_PROVIDER = "http://depart-provider/provider/depart";
  • 依次启动 nacos,provider,consumer,进入 nacos 管理后台

注意:在 nacos2.2.0.1 版本以后摈弃了 ribbon 客户端负载均衡器,使用了 springcloud 社区的 loadbalancer,consumer 中需要新增如下依赖

代码语言:javascript
复制
        <!--nacos2.2.0.1往后版本的客户端负载均衡依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
  • DepartConfig 新增注解
代码语言:javascript
复制
@Configuration
public class DepartConfig {
    @Bean
    //负载均很方式调用,Netflix组件中的负载均衡在nacos中不再使用,
    //nacos使用新的依赖spring-cloud-starter-loadbalancer实现客户端负载均衡
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
  • 如不进行上述配置 consumer 将找不到名称为depart-provider的微服务

获取服务列表

  • 在 provider-nacos-8081 中的 DepartController 中新增以下接口和配置
代码语言:javascript
复制
@RequestMapping("/provider/depart")
@RestController
public class DepartController {
    .....
    @Autowired
    private DiscoveryClient discoveryClient; //服务发现客户端
@GetMapping(&#34;/discovery&#34;)
public List&lt;String&gt; discoveryHandle() {
    List&lt;String&gt; services = discoveryClient.getServices();
    services.forEach(v1-&gt;{
        //获取微服务名称的所有微服务实例
        discoveryClient.getInstances(v1).forEach(v2-&gt;{
            HashMap&lt;String, Object&gt; map = new HashMap&lt;&gt;();
            map.put(&#34;serviceName&#34;, v1);
            map.put(&#34;serviceId&#34;, v2.getServiceId());
            map.put(&#34;serviceHost&#34;, v2.getHost());
            map.put(&#34;servicePort&#34;, v2.getPort());
            map.put(&#34;uri&#34;, v2.getUri());
            System.out.println(&#34;map = &#34; + map);
        });
    });
    return services; //返回注册中心中所有活跃的微服务名称列表
}

.......

}

  • 获取当前实例
  • 查看控制台输出信息

nacos 注册表缓存

  • nacos 服务器挂了,客户端依旧可以使用本地缓存的注册表找到 provider,只是无法进行注册表的更新
  • consumer 测试
  • discovery 找不到服务列表了
  • 注册表只在发生调用时进行下载,若 nacos 启动,服务也正常启动,在此之后 nacos 直接挂了,则 consumer 没有下载注册表到本地,无法找到 provider
  • nacos 挂了,consumer 又没有存在本地注册表时,检索微服务名称时就会找不到 provider,根据微服务名称获取微服务就会调取失败
  • discovery 不存在检索微服务名称的问题,因此可以正常访问,不过,只能获取空列表

临时实例和持久实例

如何设置

  • Nacos 中的实例分为临时实例与持久实例。
    • 在服务注册时有一个属性 ephemeral 用于描述当前实例在注册时是否以临时实例出现。
    • 为 true 则为临时实例,默认值;为false 则为持久实例。

区别

  • 临时实例与持久实例的实例存储的位置与健康检测机制是不同的。
  • 临时实例
    • 默认情况。
    • 服务实例仅会注册在Nacos 内存,不会持久化到Nacos磁盘。
    • 其健康检测机制为 Client 模式,即 Client 主动向 Server 上报其健康状态。默认心跳间隔为5 秒。在 15 秒内 Server 未收到 client 心跳,则会将其标记为“不健康”状态;在 30 秒内若收到了 client 心跳,则重新恢复“健康”状态,否则该实例将从 Server 端内存清除。
  • 持久实例
    • 服务实例不仅会注册到Nacos内存,同时也会被持久化到 Nacos 磁盘。
    • 其健康检测机制为 Server 模式,即 Server 会主动去检测 client 的健康状态,默认每 20 秒检测一次。健康检测失败后服务实例会被标记为“不健康”状态,但不会被清除,因为其是持久化在磁盘的。"

将 provider 设置成持久实例

  • 默认注册的服务均为临时实例,开发中大多数场景下也均为临时实例
  • 已经注册过的临时实例服务,不能再次注册为持久实例
代码语言:javascript
复制
spring:
  cloud:
      nacos:
        discovery:
          ephemeral: false #设置是否临时节点,默认为true,为临时节点,false,设置不为临时节点,即为持久实例,已注册的临时实例不可修改为持久实例
  • 注销持久实例
代码语言:javascript
复制
curl -d 'serviceName=depart-provider' \
  -d 'ip=192.168.1.102' \
  -d 'port=8081' \
  -d 'ephemeral=false' \
  -d 'username=nacos' \
  -d 'password=nacos' \
  -X DELETE 'http://127.0.0.1:8848/nacos/v2/ns/instance'

Nacos 的 CAP 模式

默认情况下,Nacos Discovery集群的数据一致性采用的是 AP 模式。但其也支持 CP 模式,需要进行转换。若要转换为CP 的,可以提交如下 PUT 请求,完成 AP 到 CP 的转换。

  • http://localhost:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP

数据持久化到外部 MySQL

  • 官方文档
  • 找到 mysql-schema.sql 文件
    • nacos-server-2.2.1\nacos\conf路径下
  • 创建 nacos_config 数据库
代码语言:javascript
复制
create database nacos_config
  • 运行 mysql-schema.sql 文件
  • 修改配置文件
代码语言:javascript
复制
#*************** Config Module Related Configurations ***************#
### If use MySQL as datasource:
### Deprecated configuration property, it is recommended to use `spring.sql.init.platform` replaced.
spring.datasource.platform=mysql
spring.sql.init.platform=mysql

Count of DB:

db.num=1

Connect URL of DB:

db.url.0=jdbc:mysql://127.0.0.1/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=abc123

  • 重启 nacos
  • 在 mysql 新增一个角色
  • 使用新增的角色进行登录

Nacos 集群本地部署

  • 创建集群配置文件 cluster.conf
代码语言:javascript
复制
#端口号不可连续避免系统端口被占用
192.168.1.102:8849
192.168.1.102:8851
192.168.1.102:8853
  • 创建集群,修改端口号
  • 双击 startup.cmd默认集群启动
  • provider 注册 nacos 集群
代码语言:javascript
复制
spring:
cloud:
nacos:
discovery:
#server-addr: localhost:8848 #nacos注册中心地址
#集群注册
server-addr: localhost:8849,localhost:8851,localhost:8853 #nacos注册中心地址
username: dev
password: dev01
  • 官方集群部署架构图

无论采用何种部署方式,推荐用户把Nacos集群中所有服务节点放到一个vip下面,然后挂到一个域名下面。
<http://ip1:port/openAPI> 直连ip模式,机器挂则需要修改ip才可以使用。
<http://SLB:port/openAPI> 挂载SLB模式(内网SLB,不可暴露到公网,以免带来安全风险),直连SLB即可,下面挂server真实ip,可读性不好。
<http://nacos.com/openAPI> 域名 + SLB模式(内网SLB,不可暴露到公网,以免带来安全风险),可读性好,而且换ip方便,推荐模式

在使用VIP时,需要开放Nacos服务的主端口(默认8848)以及gRPC端口(默认9848)、同时如果对Nacos的主端口有所修改的话,需要对vip中的端口映射作出配置,具体端口的映射方式参考部署手册概览-Nacos部署架构

Nacos 数据模型

Nacos 中的服务是由三元组唯一确定的:namespace、group 与服务名称 service。namespace 与 group 的作用是相同的,用于划分不同的区域范围,隔离服务。不同的是,namespace 的范围更大,不同的 namespace 中可以包含相同的group。不同的 group 中可以包含相同的 service。namespace 的默认值为 public,group 的默认值为 DEFAULT GROUP。。它们之间的关系就如官方给出的下图所示。

  • Nacos 数据模型 Key 由三元组唯一确定,Namespace默认是空串,”公共命名空间(public),分组默认是DEFAULT GROUP.

服务隔离

启动三个 provider-nacos-8081 实例,提供相同的服务,不同的 namespace,group,和 port

命名空间

分组名称

端口号

public

DEFAULT_GROUP

8081

public

MY_GROUP

8082

ns_test

MY_GROUP

8083

  • 在 DepartServiceImpl 中新增端口号
代码语言:javascript
复制
@Service
public class DepartServiceImpl implements DepartService {
@Resource
private DepartRepository departRepository;
//获取端口号
@Value(&#34;${server.port}&#34;)
private Integer port;


@Override
public DepartEntity getDepartById(Integer id) {
    if (departRepository.existsById(id)) {
        return departRepository.getReferenceById(id);
    }
    DepartEntity depart = new DepartEntity();
    depart.setName(&#34;not exist this depart port: &#34;+ port);
    return depart;
}

}

  • consumer 调用时输出 port
  • 8081 默认值配置启动
  • 8082 更改如下配
  • 8083 做如下配置
  • 启动服务
  • consumer 中不做任何 namespace,group 配置
  • consumer 新增 group 配置
  • consumer 放开上述 namespace 注释

Nacos Config 服务配置中心

集群中每一台主机的配置文件都是相同的,对配置文件的更新维护就成为了一个棘手的问题。此时就出现了配置中心,将集群中每个节点的配置文件交由配置中心统一管理。相关产品很多,例如,Spring Cloud config、Zookeeper、Apollo、Disconf等。但 Spring Cloud Alibaba官方推荐使用 Nacos 作为微服务的配置中心。”

获取远程配置

  • 这里实现的需求是,应用的配置文件不在本地,而由Nacosconfig 进行管理。

在 nacos config 中进行配置

常见配置中心工作原理

Spring Cloud Config

其存在三大问题:

  • 无法自动感知
  • 更新存在羊群效应
  • 系统架构过于复杂

Apollo

其 Config client 可以自动感知配置文件的更新。但也存在两个不足:

  • 系统架构复杂。
  • 配置文件支持类型较少,其只支持xml、text、properties,不支持json、yml。

Nacos Config

  • Config client 通知自动感知配置中心中相应配置文件的更新。
  • 架构简单。
  • 支持的配置文件类型较多(支持 JSON 与 YML).

Zookeeper

  • Zookeeper 作为配置中心,其工作原理与前面的三种都不同。其没有第三方服务器去存储配置数据,而是将配置数据存放在自己的 Znode 中了。”
  • 当配置中心中的配置数据发生了变更,Configclient 也是可以自动感知到的(Watcher 监听机制)。

一致性问题

  • 配置中心中的配置数据一般都是持久化在第三方服务器的,例如 DBMS、Git 远程库等。
  • 由于这些配置中心 Server 中根本就不存放数据,所以它们的集群中就不存在数据一致性问题。
  • 但像 Zookeeper,其作为配置中心,配置数据是存放在自己本地的。
  • 所以该集群中的节点是存在数据一致性问题的。
  • Zookeeper 集群对于数据一致性采用的是 CP 模式。。
  • 作为注册中心,这些 Server 集群间是存在数据一致性问题的,它们采用的模式是不同的。Zookeeper(CP)、Eureka(AP)、Consul(AP)、Nacos(默认 AP,也支持 CP)。”

provider 由配置中心读取配置文件

  • nacos 新建配置项
  • 将本地配置放置到 nacos 中
  • 在 pom 文件中新增依赖
代码语言:javascript
复制
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
  • 新版中由于 springcloud2021 以后不在扫描 bootstrap.yaml 文件,新版 nacos 本地配置文件任然为 application.yaml
  • 本地的现有配置如下
代码语言:javascript
复制
spring:
  #微服务名称
  application:
    name: depart-provider
  cloud:
    nacos:
      config:
        server-addr: localhost:8848
        username: nacos
        password: nacos
  • 新版 nacos 需要指定配置文件拓展名
代码语言:javascript
复制
spring:
  cloud:
    nacos:
      config:
        file-extension: yaml #读取的配置文件类型
  • 新版本需要新增如下配置
代码语言:javascript
复制
spring:
  # 新版新增配置
  config:
    import:
      - optional:nacos:${spring.application.name}.${spring.cloud.nacos.config.file-extension}

共享配置

代码语言:javascript
复制
spring:
  cloud:
    nacos:
      config:
        #实现共享配置,前置条件,同一个namespace下的同一个group的不同服务共享
       shared-configs[0]:
         data-id: shareconfig.yaml
         refresh: true
       shared-configs[1]:
         data-id: shareconfig2.yaml
         refresh: true
        #实现拓展配置,不同namespace,不同group均可共享
       extension-configs[0]:
         data-id: extensionconfig.yaml
         refresh: true
       extension-configs[1]:
         data-id: extensionconfig2.yaml
         refresh: true
        # 配置加载顺序: 共享配置=>扩展配置=>当前配置
        # 配置优先级: 后加载的会覆盖先加载的=> 当前配置>扩展配置>共享配置
  • 当前服务的配置可以存在于三个地方
    • 远程配置文件(nacos 配置中心)
  • 快照配置文件
  • 本地配置文件(主动写入配置)

这三个同名文件也存在加载顺序问题,它们的加载顺序为:本地配置文件、远程配置文件、快照配置文件。只要系统加载到了配置文件,那么后面的就不再加载。

配置动态更新

  • nacos 配置中心新增部门配置
  • 修改 provider-config-8081 中的 DepartServiceImpl
代码语言:javascript
复制
@Service
@RefreshScope //实现动态刷新
public class DepartServiceImpl implements DepartService {
@Resource
private DepartRepository departRepository;
//获取端口号
@Value(&#34;${server.port}&#34;)
private Integer port;

//获取部门名称通过远程配置中心
@Value(&#34;${depart.name}&#34;)
private String departName;


@Override
public DepartEntity getDepartById(Integer id) {
    if (departRepository.existsById(id)) {
        return departRepository.getReferenceById(id);
    }
    DepartEntity depart = new DepartEntity();
    depart.setName(&#34;not exist this depart&#34;+departName+&#34; port: &#34;+ port);
    return depart;
}

}

  • 重新启动 provider 和 consumer
  • 修改为财务部,进行发布

多环境选择

在开发应用时,通常同一套程序会被运行在多个不同的环境,例如,开发、测试、生产环境等。每个环境的数据库地址、服务器端口号等配置都会不同。若在不同环境下运行时将配置文件修改为不同内容,那么,这种做法不仅非常繁琐,而且很容易发生错误。此时就需要定义出不同的配置信息,在不同的环境中选择不同的配置。

  • 在 nacos config 中对 depart-provider.yaml进行克隆
  • 本地 nacos-config-8081 中的 application.yaml 文件中做以下配置
代码语言:javascript
复制
spring:
  profiles:
    active: dev
  # 新版新增配置
  config:
    import:
      - optional:nacos:${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

配置隔离

启动三个 provider-config-8081 实例,提供相同的服务,不同的 namespace,group,和 port

命名空间

分组名称

端口号

public

DEFAULT_GROUP

8081

public

MY_GROUP

8082

ns_test

MY_GROUP

8083

  • 在 nacos config 配置中心中进行克隆
  • 8081 默认启动
  • 8082 做动态参数启动
  • 8083 做动态参数启动
  • 依次启动 consumer 和 provider 集群
  • consumer 当前配置
  • consumer 访问 provider
  • consumer 放开 namspace 注释,重启 consumer,再次访问