SpringCloud之——Nacos注册中心与配置中心、Sentinet服务降级与熔断

0.注册中心(Nacos)

自从阿里巴巴将自己的微服务组件加入到SpringCloud中,成为现在的SpringCloudAlibaba,目前国内使用SpringCloudAlibaba的也越来越多。特别是其中的Nacos组件,同时具备了Eureka和SpringCloudConfig的功能,得到了很多国内企业的喜爱。

0.1.认识Nacos

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

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

image-20201027210027942

官网地址:https://nacos.io/zh-cn/index.html

Nacos 的关键特性包括:

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

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

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

  • 动态配置服务

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

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

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

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

0.2.安装Nacos

开发阶段采用单机安装即可。

1)下载安装包

在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:

GitHub主页:https://github.com/alibaba/nacos

GitHub的Release下载页:https://github.com/alibaba/nacos/releases

image-20201027210542250

网络不好的同学,可以直接使用课前资料提供的安装包:

image-20201027210821335

2)解压

将这个包解压到任意非中文目录下,如图:

image-20201027211200302

目录说明:

  • bin:启动脚本
  • conf:配置文件
  • data:本地数据
  • logs:日志

Nacos的默认端口是8848,如果你电脑上的其它进程占用了8848端口,请先尝试关闭该进程。

如果无法关闭占用8848端口的进程,也可以进入nacos的conf目录,修改配置文件中的端口:

image-20201027214057922

3)启动

启动非常简单,进入bin目录,结构如下:

image-20201027214339822

然后执行命令即可:

  • windows命令:

    1
    startup.cmd -m standalone
  • Linux或Mac的命令:

    1
    sh startup.sh -m standalone

4)访问

在浏览器输入地址:http://127.0.0.1:8848/nacos即可:

image-20201027214842577

默认的账号和密码都是nacos,进入后:

image-20201027214957096

5)基于Docker安装并启动nacos

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 注意:使用Nacos1.4.2版本

# 安装Nacos
docker pull nacos/nacos-server:1.4.2




# 简单版
docker run -id -e MODE=standalone -e JVM_XMS=256m -e JVM_XMX=256m -e JVM_XMN=256m --name nacos -p 8848:8848 nacos/nacos-server:1.4.2


# 安装Mysql
docker pull mysql:5.7.37

# 运行Mysql(在/opt目录下创建文件夹放置映射文件)
docker run --name mysql-server -v $PWD/conf:/etc/mysql/conf.d -v $PWD/logs:/logs -v $PWD/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d -i -p 3306:3306 mysql:5.7.37

# 导入数据库
SQL文件:nacos-mysql.sql

# 基于MySQL版本
docker run -e MODE=standalone \
-e SPRING_DATASOURCE_PLATFORM=mysql \
-e MYSQL_SERVICE_HOST=192.168.3.100 \
-e MYSQL_SERVICE_PORT=3306 \
-e MYSQL_SERVICE_DB_NAME=nacos \
-e MYSQL_SERVICE_USER=nacos \
-e MYSQL_SERVICE_PASSWORD=123456 \
-e MYSQL_DATABASE_NUM=1 \
-e JVM_XMS=256m -e JVM_XMX=256m -e JVM_XMN=256m \
--name nacos -d -p 8848:8848 nacos/nacos-server:1.4.2
Mysql导入数据库与账户
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
CREATE DATABASE nacos_config CHARACTER SET utf8;
USE nacos_config;

CREATE USER "nacos"@"%" IDENTIFIED BY "123456";
GRANT ALL ON nacos_config.* TO "nacos"@"%";




/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info */
/******************************************/
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');

0.3.搭建工程

下面我们通过搭建一个Demo工程,学习下Nacos的基本使用。

1)导入

首先,与eureka学习的时候一样,我们也需要准备服务的提供者和消费者,这次我们不再自己写了,而是导入资料中准备好的一个Demo:

image-20201027215139576

项目中包括:

  • nacos-demo:父工程,管理依赖
    • user-service:用户服务,提供根据id查询用户的功能,端口是8081
    • consumer-service:消费者服务,通过RestTemplate远程查询user-service中的用户信息,端口是8080

如图:

image-20201027215415963

2)代码流程介绍:

消费者consumer-service的启动类中,定义了RestTemplate:

image-20201027225756040

然后,在consumer的cn.itcast.consumer.web包中,提供了一个controller,并远程调用user-service

3)访问测试

启动user-service和consumer,然后在浏览器输入地址访问:http://localhost:8080/consumer/rest/1

0.4.Nacos注册中心

Nacos与Eureka一样,都可以作为注册中心使用,并且Nacos实现了SpringCloudCommon中的一些接口,并且提供了对应的自动配置,这就让Nacos的注册中心使用与Eureka几乎一模一样,没有什么学习成本。

0.4.1.服务注册

我们先将user-service注册到Nacos,基本步骤如下:

  • 导入依赖
  • 配置Nacos地址

1)导入依赖

为了统一管理SpringCloudAlibaba的组件版本,我们已经在父工程nacos-demo的pom文件中引入一个依赖,位置在<dependenciesManagement>下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

版本锁定

1
2
3
4
5
6
7
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<java.version>1.8</java.version>
<spring.cloud-version>2021.0.1</spring.cloud-version>
<spring-alibaba.version>2021.1</spring-alibaba.version>
</properties>

这样,我们在引入alibaba的其它相关组件时就无需指定版本了。

我们在user-service的pom.xml文件中引入依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--alibaba的 Nacos依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<!--不使用Ribbon 进行客户端负载均衡-->
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--Spring的健康检测依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>


2)配置Nacos地址

我们修改user-serviceapplication.yml文件,其中配置nacos的地址:

1
2
3
4
5
6
7
8
9
10
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # nacos服务地址
management:
endpoints:
web:
exposure:
include: "*" # 暴露健康检测的接口

3)启动

重启user-service,然后去访问Nacos的页面:http://127.0.0.1:8848/nacos可以发现服务已经启动

0.4.2.服务发现

服务发现可以用RestTemplate实现,也可以用OpenFeign来实现。

1)引入依赖

在consumer-service的pom文件中,引入依赖:

1
2
3
4
5
6
7
8
9
10
<!--alibaba的 Nacos依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--Spring的健康检测依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

2)配置Nacos地址

在consumer-service的bootstrap.yaml文件中,配置nacos地址:

1
2
3
4
5
6
7
8
9
10
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # nacos服务地址
management:
endpoints:
web:
exposure:
include: "*" # 暴露健康检测的接口

3)RestTemplate服务发现

RestTemplate服务发现和负载均衡与之前玩法完全一样,我们给consumer-service的启动类:ConsumerApplication中的restTemplate上添加@LoadBalanced注解

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}

@Bean
@LoadBalanced // 新加
public RestTemplate restTemplate(){
return new RestTemplate();
}
}

然后,修改consumer-service项目中的ConsumerController代码,将请求url中的IP和端口改为服务id:user-service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
@RequestMapping("consumer")
public class ConsumerController {

@Autowired
private RestTemplate restTemplate;

@GetMapping("{id}")
public User consumerUserById(@PathVariable("id") Long id){
//String url = "http://127.0.0.1:8081/user/"+id;
String url = "http://user-service/user/"+id;
return restTemplate.getForObject(url,User.class);
}
}

重启consumer-service,然后在浏览器测试

4)Feign服务发现

Feign的负载均衡也与之前没有任何区别。

首先导入Feign启动器

1
2
3
4
5
6
7
8
9
10
11
<!-- Feign启动器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!-- 2021.1版本的SpringCloudAlibaba的组件没有使用Ribbon均衡负载,而是使用loadbalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>

然后修改consumer-service的启动类ConsumerApplication类,添加@EnableFeignClients注解

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableFeignClients
public class ConsumerApplication {

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

}

然后在consumer-service中的cn.itcast.consumer.clients包中,新建一个UserClient接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package cn.itcast.consumer.clients;

import cn.itcast.consumer.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;


@FeignClient("user-service")
public interface UserClient {

@GetMapping("/user/{id}")
User queryUserById(@PathVariable("id") Long id);
}

然后,在consumer-service的cn.itcast.consumer.web.ConsumerController类中添加一段新的代码,使用UserClient来访问:

1
2
3
4
5
6
7
@Autowired
private UserClient userClient;

@GetMapping("/client/{id}")
public User queryUserById(@PathVariable("id") Long id){
return userClient.queryUserById(id);
}

1 分布式配置中心(Nacos)

1.1 什么是分布式配置中心

​ 在微服务架构下,每个服务都会都会有自己的配置文件,此时传统的配置文件方式则会造成诸多问题:

  • 时效性:修改配置,需要重启服务才能生效。
  • 局限性:无法支持动态调整,如服务地址修改。

因此,分布式配置中心应运而生。其为所有的微服务提供了一个中心化的外部配置环境, 服务的配置信息都可以存放到分布式配置上,从而解决如上的诸多问题。

image-20210214233232447

1.2 技术选型对比

​ 现在市面上对于分布式配置中心的实现有很多种,如SpringCloud的Bus+Config、携程的Apollo、百度的Disconf、XXL-Conf、Zookeeper、阿里的Nacos等等。

​ 他们各自都有一些各自的特点,其中最为活跃和主流的当属:携程的Apollo阿里的Nacos。由于当前项目已经整合了Nacos,所以此处讲解Nacos作为分布式配置中心的使用。

2.3 Nacos配置中心使用

2.3.1 整合Nacos配置中心

1)修改consumer-service.pom文件,添加nacos配置中心依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--nacos-config-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!--踩坑:Spring Cloud 2020版本之后,需要添加此依赖才能加载bootstrap.xml文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

<!--有些版本的Spring cloud需要引入该jar才能使bootstrap配置文件生效-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
</dependency>

2)修改consumer-service的bootstrap.yaml文件,添加配置中心Nacos的地址

1
2
3
4
5
 bootstrap.yml(bootstrap.properties)用来在程序引导时执行,应用于更早期配置信息读取,如可以使用来配置application.yml中使用到参数等

​ application.yml(application.properties) 应用程序特有配置信息,可以用来配置后续各个模块中需使用的公共参数等。

​ bootstrap.yml 先于 application.yml 加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 踩坑:此配置内容必须全部放在bootstrap.yml中
spring:
application:
name: consumer
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # nacos服务地址
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
management:
endpoints:
web:
exposure:
include: "*" # 暴露健康检测的接口

2.3.2 基本使用

1)nacos配置中心新建配置文件

规范如下:

  • 配置文件名称:默认和 spring.application.name一致
  • 文件后缀:nacos支持六种文件格式。分别为:TEXT、JSON、XML、YAML、HTML、Properties。默认是 properties,当项目指定后缀名,则同项目中一致。

新建配置:

image-20210624120049147

1
company: itheima

image-20210625094822708

2)修改UserController,获取配置中心内容

1
2
3
4
5
6
7
@Value("${company}")
private String companyName;

@RequestMapping("/hello")
public String hello(){
return "hello user service,company : "+companyName;
}

3)测试访问用户服务hello接口

image-20210625094945739

不同的命名空间下,可以存在相同名称的配置分组(Group) 或 配置集。

最佳实践

Nacos抽象定义了Namespace、Group、Data ID的概念,具体这几个概念代表什么,取决于我们把它们看成什么,这里推荐给大家一种用法,如下图:

  • Namespace:代表不同环境,如开发、测试、生产环境。

  • Group:代表某项目,如XX医疗项目、XX电商项目

  • DataId:每个项目下往往有若干个工程,每个配置集(DataId)是一个工程的主配置文件

image-20210422212331985

2.3.3 动态刷新

当修改配置中心内容后,项目中并不能得到及时更新。 此时就需要配置动态刷新。实现方式非常简单,只需要在需要获取配置中心内容的Controller上添加@RefreshScope即可。

image-20210215002234057

踩坑:前面的bootstrap.xml文件一定不能少配置,否则无法刷新。。。

2.3.4 多环境切换

项目开发过程中,可能会存在多种环境,并且每一种环境所设置的配置都是不同的。nacos可以同时支持多环境配置。只需要在nacos配置中心中根据dataId进行区分即可。dataId 完整的拼接格式如下:

1
${prefix}-${spring.profiles.active}.${file-extension}
  • prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix来配置。
  • spring.profiles.active 即为当前环境对应的 profile
  • file-extension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension来配置。

1)在nacos配置中心上新建配置文件consumer-dev.yml、consumer-prod.yml

consumer-dev.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
server:
port: 8085
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # nacos服务地址

management:
endpoints:
web:
exposure:
include: "*" # 暴露健康检测的接口

consumer-prod.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
server:
port: 8082
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # nacos服务地址

management:
endpoints:
web:
exposure:
include: "*" # 暴露健康检测的接口

image-20210422230842006

2)修改consumer-service服务的bootstrap.yml

1
2
3
4
5
6
7
8
9
10
11
12
spring:
application:
name: consumer
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
file-extension: yml

profiles:
active: prod # 激活Prod配置文件

3)启动consumer-service服务测试

2.3.5 共享配置

踩坑:共享配置不能实时刷新,必须重启服务(不是nacos服务)

在开发中,虽然可以在不同环境下使用不同的配置文件,但是有一些配置是通用的,需要在不同的环境下,都进行生效。

1) 当开发环境为:dev时

image-20210422231751076

2) 当开发环境为:prod时

image-20210422231620190

​ 根据上述测试,可以发现,不同的开发环境下都会去加载consumer-service.yml,也就是没有指定特定环境的文件。那么对于通用配置就可以设置在这个文件中。

也可以在bootstrap.yml设置指定通用的配置文件

1
2
3
4
5
6
7
8
9
10
spring:
cloud:
nacos:
config: # nacos配置中心地址
server-addr: 192.168.190.149:8848 # 配置中心服务器地址
file-extension: yml # 配置文件类型,配置文件的扩展名
namespace: b4804e31-5f6b-4f4e-a7f1-49e3afbbd089 # 命名空间默认为public (命名空间的id值)
shared-configs: consumer-tongyong.yml # 添加通用的配置文件
profiles:
active: prod # 设置激活的配置文件(设置当前使用的配置文件)

2.3.6 配置持久化

2.3.6.1 Nacos持久化配置说明

​ 当我们使用默认配置启动Nacos时,所有配置的信息都被Nacos保存在了内嵌数据库derby中。会存在以下问题:

  1. 使用内嵌数据库,注定会有存储上限
  2. 不适合集群,分布式环境
  3. 不方便观察数据存储的基本情况

0.7版本之后增加了支持mysql数据源能力,也是工作里面常用的方式。

2.3.6.2 Nacos持久化配置实现

注意:Docker运行时配置了数据库,并且数据库中导入了持久化sql就行

MySQL部署

(1)拉取mysql镜像

1
docker pull mysql:5.7

(2)创建容器

1
docker run -id -p 3306:3306 --name=mysql -v mysql_conf:/etc/mysql/conf.d -v mysql_logs:/logs -v mysql_data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root mysql:5.7

-p 代表端口映射,格式为 宿主机映射端口:容器运行端口

-e 代表添加环境变量 MYSQL_ROOT_PASSWORD 是root用户的登陆密码

(3)远程登录mysql

连接宿主机的IP ,指定端口为3306

如果出现异常:IPv4 forwarding is disabled. Networking will not work

解决方案:

1
2
3
4
5
6
7
vi /etc/sysctl.conf
net.ipv4.ip_forward=1 #最下方添加这段代码
#重启network服务
systemctl restart network && systemctl restart docker
#查看是否修改成功 (备注:返回1,就是成功)
[root@docker-node2 ~]# sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1

1)mysql中新建nacos_config数据库,并执行资料/nacos持久化下的nacos-mysql.sql

参考github:https://github.com/alibaba/nacos/blob/master/distribution/conf/nacos-mysql.sql

image-20210215012432156

2)进入nacos容器

1
docker exec -it nacos /bin/bash

3)上传资料中application.properties,并复制覆盖容器中的文件

1
docker cp application.properties nacos:/home/nacos/conf/application.properties

4)重启nacos容器

1
docker restart nacos

访问nacos可以发现,配置中心中没有了配置内容,因为nacos已经连接了mysql,mysql中并没有配置文件

5)新建consumer,consumer-dev.yml,consumer-prod.yml。可以发现config_info表中也新增了对应数据

image-20210625101836740

2 系统保护(Sentinet)

2.1 为什么需要系统保护

随着微服务的流行,服务和服务之间的稳定性变得越来越重要,通常一个业务调用需要经过多个后端微服务,假设在整个调用链路上,某个服务不可用的话,则有可能造成整个调用链路上的多个微服务不可用,这种效应称为服务雪崩(级联故障/失效)

image-20210214204635680

出现服务雪崩的常见场景:

  • 程序异常:执行过程出现执行异常、程序逻辑造成内存泄漏、频繁FullGC等等。
  • 流量暴增:在一些特殊场景,如秒杀、促销。造成前端的大量请求并发的发送到后端。
  • 硬件故障:服务器故障、断电等。
  • 同步等待:上游服务长时间等待下游服务的响应,造成资源耗尽。

因此在微服务架构下,需要让服务具备自我保护的能力,避免被关联服务拖垮的风险。所以系统保护技术应运而生且尤为重要。

解决方案:

  • 超时机制
  • 限流
  • 舱壁模式
  • 断路器模式(Hystrix)

2.2 技术选型对比

当前对于系统保护,常见的有三种技术实现,分别为:Spring Cloud HystrixResilience4jSpring Cloud Alibaba Sentinel。在最新版的Spring Cloud2020.0.0版本中hystrix已经被移除,转而支持使用Resilience4j。但是不管是hystrix还是Resilience4j,在功能全面性上和使用灵活性上都不如Spring Cloud Alibaba Sentinel。

image-20210214222904581

2.3 Sentinel使用

2.3.1 概述

image-20210214223022989

Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

Sentinel 具有以下特征:

  • 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
  • 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
  • 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
  • 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

Sentinel 的主要特性:

image-20210214223146720

使用 Sentinel 来进行熔断保护,主要分为几个步骤:

  1. 定义资源
  2. 定义规则
  3. 检验规则是否生效

2.3.2 Sentinel快速入门

2.3.2.1 Sentinel控制台安装

​ Sentinel可以直接基于控制台定义系统保护的相关规则,并且阿里提供了两种控制台的介入方式:本地jar包启动Docker运行、阿里云平台

官方下载地址:https://github.com/alibaba/Sentinel/releases

Wiki主页:https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E9%A1%B5

方式一:

基于cmd,启动资料/sentinel下的sentinel-dashboard.jar ,即可启动sentinel控制台。 访问路径:localhost:8080。默认账号密码:sentinel/sentinel

image-20210517151608747

1
2
# 守护进程运行
nohup java -jar sentinel-dashboard-1.8.0.jar &

方式二:

基于 Docker 运行容器启动:

1
2
3
4
docker pull bladex/sentinel-dashboard:1.8.0

docker run --name sentinel -d -p 8858:8858 bladex/sentinel-dashboard:1.8.0

访问:http://192.168.206.99:8858/

2.3.2.2 微服务接入Sentinel

1)修改consumer-service的pom文件,添加sentinel依赖,前提必须引入spring-cloud-alibaba-dependencies

1
2
3
4
5
6
7
8
9
10
11
 <!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</exclusion>
</exclusions>
</dependency>

2)修改consumer-service的application.yml,配置sentinel控制台地址

1
2
3
4
5
spring:
cloud:
sentinel:
transport:
dashboard: 192.168.190.149:8858 #sentinel控制台地址

3)服务器启动后访问sentinel看不到任何项目信息,需要访问一次项目后,Sentinel才会加载项目信息。

踩坑:Spring Boot 2.63版本可能与Sentinel2021.1版本不兼容,导致只要导入了web包(Spring MVC)就报“循环依赖”的错误,因为Spring Boot 2.6版本之后禁止了循环依赖,所以报错,只要手动允许循环依赖即可:

1
2
3
spring:
main:
allow-circular-references: true

2.3.3 流量控制规则

2.3.3.1 流量控制介绍

对于服务稳定性的 保护,限流是一个非常重要的手段。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制,避免系统被瞬时的流量高峰冲垮,从而保障应用的高可用性。

image-20210218131201787

​ Sentinel提供了非常灵活的服务限流方式,用户可以根据自己的实际项目场景,非常方便的定义各种限流规则。其原理是监控应用流量的 QPS并发线程数指标,当达到指定的阈值时对流量进行控制。

流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

image-20210424212303266

2.3.3.2 阈值类型

2.3.3.2.1 QPS

​ QPS即每秒查询率,可以对某个接口定义每秒允许的查询次数,超过则丢弃。

image-20210625000242924

  • 资源名: 唯一名称,默认请求路径。
  • 针对来源:Sentinel可以针对调用者进行限流,填写服务名,默default(不区分来源)。
  • 阈值类型/单机阈值
    • QPS(每秒钟的请求数量): 当调用该API的QPS达到阈值的时候,进行限流。
    • 线程数:当调用该API的线程数达到阈值的时候,进行限流。
  • 是否集群: 不需要集群。

打开Jemeter进行测试

设置简体中文

image-20210625000536091

设置一个线程,一秒内访问5次

添加线程组

image-20210625000758342

添加请求

image-20210625000914144

编辑线程数

image-20210625113314012

设置协议、IP、端口、请求方式、接口地址

image-20210625001139246

运行,查看接口,可知,由于定义了限流规则,每秒内只能访问两次,所以两个请求通过,三个请求被拒绝

新增查看结果:

image-20210424163933906

点击执行查看结果:

image-20210218123021951

2.3.3.2.2 线程数

修改阈值类型为线程数单机阈值为1

image-20210625001356749

使用Jemeter进行测试

修改setup线程组,0秒内启动5个线程循环一次。

image-20210218123643691

指定访问接口

运行,查看接口,可知,由于定义了限流规则,每秒内只能一个线程访问,所以一个线程通过,四个线程被拒绝

image-20210218123730442

2.3.3.3 流控模式

​ 流控模式分为三种:直接、关联、链路。 每种流控模式的效果都不同,可以根据当前需求选择对应的流控模式。

image-20210625001807382

2.3.3.3.1 直接

该模式为默认模式,其会针对某个资源直接操作,当达到了阈值则触发。

2.3.3.3.2 关联

在该模式下,当关联的资源触发规则,原来的资源触发流控效果。

新建SentinelUserController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
@RequestMapping("/user")
public class SentinelUserController {
//资源1
@GetMapping("/hello1")
public String hello1() {
return "Hello Sentinel1";
}

//资源2
@GetMapping("/hello2")
public String hello2() {
return "Hello Sentinel2";
}
}

配置流控规则

image-20210218130057509

打开Jemeter进行测试

image-20210218125628700

添加http请求,无限访问hello2

image-20210218125803456

浏览器访问hello1,可以发现,其已经被限流,无法被访问。

image-20210625003011846

2.3.3.3.3 链路

在链路模式下会对一条链路的访问进行控制。当从某个接口过来的资源达到限流条件时,则开启限流。

1)修改consumer-service的SentinelUserController,添加方法

1
void dosomething();

2)声明方法为链路资源

1
2
3
4
5
// 将此方法标注为sentinel的资源。value=资源名
@SentinelResource("chain")
public void dosomething() {
System.out.println("chain");
}

3)添加两个handlerMethod,调用相同资源

1
2
3
4
5
6
7
8
9
10
11
12
13
//资源3
@GetMapping(value = "/hello3")
public String hello3() {
dosomething();
return "Hello Sentinel3";
}

//资源4
@GetMapping(value = "/hello4")
public String hello4() {
dosomething();
return "Hello Sentinel4";
}

4)修改consumer-service的application.yml

1
2
3
4
spring:
cloud:
sentinel:
web-context-unify: false #关闭context收敛防止chain限流不生效

5)配置sentinel规则

image-20210218134254739

6)Jemter中配置一个线程无限循环访问

image-20210218134346544

7)Jemeter中配置http请求,访问/user/hello3

image-20210218134422241

8)请求访问可以发现/user/hello3被限流

image-20210218134448147

9)通过浏览器访问/user/hello4,可以发现仍然可以对chain资源进行访问

image-20210218134525972

2.3.3.4 流控效果

​ Sentinel中存在三种流控效果:快速失败WarmUp排队等待

2.3.3.4.1 快速失败

​ 该效果为默认效果,当达到限流规则后,则不允许进行访问。

2.3.3.4.2 WarmUp

​ 当流量突然增大的时候,我们常常会希望系统从空闲状态到繁忙状态的切换的时间长一些。即如果系统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步的增多,经过预期的时间以后,到达系统处理请求个数的最大值。Warm Up(冷启动,预热)模式就是为了实现这个目的的。

image-20210219155550313

 该模式主要用于启动需要额外开销的场景,例如建立连接等。

它的使用存在一个公式threshold (阈值)/coldFactor(冷加载因子,默认为3)=最初的阈值。接着最初的阈值*预热时长来最终实现限流。

image-20210218135803393

2.3.3.4.3 排队等待

​ 所谓排队等待又可以称为匀速器即让请求以均匀的速度通过。

​ 实现原理是当请求到来的时候:

  • 如果当前请求距离上个通过的请求通过的时间间隔大于预设值,则让当前请求通过;

  • 否则,计算当前请求的预期通过时间,如果该请求的预期通过时间小于规则预设的 timeout 时间,则该请求会等待直到预设时间到来通过(排队等待处理);若预期的通过时间超出最大排队时长,则直接拒接这个请求。

image-20210218135936673

​ 这种方式主要用于处理间隔性突发的流量。假设现在有一个消息队列,突然接收到大量的消息,对于这些消息肯定不会直接拒绝多余的请求,而是希望系统以稳定的速度,逐步处理这些请求,以起到“削峰填谷”的效果。

 如下:设置为每秒允许有10次请求,如果每秒超过了10个请求,则需要排队等待,每个请求最长等待时间为10s。

image-20210218140145615

2.3.4 熔断降级规则

2.3.4.1 熔断降级介绍

现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。如果链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。

image-20210218142220134

  • 熔断:当某服务出现不可用或响应超时的情况时,停止对该服务的调用,让其不可访问。
  • 降级:当服务被熔断后,需要有一个兜底策略,如返回一个错误友好界面,或让被熔断的服务过一段时间后,再对外提供访问。

Sentinel中提供了三种熔断降级策略:慢比例调用、异常比例、异常数。

2.3.4.2 根据平均响应时间降级

​ 当统计时长内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。比例阈值范围是 [0.0, 1.0],代表 0% - 100%。

​ 经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。

image-20210218142823885

由于版本bug,需要切换资料中提供的sentinel

image-20210625011617694

修改consumer-service的application.yml

image-20210625011838865

1)修改SentinelUserController,增加等待时长

1
2
3
4
5
6
7
8
//资源5
@GetMapping(value = "/hello5/{flag}")
public String hello5(@PathVariable("flag") int flag) throws InterruptedException {
if (flag == 1){
TimeUnit.SECONDS.sleep(3);
}
return "Hello Sentinel5";
}

2)定义熔断降级规则

image-20210218143727863

1
解释:两秒内,请求数量超过5,且响应时长超过2秒的请求数量比例大于10%,则对该资源熔断3秒

3)Jemter配置10个线程无限访问

image-20210218144307138

4)Jemter配置访问/user/hello5资源,并携带参数为1

image-20210218144354439

5)Jemter启动访问,可以发现请求被拒绝,且浏览器访问该资源携带参数为2,也被拒绝访问

image-20210218144458223

image-20210625012938584

6)停止Jemter,过了3秒后,则/user/hello5/2即可访问。

image-20210625013007974

2.3.4.3 根据异常比例降级

当统计时长内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成则结束熔断。

image-20210218144904779

1)修改SentinelUserController

1
2
3
4
5
6
7
8
//资源6
@GetMapping(value = "/hello6/{flag}")
public String hello6(@PathVariable("flag") int flag) throws InterruptedException {
if (flag == 1){
int i =1/0;
}
return "Hello Sentinel6";
}

2)定义熔断降级规则

image-20210218150945939

1
解释:一秒内,请求数量超过5个,且请求异常比例超过10%,则3秒内不允许访问。

3)浏览器访问测试

​ 可以发现,当达到熔断降级规则后,该资源3秒内不允许访问,过了三秒则继续允许访问。

image-20210625145701782

熔断后访问其它服务地址:http://localhost:8089/user/hello6/3

也会出现提示:Blocked by Sentinel (flow limiting)

2.3.4.4 根据异常数降级

当统计时长内的异常数目超过阈值之后会自动进行熔断。过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成则结束熔断。

image-20210218151508422

1)定义熔断降级规则

image-20210218152705631

2)浏览器访问测试

​ 可以发现,当一秒内超过5个请求访问,且出现了3次异常,则该接口会被降级5秒。

image-20210625150132820

2.3.5 系统保护规则

系统保护的目的:

  • 保证系统不被拖垮
  • 在系统稳定的前提下,保持系统的吞吐量

系统保护规则是从应用级别的入口流量进行控制,从单台机器的 loadCPU 使用率平均 RT入口 QPS并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

​ 系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量,比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。

系统规则支持以下的模式:

  • Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5
  • CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
  • 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

2.3.6 热点规则

2.3.6.1 热点参数限流介绍

​ 热点即经常访问的数据。假设希望统计某个热点数据中访问频次最高的 TopN数据,并对其访问进行限制。比如:

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制

image-20210218153658551

2.3.6.2 参数位限流

image-20210218154134473

  • 参数索引:即要对第几个请求参数限流,从0开始。
  • 统计窗口时长:多久统计一次。

1)修改SentinelUserController

1
2
3
4
5
6
7
//资源  热点参数限流
@SentinelResource(value = "hello66") // ***** 添加资源注解
@GetMapping(value = "/hello66")
public String hello6(
@RequestParam(value = "username",required = false) String username) {
return "Hello Sentinel66 username="+username;
}

2)定义热点限流规则

image-20210625151328372

1
解释:如果hello66资源携带参数,则每秒只能访问一次

3)浏览器访问测试

​ 通过测试可知,当携带请求参数时,会被触发限流。如果不携带则不会触发限流。

限流异常提示:

image-20210625151404898

控制台错误提示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException: heima

2021-06-25 15:12:01.771 ERROR 26540 --- [nio-8089-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.reflect.UndeclaredThrowableException] with root cause

com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException: heima

2021-06-25 15:12:01.961 ERROR 26540 --- [nio-8089-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.reflect.UndeclaredThrowableException] with root cause

com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException: heima

2021-06-25 15:12:03.941 ERROR 26540 --- [nio-8089-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.reflect.UndeclaredThrowableException] with root cause

com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException: heima

2021-06-25 15:12:04.179 ERROR 26540 --- [nio-8089-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.reflect.UndeclaredThrowableException] with root cause

com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException: heima

2021-06-25 15:12:04.643 ERROR 26540 --- [nio-8089-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.reflect.UndeclaredThrowableException] with root cause

com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException: heima

2021-06-25 15:13:44.133 ERROR 26540 --- [nio-8089-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.reflect.UndeclaredThrowableException] with root cause

com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException: heima

4)返回友好错误提示

​ 如上所示,当触发热点参数限流后,前端会出现异常提示,但是一般情况下发生了错误,不应该给用户返回错误500信息,而应该给用户返回友好的错误提示。

修改代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
//资源  热点参数限流
@SentinelResource(value = "hello66",blockHandler = "hotHandler") // ***** 添加资源注解
@GetMapping(value = "/hello66")
public String hello6(
@RequestParam(value = "username",required = false) String username) {
return "Hello Sentinel66 username="+username;
}


public String hotHandler(String username, BlockException e) {
e.printStackTrace();
return "抱歉,关于:"+username+" 的查询有误";
}

5)浏览器访问测试

http://localhost:8089/user/hello66?username=heima

​ 重新通过浏览器访问测试,可以发现,前端不再出现500异常,而是返回自定义错误提示信息。

image-20210625152004191

2.3.6.3 参数值限流

在一些特定场景下,可能光对指定参数位限流可能满足不了实际需求,有可能还需要对具体参数值进行限流。

例如:

​ 商品id为2设定限流阈值为2

​ 商品id为3设定限流阈值为1000

1)定义限流规则

编辑限流规则,打开高级选项,设置参数类型、参数值、限流阈值

image-20210625152353914

1
解释:当访问hello66资源时,如果参数值为heima,则每秒只能访问2次。其他参数值每秒最多10次

2)浏览器访问测试

​ 通过测试可知,当参数值为2,当达到限流阈值则触发限流。

注意:热点规则的参数必须是基本数据类型或者是String类型。

2.3.7 访问控制规则

黑白名单授权控制

很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制(黑白名单控制)的功能。

根据请求来源限制是否通过:

  • 若配置白名单则只有请求来源位于白名单内时才可通过;
  • 若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。

image-20210218161850744

1)定义Bean,用于解析IP来源

1
2
3
4
5
6
7
@Component
public class IPLimiter implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
return httpServletRequest.getRemoteAddr();
}
}

2)定义授权规则

image-20210625162655869

1
解释:不允许ip为127.0.0.1的来源访问该资源,其他IP均可以访问,如localhost

3)浏览器测试

​ 经过测试可以,当IP为127.0.0.1时,无法访问该资源,被限制访问,后台出现认证异常。

image-20210218162955258

2.3.8 注解方式定义资源

2.3.8.1 注解@SentinelResource介绍

​ Sentinel 提供了 @SentinelResource 注解用于定义资源,并提供了 AspectJ 的扩展用于自动定义资源、处理 BlockException 等。常见属性:

  • value

    资源名称,必需项,因为需要通过resource name找到对应的规则,这个是必须配置的。

  • blockHandler

    blockHandler 对应处理 BlockException 的函数名称,可选项。
    blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,
    参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。

  • blockHandlerClass

    ​ blockHandler 函数默认需要和原方法在同一个类中,如果希望使用其他类的函数,则需要指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

  • fallback

    ​ fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。

  • fallbackClass

    fallbackClass的应用和blockHandlerClass类似,fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

2.3.8.2 fallback属性

​ fallback属性 用于在业务发生异常的时候,指定兜底方法,从而完成后续异常处理实现。

1)修改SentinelUserController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//资源7
@GetMapping(value = "/hello7")
@SentinelResource(value = "hello7", blockHandler = "blockHandler",fallback = "fallbackHandler")
public String hello7(@RequestParam("flag") int flag){
int i =1/0;
return "Hello Sentinel7";
}

//定义兜底方法,该方法的返回值类型、形参数量、形参类型均与原方法一致
public String fallbackHandler(int flag){
return "业务执行出现异常";
}

public String blockHandler(int flag, BlockException bk) {
bk.printStackTrace();
return "blockHandler 被限流了";
}

2)浏览器访问测试

image-20210625163036701

小结:

若 blockHandler 和 fallback 都进行了配置则:

  • 若配置限流操作,达到限流则执行 blockHandler 方法
  • 若没有达到限流配置,程序出现异常则执行 fallback 方法

2.3.8.3 全局使用

​ 有时多个方法定义相同的异常处理逻辑,则可以定义全局异常逻辑处理类

1)定义全局异常处理Handler

1
2
3
4
5
6
public class GlobalFallBackHandler {

public static String fallbackHandler(int flag){
return "业务执行出现异常-全局处理";
}
}

2)修改UserController,指定全局异常处理Handler

1
2
3
4
5
6
@GetMapping(value = "/hello7")
@SentinelResource(value = "hello7",fallbackClass = GlobalFallBackHandler.class,fallback = "fallbackHandler")
public String hello7(@RequestParam("flag") int flag){
int i =1/0;
return "Hello Sentinel7";
}

**3)浏览器访问测试 http://localhost:8089/user/hello7?flag=1

image-20210625163408397

2.3.9 网关限流

Sentinel提供了与Spring Cloud GateWay的依赖适配,开发者在使用Sentinel时,可以直接基于网关对后端微服务资源进行逐一规则配置,而不需要在每个微服务中配置,从而简化开发,提升开发效率。

image-20210426162613686

2.3.9.1 网关整合Sentinel

1)网关服务添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    <dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<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-actuator</artifactId>
</dependency>
<!-- gateway与sentinel适配依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

2)修改网关服务application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server:
port: 10010

spring:
application:
name: gateway-service
cloud:
gateway:
default-filters:
- StripPrefix=1
routes:
- id: consumer # 当前路由的唯一标识
uri: lb://consumer
predicates: # 断言
- Path=/consumer/** # 按照路径匹配的规则
sentinel:
transport:
dashboard: 192.168.190.149:8858
filter:
enabled: false
nacos:
server-addr: 192.168.190.149:8848

3)基于网关访问后端微服务,可以看到sentinel中出现网关服务

image-20210625140208144

4)网关服务 选择 “请求链路” 定义限流规则

方式一:根据 route id 设置限流或者是降级规则

image-20210625140423973

方式二:根据 API分组 设置限流或者是降级规则

点击 API管理,先添加 API分组

image-20210625140752042

添加成功:

image-20210625140821639

设置限流或者降级规则

image-20210625140948601

5)测试访问限流规则生效

地址:http://localhost:10010/consumer/user/hello2

image-20210625141218809

2.3.9.2 自定义返回限流数据

上述返回的数据还是 Blocked by Sentinel: ParamFlowException, 在实际开发中,需要返回友好的提示信息。

官网地址:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel#%E9%85%8D%E7%BD%AE

两种方式:

方式一:显示当应用的 ApplicationContext 中存在对应的Bean的类型时,会进行自动化设置:

image-20210426172839570

实现:在网关编写一个配置类即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.itheima.gateway.config;

import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.fastjson.JSON;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.HashMap;
import java.util.Map;


@Configuration
public class SentinelConfig {

public SentinelConfig() {

GatewayCallbackManager.setBlockHandler(new BlockRequestHandler() {

@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
ServerHttpResponse response = serverWebExchange.getResponse();
response.getHeaders().add("Content-Type", "application/json");

//设置返回json数据
Map<String, String> map = new HashMap<>();
map.put("code", "502");
map.put("msg", "被限流了");

System.out.println("SentinelConfig 流控.....");

return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
.body(Mono.just(JSON.toJSONString(map)), String.class);
}
});
}

}

方式二:Spring Cloud Alibaba Sentinel 提供了这些配置选项

image-20210426172917824

在网关 application.yml 配置:

1
2
3
4
5
6
7
8
9
10
11
12
sentinel:
transport:
dashboard: 192.168.206.99:8858
filter:
enabled: false

scg:
fallback:
content-type: "application/json"
response-body: "{\"msg\": \"访问过快...请稍后重试\",\"code\": \"502\"}"
response-status: 502
mode: "response"

2.3.10 Sentinel 整合 Feign

踩坑:测试了没效果,不知道版本问题还是什么,没报错,就是没效果,不过使用@SentinelResource自定义限流返回信息即可,不一定非要用Feign

Sentinel 适配了 Feign 组件。如果想使用,除了引入 spring-cloud-starter-alibaba-sentinel 的依赖外还需要 2 个步骤:

  • 配置文件打开 Sentinel 对 Feign 的支持:feign.sentinel.enabled=true
  • 加入 spring-cloud-starter-openfeign 依赖使 Sentinel starter 中的自动化配置类生效

实现步骤:

0)添加feign依赖,在启动类上加feign开关

1
2
3
4
5
<!--feign依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
1
2
3
4
5
6
7
@SpringBootApplication
@EnableFeignClients //开启feign支持
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}

1)在 consumer-service服务 application.yml 配置文件开启对feign支持

1
2
3
4
5
6
7
8
feign:
sentinel:
enabled: true

# 踩坑:Spring Boot 2.6以上需要开启循环依赖
spring:
main:
allow-circular-references: true # 允许循环依赖,

2)在consumer-service中定义UserClient.java作为feign接口

1
2
3
4
5
6
@FeignClient("user-service")
public interface UserClient {

@GetMapping("/address/me")
String myAddress();
}

3)修改consumer-service的ConsuerController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
@RequestMapping("/user")
public class ConsumerController {

@Autowired
private UserClient userClient;


@GetMapping("/address")
public String getAddress(){
return this.userClient.myAddress();
}

}

4)定义 fallback 类实现 feign接口

1
2
3
4
5
6
7
8
@Component
public class UserClientFallback implements UserClient {

@Override
public String myAddress() {
return "抱歉:请求超时,请稍后重试";
}
}

3)在feign接口中指定 fallback 类

1
@FeignClient(value = "user-service",fallback = UserClientFallback.class)

4)请求测试:http://localhost:10010/consumer/user/address

image-20210625144113505

5)定义限流规则测试

image-20210625144228387

服务提供方也可以整合Sentinel,在user-service服务里设置限流规则。

添加流控

image-20210625144458456

访问 http://localhost:10010/consumer/user/address结果:

image-20210625144537202

Sentinel 使用总结:

image-20210426211336363

2.3.11 规则持久化(了解)

​ Sentinel默认会把规则信息保存到内存中,当服务重启之后,规则就会丢失,生产环境下绝对不会这么做。更多时候规则会存储在文件、数据库或者配置中心当中。

参考官网:生产环境中使用Sentinel

阿里云AHAS:应用防护接入