Spring Cloud Alibaba Nacos - 服务治理

1,147 阅读10分钟

🌈往期回顾

第一期:Spring Cloud Alibaba前景如何?

在我大学的期间,由于当时的Spring Cloud的崛起,其实我一开始接触使用的服务发现是Eureka,它其实是Spring Cloud Netfix的一个重要组件,目前Eurake已经停止维护了,目前国内阿里微服务Dubbo+Nacos+SpringCloudAlibaba/Seata/Sentinel最佳实践,可以说是Java微服务生态的最佳解决方案了。

本期内容主要是围绕以下几点展开说明:

1️⃣ 什么是服务治理
2️⃣ 常见有哪些服务注册中心
3️⃣ Nacos发展历程
4️⃣ Nacos核心概念介绍
5️⃣ Nacos服务注册与发现原理(通过一个示例来说明)
6️⃣ Nacos服务注册流程(源码分析)
7️⃣ Nacos实现配置动态刷新
8️⃣ Nacos配置持久化

一、🙋‍♂️什么是服务治理?

服务治理.png

简单来说,服务治理就是提供了微服务架构中各服务实例的快速上线或下线且保持个服务能够正常通信的能力的方案总称。

二、常见的注册中心

image.png

  • Eureka Eureka是SpringCloud Netflix中的重要组件,主要作用就是用于服务发现,目前已经停止维护了。
  • Zookeeper zookeeper是一个分布式服务框架,是Apache Hadoop的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,比如:统一命名服务、状态同步服务、集群服务、分布式应用配置项的管理等。
  • Consul Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。主要面向分布式,服务化的系统提供服务发现、服务注册和配置管理功能,Consul核心功能包括:服务注册与发现、健康检查、Key/Value存储、多数据中心和分布式一致性保证等特性。
  • Nacos Nacos 是一个更易于构架云原生应用的动态服务发现、配置管理和服务管理平台,它也是Spring Cloud Alibaba的组件之一,主要负责服务注册发现与服务配置,可以这样认为:nacos=eureka+config+Bus,一个Nacos等于Spring Cloud的三大组件,分别是注册中心Eureka服务配置Config服务总线Bus,可见微服务的灵魂摆渡者Nacos究竟有多强。

了解了常见的注册中心后,我总结了一下💭:

Zookeeper是一款经典的服务注册产品,在之前的很长一段时间里,它是国人在提起RPC服务注册中心想到的唯一选择,这可能也是与Dubbo在中国的普及程度有关,ConsulEureka基本上是同时期产品,Consul在设计上把很多分布式服务治理上要用到的功能都包含在内,可以支持服务注册、健康检查、配置管理、Service Mech等,而Eureka算是借助微服务概念的流行,与Spring Cloud生成深度结合,也获取了大量的用户。

ZookeeperConsulEureka在开源层面都 没有很明确的针对服务隔离模型Nacos在1.0版本开始的时候就考虑到如何让用户能够以多种维度进行数据隔离,为用户提供不同服务级别的物理集群,另外,Nacos除了对阿里生态体系Dubbo自身的而支持,它也 完全兼容了Java微服务Spring Cloud、Kubernetes等云原生生态体系,为Spring Cloud用户提供简单便捷的配置中心和注册中心的解决方案 ,我认为这也是Nacos自身其中一个强大的优点!

注册中心对比和选型:Zookeeper、Eureka、Nacos、Consul和ETCD

三、Nacos发展历程

Nacos诞生

由于当前提供服务的各种分布式基础设施正在变得越来越重要,可沉淀和可共享服务体系的服务注册服务治理平台是这个体系的关键要素之一,在阿里巴巴经过近10年的服务发展经验上看,在初步发现和服务监控共享领域现有的解决方案都一个没有完美的解决阿里巴巴服务管理的一个举措,所以在2018年8月份时候,阿里巴巴开源了Nacos

Nacos/nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一句话概括:Nacos是⼀个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 Nacos 致力于提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置管理、动态DNS服务及流量管理,旨在打造更敏捷和容易地构建、交付和管理微服务平台。

image.png

服务注册与发现:Naocs Discovery
服务发现是微服务架构体系中最关键的组件之一,如果尝试着手动的方式给每一个客户端来配置所有服务提供者的服务列表是一件非常困难的事,而且不利于服务的动态扩缩容。
Nacos Discovery可以帮助我们将服务自动注册到Nacos Server端并且能够动态感知和刷新某个服务实例的服务列表。

Nacos 2.0

主要关注在统一服务管理、服务共享及服务治理体系的开放的服务平台的建设上,主要包括两个方面:

  • Dubbo 4.0 + Nacos 2.0 开放的服务平台
  • Kubernetes + Spring Cloud 统一服务管理

阿里巴巴将通过Dubbo+Nacos以及一系列开源项目打造服务发现、服务及流量管理、服务共享平台。

image.png

Dubbo+Nacos,专为Dubbo而生成的注册中心和配置中心

image.png

🎉可以多关注一下Nacos发展历程:

四、Nacos概念介绍

🌊如果对Naocs概念没有理解透彻的,推荐后续可以看看:
《Naocs官方文档 - 什么是Nacos?》
《Nacos官方文档 - Nacos架构》

4.1 数据模型

Nacos 数据模型 Key 由三元组唯一确认。如下图所示: Nacos数据模型.png

  • 作为注册中心时Namespace + Group + Service
  • 作为配置中心时Namespace + Group + DataId

我们来看看 Namespace、Group、Service 的概念。

4.1.1 Namespace命名空间

用于进行租户粒度的配置隔离。不同的命名空间下,可以存在相同的 GroupData ID 的配置。Namespace 的常用场景之⼀是不同环境的配置的区分隔离,例如开发环境生产环境的资源(如数据库配置、限流阈值、降级开关)隔离等。如果在没有指定 Namespace 的情况下,默认使用 public 命名空间。

4.1.2 Group服务分组

Nacos 中的⼀组配置集,是配置的维度之⼀。当在Nacos上创建⼀个配置时,如果未填写配置分组的名称,则配置分组的名称默认采用 DEFAULT_GROUP。将若⼲个服务或者若⼲个配置集归为⼀组,通常习惯⼀个系统归为⼀个组。

不同的服务可以归类到同一个分组,默认是DEFAULT_GROUP(默认分组)。

image.png

4.1.3 Service服务

例如说,用户服务、订单服务、支付服务、商品服务等等。

4.1.4 配置ID(Data ID)

Nacos 中的某个配置集的ID。配置集ID是划分配置的维度之⼀。DataID通常用于划分系统的配置集。⼀个系统或者应用可以包含多个配置集,每个配置集都可以被⼀个有意义的名称标识。DataID尽量保障全局唯⼀,可以参考Nacos Spring Cloud 中的命名规则:

${prefix}-${spring.profiles.active}-${file-extension}

image.png

4.2 服务领域模型

实际上Service可以进一步细拆分为服务领域模型,如下图:

image.png

4.2.1 Instance 实例

提供一个或者多个服务的具有可访问网络地址(IP:Port)的进程。

4.2.2 Cluster 集群

同一个服务下的所有服务实例组成一个默认集群(Default)。集群可以被进一步按需求划分,划分的单位可以是虚拟集群。

例如说,我们将服务部署在多个机房之中,每个机房可以创建为一个虚拟集群。每个服务在注册到 Nacos 时,设置所在机房的虚拟集群。这样,服务在调用其它服务时,可以通过虚拟集群,优先调用本机房的服务。如此,在提升服务的可用性的同时,保证了性能。

4.2.3 Metadata原数据

Nacos 元数据(如配置和服务)描述信息,如服务版本、权重、容灾策略、负载均衡策略、鉴权配置、各种自定义标签 (label)。

从作用范围来看,分为服务级别的元信息、集群的元信息及实例的元信息。如下图:

image.png

小结汇总

为了更好理解,我把数据模型和服务领域模型整理如下图所示:

Nacos服务发现领域模型.jpg

五、Nacos服务注册与发现原理

Nacos基本架构和理念

Nacos架构图.jpeg

  • Provider APP:服务提供者
  • Consumer APP:服务消费者
  • Name Server:通过VIP(Vritual IP)或者DNS的方式实现Nacos高可用集群服务路由
  • Nacos Server:Nacos服务提供者,里面包含的Open API是功能的入口,Config Service、Naming Service是Nacos提供服务配置、命名服务模块、Consistency Protocol是一致性协议,同来实现Nacos集群的节点数据同步,这里使用的是Raft算法(类似使用该算法的有Redis哨兵选举,有兴趣的可以了解一下Redis的哨兵机制)
  • Nacos Console:Naocs控制台

在服务启动的时候,通过配置@@EnableDiscoveryClient将我们的服务告诉注册中心,注册中心登记每一个服务的详情信息,并在注册中心形成一张服务清单,服务中心会已心跳的方式去监测清单中的服务是否可用,如果不可用,会自动在服务清单中剔除不可用的服务,服务调用方(消费者)向服务中心咨询服务,并获取所有的实例清单,实现对具体服务实例的调用访问,不需要我们手动管理,实现各个微服务的自动化注册与发现

Snipaste_2022-04-08_23-22-44.png

上面我自己画了一个调用图,除了微服务,核心组件是服务注册中心,它是微服务架构中比较核心的一个组件,在微服务架构中起着协调者的作用,可见注册中心一般包含以下几个功能:

1️⃣ 服务注册

服务启动时,Nacos Client客户端通过请求方式向Nacos Server服务端注册自己的服务实例,提供自身的元数据,比如:ip地址、端口等信息,Nacos Server接收到注册请求后,会把这些数据存储在一个双层的内存Map中。

2️⃣ 服务订阅与服务发现

  • 服务订阅:服务调用者订阅服务提供者的信息,注册中心向订阅者推送提供者信息,保证服务于服务之间的正常调用。
  • 服务发现:服务消费者(Nacos Client)在需要调用服务的时候,会发生一个REST请求给Nacos Server,获取上面的服务注册清单,实现服务接口调用。

3️⃣ 服务心跳与健康检查

在服务注册完以后,Nacos Client会通过一个心跳的方式来通知Nacos Server,告诉Nacos一直处于可用的健康状态(Healthy Staus),默认是5s发送一次心跳,Nacos Server负责检测服务提供者的健康情况,如果发现异常,执行服务剔除。

4️⃣ 动态配置服务

业务服务一般都会维护一个本地配置文件,然后把一些常量配置到这个文件中。这种方式的缺点就是配置变更时需要重新部署应用,而Nacos动态配置服务可以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置,可以是的配置管理变得更加高效和敏捷,配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。

推荐阅读:

💡推荐阅读一下Nacos官方博客以及Nacos官方书籍:

以我其中一个项目 mamba-block 为🌰,项目采用Nacos作为注册中心,假设目前是有两个服务:用户中心服务user-center文章内容中心服务content-center,用户中心(广州集群)目前分别起了两个实例192.168.1.102:8081、192.168.1.102:8082,项目启动Nacos会根据配置将服务注册用户Nacos Server端:

image.png

Pom.xml依赖

<?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.1.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.jacklin</groupId>
    <artifactId>mamba-block-content-center</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>mamba-block-content-center</name>
    <description>曼巴街区-内容中心</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!-- mamba-block-common -->
        <dependency>
            <groupId>org.jacklin</groupId>
            <artifactId>mamba-block-common</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- nacos服务发现 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <!--<version>${latest.version}</version>-->
        </dependency>
        <!-- nacos配置管理 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!-- sentinel容错限流 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <!-- 整合spring cloud -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- 整合spring cloud alibaba -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.1.4.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

《Spring Cloud 官方文档 —— 版本说明》文档中,推荐了三者的依赖关系。

user-center.yml

server:
  port: 8081
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/user_center?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=convertToNull
    username: root
    password: admin
    driver-class-name: com.mysql.cj.jdbc.Driver
  application:
    name: user-center
  cloud:
    nacos:
      discovery:
        #指定nacos server地址
        server-addr: localhost:8848
        #服务名称
        service: user-center
        # 指定namespace
        namespace: 82758abc-a175-4b25-be4e-af74ee82697d
        # 指定集群名称(广州集群-GZ)
        cluster-name: GZ
        metadata:
          instance: a
          version: v1.0.0

content-center.yml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/content_center?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=convertToNull
    username: root
    password: admin
    driver-class-name: com.mysql.cj.jdbc.Driver
  application:
    name: content-center
  # prod(9080)->dev(9081)->test(9082)
  profiles:
    active: dev
  cloud:
    nacos:
      discovery:
        #指定nacos server地址
        server-addr: localhost:8848
        #服务名称
        service: content-center
        #集群部署在北京机房(命名为:BJ)
        cluster-name: BJ
      config:
        # 配置内容的数据格式,目前只支持properties和yaml类型。这个和dataId有关->${prefix}-${spring.profiles.active}.${file-extension}
        file-extension: properties
    sentinel:
      transport:
        # 指定sentinel控制台地址
        dashboard: localhost:8080
# 开启/actuator/sentinel端点: http://localhost:9081/actuator/sentinel
management:
  endpoints:
    web:
      exposure:
        include: "*"

# 配置文件指定负载均衡规则
#user-center:
#  ribbon:
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

# 开启Ribbon饥饿加载(Ribbon默认是懒加载模式,会导致首次情况RT过长)
ribbon:
  eager-load:
    enabled: true
    # 指定Ribbon请求user-center服务的时候启饥饿加载,多个可以用","分隔
    clients: user-center

服务列表如下图所示:

image.png

用户中心实例信息:

image.png

可见,用户中心内容文章中心都已经注册到了Nacos,现在内容中心文章用户需要调用用户中心来获取用户信息,在内容中心声明一个客户端:

@FeignClient(name = "user-center")
public interface UserCenterFeignClient {


    /**
     * @Describe: 根据id查询用户  http://user-center/users/{id}
     *
     * @author jacklin
     * @Date: 2022/3/1
     **/
    @GetMapping("/users/{id}")
    UserDTO findById(@PathVariable Integer id);
}

PostService接口

/**
 * @author jacklin
 * @Description: 帖子内容表
 * @Date: 2021-01-17
 * @Version: V1.0
 */
public interface IPostService extends IService<Post> {

    /**
     * 根据id查询文章详情
     *
     * @author: jacklin
     * @since 2022/7/24 11:08
     **/
    PostDTO findById(Integer id);
}

PostServiceImpl

/**
 * 引入feign,通过userCenterFeignClient实现调用,解决url变化难以维护等问题
 *
 * @author: jacklin
 * @since 2022/6/13 0:00
 **/
@Override
public PostDTO findById(Integer id) {
    //获取帖子详情
    Post post = postMapper.selectById(id);
    //发布人id
    Integer userId = post.getUserId();
    //通过userCenterFeignClient调用用户中心服务获取用户详情信息
    UserDTO userDTO = this.userCenterFeignClient.findById(userId);
    //消息装配
    PostDTO postDTO = new PostDTO();
    BeanUtils.copyProperties(post, postDTO);
    postDTO.setWxNickname(userDTO.getNickname());
    postDTO.setAvatar(userDTO.getAvatar());
    return postDTO;
}

在内容文章中心获取文章信息,通过OpenFeign远程调用用户中心不同实例上的用户信息,访问:http://localhost:9081/post/1 ,响应结果:

image.png

六、Nacos服务注册流程分析

在Spring Cloud中,我们一般都是在应用启动类上添加Nacos注解 @EnableDiscoveryClient,就会自动吧服务实例注册到Nacos,那么这个是如何做到的呢?

在Spring-Cloud-Common包中有一个类org.springframework.cloud. client.serviceregistry .ServiceRegistry ,它是Spring Cloud提供的服务注册的标准。集成到Spring Cloud中实现服务注册的组件,都会实现该接口。

ServiceRegister

image.png

NacosServiceRegister就是ServiceRegister的一个实现类,如图所示:

image.png

根据以往的SpringBoot的自动配置原理,在引入依赖之后,通过注解@EnableDiscoveryClient加载AutoConfigurationImportSelector类中的selectImports方法,SpringBoot会通过SPI的方式读取META-INF/spring.factories目录下的配置信息,并把对应的配置类加载到Spring容器中:

image.png 通过源码分析,事实上nacos的服务注册逻辑就在上图红框的NacosServiceRegistryAutoConfiguration类中,源码如下:

@Configuration
@EnableConfigurationProperties
@ConditionalOnNacosDiscoveryEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
      AutoServiceRegistrationAutoConfiguration.class,
      NacosDiscoveryAutoConfiguration.class })
public class NacosServiceRegistryAutoConfiguration {

   //生成bean对象:NacosServiceRegistry
   @Bean
   public NacosServiceRegistry nacosServiceRegistry(
         NacosDiscoveryProperties nacosDiscoveryProperties) {
      return new NacosServiceRegistry(nacosDiscoveryProperties);
   }

   //生成bean对象:NacosRegistration
   @Bean
   @ConditionalOnBean(AutoServiceRegistrationProperties.class)
   public NacosRegistration nacosRegistration(
         ObjectProvider<List<NacosRegistrationCustomizer>> registrationCustomizers,
         NacosDiscoveryProperties nacosDiscoveryProperties,
         ApplicationContext context) {
      return new NacosRegistration(registrationCustomizers.getIfAvailable(),
            nacosDiscoveryProperties, context);
   }

   /**
    * 生成bean对象:NacosAutoServiceRegistration,而nacos的注册逻辑真正是存在这个
    * NacosAutoServiceRegistration中,上面的两个bean是生成NacosAutoServiceRegistration的必要参数!
    *
    */
   @Bean
   @ConditionalOnBean(AutoServiceRegistrationProperties.class)
   public NacosAutoServiceRegistration nacosAutoServiceRegistration(
         NacosServiceRegistry registry,
         AutoServiceRegistrationProperties autoServiceRegistrationProperties,
         NacosRegistration registration) {
      return new NacosAutoServiceRegistration(registry,
            autoServiceRegistrationProperties, registration);
   }
}

从上面源码看到NacosAutoServiceRegistration才是在真正注册类,而NacosAutoServiceRegistration类的继承关系图如下:

image.png

可以看到NacosAutoServiceRegistration继承了AbstractAutoServiceRegistration,实现了事件监听接口ApplicationListener,那么必然会在onApplicationEvent方法中监听某个事件,如下图所示,AbstractAutoServiceRegistration的的onApplicationEvent方法监听的是WebServerInitializedEvent事件:

@Override
@SuppressWarnings("deprecation")
public void onApplicationEvent(WebServerInitializedEvent event) {
   bind(event);
}

监听WebServerInitializedEvent事件发布后,会在onApplicationEvent方法中执行bind(event)方法:

@Deprecated
public void bind(WebServerInitializedEvent event) {
   ApplicationContext context = event.getApplicationContext();
   if (context instanceof ConfigurableWebServerApplicationContext) {
      if ("management".equals(
            ((ConfigurableWebServerApplicationContext) context).getServerNamespace())) {
         return;
      }
   }
   this.port.compareAndSet(0, event.getWebServer().getPort());
   //进入start()方法,执行服务注册逻辑~
   this.start();
}

然后调用this.start();最终会调用NacosServiceRegistry.register()方法进行服务注册:

image.png

而在NacosServiceRegistry.registry方法中,调用了Nacos Client SDK中的namingService.registerInstance完成服务的注册。

//方法位置:com.alibaba.nacos.client.naming.NacosNamingService#registerInstance()
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
    NamingUtils.checkInstanceIsLegal(instance);
    String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
    if (instance.isEphemeral()) {
        //心跳
        BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
        //每隔5秒向服务端发送一次心跳,证明当前服务是存活的!
        beatReactor.addBeatInfo(groupedServiceName, beatInfo);
    }
    //注册实例
    serverProxy.registerService(groupedServiceName, groupName, instance);
}

通过源码查阅,在registerInstance()注册方法主要做了两件事:

  1. 定时向服务端发送心跳,开启一个定时线程池,每隔period: 5s秒向服务端发送一次心跳,告知当前服务是健康的,其中在BeatTaskrun方法中采用递归调用this.executorService.schedule来达到不断发送心跳的目的,发心跳更新服务实例的最后心跳时间,防止该实例被服务端剔除。
public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
    String key = buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());
    BeatInfo existBeat = null;
    if ((existBeat = dom2Beat.remove(key)) != null) {
        existBeat.setStopped(true);
    }
    dom2Beat.put(key, beatInfo);
    //发送心跳的定时任务,Period:5秒
    executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
    MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}
  1. 请求服务端的服务注册接口,向服务端注册服务,其中serverProxy.registerService()会向Nacos发起注册请求,请求体包含namespaceIdserviceNameipport等等。
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
    //封装服务参数
    final Map<String, String> params = new HashMap<String, String>(16);
    params.put(CommonParams.NAMESPACE_ID, namespaceId);
    params.put(CommonParams.SERVICE_NAME, serviceName);
    params.put(CommonParams.GROUP_NAME, groupName);
    params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
    params.put("ip", instance.getIp());
    params.put("port", String.valueOf(instance.getPort()));
    params.put("weight", String.valueOf(instance.getWeight()));
    params.put("enable", String.valueOf(instance.isEnabled()));
    params.put("healthy", String.valueOf(instance.isHealthy()));
    params.put("ephemeral", String.valueOf(instance.isEphemeral()));
    params.put("metadata", JacksonUtils.toJson(instance.getMetadata()));
    //Http调用Nacos注册接口地址,UtilAndComs.nacosUrlInstance就是/nacos/v1/ns/instance
    reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);
}

UtilAndComs

image.png

最终调用的注册路径为: /nacos/v1/ns/instance,Nacos官方文档暴露的接口地址如下所示,与reqApi()的请求地址一致,那么以上就是Nacos的整体的注册实现逻辑了。

Nacos Open API

七、Nacos实现配置动态刷新

在单体架构的时候我们可以将配置写在配置文件中,但是有一个缺点就是每次修改配置都需要重启服务才能生效🤢。

当应用程序比较少的时候还可以维护,如果转向微服务架构有成百上千的实例,每次修改一次配置都要将全部实例启动,不仅增加了系统的不便性,也提高了系统的维护成本。

🙋‍♂️那么如何能做到服务不重启就可以修改配置呢? 因此产生了四个基本的诉求:

  • 需要支持动态修改配置
  • 需要动态变更有多实时
  • 变更快乐之后如何挂空风险,如灰度、回滚等
  • 敏感信息如何做到安全配置

配置yml文件

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/content_center?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=convertToNull
    username: root
    password: admin
    driver-class-name: com.mysql.cj.jdbc.Driver
  application:
    name: content-center
  # prod(9080)->dev(9081)->test(9082)
  profiles:
    active: dev
  cloud:
    nacos:
      discovery:
        #指定nacos server地址
        server-addr: localhost:8848
        #服务名称
        service: content-center
        #集群部署在北京机房(命名为:BJ)
        cluster-name: BJ
      config:
        # 配置内容的数据格式,目前只支持properties和yaml类型。这个和dataId有关->${prefix}-${spring.profiles.active}.${file-extension}
        file-extension: properties

# 配置文件指定负载均衡规则
#user-center:
#  ribbon:
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

# 开启Ribbon饥饿加载(Ribbon默认是懒加载模式,会导致首次情况RT过长)
ribbon:
  eager-load:
    enabled: true
    # 指定Ribbon请求user-center服务的时候启饥饿加载,多个可以用","分隔
    clients: user-center

DataID是什么?

dataId是Nacos的一个配置的唯一标识,它的取值方式格式如下: ${prefix}-${spring.profiles.active}.${file-extension}

  • prefix: 前缀,默认是spring.application.name的值,也可以通过配置项spring.cloud.name.config.prefix来配置;
  • spring.profiles.active:即为当前环境对应的 profile。当 spring.profiles.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension}
  • file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。目前只支持 properties 和 yaml 类型。

在Nacos控制台添加一个配置

下面在nacos中添加一个config.version的配置,如下图:

image.png 以上就是添加的config.version的配置,发布之后查看列表如下图:

image.png

获取nacos中的配置

获取nacos中的配置很简单,使用原生注解@Value()直接读取即可,步骤如下:

  • 自定义实体类NacosDynamicConfigProperties:
/**
 * 动态配置NacosDynamicConfigProperties
 *
 * @author: jacklin
 * @date: 2022/6/22 23:36
 */
@Data
@Component
public class NacosDynamicConfigProperties {

    /**
     * 直接读取nacos中的config.version的配置
     **/
    @Value("${config.version}")
    private String version;
}
  • 新建一个NacosDynamicConfigController测试类,如下:
/**
 * 动态获取Nacos中配置Controller
 *
 * @author: jacklin
 * @date: 2022/7/22 23:41
 */
@RestController
@RequestMapping(value = "/nacos")
public class NacosDynamicConfigController {

    @Autowired
    private NacosDynamicConfigProperties properties;
    
    @GetMapping(value = "/getDynamicConfig/{id}")
    public String getDynamicConfig(@PathVariable("id") String id) {
        return "动态获取nacos config id = " + id + ", version=" + properties.getVersion();
    }

}

image.png

结果很明细,成功读取了Nacos的配置!!!

配置如何实现动态刷新❓

添加依赖

<!-- nacos配置管理 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

设想一下,由于需求的变动,需要将config.version版本配置成v2.0,那么如何能实现直接在控制台修改Nacos的配置能直接生效呢?

现在直接将Nacos的配置修改成v2.0,如图所示:

image.png

此时在不重启项目的情况下访问:http://localhost:9081/nacos/getDynamicConfig/1 结果如下图:

image.png

🤦‍♂️WHAT??? 实际上想要Nacos自动刷新配置还需要结合原生注解@RefreshScope,这个注解是不是很眼熟,在Config中也是用这个注解刷新配置,我们只需要将该注解标注在配置的实体类上即可,如下:

@Data
@Component
@RefreshScope
public class NacosDynamicConfigProperties {

    /**
     * 直接读取nacos中的config.version的配置
     **/
    @Value("${config.version}")
    private String version;
}

此时加上@RefreshScope注解重启之后将Nacos配置中的config.version修改为v2.0,再次访问,结果如下: image.png

👏👏👏实现了配置的动态更新~

🎉总结

Nacos真正实现了配置与应用的分离,统一管理,优雅的解决了 配置的动态化、持久化、运维成本 等问题,对于Nacos Spring的用户来说,只需要在自己的应用程序中设置autoRefreshed为true,当需要修改配置时,调用Nacos修改配置接口,或者使用Nacos控制台进行修改,配置更改后,Nacos会将最新的配置推送到应用的所有的机器上,简单高效。

八、Nacos配置MySQL持久化(单机模式)

下载完nacos解压之后,进入E:\ServiceComponent\nacos-server-2.0.3\nacos\conf目录,找到application.properties文件,给Nacos配置自定义的MySQL持久化:

修改配置,指定MySQL地址、用户名、端口号

### If use MySQL as datasource:
spring.datasource.platform=mysql

### Count of DB:
db.num=1

### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=admin

### Connection pool configuration: hikariCP
db.pool.config.connectionTimeout=30000
db.pool.config.validationTimeout=10000
db.pool.config.maximumPoolSize=20

Nacos已经有提供了一些建表的sql,在目录:E:\ServiceComponent\nacos-server-2.0.3\nacos\conf下有一个nacos-mysql.sqlschema.sql文件:

image.png

启动后,当服务注册成功后,config_info会新增一条记录。

image.png

到这里我们就可以将我们的nacos相关配置持久化到我们的MySQL数据库了。

参考: