Spring Cloud Netflix项目进入维护模式
https://spring.io/blog/2018/12/12/spring-cloud-greenwich-rc1-available-now
Spring Cloud Netflix Projects Entering Maintenance Mode
什么是维护模式
将模块置于维护模式,意味着 Spring Cloud 团队将不会再向模块添加新功能。
我们将修复 block 级别的 bug 以及安全问题,我们也会考虑并审查社区的小型 pull request。
进入维护模式意味着什么呢?
官网:
https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md
诞生:
2018.10.31,Spring Cloud Alibaba 正式入驻了 Spring Cloud 官方孵化器,并在 Maven 中央库发布了第一个版本。
https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md
https://spring.io/projects/spring-cloud-alibaba#overview
https://github.com/alibaba/spring-cloud-alibaba
英文文档
https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html
https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md
前四个字母分别为Naming和Configuration的前两个字母,最后的s为Service。
一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
Nacos: Dynamic Naming and Configuration Service
Nacos就是注册中心 + 配置中心的组合等价于Nacos = Eureka+Config +Bus
下载:https://github.com/alibaba/Nacos
官方文档:
中文地址:https://nacos.io/zh-cn/index.html
英文地址:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_nacos_discovery
据说 Nacos 在阿里巴巴内部有超过 10 万的实例运行,已经过了类似双十一等各种大型流量的考验
本地Java8+Maven环境
https://github.com/alibaba/nacos/releases
下载后解压
解压安装包,直接运行bin目录下的startup.cmd
startup.cmd -m standalone
命令运行成功后直接访问http://localhost:8848/nacos
默认账号密码都是nacos
https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_nacos_config
父项目pom.xml
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.1.0.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
本项目pom.xml
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
server:
port: 9001
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址
management:
endpoints:
web:
exposure:
include: '*'
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain9001.class, args);
}
}
@Controller
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@RequestMapping(value = "/payment/nacos/{id}")
@ResponseBody
public String getPayment(@PathVariable("id") Integer id) {
return "nacos registry, serverPort: " + serverPort + "\t id" + id;
}
}
http://localhost:9001/payment/nacos/1
nacos控制台
为了下一章节演示nacos的负载均衡,参照9001新建9002
cloudalibaba-consumer-nacos-order83
com.alibaba.cloud
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.stonebridge.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
为什么nacos支持负载均衡
server:
port: 83
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
nacos-user-service: http://nacos-payment-provider
@EnableDiscoveryClient
@SpringBootApplication
public class OrderNacosMain83 {
public static void main(String[] args) {
SpringApplication.run(OrderNacosMain83.class, args);
}
}
config
负载均衡一定要加@LoadBalanced注解
@Configuration
public class ApplicationContextBean {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
controller
@Controller
public class OrderNacosController {
@Resource
private RestTemplate restTemplate;
@Value("${service-url.nacos-user-service}")
private String serverURL;
@RequestMapping("/consumer/payment/nacos/{id}")
@ResponseBody
public String paymentInfo(@PathVariable("id") Long id) {
return restTemplate.getForObject(serverURL + "/payment/nacos/" + id, String.class);
}
}
启动
访问:http://localhost:83/consumer/payment/nacos/13
83访问9001/9002,轮询负载OK
Nacos 支持AP和CP模式的切换
C是所有节点在同一时间看到的数据是一致的;而A的定义是所有的请求都会收到响应。
何时选择使用何种模式?
一般来说,
如果不需要存储服务级别的信息且服务实例是通过nacos-client注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如 Spring cloud 和 Dubbo 服务,都适用于AP模式,AP模式为了服务的可能性而减弱了一致性,因此AP模式下只支持注册临时实例。
如果需要在服务级别编辑或者存储配置信息,那么 CP 是必须,K8S服务和DNS服务则适用于CP模式。
CP模式下则支持注册持久化实例,此时则是以 Raft 协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误。
curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'
cloudalibaba-config-nacos-client3377
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
Nacos同springcloud-config一样,在项目初始化时,要保证先从配置中心进行配置拉取,
拉取配置之后,才能保证项目的正常启动。
springboot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application
bootstrap.yaml
# nacos配置
server:
port: 3377
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos服务注册中心地址
config:
server-addr: localhost:8848 #Nacos作为配置中心地址
file-extension: yaml #指定yaml格式的配置
# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
application.yaml
spring:
profiles:
active: dev # 表示开发环境
通过Spring Cloud 原生注解@RefreshScope实现配置自动更新:
@Controller
@RefreshScope //在控制器类加入@RefreshScope注解使当前类下的配置支持Nacos的动态刷新功能。
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@RequestMapping("/config/info")
@ResponseBody
public String getConfigInfo() {
return configInfo;
}
}
Nacos中的匹配规则
理论
Nacos中的dataid的组成格式及与SpringBoot配置文件中的匹配规则
官网
最后公式:
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
根据公式拼接的dataId为nacos-config-client-dev.yaml。因此在Nacos注册中心配置的配置新的dataId为nacos-config-client-dev.yaml,便可以被该应用获取。
实操
注意应用的配置的是spring.cloud.nacos.config.file-extension是yaml还是yml
启动前需要在nacos客户端-配置管理-配置管理栏目下有对应的yaml配置文件
运行cloud-config-nacos-client3377的主启动类
调用接口查看配置信息:http://localhost:3377/config/info
修改下Nacos中的yaml配置文件,再次调用查看配置的接口,就会发现配置已经刷新
多环境多项目管理
问题1:
实际开发中,通常一个系统会准备
dev开发环境
test测试环境
prod生产环境。
如何保证指定环境启动时服务能正确读取到Nacos上相应环境的配置文件呢?
问题2:
一个大型分布式微服务系统会有很多微服务子项目,
每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境…
那怎么对这些微服务配置进行管理呢?
是什么
类似Java里面的package名和类名
最外层的namespace是可以用于区分部署环境的,Group和DataID逻辑上区分两个目标对象。
三者情况
默认情况:
Namespace=public,Group=DEFAULT_GROUP, 默认Cluster是DEFAULT
Nacos默认的命名空间是public,Namespace主要用来实现隔离。
比方说我们现在有三个环境:开发、测试、生产环境,我们就可以创建三个Namespace,不同的Namespace之间是隔离的。
Group默认是DEFAULT_GROUP,Group可以把不同的微服务划分到同一个分组里面去
Service就是微服务;一个Service可以包含多个Cluster(集群),Nacos默认Cluster是DEFAULT,Cluster是对指定微服务的一个虚拟划分。
比方说为了容灾,将Service微服务分别部署在了杭州机房和广州机房,
这时就可以给杭州机房的Service微服务起一个集群名称(HZ),
给广州机房的Service微服务起一个集群名称(GZ),还可以尽量让同一个机房的微服务互相调用,以提升性能。
最后是Instance,就是微服务的实例。
指定spring.profile.active和配置文件的DataID来使不同环境下读取不同的配置
默认空间+默认分组+新建dev和test两个DataID
通过spring.profile.active属性就能进行多环境下配置文件的读取
测试
重启配置spring.profile.active的3377应用
访问:http://localhost:3377/config/info
通过Group实现环境区分
新建Group
在nacos图形界面控制台上面新建配置文件DataID
bootstrap+application
在config下增加一条group的配置即可。可配置为DEV_GROUP或TEST_GROUP
https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html
VIP的意思是virtual IP,就是Nginx
默认Nacos使用嵌入式数据库实现数据的存储(比如现在的单机模式中,保存的配置信息就保存在内嵌数据库Derby中)。
所以,如果启动多个默认配置下的Nacos节点,数据存储是存在一致性问题的。为了解决这个问题,Nacos采用了集中式存储的方式来支持集群化部署,目前只支持MySQL的存储。数据源只有一份就可以保持数据的一致性
https://nacos.io/zh-cn/docs/deployment.html
https://github.com/alibaba/nacos/blob/develop/config/pom.xml
nacos\conf目录下找到sql脚本
nacos-mysql.sql
先建库结构nacos_config
/*
* 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');
nacos\conf目录下找到application.properties
添加数据库配置
nacos.istio.mcp.server.enabled=false
############################################
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&serverTimezone=UTC
db.user=root
db.password=123456
startup.cmd -m standalone
新建配置
查看数据库
查看Liunx版本
[root@192 bin]# cat /etc/redhat-release
下载MySQL
https://dev.mysql.com/downloads/mysql/5.7.html#downloads
解压
安装目录:/usr/local/mysqldb/mysql
数据目录:/usr/local/mysqldb/data
[root@192 Downloads]# tar -zxvf mysql-5.7.29-linux-glibc2.12-x86_64.tar.gz
重命名到usr/local/mysqldb目录下,名字为mysql
[root@192 Downloads]# mkdir -p /usr/local/mysqldb && mv mysql-5.7.29-linux-glibc2.12-x86_64 /usr/local/mysqldb/mysql
增加一个mysql用户并赋权限
[root@192 Downloads]# useradd mysql
[root@192 Downloads]# chown -R mysql /usr/local/mysqldb/
[root@192 Downloads]# chgrp -R mysql /usr/local/mysqldb/
切换到mysql安装目录
[root@192 Downloads]# cd /usr/local/mysqldb/mysql/bin
生成初始化密码并记住这个初始化密码
[root@192 bin]# mkdir -p /usr/local/mysqldb/data && ./mysqld --initialize --user=mysql --basedir=/usr/local/mysqldb/mysql --datadir=/usr/local/mysqldb/data
在/etc/目录新增my.cnf文件并配置如下
mysql5.7.29从之前的某个版本开始,在/usr/local/mysqldb/mysql/support-files目录下面呢,就没有名为
my_default.cnf这个文件了,该文件作为配置my.cnf的配置模板
[root@192 bin]# rm -rf /etc/my.cnf
[root@192 bin]# vim /etc/my.cnf
[mysql]
# 设置mysql客户端默认字符集
default-character-set=utf8
socket=/var/lib/mysql/mysql.sock
[mysqld]
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
skip-name-resolve
#设置3306端口
port = 3306
socket=/var/lib/mysql/mysql.sock
# 设置mysql的安装目录
basedir=/usr/local/mysqldb/mysql
# 设置mysql数据库的数据的存放目录
datadir=/usr/local/mysqldb/data
# 允许最大连接数
max_connections=200
# 服务端使用的字符集默认为8比特编码的latin1字符集
character-set-server=utf8
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
lower_case_table_names=1
max_allowed_packet=16M
#mysql 5.6占用内存400多兆,太大了,配置优化
performance_schema_max_table_instances=600
table_definition_cache=400
table_open_cache=256
创建上面用到的文件夹
[root@192 bin]# mkdir -p /var/lib/mysql&&chmod 777 /var/lib/mysql
mysqld
[root@192 bin]# mv /usr/local/mysqldb/mysql/support-files/mysql.server /etc/init.d/mysqld
增加可执行权限
[root@192 bin]# chmod +x /etc/init.d/mysqld
配置/etc/init.d/mysqld文件,修改内容如下:
vim /etc/init.d/mysqld
basedir=/usr/local/mysqldb/mysql
datadir=/usr/local/mysqldb/data
配置mysql开机自动启动(可选:不想开机启动可以不执行)
chkconfig --add mysqld
chkconfig --list mysqld
chkconfig --level 35 mysqld on
启动mysql
[root@192 bin]# systemctl start mysqld
登录
[root@192 bin]# ln -s /usr/local/mysqldb/mysql/bin/mysql /usr/bin
[root@192 bin]# mysql -uroot -p
输入临时密码
重置默认密码
mysql> set password = password('itcast');
后续修改密码
-- 改root的默认生成密码
alter user 'root'@'localhost' identified by '123456';
flush privileges;
-- 修改用户密码的方式
-- 方式一
SET PASSWORD FOR 'username'@'host' = PASSWORD('newpassword');
flush privileges;
-- 方式二:5.7版本没有password这个字段了,改成了authentication_string
update user set authentication_string=password('123') where user='root';
flush privileges;
-- 方式三
alter user 'root'@'%' IDENTIFIED BY '123456';
flush privileges;
-- 创建新用户格式 host可以是localhost、%、具体的ip
CREATE USER 'username'@'host' IDENTIFIED BY 'password';
-- 创建新用户举例:
create user 'aaa'@'%' IDENTIFIED BY '123';
-- 对用户的操作命令其实是操作的mysql.user表
flush privileges;
授权操作
查看用户信息,HOST为localhost,root用户只能本机登录
-- 授权语法 privileges可以是select、INSERT,UPDATE等,如果要授予所的权限则使用ALL
-- 所有数据库的所有表: *.*
-- []代表此用户可以再给其他用户授权
GRANT privileges ON databasename.tablename TO 'username'@'host' [WITH GRANT OPTION];
flush privileges; -- 授权后需要刷新
-- 举例: 会新增一条记录
grant all on *.* to 'root'@'%';
flush privileges; -- 授权后需要刷新
或者
开放3306端口
sudo firewall-cmd --add-port=3306/tcp --permanent
防火墙重启
firewall-cmd --reload
查看开放的端口号是否存在3306
firewall-cmd --list-all
连接
重启
[root@localhost conf]# cd /usr/local/mysqldb/mysql/bin
[root@localhost bin]# systemctl start mysqld
下载
https://github.com/alibaba/nacos/releases/tag/1.1.4
解压
tar -zxvf nacos-server-1.1.4.tar.gz
切换root,将解压后的文件转义到opt目录
[root@localhost Downloads]# cp -r nacos /mynacos/
进入bin
修改startup.sh
startup.sh修改内容
复制出cluster.conf
修改内容
这个IP不能写127.0.0.1,必须是Linux命令hostname -i能够识别的IP
startup.sh修改完成后集群启动
./startup.sh -p 3333
ps -ef|grep nacos |grep -v grep|wc -l
关闭nacos集群
安装Nginx必须具备的组件
安装 pcre 依赖
联网下载 pcre 压缩文件依赖
wget http://downloads.sourceforge.net/project/pcre/pcre/8.37/pcre-8.37.tar.gz
解压该文件
tar -zxvf pcre-8.37.tar.gz
将解压文件复制到/usr/local/pcre目录下
mkdir -p /usr/local/pcre && mv pcre-8.37 /usr/local/pcre
进入/usr/local/pcre/pcre-8.37/
cd /usr/local/pcre/pcre-8.37/
执行 ./configure编译
./configure
执行./configure后如果出现
需要执行
yum install -y gcc gcc-c++
再次执行
./configure
./configure执行正常
安装
make && make install
校验是否安装成功,查看版本
pcre-config --version
安装 openssl 、zlib 、 gcc 依赖
yum -y install make zlib zlib-devel gcc-c++ libtool openssl openssl-devel
安装Nginx
下载Nginxhttp://nginx.org/
下载成功
解压Nginx
tar -zxvf nginx-1.12.2.tar.gz
将nginx移动到指定文件夹
此处最好创建/usr/local/mynginx文件夹,因此安装成功会创建/usr/local/nginx文件夹
mkdir -p /usr/local/nginx && mv nginx-1.12.2 /usr/local/nginx
切换到nginx目录
cd /usr/local/nginx/nginx-1.12.2
检查
./configure
编译安装
make && make install
安装成功后在/usr/local多出一个文件nginx文件夹
在里面sbin有启动脚本
启动
查看配置文件的监听端口
访问:
修改nginx的配置文件访问nacos集群
修改内容
upstream cluster{
server 127.0.0.1:3333;
server 127.0.0.1:4444;
server 127.0.0.1:5555;
}
server {
listen 1111;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
#root html;
#index index.html index.htm;
proxy_pass http://cluster;
}
根据配置启动nginx
开放1111端口
Linux关于防火墙端口管理
关闭nginx
./nginx -s stop
执行sql脚本
nacos.istio.mcp.server.enabled=false
############################################
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&serverTimezone=UTC
db.user=root
db.password=123456
梳理出3台nacos集器的不同服务端口号
使它能够接受不同的启动端口
思考
/mynacos/nacos/bin 目录下有startup.sh
平时单机版的启动,都是./startup.sh即可。
但是集群启动,我们希望可以类似其它软件的shell命令,传递不同的端口号启动不同的nacos实例。
命令:./startup.sh -p 3333 表示启动端口号为3333的nacos服务器实例,和上一步的cluster.conf配置的一致。
修改内容
修改nginx的配置文件
nginx.conf
修改位置
upstream cluster{
server 127.0.0.1:3333;
server 127.0.0.1:4444;
server 127.0.0.1:5555;
}
server {
listen 1111;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
#root html;
#index index.html index.htm;
proxy_pass http://cluster;
}
截止到此处,1个Nginx+3个nacos注册中心+1个mysql已经准备就绪
发布新配置
微服务cloudalibaba-provider-payment9002启动注册进nacos集群
启动
Sentinel作为一个组件,可以独立出来。可以直接界面化的细粒度统一配置管理。
GitHub
https://github.com/alibaba/Sentinel
中文文档
https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
A powerful flow control component enabling reliability, resilience and monitoring for microservices. (面向云原生微服务的高可用流控防护组件)
一句话解释,之前我们讲解过的Hystrix
https://github.com/alibaba/Sentinel/releases
Sentinel 具有以下特征:
https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_sentinel
服务使用中的各种问题
Sentinel 分为两个部分:
https://github.com/alibaba/Sentinel/releases
下载到本地sentinel-dashboard-1.7.0.jar
前提: java8环境、8080端口不能被占用
java -jar sentinel-dashboard-1.7.0.jar
http://localhost:8080
登录账号密码均为sentinel
http://localhost:8848/nacos/#/login
新建的cloudalibaba-sentinel-service8401项目注册到Nacos8848,并且被Sentinel8080保护。
主要是:spring-cloud-starter-alibaba-sentinel
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>4.6.3version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
#Nacos服务注册中心地址
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard地址,即8080会监控8401
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
management:
endpoints:
web:
exposure:
include: '*'
@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401 {
public static void main(String[] args) {
SpringApplication.run(MainApp8401.class, args);
}
}
@Controller
public class FlowLimitController {
@RequestMapping("/testA")
@ResponseBody
public String testA() {
return "------testA";
}
@RequestMapping("/testB")
@ResponseBody
public String testB() {
return "------testB";
}
}
java -jar sentinel-dashboard-1.7.0.jar
簇点链路会显示所有的访问的路径
流控规则
Sentinel名词解释
资源名:唯一名称,默认请求路径
针对来源:Sentinel可以针对调用者进行限流,填写微服务名,指定对哪个微服务进行限流 ,默认default(不区分来源,全部限制)
阈值类型/单机阈值:
QPS(每秒钟的请求数量):当调用该接口的QPS达到了阈值的时候,进行限流;
线程数:当调用该接口的线程数达到阈值时,进行限流
是否集群:不需要集群
流控模式:
流控来源
流控模式是只设置流控时的具体模式
资源名的解释:目前的流控规则首先拿请求地址(@RequestMapping映射)进行控制,即为唯一名称,资源名,默认请求路径。
QPS(每秒钟的请求数量):当调用该接口的QPS达到了阈值的时候,进行限流;
现象:直接->快速失败
配置及说明
表示1秒钟内查询1次就是OK,若超过次数1,就直接-快速失败,报默认错误
测试
快速点击访问http://localhost:8401/testA
结果:Blocked by Sentinel (flow limiting)
思考
直接调用默认报错信息,技术方面OK。but,是否应该有我们自己的后续处理?类似有个fallback的兜底方法?
线程数:当调用该接口的线程数达到阈值时,进行限流
当线程数设置为2时,表示现在只有两个线程对该路径的请求进行处理,如果发起的请求时,两个线程正在处理之前的请求,则会返回Blocked by Sentinel (flow limiting)
基本概念:当关联的资源达到阈值时,就限流自己。即当与A关联的资源B达到阀值后,就限流A自己。比如支付接口达到阈值,就限流下订单的接口。
设置效果
当关联资源/testB的qps阀值超过1时,就限流/testA的Rest访问地址,当关联资源到阈值后限制配置好的资源名
postman模拟并发密集访问testB
大批量线程高并发访问B,导致A失效了
略
直接失败,抛出异常:Blocked by Sentinel (flow limiting)
源码:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
说明
默认 coldFactor 为 3,即请求QPS从(threshold / 3) 开始,经多少预热时长才逐渐升至设定的 QPS 阈值。
案例,阀值为10+预热时长设置5秒。
系统初始化的阀值为10 / 3 约等于3,即阀值刚开始为3;然后过了5秒后阀值才慢慢升高恢复到10
官网
https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6
默认coldFactor为3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。
限流 冷启动
https://github.com/alibaba/Sentinel/wiki/%E9%99%90%E6%B5%81—%E5%86%B7%E5%90%AF%E5%8A%A8
源码
com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController
WarmUp配置
默认 coldFactor 为 3,即请求QPS从(threshold / 3) 开始,经多少预热时长才逐渐升至设定的 QPS 阈值。案例,阀值为10+预热时长设置5秒。
系统初始化的阀值为10 / 3 约等于3,即阀值刚开始为3;然后过了5秒后阀值才慢慢升高恢复到10
测试
多次点击http://localhost:8401/testB,刚开始不行,后续慢慢OK
应用场景
如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。
匀速排队,让请求以均匀的速度通过,阀值类型必须设成QPS,否则无效。
设置含义:/testA每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒。
官网
https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6
com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController
配置
测试
结果
https://github.com/alibaba/Sentinel/wiki/%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,
让请求快速失败,避免影响到其它的资源而导致级联错误。
当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
注意:Sentinel的断路器是没有半开状态的
半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用。具体可以参考Hystrix
RT(平均响应时间,秒级)
一秒钟的请求数大于或者等于5个时,如果所有请求的平均响应时间大于阈值。在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用
RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)
异常比列(秒级)
QPS >= 5 且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
异常数(分钟级)
异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
一秒钟的请求数大于或者等于5个时,如果所有请求的平均响应时间大于阈值。在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用
RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)
代码
@Controller
@Slf4j
public class FlowLimitController {
@RequestMapping("/testD")
@ResponseBody
public String testD() {
//暂停几秒钟线程
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("testD 测试RT");
return "------testD";
}
}
配置
jmeter压测
开启Jmeter循环请求后,断路器开启
关闭Jmeter循环请求后,断路器关闭
结论
按照上述配置,
永远一秒钟打进来10个线程(大于5个了)调用testD,我们希望200毫秒处理完本次任务,
如果超过200毫秒还没处理完,在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了
后续我停止jmeter,没有这么大的访问量了,断路器关闭(保险丝恢复),微服务恢复。
代码
@Controller
@Slf4j
public class FlowLimitController {
@RequestMapping("/testE")
@ResponseBody
public String testE() {
System.out.println("testE 测试RT");
int age = 10 / 0;
return "------testE";
}
}
配置
jmeter配置
开启Jmeter循环请求后,断路器开启
关闭Jmeter循环请求后,断路器关闭
结论
按照上述配置,
单独访问一次,必然来一次报错一次(int age = 10/0),调一次错一次;
开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了。
时间窗口一定要大于等于60秒。
异常数是按照分钟统计的
代码
@Controller
@Slf4j
public class FlowLimitController {
@RequestMapping("/testF")
@ResponseBody
public String testF() {
System.out.println("testF 测试RT");
int age = 10 / 0;
return "------testF";
}
}
配置
浏览器访问http://localhost:8401/testF五次
访问五次后一分钟的异常数目达到5次,再次访问时,Sentinel的断路器打开。
热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作。
https://github.com/alibaba/Sentinel/wiki/%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。
比如:
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。
兜底方法
分为系统默认方法和用户自定义义方法两种
之前的case,限流出问题后,都是用sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
我们能不能自定?类似hystrix,某个方法出问题了,就找对应的兜底降级方法?
结论
从Hystrix的@Command 到@SentinelResource
默认:com.alibaba.csp.sentinel.slots.block.BlockException
sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
@Controller
@Slf4j
public class FlowLimitController {
@RequestMapping("/testHotKey")
@ResponseBody
@SentinelResource(value = "testHotKey", blockHandler = "dealHandler_testHotKey")
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
@RequestParam(value = "p2", required = false) String p2) {
return "------testHotKey";
}
public String dealHandler_testHotKey(String p1, String p2, BlockException exception) {
return "-----dealHandler_testHotKey";
}
}
@SentinelResource注解标签类似于Hystrix的@Command。value是资源名称;blockHandler是如果触发Sentinel的限流规则就会触发的取代testHotKey的兜底函数。
配置内容
@RequestMapping("/testHotKey")
@ResponseBody
@SentinelResource(value = "testHotKey")
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
@RequestParam(value = "p2", required = false) String p2) {
return "------testHotKey";
}
异常打到了前台用户界面看到,不友好。
@RequestMapping("/testHotKey")
@ResponseBody
@SentinelResource(value = "testHotKey", blockHandler = "dealHandler_testHotKey")
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
@RequestParam(value = "p2", required = false) String p2) {
return "------testHotKey";
}
方法testHotKey里面第一个参数只要QPS超过每秒1次,马上降级处理。调用自定义的降级处理方法dealHandler_testHotKey
正常访问,低于配置的降级规则:(携带了对应函数中的第一个参数,且在一秒访问次数超过1次就会触发)
触发降级规则调用降级处理函数:
上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流
热点参数的注意点,参数必须是基本类型
资源名为testHotKey请求,索引序号为0的参数,参数值为5时,限流阈值为10,当不为5时,限流阈值为1。
添加按钮不能忘记
@SentinelResource处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;
RuntimeException,int age = 10/0,这个是java运行时报出的运行时异常RunTimeException,@SentinelResource不管
总结:@SentinelResource主管配置出错,运行出错该走异常走异常
https://github.com/alibaba/Sentinel/wiki/%E7%B3%BB%E7%BB%9F%E8%87%AA%E9%80%82%E5%BA%94%E9%99%90%E6%B5%81
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
http://localhost:8848/nacos/#/login
java -jar sentinel-dashboard-1.7.0.jar
pom.xml
新增引入cloud-api-commons
<dependency>
<groupId>com.stonebridge.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
application.yaml不变
业务类
@Controller
public class RateLimitController {
@RequestMapping("/byResource")
@ResponseBody
@SentinelResource(value = "byResource", blockHandler = "handleException")
public CommonResult byResource() {
return new CommonResult(200, "按资源名称限流测试OK", new Payment(2020L, "serial001"));
}
public CommonResult handleException(BlockException exception) {
return new CommonResult(444, exception.getClass().getCanonicalName() + "\t 服务不可用");
}
}
此时关闭问服务8401,Sentinel控制台,流控规则消失。
通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息
@Controller
public class RateLimitController {
@RequestMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult byUrl() {
return new CommonResult(200, "按url限流测试OK", new Payment(2020L, "serial002"));
}
}
频繁点击http://localhost:8401/rateLimit/byUrl
会返回Sentinel自带的限流处理结果
定义的方法必须在是public static修饰
public class CustomerBlockHandler {
public static CommonResult handleException1(BlockException exception) {
return new CommonResult(1234, "自定义的限流处理信息......CustomerBlockHandler");
}
public static CommonResult handleException2(BlockException exception) {
return new CommonResult(4567, "自定义的限流处理信息......CustomerBlockHandler");
}
}
@Controller
public class RateLimitController {
@RequestMapping("/rateLimit/TestBlockHandler")
@ResponseBody
@SentinelResource(value = "TestBlockHandler", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handleException2")
public CommonResult TestBlockHandler() {
return new CommonResult(200, "按url限流测试OK", new Payment(2020L, "serial002"));
}
}
http://localhost:8401/rateLimit/customerBlockHandler
sentinel整合ribbon+openFeign+fallback
创建项目cloudalibaba-provider-payment9003/9004
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.atguigu.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
server:
port: 9002
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
# 换成nginx的1111端口,做集群
server-addr: 192.168.174.148:1111
management:
endpoints:
web:
exposure:
include: '*'
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9003 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain9003.class, args);
}
}
@Controller
public class PaymentController {
@Value("${server.port}")
private String serverPort;
public static HashMap<Long, Payment> hashMap = new HashMap<>();
static {
hashMap.put(1L, new Payment(1L, "28a8c1e3bc2742d8848569891fb42181"));
hashMap.put(2L, new Payment(2L, "bba8c1e3bc2742d8848569891ac32182"));
hashMap.put(3L, new Payment(3L, "6ua8c1e3bc2742d8848569891xt92183"));
}
@RequestMapping(value = "/paymentSQL/{id}")
@ResponseBody
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
Payment payment = hashMap.get(id);
CommonResult<Payment> result = new CommonResult(200, "from mysql,serverPort: " + serverPort, payment);
return result;
}
}
http://localhost:9003/paymentSQL/1
参照cloudalibaba-provider-payment9003创建项目cloudalibaba-provider-payment9004
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
<dependency>
<groupId>com.stonebridge.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
server:
port: 84
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard地址
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
nacos-user-service: http://nacos-payment-provider
@EnableDiscoveryClient
@SpringBootApplication
public class OrderNacosMain84 {
public static void main(String[] args) {
SpringApplication.run(OrderNacosMain84.class, args);
}
}
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
@Controller
@Slf4j
public class CircleBreakerController {
public static final String SERVICE_URL = "http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
@ResponseBody
@SentinelResource(value = "fallback")
public CommonResult<Payment> fallback(@PathVariable Long id) {
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
if (id == 4) {
throw new IllegalArgumentException("IllegalArgumentException,非法参数异常....");
} else if (result.getData() == null) {
throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
}
return result;
}
}
访问:http://localhost:84/consumer/fallback/1
sentinel整合ribbon+openFeign+fallback
访问:http://localhost:84/consumer/fallback/4
没有任何配置,给客户error页面,不友好
fallback负责业务异常
代码实现
@RequestMapping("/consumer/fallback/{id}")
@ResponseBody
@SentinelResource(value = "fallback", fallback = "handlerFallback") //fallback负责业务异常
public CommonResult<Payment> fallback(@PathVariable Long id) {
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
if (id == 4) {
throw new IllegalArgumentException("IllegalArgumentException,非法参数异常....");
} else if (result.getData() == null) {
throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
}
return result;
}
public CommonResult handlerFallback(@PathVariable Long id, Throwable e) {
Payment payment = new Payment(id, "null");
return new CommonResult<>(444, "兜底异常handlerFallback,exception内容 " + e.getMessage(), payment);
}
图解
测试
代码实现
@RequestMapping("/consumer/fallback/{id}")
@ResponseBody
@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler负责在sentinel里面配置的降级限流
public CommonResult<Payment> fallback(@PathVariable Long id) {
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
if (id == 4) {
throw new IllegalArgumentException("非法参数异常....");
} else if (result.getData() == null) {
throw new NullPointerException("NullPointerException,该ID没有对应记录");
}
return result;
}
public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) {
Payment payment = new Payment(id, "null");
return new CommonResult<>(445, "blockHandler-sentinel限流,无此流水: blockException " + blockException.getMessage(), payment);
}
图解配置
配置限流
测试
代码实现
@Controller
@Slf4j
public class CircleBreakerController {
public static final String SERVICE_URL = "http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
@ResponseBody
@SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler")
public CommonResult<Payment> fallback(@PathVariable Long id) {
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
if (id == 4) {
throw new IllegalArgumentException("IllegalArgumentException,非法参数异常....");
} else if (result.getData() == null) {
throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
}
return result;
}
//本例是fallback
public CommonResult handlerFallback(@PathVariable Long id, Throwable e) {
Payment payment = new Payment(id, "null");
return new CommonResult<>(444, "兜底异常handlerFallback,exception内容 " + e.getMessage(), payment);
}
//本例是blockHandler
public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) {
Payment payment = new Payment(id, "null");
return new CommonResult<>(445, "blockHandler-sentinel限流,无此流水: blockException " + blockException.getMessage(), payment);
}
}
配置图解
配置流控规则
访问测试
说明
若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。
代码实现
@RequestMapping("/consumer/fallback/{id}")
@ResponseBody
@SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler", exceptionsToIgnore = {IllegalArgumentException.class})
public CommonResult<Payment> fallback(@PathVariable Long id) {
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
if (id == 4) {
throw new IllegalArgumentException("IllegalArgumentException,非法参数异常....");
} else if (result.getData() == null) {
throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
}
return result;
}
配置图解
测试
访问会报IllegalArgumentException的地址:http://localhost:84/consumer/fallback/4
创建项目cloudalibaba-consumer-nacos-order85
pom.xml
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>com.stonebridge.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
application.yaml
激活Sentinel对Feign的支持
server:
port: 85
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
#Nacos服务注册中心地址
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard地址
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
management:
endpoints:
web:
exposure:
include: '*'
# 激活Sentinel对Feign的支持
feign:
sentinel:
enabled: true
主启动类
添加@EnableFeignClients启动Feign的功能
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients
public class OrderNacosMain85 {
public static void main(String[] args) {
SpringApplication.run(OrderNacosMain85.class, args);
}
}
业务类
controller
@Controller
@Slf4j
public class CircleBreakerController {
@Resource
private PaymentService paymentService;
@RequestMapping(value = "/consumer/openfeign/{id}")
@ResponseBody
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
if (id == 4) {
throw new RuntimeException("没有该id");
}
return paymentService.paymentSQL(id);
}
}
service
PaymentService
@FeignClient(value = "nacos-payment-provider", fallback = PaymentFallbackService.class)
public interface PaymentService {
@RequestMapping(value = "/paymentSQL/{id}")
CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
PaymentFallbackService
PaymentService对应的异常处理处理方法
@Component
public class PaymentFallbackService implements PaymentService {
@Override
public CommonResult<Payment> paymentSQL(Long id) {
return new CommonResult<>(444, "服务降级返回,没有该流水信息", new Payment(id, "errorSerial......"));
}
}
测试
启动的项目消费侧微服务(端口85),服务提供微服务(端口9004)
访问:http://localhost:85/consumer/openfeign/1
因为启动的项目只有消费侧端口为85,服务侧服务提供者9004。此时关闭服务侧服务提供者,触发消费侧降级。
一旦我们重启应用,在sentinel为其接口创建的流控规则,生产环境需要将配置规则进行持久化。
将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效。
修改cloudalibaba-sentinel-service8401
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
dependency>
添加Nacos数据源配置
spring:
cloud:
sentinel:
transport:
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: cloudalibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
[
{
"resource": "/rateLimit/byUrl",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
resource:资源名称;
limitApp:来源应用;
grade:阈值类型,0表示线程数,1表示QPS;
count:单机阈值;
strategy:流控模式,0表示直接,1表示关联,2表示链路;
controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
clusterMode:是否集群。
http://localhost:8401/rateLimit/byUrl
出现限流
多次调用:http://localhost:8401/rateLimit/byUrl
重新配置出现了,持久化验证通过
分布式前:单机单库没这个问题
我们的系统从1:1 (单机单库) 到 1:N(多台数据库) 到现在的 N:N多系统多数据库的
分布式之后
单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。
一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题。
Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
官网地址:http://seata.io/zh-cn/
一个典型的分布式事务过程
分布式事务处理过程的一ID+三组件模型
Transaction ID XID
全局唯一的事务ID。
比如一次事务的开始,比如一次操作涉及三个数据库,三个数据源。每一次事务的所有操作都有全局唯一的相同的事务id
3组件概念
Transaction Coordinator (TC)
事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;
Transaction Manager ™
控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
Resource Manager (RM)
控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚
直白解释:
一个班60个同学,将Microservices想象成分为20人一组,一共三组。TC是授课老师,TM是班主任。
首先TM(班主任)向TC(授课老师)申请开启一个全局事务(新的课程),XID就是新的课程的微信群号,TM(班主任)将XID(课程的微信群号)上下传播。
所有的同学要向TC(授课老师)注册分支事务,将其纳入全局事务管理。
TM(班主任)向TC(授课老师)发起全局提交或者回滚事务。
上课完毕TC(授课老师)在XID(课程的微信群号)下管辖的全部分支事务完成提交或回滚请求。
发布说明: https://github.com/seata/seata/releases
本地@Transactional
全局@GlobalTransactional
SEATA 的分布式交易解决方案
http://seata.io/zh-cn/
https://github.com/seata/seata/releases
seata-server-1.4.0.zip解压到指定目录并修改conf目录下的file.conf配置文件
先备份原始file.conf文件
修改file.conf
主要修改:自定义事务组名称+事务日志存储模式为db+数据库连接信息
下订单->减库存->扣余额->改(订单)状态
<strong>strong>
<font color=red><strong>strong>font>
<font color=red> font>
rMode": false
}
]
resource:资源名称;
limitApp:来源应用;
grade:阈值类型,0表示线程数,1表示QPS;
count:单机阈值;
strategy:流控模式,0表示直接,1表示关联,2表示链路;
controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
clusterMode:是否集群。
#### 21.10.3.4.启动8401后刷新sentinel发现业务规则有了
#### 21.10.3.5.快速访问测试接口
http://localhost:8401/rateLimit/byUrl
出现限流
#### 21.10.3.6.停止8401再看sentinel
#### 21.10.3.7.重新启动8401再看sentinel
多次调用:http://localhost:8401/rateLimit/byUrl
重新配置出现了,持久化验证通过
# 22.SpringCloud Alibaba Seata处理分布式事务
## 22.1.分布式事务问题
分布式前:单机单库没这个问题
我们的系统从1:1 (单机单库) 到 1:N(多台数据库) 到现在的 N:N多系统多数据库的
分布式之后
单体应用被拆分成微服务应用,原来的三个模块被拆分成**三个独立的应用**,分别使用**三个独立的数据源**,业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。
一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题。
## 22.2.Seata简介
### 22.2.1.是什么
Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
官网地址:http://seata.io/zh-cn/
### 22.2.2.功能
一个典型的分布式事务过程
#### 2.2.2.1.分布式事务处理的三组件模型
分布式事务处理过程的一ID+三组件模型
- Transaction ID XID
全局唯一的事务ID。
比如一次事务的开始,比如一次操作涉及三个数据库,三个数据源。每一次事务的所有操作都有全局唯一的相同的事务id
- 3组件概念
1. Transaction Coordinator (TC)
事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;
2. Transaction Manager (TM)
控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
3. Resource Manager (RM)
控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚
#### 2.2.2.2.处理过程
1. TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;
2. XID 在微服务调用链路的上下文中传播;
3. RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
4. TM 向 TC 发起针对 XID 的全局提交或回滚决议;
5. TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。
直白解释:
一个班60个同学,将Microservices想象成分为20人一组,一共三组。TC是授课老师,TM是班主任。
首先TM(班主任)向TC(授课老师)申请开启一个全局事务(新的课程),XID就是新的课程的微信群号,TM(班主任)将XID(课程的微信群号)上下传播。
所有的同学要向TC(授课老师)注册分支事务,将其纳入全局事务管理。
TM(班主任)向TC(授课老师)发起全局提交或者回滚事务。
上课完毕TC(授课老师)在XID(课程的微信群号)下管辖的全部分支事务完成提交或回滚请求。
### 22.2.3.下载
发布说明: https://github.com/seata/seata/releases
### 22.2.4.怎么用
本地@Transactional
全局@GlobalTransactional
SEATA 的分布式交易解决方案
## 22.3.Seata-Server安装
### 22.3.1.官网地址
http://seata.io/zh-cn/
### 22.3.2.下载版本
https://github.com/seata/seata/releases
### 22.3.3.配置文件
seata-server-1.4.0.zip解压到指定目录并修改conf目录下的file.conf配置文件
1. 先备份原始file.conf文件
2. 修改file.conf
主要修改:自定义事务组名称+事务日志存储模式为db+数据库连接信息
## 22.4.订单/库存/账户业务数据库准备
## 22.5.订单/库存/账户业务微服务准备
### 22.5.1.业务需求
下订单->减库存->扣余额->改(订单)状态
### 22.5.2.新建订单Order-Module
[外链图片转存中...(img-jQhXNFVr-1635212140008)]
[外链图片转存中...(img-C2jW3One-1635212140009)]
```html