Spring Cloud 整合 Nacos 详解

佴阳辉
2023-12-01

概述

Nacos官方文档:https://nacos.io/zh-cn/docs/what-is-nacos.html

Nacos 致力于发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,快速实现动态服务发现、服务配置、服务元数据及流量管理。

Nacos 更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。


服务(Service)是 Nacos 世界的一等公民。Nacos 支持几乎所有主流类型的“服务”的发现、配置和管理:

  • Kubernetes Service

  • gRPC & Dubbo RPC Service

  • Spring Cloud RESTful Service


Nacos 的关键特性包括:

  • 服务发现和服务健康监测

    Nacos 支持基于 DNS 和基于 RPC 的服务发现。服务提供者使用 原生SDK、OpenAPI、或一个独立的Agent TODO注册 Service 后,服务消费者可以使用DNS TODO 或HTTP&API查找和发现服务。

    Nacos 提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求。Nacos 支持传输层 (PING 或 TCP)和应用层 (如 HTTP、MySQL、用户自定义)的健康检查。 对于复杂的云环境和网络拓扑环境中(如 VPC、边缘网络等)服务的健康检查,Nacos 提供了 agent 上报模式和服务端主动检测2种健康检查模式。Nacos 还提供了统一的健康检查仪表盘,帮助根据健康状态管理服务的可用性及流量。

  • 动态配置服务

    动态配置服务以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。

    动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。

    配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。

    Nacos 提供了一个简洁易用的UI 管理所有的服务和应用的配置。Nacos 还提供包括配置版本跟踪、金丝雀发布、一键回滚配置以及客户端配置更新状态跟踪在内的一系列开箱即用的配置管理特性,可以更安全地在生产环境中管理配置变更和降低配置变更带来的风险。

  • 动态 DNS 服务

    动态 DNS 服务支持权重路由,可以更容易地实现中间层负载均衡、更灵活的路由策略、流量控制以及数据中心内网的简单DNS解析服务。动态DNS服务还能更容易地实现以 DNS 协议为基础的服务发现,以消除耦合到厂商私有服务发现 API 上的风险。

    Nacos 提供了一些简单的 DNS APIs TODO 管理服务的关联域名和可用的 IP:PORT 列表.

  • 服务及其元数据管理

    Nacos 能从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服务的 SLA 以及最首要的 metrics 统计数据。


nacos整合

参考:https://blog.csdn.net/ZHANGLIZENG/article/details/119065077

依赖引入

	<!-- nacos -->
	<dependency>
		<groupId>com.alibaba.cloud</groupId>
    	<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    	<version>2.2.1.RELEASE</version>
	</dependency>
    <dependency>
    	<groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        <version>2.2.1.RELEASE</version>
    </dependency>

注意:

  • 若使用示例的依赖版本号,Spring Boot版本要低于2.4,否则启动应用会报错。

  • SpringBoot、SpringCloud和nacos集成版本对应关系对照(版本若对应不上,应用可能会启动报错):

    https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E


配置Nacos

项目中默认配置文件是 application.properties ,Nacos 配置加在此配置文件中的话,应用启动会报连接 Nacos 失败,需要创建 bootstrap.properties 或 bootstrap.yml 配置文件(添加任意一个即可),下面以 bootstrap.yml为例:

spring:
  application:
    # 项目(微服务)名称
    name: apm-mobile-android
  cloud:
    nacos:
      # nacos用户名
      username: nacos
      # nacos用户密码
      password: nacos
      # nacos服务器地址
      server-addr: 10.0.7.115:18117
      # nacos配置中心相关
      config:
        # 开启nacos作为配置中心,默认值:true
        enabled: true
        # 作为配置中心的nacos服务器地址,默认值:${spring.cloud.nacos:server-addr}
        #server-addr: 10.0.7.115:18117
        # 配置文件读取的nacos命名空间ID,默认值:public
        namespace: PROD
        # 配置文件在nacos命名空间中的分组,默认值:DEFAULT_GROUP
        group: apm
        # 配置文件的文件前缀(配置文件名称),默认值:${spring.application.name}
        prefix: ${spring.application.name}
        # 配置文件的文件后缀(文件类型),默认值:properties
        file-extension: properties
        # 配置内容的编码方式,默认值:UTF-8
        encode: UTF-8
        # 获取配置的超时时间,单位:ms,默认值:3000
        timeout: 3000
        # 开启监听和自动刷新,动态感知配置变化,默认值:true
        refresh-enabled: true
        # AccessKey
        #access-key: 123
        # SecretKey
        #secret-key: 123
        # 引入共享配置(同一分组)
        shared-configs:
            # 配置支持共享的 Data Id
          - data-id: comm.properties
        # 配置 Data Id 所在分组,缺省默认 DEFAULT_GROUP
            group: DEFAULT_GROUP
        # 配置Data Id 在配置变更时,是否动态刷新,缺省默认 false
            refresh: true
        # 引入扩展配置(同一分组或不同分组)
        extension-configs:
            # 配置支持共享的 Data Id
          - data-id: comm.properties
            # 配置 Data Id 所在分组,缺省默认 DEFAULT_GROUP
            group: DEFAULT_GROUP
            # 配置Data Id 在配置变更时,是否动态刷新,缺省默认 false
            refresh: true
      # nacos注册中心相关
      discovery:
        # 开启nacos作为服务注册中心,默认值:true
        enabled: true
        # 作为注册中心的nacos服务器地址,默认值:${spring.cloud.nacos:server-addr}
        #server-addr: 10.0.7.115:18117
        # 服务注册在nacso上的命名空间ID,默认值:public
        namespace: PROD

加载Nacos配置中心配置项

在初始化类中添加 @EnableDiscoveryClient 注解即可:

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

@EnableDiscoveryClient		// 开启服务发现客户端,加载Nacos配置中心配置项
@SpringBootApplication
public class SpringbootdemoApplication {

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

Nacos配置中心配置项动态生效

前置条件:

  • bootstrap配置文件中nacos的配置项refresh-enabled: true(默认开启)

    # 开启监听和自动刷新,动态感知配置变化,默认值:true
    spring.cloud.nacos.config.refresh-enabled=true
    

方式一:@Value + @RefreshScope 获取最新值

@RestController
@RefreshScope	// 配置项动态生效
public class TestController {
    @NacosValue(value = "${test.data}", autoRefreshed = true)
    private String data;
    @Value(value = "${test.data}")
    private String datas;
    @GetMapping("test")
    public String test() {
        return "data :" + data + ",datas="+datas;
    }
}

方式二:通过applicationContext.getEnvironment.getProperty 获取最新值

@SpringBootApplication
public class NacosConfigSimpleApplication {
    public static void main(String[] args) throws InterruptedException {
        ConfigurableApplicationContext applicationContext =
                  SpringApplication.run(NacosConfigSimpleApplication.class, args);
        //取到Spring的配置环境
        while(true){
            ConfigurableEnvironment environment = applicationContext.getEnvironment();
            String username = environment.getProperty("user.name");
            String age = environment.getProperty("user.age");
            System.out.println("username:"+username+" | age:"+age);
            TimeUnit.SECONDS.sleep(1);
        }
    }
}

方式三:通过@NacosValue获取最新值

standalone使用,@NacosValue获取最新值nacos,配置信息需要写在配置类上

@Configuration
@EnableNacosConfig(globalProperties = @NacosProperties(serverAddr = "127.0.0.1:8848"))
@NacosPropertySource(dataId = "example", group="test",autoRefreshed = true)
public class NacosConfiguration { }
@Controller
public class ConfigController {
    @NacosValue(value = "${test.data}", autoRefreshed = true)
    private boolean data;                                     
    @RequestMapping(value = "/test", method = GET)
    @ResponseBody
    public boolean get() { return data; }
}

引入公共的配置文件

默认读取的配置文件是与服务名相同的配置文件的配置;若是还有其它公共的配置需要读取进来,就涉及到多个配置文件的读取。

在bootstrap配置文件中引入公共配置文件的方式:

  • 方式一:拓展配置方式

    可以引入与默认配置文件同一分组或不同分组的公共配置文件

    • 使用 extension-configs 按数组的方式引入配置,设置data_id、group、refresh值

      # 配置支持共享的 Data Id
      spring.cloud.nacos.config.extension-configs[0].data-id=comm.properties
      # 配置 Data Id 所在分组,缺省默认 DEFAULT_GROUP
      spring.cloud.nacos.config.extension-configs[0].group=DEFAULT_GROUP
      # 配置Data Id 在配置变更时,是否动态刷新,缺省默认 false
      spring.cloud.nacos.config.extension-configs[0].refresh=true
      
    • 使用 ext-configs 按数组的方式引入配置,设置data_id、group、refresh值

      (ext-config 官方标记废弃;取而代之的是 extension-configs)

  • 方式二:共享配置方式

    只能引入与默认配置文件同一分组的公共配置文件

    • 使用 shared-configs 按数组的方式引入配置,设置data_id、group、refresh值

      # 配置支持共享的 Data Id
      spring.cloud.nacos.config.shared-configs[0].data-id=comm.properties
      # 配置 Data Id 所在分组,缺省默认 DEFAULT_GROUP
      spring.cloud.nacos.config.shared-configs[0].group=DEFAULT_GROUP
      # 配置Data Id 在配置变更时,是否动态刷新,缺省默认 false
      spring.cloud.nacos.config.shared-configs[0].refresh=true
      
    • 使用 shared-dataids 来指定要读取共享配置文件的 DataID ,多个文件用 , 分隔使用 refreshable-dataids 指定共享配置文件,支持自动刷新

      (shared-dataids 官方标记废弃;取而代之的是 shared-configs)

      spring.cloud.nacos.config.shared-dataids=shareconfig1.yml,shareconfig2.yml
      spring.cloud.nacos.config.refreshable-dataids=shareconfig1.yml,shareconfig2.yml
      

配置文件读取优先级:

  • profile > 默认 > extension-configs(数组下标越大优先级越大) > shared-configs(数组下标越大优先级越大)
  • 数组下标越大优先级越大 即 数组中后面引入的会覆盖前面引入的相同项

支持profile粒度的配置

spring-cloud-starter-alibaba-nacos-config在加载配置的时候,不仅仅加载了以dataid为 ${spring.application.name}.${file-extension:properties} 为前缀的文件,还加载了dataid为 ${spring.application.name}-${profile}.${file-extension:properties} 的基础配置。在日常开发环境中,若是遇到多套配置的情况,可以使用spring提供的​ ${spring.profiles.active} 这个配置项配置选择哪个配置

#选择加载的文件为application-dev.properties
spring.profiles.active=dev

注意:

  • 只有默认的配置文件才能结合profile使用;除了默认配置文件,其它配置文件(例如:dev)都需要写上后缀。并且后缀必须跟配置的 boostrap.properties中 配置的扩展名(spring.cloud.nacos.config.file-extension=?)一致

  • 默认配置文件:跟服务名spring.application.name相同的DataId的配置文件(无文件扩展名),称之为默认配置文件


部署nacos

docker stack 部署 nacos 集群

docker stack部署nacos集群的部署模板:docker-compose-swarm-nacos.yml

注:

  • @nacos_image、@mysql_image 替换为实际的镜像
  • @nacos_logs_path、@nacos_data_path替换为实际的存储路径
version: "3.5"
services:
  nacos1:
    hostname: nacos1
    container_name: nacos1
    image: @nacos_image
    volumes:
      - @nacos_logs_path/nacos1:/home/nacos/logs
    ports:
      # 8848端口是Nacos对客户端提供服务的端口
      - "32101:8848"
    expose:
      # 7848是Nacos集群通信端口,用于Nacos集群间进行选举,检测等
      - "7848"
    environment:
      NACOS_REPLICAS: 3
      MYSQL_SERVICE_HOST: nacos-mysql
      MYSQL_SERVICE_DB_NAME: nacos_devtest
      MYSQL_SERVICE_PORT: 3306
      MYSQL_SERVICE_USER: nacos
      MYSQL_SERVICE_PASSWORD: nacos
      MODE: cluster
      NACOS_SERVER_PORT: 8848
      PREFER_HOST_MODE: hostname
      NACOS_SERVERS: nacos1:8848 nacos2:8848 nacos3:8848
    restart: always
    networks:
      - apps_net
    depends_on:
      - nacos-mysql
    deploy:
      labels: 
        name: nacos1
      placement:
        constraints:
          - node.role == manager
  
  nacos2:
    hostname: nacos2
    image: @nacos_image
    container_name: nacos2
    volumes:
      - @nacos_logs_path/nacos2:/home/nacos/logs
    expose:
      - "8848"
      - "7848"
    environment:
      NACOS_REPLICAS: 3
      MYSQL_SERVICE_HOST: nacos-mysql
      MYSQL_SERVICE_DB_NAME: nacos_devtest
      MYSQL_SERVICE_PORT: 3306
      MYSQL_SERVICE_USER: nacos
      MYSQL_SERVICE_PASSWORD: nacos
      MODE: cluster
      NACOS_SERVER_PORT: 8848
      PREFER_HOST_MODE: hostname
      NACOS_SERVERS: nacos1:8848 nacos2:8848 nacos3:8848
    restart: always
    networks:
      - apps_net
    depends_on:
      - nacos-mysql
    deploy:
      labels: 
        name: nacos2
      placement:
        constraints:
          - node.role == worker
  nacos3:
    hostname: nacos3
    image: @nacos_image
    container_name: nacos3
    volumes:
      - @nacos_logs_path/nacos3:/home/nacos/logs
    expose:
      - "8848"
      - "7848"
    environment:
      NACOS_REPLICAS: 3
      MYSQL_SERVICE_HOST: nacos-mysql
      MYSQL_SERVICE_DB_NAME: nacos_devtest
      MYSQL_SERVICE_PORT: 3306
      MYSQL_SERVICE_USER: nacos
      MYSQL_SERVICE_PASSWORD: nacos
      MODE: cluster
      NACOS_SERVER_PORT: 8848
      PREFER_HOST_MODE: hostname
      NACOS_SERVERS: nacos1:8848 nacos2:8848 nacos3:8848
    restart: always
    networks:
      - apps_net
    depends_on:
      - nacos-mysql
    deploy:
      labels: 
        name: nacos3
      placement:
        constraints:
          - node.role == worker
  
  nacos-mysql:
    hostname: nacos-mysql
    container_name: nacos-mysql
    image: @mysql_image
    volumes:
      - @nacos_data_path:/var/lib/mysql
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: nacos_devtest
      MYSQL_USER: nacos
      MYSQL_PASSWORD: nacos
    restart: always
    networks:
      - apps_net
    deploy:
      labels: 
        name: mysql
      placement:
        constraints:
          - node.role == manager

networks:
  apps_net:
    # 引用已创建的网络。手动创建命令:docker network create -d overlay --attachable apps_net
    external: true

k8s 部署 nacos 集群

使用 k8s 的 helm 工具部署nacos集群的部署模板:

values.yaml

mysqlImage: "harbor.paic.com.cn/library/nacos-mysql:latest"
nacosImage: "harbor.paic.com.cn/library/nacos-service:1.4.1"
busyboxImage: "harbor.paic.com.cn/library/busybox:1.30.0"
mysqlHost: "mysql"
mysqlPort: "3306"
mysqlDatabase: "nacos_devtest"
mysqlRootPassword: password
mysqlUser: nacos
mysqlPassword: nacos
nfsPath: /home/nacos/
nacosDataPath: /home/nacos/data
service_name: nacos
namespace: public

Chart.yaml

apiVersion: v1
appVersion: "1.0"
description: A Helm chart for Kubernetes
name: nacos
version: 0.1.0
maintainers:
- name: name

templates文件夹下 configmap.yaml

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: nacos-cm
  namespace: {{ .Values.namespace }}
data:
  mysql.host: mysql
  mysql.port: "3306"
  mysql.db.name: {{ .Values.mysqlDatabase }}
  mysql.user: {{ .Values.mysqlUser }}
  mysql.password: {{ .Values.mysqlPassword }}

templates文件夹下 mysql.yaml

apiVersion: v1
kind: ReplicationController
metadata:
  name: mysql
  namespace: {{ .Values.namespace }}
  labels:
    name: mysql
spec:
  replicas: 1
  selector:
    name: mysql
  template:
    metadata:
      labels:
        name: mysql
    spec:
      containers:
      - name: mysql
        image: {{ .Values.mysqlImage }}
        ports:
        - containerPort: 3306
        volumeMounts:
        - name: mysql-data
          mountPath: /var/lib/mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: {{ .Values.mysqlRootPassword }}
        - name: MYSQL_DATABASE
          value: {{ .Values.mysqlDatabase }}
        - name: MYSQL_USER
          value: {{ .Values.mysqlUser }}
        - name: MYSQL_PASSWORD
          value: {{ .Values.mysqlPassword }}
      volumes:
      - name: mysql-data
        hostPath:
          path: {{ .Values.nacosDataPath }}
      nodeSelector:
        nacos: nacos-mysql
---
apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: {{ .Values.namespace }}
  labels:
    name: mysql
spec:
  ports:
  - port: 3306
    targetPort: 3306
  selector:
    name: mysql

templates文件夹下 nacos.yaml

###使用自建数据库;使用Ingress发布配置后台###
---
apiVersion: v1
kind: Service
metadata:
  name: nacos-headless
  namespace: {{ .Values.namespace }}
  labels:
    caas_service: nacos-headless
spec:
  ports:
    - port: 8848
      name: server
      protocol: TCP
      targetPort: 8848
    - port: 7848
      name: rpc
      targetPort: 7848
  clusterIP: None
  sessionAffinity: None
  type: ClusterIP
  publishNotReadyAddresses: true
  selector:
    caas_service: {{ .Values.service_name }}
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: {{ .Values.service_name }}
  namespace: {{ .Values.namespace }}
spec:
  serviceName: nacos-headless
  replicas: 3
  template:
    metadata:
      labels:
        caas_service: {{ .Values.service_name }}
      annotations:
        pod.alpha.kubernetes.io/initialized: "true"
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                    - key: "caas_service"
                      operator: In
                      values:
                         - nacos-headless
              topologyKey: "kubernetes.io/hostname"
      containers:
        - name: k8snacos
          imagePullPolicy: Always
          image: {{ .Values.nacosImage }}
          resources:
            requests:
              memory: "2Gi"
              cpu: "500m"
          ports:
            - containerPort: 8848
              name: client
            - containerPort: 7848
              name: rpc
          livenessProbe:
            httpGet:
              path: /nacos
              port: 8848
            initialDelaySeconds: 10
            periodSeconds: 3
            timeoutSeconds: 1
            failureThreshold: 5
          readinessProbe:
            httpGet:
              path: /nacos
              port: 8848
            initialDelaySeconds: 10
            periodSeconds: 3
            timeoutSeconds: 1
            failureThreshold: 5
          env:
            - name: NACOS_REPLICAS
              value: "3"
            - name: MYSQL_SERVICE_HOST
              valueFrom:
                configMapKeyRef:
                  name: nacos-cm
                  key: mysql.host
            - name: MYSQL_SERVICE_DB_NAME
              valueFrom:
                configMapKeyRef:
                  name: nacos-cm
                  key: mysql.db.name
            - name: MYSQL_SERVICE_PORT
              valueFrom:
                configMapKeyRef:
                  name: nacos-cm
                  key: mysql.port
            - name: MYSQL_SERVICE_USER
              valueFrom:
                configMapKeyRef:
                  name: nacos-cm
                  key: mysql.user
            - name: MYSQL_SERVICE_PASSWORD
              valueFrom:
                configMapKeyRef:
                  name: nacos-cm
                  key: mysql.password
            - name: MODE
              value: "cluster"
            - name: NACOS_SERVER_PORT
              value: "8848"
            - name: PREFER_HOST_MODE
              value: "hostname"
            - name: NACOS_SERVERS
              value: {{ .Values.service_name }}-0.nacos-headless.{{ .Values.namespace }}.svc.cluster.local:8848 {{ .Values.service_name }}-1.nacos-headless.{{ .Values.namespace }}.svc.cluster.local:8848 {{ .Values.service_name }}-2.nacos-headless.{{ .Values.namespace }}.svc.cluster.local:8848
      initContainers:
      - command:
        - sh
        - -c
        - sleep 10; mkdir /wls/logs/nacos-0 /wls/logs/nacos-1 /wls/logs/nacos-2 -p;chown -R 798:5682 /wls/logs/nacos-0 /wls/logs/nacos-1 /wls/logs/nacos-2 ; echo init finished
        env:
        image: {{ .Values.busyboxImage }}
        imagePullPolicy: IfNotPresent
        name: init
        volumeMounts:
        - mountPath: /wls/logs/
          name: logs
      volumes:
      - name: logs
        hostPath:
          path: {{ .Values.nfsPath }}/logs
  selector:
    matchLabels:
      caas_service: {{ .Values.service_name }}

---
# ------------------- App Service ------------------- #
apiVersion: v1
kind: Service
metadata:
  name: {{ .Values.service_name }}-nodeport
  annotations:
    {{- range $key, $value := .Values.annotations }}
      {{ $key }}: {{ $value | quote }}
    {{- end }}
  labels:
    caas_service: {{ .Values.service_name }}
spec:
  ports:
  - name: "nodeport"
    port: 8848
    protocol: TCP
    targetPort: 8848
    nodePort: 32101
  publishNotReadyAddresses: true
  selector:
    caas_service: {{ .Values.service_name }}
  type: NodePort

检查nacos集群是否健康启动

nacos集群启动后,可能会出现容器状态是 health/running,但实际并未健康启动的情况,会导致微服务注册有问题,需要手动检查下

  1. 进入到nacos实例的容器内后执行命令以下,查看响应信息
curl -X PUT '127.0.0.1:8848/nacos/v1/ns/instance/beat?serviceName=nacos'
  1. 若nacos实例未健康启动,重启nacos的实例容器后再次检查(一般重启一次后就能健康启动)

    # nacos实例健康启动的响应信息
    {"clientBeatInterval":5000,"code":20404}
    
 类似资料: