Nacos

35

Nacos

Nacos是阿里巴巴的一个开源产品,它是针对微服务架构中的服务发现、配置管理、服务治理的综合型解决方案

官方文档

优点

服务发现与服务健康检查

Nacos使服务更容易注册,并通过DNS或HTTP接口发现其他服务,Nacos还提供服务的实时健康检查,以防止向不健康的主机或服务实例发送请求

动态配置管理

动态配置服务允许您在所有环境中以集中和动态的方式管理所有服务的配置,Nacos消除了在更新配置时重新部署应用程序,这使配置的更改更加高效和灵活

动态DNS服务

Nacos提供基于DNS协议的服务发现能力,旨在支持异构语言的服务发现,支持将注册在Nacos上的服务以域名的方式暴露端点,让三方应用方便的查阅及发现

服务和元数据管理

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

准备SQL

创建库名为nacos_config,在MySQL中执行

https://github.com/alibaba/nacos/blob/master/config/src/main/resources/META-INF/nacos-db.sql

并准备提供给Nacos的Mysql账号密码

CREATE USER 'nacos'@'%' IDENTIFIED BY 'nacos';
GRANT ALL PRIVILEGES ON nacos_config.* TO nacos@'%' IDENTIFIED BY 'nacos';

安装

参考配置

# 克隆docker-compose
git clone https://github.com/nacos-group/nacos-docker.git
cd nacos-docker
​
# MySQL5.7版本
COMPOSE_PROJECT_NAME=mysql_nacos docker-compose -f example/standalone-mysql-5.7.yaml up
# 集群
COMPOSE_PROJECT_NAME=cluster_nacos docker-compose -f example/cluster-hostname.yaml up 

最小启动

docker run -d -p 8848:8848 \
-e MODE=standalone \
--name nacos nacos/nacos-server:1.1.3

使用外部mysql

MYSQL_SLAVE_SERVICE_HOST也必须配置,可以与主机IP一致

详细内容参考官方文档

docker run -d \
-p 8848:8848 \
--name mysql_nacos \
--rm \
-e MODE=standalone \
-e SPRING_DATASOURCE_PLATFORM=mysql \
-e MYSQL_MASTER_SERVICE_HOST=106.12.80.131 \
-e MYSQL_MASTER_SERVICE_PORT=3306 \
-e MYSQL_MASTER_SERVICE_USER=nacos \
-e MYSQL_MASTER_SERVICE_PASSWORD=nacos \
-e MYSQL_MASTER_SERVICE_DB_NAME=nacos_config \
-e MYSQL_SLAVE_SERVICE_HOST=106.12.80.131 \
-e JVM_XMS=256m \
-e JVM_XMX=256m \
nacos/nacos-server:1.1.3

最后可以通过http://[ip地址]:8848/nacos/ 访问web界面 账号密码默认为nacos/nacos

添加登录用户

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>5.1.4.RELEASE</version>
</dependency>

通过spring-security将密文打印在控制台

System.out.println(new BCryptPasswordEncoder().encode("admin"));

执行以下SQL

第一个参数为用户名,第二个参数为控制台中打印的字符串,赋予ADMIN权限

USE nacos_config ;
​
INSERT INTO users (username, password, enabled) 
VALUES
  ('root', '$2a$10$9CMXe.ZXHjmDGuC7jXORTeYxTHf1/jrGkS.Opqyb5Vsg/1OtEqNf6', TRUE);
  
INSERT INTO roles(username, role)VALUES('root','ROLE_ADMIN');

修改配置

关闭登录功能

进入容器后

修改配置文件/home/nacos/conf/application.properties,后重启

## spring security config
### turn off security
# 添加以下
spring.security.enabled=false
management.security=false
security.basic.enabled=false
nacos.security.ignore.urls=/**
# 注释以下
#nacos.security.ignore.urls=/,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/login,/v1/console/health/**,/v1/cs/**,/v1/ns/**,/v1/cmdb/**,/actuator/**,/v1/console/server/**

关闭配置中心

/home/nacos/init.d/custom.properties

spring.cloud.nacos.config.enabled=false

约束

客户端约束

<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-client</artifactId>
    <version>1.1.3</version>
</dependency>

Java API

配置中心

获取配置工厂

ConfigService createConfigService(Properties properties)
  • properties 配置Properties

    serverAddr nacos所在的地址和端口号字符串

    namespace 名称空间(开发环境)

获取配置

String getConfig(String dataId, String group, long timeoutMs)
  • dataId 配置集(具体配置文件)

  • group 配置分组(具体项目)

  • timeoutMs 超时时间

配置监听

名称空间为配置工厂中配置的值

void addListener(String dataId, String group, Listener listener)
  • dataId 配置集

  • group 配置分组

  • listener 配置监听类

配置中心

配置

应用程序在启动和运行时需要读取配置信息,配置信息是伴随着应用程序的整个生命周期

  • 配置是独立于程序的只读变量

配置对于程序是只读的,程序通过读取配置来改变自己的行为,但是程序自身无法改变

  • 伴随应用整个生命周期

配置贯穿于应用的整个生命周期,应用在启动时通过读取配置来初始化,在运行时根据配置调整行为

  • 可以有多重加载方式

常见的有程序内部硬编码,配置文件,环境变量,启动参数,基于数据库等

  • 配置治理

同一份程序在不同的环境(开发、测试、生产),不同的集群(如不同的数据中心)经常需要有不同的配置,所以需要有完善的环境,集群配置管理

配置中心

在微服务架构中,当系统从一个单体应用,被拆分成分布式系统上一个个服务节点后,配置文件也必须跟着迁移(分割),这样配置就分散了,不仅如此,分散中还包含着冗余

配置中心将配置从各应用中剥离出来,对配置进行统一管理,应用自身不需要自己去管理配置

  • 用户在配置中心更新配置信息

  • 服务A和服务B及时得到配置更新通知,从配置中心获取配置

在传统巨型单体应用纷纷转向细粒度微服务架构的历史进程中,配置中心是微服务化不可缺少的一个系统组件,在这种背景下中心化的配置服务即配置中心应运而生,一个合格的配置中心需要满足如下特性

  • 配置项容易读取和修改

  • 分布式环境下应用配置的可管理性,即提供远程管理配置的能力

  • 支持对配置的修改的检视以把控风险

  • 可以查看配置修改的历史记录

  • 不同部署环境下应用配置的隔离性

命令行的方式

发布配置

成功返回true,也可以通过这种方式判断是否安装成功

curl -X POST "http://[IP]:8848/nacos/v1/cs/configs?dataId=nacos.cfg.dataId&group=test&content=HelloWorld"

获取配置

返回上面发布配置的HelloWorld

curl -X GET "http://[IP]:8848/nacos/v1/cs/configs?dataId=nacos.cfg.dataId&group=test"

发布配置

通过浏览器访问ip:8848/nacos,单配置管理->配置列表中查看和发布配置

发布配置

发布配置

Data ID : nacos-simple-demo.yaml
Group   : DEFAULT_GROUP
配置格式:  YAML
配置内容:  common: 
			config1: something

获取配置

public class NacosTest {
  private static final String serverAddr = "[IP]:8848";
  private static final String dataId = "nacos-simple-demo.yaml";
  private static final String group = "DEFAULT_GROUP";

  public static void main(String[] args) throws NacosException {
    Properties properties = new Properties();
    properties.put("serverAddr", serverAddr);
    ConfigService configService = NacosFactory.createConfigService(properties);
    String context = configService.getConfig(dataId, group, 5000);
    System.out.println(context);
  }
}

Nacos配置管理模型

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

配置管理模型

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

配置项 配置集中包含的一个个配置内容就是配置项,它代表一个具体的可配置的参数与其值域,通常以键值对的形式存在

Group 配置分组 配置分组是对配置集进行分组,通过一个字符串来表示,不同的配置分组下可以有相同的配置集(Data ID)。在Nacos上创建一个配置时,如果未填写配置分组的名称,则配置分组的名称默认采用DEFAULT_GROUP

学生管理系统的配置集可以定义为STUDENT_GROUP

Namespace 命名空间 可用于进行不同环境的配置隔离。例如可以隔离开发环境、测试环境和生产环境,因为 它们的配置可能各不相同,或者是隔离不同的用户,不同的开发人员使用同一个nacos管理各自的配置,可通过 namespace隔离。不同的命名空间下,可以存在相同名称的配置分组(Group) 或 配置集

隔离设计

namespace的设计是nacos基于此做多环境以及多租户(多个用户共同使用nacos)配置和服务互相隔离 可以通过名称空间区分开发、测试和生产环境

了解配置模型

添加命名空间

在服务管理 -> 命名空间中可以对命名空间进行管理,填写命名空间名称并生成命名空间ID

之后配置管理-> 配置列表中会增加新的命名空间

并为该命名空间添加新的配置后查询

默认的public为空字符串

Properties properties = new Properties();
properties.put("serverAddr", serverAddr);
properties.put("namespace", namespace);
ConfigService configService = NacosFactory.createConfigService(properties);
String context = configService.getConfig(dataId, group, 5000);
System.out.println(context);

其他配置

配置管理 -> 配置列表中可以通过导出选中配置和导入配置

克隆 可用于将配置迁移到其它Namespace

历史版本 可以对配置进行回滚

监听查询

Nacos提供配置订阅者即监听者查询能力,同时提供客户端当前配置的MD5校验值,以便帮助用户更好的检查配置变更是否推送到客户端

每次修改配置,都将触发事件,打印在控制台上

Properties properties = new Properties();
properties.put("serverAddr", serverAddr);
properties.put("namespace", namespace);
ConfigService configService = NacosFactory.createConfigService(properties);
configService.addListener(dataId, group, (ListenerAdapter) System.out::println);
Thread.sleep(Integer.MAX_VALUE);

适配器

// com.alibaba.nacos.api.config.listener.Listener
@FunctionalInterface
public interface ListenerAdapter extends Listener {
  default Executor getExecutor() {
    return null;
  }
  void receiveConfigInfo(String configInfo);
}

运用于分布式系统

单体架构

  • 开发效率高 模块之间交互采用本地方法调用,并节省微服务之间的交互讨论时间与开发成本

  • 容易测试 IDE都是为开发单个应用设计的、容易测试

  • 容易部署运维成本小,直接打包为一个完整的包,拷贝到web容器中即可运行

但是,上述的好处是有条件的,它适用于小型简单应用,对于大规模的复杂应用,就会展现出来以下的不足

  • 复杂性逐渐变高,可维护性逐渐变差 所有业务模块部署在一起,复杂度越来越高,修改时牵一发动全身

  • 版本迭代速度逐渐变慢,修改一个地方就要将整个应用全部编译、部署、启动时间过长、回归测试周期过长

  • 阻碍技术更新若更新技术框架,除非将系统全部重写,无法实现部分技术更新

  • 无法按需伸缩通过冗余部署完整应用的方式来实现水平扩展,无法针对某业务按需伸缩

微服务

应用分解为小的、互相连接的微服务解决以上问题

一个微服务一般完成某个特定的功能,比如订单服务、用户服务等等。每一个微服务都是完整应用,都有自己的业务逻辑和数据库

微服务架构的好处

  • 分而治之,职责单一

  • 易于开发、理解和维护、方便团队的拆分和管理

  • 可伸缩,能够单独的对指定的服务进行伸缩

  • 局部容易修改,容易替换,容易部署,有利于持续集成和快速迭代

  • 不会受限于任何技术栈

父工程

主要约束

<packaging>pom</packaging>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2.1.0.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Greenwich.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.1.3.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

子工程

<dependencies>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

配置文件

使用了配置中心,配置文件的名称改为bootstrap.yaml来代替application.yaml

指定了namespace,Group和Data ID

server:
  port: 56010

# 缺少了的Data id为spring.application.name和spring.cloud.file-extension拼接而成
# Data ID在这里为service1.yaml
spring:
  application:
    name: service1

  cloud:
    nacos:
      config:
        server-addr: [IP]:8848
        file-extension: yaml
        namespace: 6edf1ae8-3fa2-4ac1-87ae-4398fd84e011 # 不指定,默认为空
        group: TEST_GROUP # 不指定默认为DEFAULT_GROUP

动态更新

alibaba-nacos-config支持配置动态更新 需要在类上添加RefreshScope注解

@Autowired
private ConfigurableApplicationContext applicationContext;
// @Value注解获取配置文件,无法动态获取
@Value("${common.name}")
private String config;

@GetMapping("/config")
public String getConfig() {
  // 从spring上下文环境中获取 
  return applicationContext.getEnvironment().getProperty("common.name");
}

自定义扩展

多个微服务可能共用一部分配置

需要指定Data ID,同样不指定为DEFAULT_GROUP

ext-config的扩展配置只有配置了refresh: true才能动态更新

spring:
  application:
    name: service2
  cloud:
    nacos:
      # 主配置
      config:
        server-addr: [IP地址]:8848
        file-extension: yaml
        namespace: 6edf1ae8-3fa2-4ac1-87ae-4398fd84e011
        group: TEST_GROUP
        # 扩展配置文件,从0开始
        ext-config[0]:
          data-id: ext-config-common.properties # 同样不指定为DEFAULT_GROUP
        ext-config[1]:
          data-id: ext-config-common2.properties # GLOBAL_GROUP
          group: GLOBAL_GROUP
        ext-config[2]:
          data-id: ext-config-common3.properties
          group: REFRESH_GROUP                  # REFRESH_GROUP
          refresh: true # 动态刷新

共享配置,用这种方式也可以起到类似的效果

缺点是使用shared-dataids的配置文件group只能为DEFAULT_GROUP

spring:
  application:
    name: service2
  cloud:
    nacos:
      config:
        server-addr: [IP地址]:8848
        file-extension: yaml
        namespace: 6edf1ae8-3fa2-4ac1-87ae-4398fd84e011
        group: TEST_GROUP
        # 多个配置文件用逗号隔开,group都为DEFAULT_GROUP
        shared-dataids: ext-config-common.properties,ext-config-common2.properties,ext-config-common3.properties
        # 指定动态刷新的配置文件
        refreshable-dataids: ext-config-common3.properties

配置优先级

SpringCloudAlibaba Nacos Config提供了三种配置方式

  • spring.cloud.nacos.config.shared-dataids 支持多个Data ID的配置

  • spring.cloud.nacos.config.ext-config[n] 支持多个Data ID的配置,其中n的值越大优先级越高

  • 通过内部规则生成的Data ID配置

以上配置优先级从下到上依次提高

集群

docker-compose

启动失败可以查看logs/start.out日志

version: "2"
services:
  nacos1:
    hostname: nacos1
    container_name: nacos1
    image: nacos/nacos-server:1.1.3
    volumes:
      - /data/nacos/logs:/home/nacos/logs
      - /data/nacos/conf:/home/nacos/init.d
    ports:
      - "8848:8848"
      - "9555:9555"
    env_file:
      - ./nacos-hostname.env
    restart: always
    depends_on:
      - mysql
  nacos2:
    hostname: nacos2
    image: nacos/nacos-server:1.1.3
    container_name: nacos2
    volumes:
      - /data/nacos2/logs:/home/nacos/logs
      - /data/nacos2/conf:/home/nacos/init.d
    ports:
      - "8849:8848"
    env_file:
      - ./nacos-hostname.env
    restart: always
    depends_on:
      - mysql
  nacos3:
    hostname: nacos3
    image: nacos/nacos-server:1.1.3
    container_name: nacos3
    volumes:
      - /data/nacos3/logs:/home/nacos/logs
      - /data/nacos3/conf:/home/nacos/init.d
    ports:
      - "8850:8848"
    env_file:
      - ./nacos-hostname.env
    restart: always
    depends_on:
      - mysql
  mysql:
    container_name: mysql
    image: mysql:5.7
    volumes:
      - /data/mysqlMaster/my.cnf:/etc/mysql
      - /data/mysqlMaster/data:/var/lib/mysql
    ports:
      - "3306:3306"

环境

nacos-hostname.env

# 可以使用主机名作为参数,默认为IP
PREFER_HOST_MODE=hostname
# 集群
NACOS_SERVERS=nacos1:8848 nacos2:8848 nacos3:8848
# 数据库配置
SPRING_DATASOURCE_PLATFORM=mysql
MYSQL_MASTER_SERVICE_HOST=mysql
MYSQL_MASTER_SERVICE_PORT=3306
MYSQL_MASTER_SERVICE_USER=nacos
MYSQL_MASTER_SERVICE_PASSWORD=nacos
MYSQL_MASTER_SERVICE_DB_NAME=nacos_config 
MYSQL_SLAVE_SERVICE_HOST=mysql
# 防止内存溢出
JVM_XMS=128m
JVM_XMX=128m

集群

客户端配置

只需要在server-addr添加集群的IP端口号即可,多个用逗号隔开

spring:
  cloud:
    nacos:
      config:
        server-addr: [IP]:8848,[IP]:8849,[IP]:8850

高可用

当关掉Leader服务,剩下的服务中会选举出一个新的Leader(其他节点无法将已消失的节点剔除,可以查看集群任期的值变化),紧接着重启被关闭的服务,经过短暂的时间后,通信恢复

通过测试可以了解到集群部署已经达到了高可用的效果

官方推荐域名+VIP模式进行实现,当Nacos迁移时,客户端无需修改,数据库建议至少主备模式

服务发现

在微服务架构中,整个系统会按职责能力划分为多个服务,通过服务之间协作来实现业务目标。服务的消费方要调用服务的生产方,为了完成一次请求,消费方需要知道服务生产方的网络位置(IP地址和端口号)

在微服务环境中,由于服务运行实例的网络地址是不断动态变化的,服务实例数量的动态变化 ,因此无法使用固定的配置文件来记录服务提供方的网络地址,必须使用动态的服务发现机制用于实现微服务间的相互感知。各服务实例会上报自己的网络地址,这样服务中心就形成了一个完整的服务注册表,各服务实例会通过服务发现中心来获取访问目标服务的网络地址,从而实现服务发现的机制

服务发现解决了服务之间相互感知、服务管理

  • 在每个服务启动时会向服务发现中心上报自己的网络位置。在服务发现中心内部会形成一个服务注册表,服务注册表是服务发现的核心部分,是包含所有服务实例的网络地址的数据库

  • 服务发现客户端会定期从服务发现中心同步服务注册表,并缓存在客户端

  • 当需要对某服务进行请求时,服务实例通过该注册表,定位目标服务网络地址。若目标服务存在多个网络地址,则使用负载均衡算法从多个服务实例中选择出一个,然后发出请求

配置

子工程约束

主要约束

<dependencies>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery
        </artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
</dependencies>

客户端使用

服务端代码类似略

启动类

@SpringBootApplication
// 开启服务发现
@EnableFeignClients
// 开启服务调用
@EnableDiscoveryClient
public class ConsumerMain {
  public static void main(String[] args) {
    SpringApplication.run(ConsumerMain.class, args);
  }
}

Feign

调用远程接口

@FeignClient的value为服务端配置的spring.application.name

如果nacos服务发现中注册了多个同名的,则负载均衡使用,默认轮询

@FeignClient("quickstart-provider")
@Service
public interface ProviderClient {
  @GetMapping("/service")
  String service();
}

配置

application.yaml

server:
  port: 56010

spring:
  application:
    name: quickstart-provider
  cloud:
    nacos:
      discovery:
        # nacos作为服务发现
        server-addr: 106.14.8.213:8848

Controller

调用Feign接管的类

@RestController
public class ProviderController {
  @Autowired
  private ProviderClient providerClient;

  @GetMapping("/service")
  public String service() {
    String service = providerClient.service();
    return "consumer invoke: " + service;
  }
}

服务管理

需要在服务注册后,通过图形化界面来查看服务的注册情况,包括当前系统注册的所有服务和每个服务的详情,进行服务的查看、编辑注册的服务

  • 列表管理 可以在服务管理 -> 服务列表中查看Nacos注册中心中注册的微服务,并且还可以进行删除、搜索、创建等功能

  • 权重支持 服务列表中的服务集群的详情按钮,可以查看集群中具体每个服务的健康状态,通过编辑按钮修改服务权重,如果想让服务增加流量可以加大权重,如果不想接受可以调为0

  • 元数据管理 Nacos提供服务基于键值对的元数据的存储

  • 服务上下线 可以将服务上线和下线,如果列表中有设备处于下线状态,则该集群不会被归为健康

raft协议

在raft中,任何时候一个服务器可以扮演下面角色之一

  • Leader 所有请求的处理者,Leader副本接受client的更新请求,本地处理后再同步至多个其他副本

  • Follower 请求的被动更新者,从Leader接受更新请求,然后写入本地日志文件

  • Candidate候选人 如果Follower副本在一段时间内没有收到Leader副本的心跳,则判断Leader可能已经故障,此时启动选主过程,此时副本会变成Candidate状态,直到选主结束

  • term 这根民主社会的选举很像,每一届新的履职期称之为一届任期

    选举过程如下

...