SpringCloud Alibaba:理论+实践通关微服务灵魂摆渡者—Nacos

引言

我们都知道微服务项目需要一个注册中心来实现对服务的注册与发现,便于监控服务的健康状态,统一管理每个服务,例如eurka,zookeeper等都可以作为服务注册中心来使用,18年7月的时候阿里推出了一个名为Nacos的注册中心,随即便受到广大互联网公司的强烈追捧,很多使用eureka的项目都陆续被替换成Nacos,Nacos基本完美贴合了微服务的各大生态,不仅可以做注册中心,也可以做配置中心,同时管理界面符合国人审美,而且同时支持AP(高可用)和CP(强一致)模式,社区活跃,经历过双十一等大风大浪的洗礼,如今已被Spring收录为官方组件。下面就让我们一起来领略一下微服务灵魂摆渡者:Nacos。

Nacos集群搭建

Nacos可以选择单点或者集群部署,在生产环境中为了避免单点故障需要采用集群部署的方式。

准备工作

本文我们选择使用集群部署,结构图如下,使用Nginx做负载均衡,Mysql做数据存储:


集群结构图

三个 nacos 节点地址如下,正常应该使用三个不同的服务器,本文为了方便演示则使用本地电脑分配不同端口来进行模拟:

nacos1:
ip:192.168.31.122
port: 8845

nacos2:
ip:192.168.31.122
port: 8846

nacos3:
ip:192.168.31.122
port: 8847

初始化数据库

Nacos默认数据存储在内嵌数据库Derby中,不属于生产可用的数据库。官方推荐的最佳实践是使用带有主从的高可用数据库集群,这里我们以单点的数据库为例来讲解,首先新建一个数据库,命名为nacos,然后执行下面的SQL:

CREATE TABLE `config_info` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `data_id` varchar(255) NOT NULL COMMENT 'data_id',
  `group_id` varchar(255) DEFAULT NULL,
  `content` longtext NOT NULL COMMENT 'content',
  `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
  `src_user` text COMMENT 'source user',
  `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
  `app_name` varchar(128) DEFAULT NULL,
  `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
  `c_desc` varchar(256) DEFAULT NULL,
  `c_use` varchar(64) DEFAULT NULL,
  `effect` varchar(64) DEFAULT NULL,
  `type` varchar(64) DEFAULT NULL,
  `c_schema` text,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';

/******************************************/
/*   数据库全名 = nacos_config   */
/*   表名称 = config_info_aggr   */
/******************************************/
CREATE TABLE `config_info_aggr` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `data_id` varchar(255) NOT NULL COMMENT 'data_id',
  `group_id` varchar(255) NOT NULL COMMENT 'group_id',
  `datum_id` varchar(255) NOT NULL COMMENT 'datum_id',
  `content` longtext NOT NULL COMMENT '内容',
  `gmt_modified` datetime NOT NULL COMMENT '修改时间',
  `app_name` varchar(128) DEFAULT NULL,
  `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段';


/******************************************/
/*   数据库全名 = nacos_config   */
/*   表名称 = config_info_beta   */
/******************************************/
CREATE TABLE `config_info_beta` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `data_id` varchar(255) NOT NULL COMMENT 'data_id',
  `group_id` varchar(128) NOT NULL COMMENT 'group_id',
  `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
  `content` longtext NOT NULL COMMENT 'content',
  `beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps',
  `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
  `src_user` text COMMENT 'source user',
  `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
  `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';

/******************************************/
/*   数据库全名 = nacos_config   */
/*   表名称 = config_info_tag   */
/******************************************/
CREATE TABLE `config_info_tag` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `data_id` varchar(255) NOT NULL COMMENT 'data_id',
  `group_id` varchar(128) NOT NULL COMMENT 'group_id',
  `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
  `tag_id` varchar(128) NOT NULL COMMENT 'tag_id',
  `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
  `content` longtext NOT NULL COMMENT 'content',
  `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
  `src_user` text COMMENT 'source user',
  `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';

/******************************************/
/*   数据库全名 = nacos_config   */
/*   表名称 = config_tags_relation   */
/******************************************/
CREATE TABLE `config_tags_relation` (
  `id` bigint(20) NOT NULL COMMENT 'id',
  `tag_name` varchar(128) NOT NULL COMMENT 'tag_name',
  `tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',
  `data_id` varchar(255) NOT NULL COMMENT 'data_id',
  `group_id` varchar(128) NOT NULL COMMENT 'group_id',
  `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
  `nid` bigint(20) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`nid`),
  UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
  KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';

/******************************************/
/*   数据库全名 = nacos_config   */
/*   表名称 = group_capacity   */
/******************************************/
CREATE TABLE `group_capacity` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
  `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
  `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
  `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
  `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',
  `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
  `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';

/******************************************/
/*   数据库全名 = nacos_config   */
/*   表名称 = his_config_info   */
/******************************************/
CREATE TABLE `his_config_info` (
  `id` bigint(64) unsigned NOT NULL,
  `nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `data_id` varchar(255) NOT NULL,
  `group_id` varchar(128) NOT NULL,
  `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
  `content` longtext NOT NULL,
  `md5` varchar(32) DEFAULT NULL,
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `src_user` text,
  `src_ip` varchar(50) DEFAULT NULL,
  `op_type` char(10) DEFAULT NULL,
  `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
  PRIMARY KEY (`nid`),
  KEY `idx_gmt_create` (`gmt_create`),
  KEY `idx_gmt_modified` (`gmt_modified`),
  KEY `idx_did` (`data_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';


/******************************************/
/*   数据库全名 = nacos_config   */
/*   表名称 = tenant_capacity   */
/******************************************/
CREATE TABLE `tenant_capacity` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',
  `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
  `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
  `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
  `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
  `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
  `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';


CREATE TABLE `tenant_info` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `kp` varchar(128) NOT NULL COMMENT 'kp',
  `tenant_id` varchar(128) default '' COMMENT 'tenant_id',
  `tenant_name` varchar(128) default '' COMMENT 'tenant_name',
  `tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',
  `create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',
  `gmt_create` bigint(20) NOT NULL COMMENT '创建时间',
  `gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
  KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';

CREATE TABLE `users` (
    `username` varchar(50) NOT NULL PRIMARY KEY,
    `password` varchar(500) NOT NULL,
    `enabled` boolean NOT NULL
);

CREATE TABLE `roles` (
    `username` varchar(50) NOT NULL,
    `role` varchar(50) NOT NULL,
    UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE
);

CREATE TABLE `permissions` (
    `role` varchar(50) NOT NULL,
    `resource` varchar(255) NOT NULL,
    `action` varchar(8) NOT NULL,
    UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
);

INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);

INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');

下载nacos

nacos在GitHub上有下载地址:https://github.com/alibaba/nacos/releases

本例采用2.0.3版本(如果有跟着做的小伙伴最好使用同一个版本,版本不同可能会有问题):


在这里插入图片描述

nacos配置

本文我们以windows系统为例,linux操作方式是一模一样的,将下载的压缩包解压到任意非中文目录下:


nacos目录

目录说明:

  • bin:启动脚本
  • conf:配置文件

进入 nacos 的 conf 目录,修改配置文件 cluster.conf.example,重命名为 cluster.conf,删除原有内容,添加三个节点的 ip 和端口配置(注意ip填写电脑的真实 ip 地址,因为nacos启动的时候会获取本机 ip 判断是否已经成为集群节点,如果不是,会再次进行注册,而获取的本机 ip 是真实的 ip 地址,从而导致产生错误):

192.168.31.122:8845
192.168.31.122:8855
192.168.31.122:8865

注意:端口不要写连续的端口,会出现端口占用问题,一个 nacos 服务默认需要以下 4 个端口:

server.port(默认8848)
raft port: ${server.port} - 1000
grpc port: ${server.port} + 1000
# 如果端口连续,会出现此服务端口占用,导致服务启动失败
grpc port for server: ${server.port} + 1001

然后修改 application.properties 文件,最前面找到 “If use MySQL as datasource” ,下面添加数据库配置:

spring.datasource.platform=mysql

db.num=1

db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
#此处写自己数据的用户名
db.user.0=root
#此处写自己数据的密码
db.password.0=root

然后找到 nacos.inetutils.ip-address 配置项(此处如果不配置nacos可能会获取不到正确的ip,影响选举集群 leader 导致不可用):

# 填写自己电脑的ip
nacos.inetutils.ip-address=192.168.31.122

启动nacos集群

因为我们要部署三个节点,所以将解压后 nacos 文件夹复制三份,分别命名为:nacos1、nacos2、nacos3:


复制三份

然后分别修改三个文件夹中的 application.properties

nacos1:

server.port=8845

nacos2:

server.port=8855

nacos3:

server.port=8865

最后分别启动三个nacos节点,依次执行bin文件夹下的 startup.cmd 即可,(nacos依赖于jdk启动,需要配置jdk的环境变量才可正常启动)三个窗口均出现下图中的提示则代表集群启动成功!

nacos启动成功

nginx反向代理

我们已经成功启动了 nacos 集群,在这里我们选择使用 nginx 来对 nacos 进行反向代理,实现负载均衡,首先去nginx官网 http://nginx.org/en/download.html 下载 nginx 安装包,解压至任意非中文目录下:

nginx

修改conf/nginx.conf文件,在 http 节点下添加以下配置:

# 反向代理三个nacos节点
upstream nacos-cluster {
    server 127.0.0.1:8845;
    server 127.0.0.1:8855;
    server 127.0.0.1:8865;
}
# 监听8848端口
server {
    listen       8848;
    server_name  localhost;

    location /nacos {
        proxy_pass http://nacos-cluster;
    }
}

启动 nginx,window下双击nginx.exe即可,相信大家对 nginx 再熟悉不过了,不多赘述,一闪而过,启动成功。

然后在浏览器访问 http://127.0.0.1:8848/nacos

nacos

用户名和密码都是 nacos,对应之前创建的 users 表中插入的数据,插入到数据库中的密码是加密的,登录,查看一下集群管理菜单下的节点列表:
集群列表

展开节点元数据,发现 leader 已经选举成功了:
leader选举成功

到这里,nacos 集群就算部署成功了,nacos 和 eurka,nacos集群中的所有节点会选举出一个 leader 来执行更新,同步等操作,保证数据的一致性,而 eurka 所有的节点都是平等的,一致性相对较弱。

nacos 的集群选举算法采用的是 raft 算法,每个节点都有一个随机的时间计数器,倒计时结束后会推选自己作为 leader,如果半数以上的节点投票通过,则上位成功,然后持续发送心跳给其他节点维持自己的 leader 身份,如果心跳停止,其他节点会重新选举 leader,推选出新 leader 之后 老 leader 恢复会自动降级为follower。

只有大于半数的投票才能成为 leader 主要是为了防止 脑裂 现象,在这里就不深入探讨了,感兴趣的朋友可以自行查阅。

如果部署完之后节点元数据里没有 leader 信息,会导致 nacos 集群的很多功能不可用,可以去 log 文件夹底下的 nacos.log 查看具体报错解决(如果是按照本文步骤操作,一般不会出现报错)。

向Nacos注册服务

下面我们来测试一下向 nacos 注册服务实例。

创建srpingCloud微服务项目

利用idea创建一个 SpringCloud 项目,先创建 pom 父工程:


pom父工程

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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cloud.demo</groupId>
    <artifactId>cloud-demo</artifactId>
    <version>1.0</version>

    <packaging>pom</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
        <mysql.version>5.1.47</mysql.version>
        <mybatis.version>2.1.1</mybatis.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- springCloud -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--alibaba的管理依赖-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.5.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
</project>

创建一个子模块 demoService:


子模块

创建成功,如下:


子模块

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>cloud.demo</groupId>
        <artifactId>cloud-demo</artifactId>
        <version>1.0</version>
    </parent>

    <name>demo-service</name>
    <artifactId>demo-service</artifactId>
    <description>demo-service</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <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>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <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:

spring:
  cloud:
    nacos:
      server-addr: localhost:8848
  application:
    name: demoService
server:
  port: 8080

启动:


启动

控制台提示服务已经注册完成,看一下 nacos 的服务管理列表,服务已经被成功注册进来了:


服务注册成功

体验Nacos的服务分级存储

一个服务可以存在有多个实例,例如我们的demoService,可以启动三次,生成三个实例:

  • 127.0.0.1:8080
  • 127.0.0.1:8081
  • 127.0.0.1:8082

假如这些实例分布于全国各地的不同机房,例如:

  • 127.0.0.1:8080,在天津机房
  • 127.0.0.1:8081,在天津机房
  • 127.0.0.1:8082,在北京机房

使用Nacos可以将同一机房内的实例划分为一个集群,也就是说,一个服务可以包含多个集群,如天津集群北京集群,每个集群下可以有多个实例,形成分级模型,如图:

集群模型

下面让我们给 demoService 配置集群,然后重启:

spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        # 集群名称
        cluster-name: TJ 
  application:
    name: demoService
server:
  port: 8080

复制 application.yml,分别改名为 application-tj.yml 和 application-bj.yml

application-tj.yml:

spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        # 集群名称
        cluster-name: TJ
  application:
    name: demoService
server:
  port: 8081

application-bj.yml:

spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        # 集群名称
        cluster-name: BJ
  application:
    name: demoService
server:
  port: 8082

然后复制两个实例启动:


copy

配置

启动完成后,查看 nacos 服务列表管理界面,集群数变为 2,实例数变为 3:


集群和实例

点击详情查看:
查看

到此,我们便实现了服务的分级存储。

正常情况,微服务互相访问时,应该尽可能访问同集群实例,因为本地访问速度更快,当本集群内不可用时,才访问其它集群。

我们需要搭建另一个服务来访问 demoService 体验同集群优先访问的效果,首先在 demoService 新增一个接口供外界访问(具体代码就不贴了),添加之后重启三个 demoService 服务:


代码

然后搭建 requestService 服务访问 demoService 服务的 now 接口,这里我们使用了 feign 来进行访问:


项目结构

配置 application.yml,集群名称配置 TJ:
spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        # 集群名称
        cluster-name: TJ
  application:
    name: requestService
server:
  port: 8083
  
demoService:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule  # 负载均衡规则
    
feign:
  httpclient:
    enabled: true # 支持HttpClient的开关
    max-connections: 200 # 最大连接数
    max-connections-per-route: 50 # 单个路径的最大连接数

springCloud 默认的负载均衡策略并不能实现根据同集群优先来实现负载均衡,因此Nacos中提供了一个NacosRule 的实现,可以优先从同集群中挑选实例。

修改 requestService 的application.yml文件,添加负载均衡规则(上面的 yaml 中已经有了):

demoService:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule  # 负载均衡规则

启动,访问 http://localhost:8083/request/now,观察之前启动的三个 demoService 的控制台输出,发现只有 TJ 节点 8080 和 8081 处理了请求:

8080

8081

8082

然后关闭 8080 和 8081 服务,TJ 集群内没有可用服务,则不得不去访问 BJ 的服务,日志如下:
跨集群访问发生

如此一来,便实现了优先访问同一集群内的效果,分级存储模型的优点也因此得到体现。

Nacos权重配置

启动之前关闭的两个 demoService服务,接下来看一下权重配置。

实际部署中会出现这样的场景:

服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求。

但默认情况下NacosRule是同集群内随机挑选,不会考虑机器的性能问题。因此,nacos 提供了权重配置来控制访问频率,权重越大则访问频率越高。

通过 nacos 后台管理界面我们可以很方便的修改每个服务的权重,在服务列表点击详情,然后选择实例即可编辑实例的权重:


权重配置

注意:如果权重修改为0,则该实例永远不会被访问。

在实际运用中,如果服务有更新,可以先部署新服务,权重设置小一些,观察情况,如果一切正常,可以把权重设置高一些,这样大部分流量便会打到新服务节点,随后逐渐下线老服务即可,实现服务的灰度发布(用户无感知)。

Nacos环境隔离

Nacos提供了namespace来实现环境隔离功能。

  • nacos中可以有多个namespace
  • namespace下可以有group、service等
  • 不同namespace之间相互隔离,例如不同namespace的服务互相不可见:


    环境隔离

    默认情况下,所有service、data、group都在同一个namespace,名为public:


    public

    下面我们自己添加一个 namespece:
    添加namespace

    添加完毕

    接下来我们给 requestService 配置 namespace,修改 application.yml:

spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        # 集群名称
        cluster-name: TJ
        # 控制台命名空间的id
        namespace: a58f82bf-ab3d-4051-8134-25ba052ec373
  application:
    name: requestService
server:
  port: 8083
demoService:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule  # 负载均衡规则
feign:
  httpclient:
    enabled: true # 支持HttpClient的开关
    max-connections: 200 # 最大连接数
    max-connections-per-route: 50 # 单个路径的最大连接数

重启,可以看到管理界面已经出现了我们配置的 namespace:

demo

再次访问 http://localhost:8083/request/now,日志报错:
日志报错

至此,我们成功实现了 nacos 的环境隔离效果。

Nacos配置管理

Nacos除了可以做注册中心,还可以做配置中心来使用。

当微服务部署的实例越来越多,达到数十、数百时,逐个修改微服务配置就会让人抓狂,而且很容易出错。我们需要一种统一配置管理方案,可以集中管理所有实例的配置。

Nacos一方面可以将配置集中管理,另一方可以在配置变更时,及时通知微服务,实现配置的热更新。

在nacos中添加配置文件

下面我们再 nacos 管理后台新增配置文件。

点击配置列表,点击新增:


1

然后在弹出的表单中,填写配置信息,填写完毕,点击发布:


新增配置

发布成功:
发布成功

注意:项目的核心配置,需要热更新的配置才有放到nacos管理的必要。基本不会变更的一些配置还是保存在微服务本地比较好。

从微服务拉取配置

微服务要拉取nacos中管理的配置,并且与本地的application.yml配置合并,才能完成项目启动。

但如果尚未读取application.yml,又如何得知nacos地址呢?

因此spring引入了一种新的配置文件:bootstrap.yaml文件,会在application.yml之前被读取,流程如下:


拉取配置

首先,在 demoService 服务中,引入 nacos-config 的客户端依赖:

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

然后,在 demoService 中添加一个 bootstrap.yml 文件,内容如下:

spring:
  application:
    name: demoService # 服务名称
  profiles:
    active: dev #开发环境,这里是dev
  cloud:
    nacos:
      server-addr: localhost:8848 # Nacos地址
      config:
        file-extension: yaml # 文件后缀名

这里 demoService 服务会根据 spring.cloud.nacos.server-addr 获取nacos地址,再根据

${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}作为文件id,来读取配置。

本例中,就是去读取demoService-dev.yaml

配置热更新

我们最终的目的,是修改nacos中的配置后,微服务中无需重启即可让配置生效,也就是配置热更新

有两种方式可以实现:

方式一:

@Value注入的变量所在类上添加注解@RefreshScope

@Slf4j
@RestController
@RequestMapping("/demo")
@RefreshScope
public class DemoController {

    @Value("${pattern.dateformat}")
    private String dateformat;

    @GetMapping("/now")
    public String now(){
        log.info("收到,over");
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
    }
}

方式二:

使用@ConfigurationProperties注解代替@Value注解。

demoService服务中,添加一个类,读取patterrn.dateformat属性:

@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
    private String dateformat;
}

使用这个类代替@Value

@Slf4j
@RestController
@RequestMapping("/demo")
public class DemoController {

    @Autowired
    private PatternProperties patternProperties;

    @GetMapping("/now")
    public String now(){
        log.info("收到,over");
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));
    }
}

同学们自行测试即可。

配置共享

微服务启动时,会去 nacos 读取多个配置文件,例如:

  • [spring.application.name]-[spring.profiles.active].yaml,例如:demoService-dev.yaml
  • [spring.application.name].yaml,例如:userservice.yaml

[spring.application.name].yaml不包含环境,因此可以被多个环境共享,同上一节讲到的内容,大家可以在配置管理界面新增配置 userservice.yaml,进行配置共享的测试。

配置共享的优先级

当nacos、服务本地同时出现相同属性时,优先级有高低之分,如下图:


配置优先级

Nacos实例类型

Nacos的服务实例分为两种类型:

  • 临时实例:如果实例宕机超过一定时间,会从服务列表剔除,默认的类型。
  • 非临时实例:如果实例宕机,不会从服务列表剔除,也可以叫永久实例。

配置一个服务实例为永久实例:

spring:
  cloud:
    nacos:
      discovery:
        ephemeral: true# 设置为永久实例

Nacos与Eureka的区别

  • Nacos与eureka的共同点:

    • 都支持服务注册和服务拉取
    • 都支持服务提供者心跳方式做健康检测
  • Nacos与Eureka的区别:

    • Nacos支持服务端主动检测实例状态:临时实例采用心跳模式,非临时实例采用主动检测模式
    • 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
    • Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
    • Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式

综上,nacos更香!

结语

关于 nacos 的介绍到这里就结束了,现在 nacos 在微服务项目中有着十分广泛的应用,我们需要去理解和掌握,nacos 选举 leader 采用的 raft 算法同学们可以自行查阅资料了解一下,这也属于高频面试点,本文主要是讲了 nacos 的应用,关于它的思想和原理还是需要大家在使用的过程中逐渐体会!

纸上学来终觉浅,绝知此事要躬行,在此与诸君共勉!

关注公众号 螺旋编程极客 发送 微服务 可以获取本文的源码以及微服务大礼包哦!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容