SpringCloud续篇SpringCloud Alibaba
SpringCloud Alibaba同SpringCloud一样,SpringCloud Alibaba也是一套微服务解决方案,也有完整的微服务必要的组件,并且阿里开源的组件都经历过了考验,性能强悍,设计合理,现在开源出来大家用成套的产品搭配完善的可视化界面给开发运维带来了极大的便利。
1、SpringCloud Alibaba入门概述
1.1、出现的原因
前面漫长而又枯燥的学习,我们已经几乎学完了SpringCloud
所有常用的组件!也明白了SpringCloud
版本适配等非常麻烦,项目启动很容易报错!并且部分环境搭建也十分复杂,没有完善的可视化界面,我们需要大量的二次开发和定制SpringCloud
的复杂配置,还是难以上手,部分差别难以区分和合理应用。这也是SpringCloud
的几大痛点。
除了以上几点原因,更重要的是Netflix
相关的部分组件停止维护与更新,让我们开发处于被动,给开发带来了不便!部分组件也没有稳定的趋势!
无论部分组件停更还是阿里商业运营等一系列因素,SpringCloud Alibaba
都给我们带来很多的惊喜,只需要少量的注解和少量的配置,就能将SpringCloud
应用接入到阿里微服务解决方案!并且SpringCloud官方也很支持SpringCloud Alibaba
整合起来也非常方便。
SpringCloud Alibaba
的诞生:2018年10月31日,SpringCloud Alibaba
正式入驻SpringCloud官方孵化器,并在Maven
中央仓库发布了第一个版本。
1.2、主要功能一览
官方博客:https://github.com/alibaba/spring-cloud-alibaba/blob/2.2.x/README-zh.md
微服务组件
如何引入依赖呢?
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.7.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
然后在dependencies
中添加自己所需使用的依赖即可使用。
2、Nacos注册与发现、配置中心
2.1、Nacos概述
前面我们也说了,Nacos
属于重点微服务组件。集服务发现、配置、管理于一体(代替Eureka
做服务注册中心,代替Config
做服务配置中心),是一个易于构建云原生应用的动态服务发现、配置管理和服务管理平台。可帮助我们快速容易的构建和管理微服务基础设施。英文全称Dynamic Naming Configuration Service
。
官方文档地址:https://nacos.io/zh-cn/docs/concepts.html
更多概念与架构请参照官方文档,官方文档都有详细介绍。
几种服务注册与发现组件对比:
服务注册与发现组件 | CAP模型 | 控制台管理 | 社区活跃 |
---|---|---|---|
Eureka | AP | 支持 | 低(2.x版本闭源) |
Zookeeper | CP | 不支持 | 中 |
Consul | CP | 支持 | 高 |
Nacos | AP | 支持 | 高 |
据说Nacos
在阿里巴巴内有超过10万的实例运行,已经过了类似双十一等各种大型流量的考验。
2.2、安装并运行Nacos
遵循一切上云的原则,我们选择把Nacos
站点通过Docker
部署在Linux
服务器上。
# 1.下载镜像
docker pull nacos/nacos-server:2.0.2
# 2.运行 nacos 注意防火墙、服务器安全组的 8848 和 9848 端口要开放
docker run --name nacos-server -e MODE=standalone -p 8848:8848 -p 9848:9848 -d nacos/nacos-server:2.0.2
原因如下
启动成功后,我们访问http://IP地址:8848/nacos/index.html
即可登录Nacos
站点,注意默认账户与密码都是nacos
。
至此Nacos
环境我们已经搭建好了。
2.3、Nacos Discovery
前面我们已经搭建好了Nacos
服务,接下来就是演示以Nacos
作为服务的注册/发现中心。我们还是参照之前的服务模块,分别搭建一个服务提供者与服务消费者模块,完成一次远程服务的调用。
注意以下所有服务都要注册到Nacos
!
1、使用SpringBoot
的初始化向导,搭建cloud_payment_8001
服务,作为服务提供者。初始pom.xml
如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.laizhenghua</groupId>
<artifactId>cloud_payment_8001</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cloud_payment_8001</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.1</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.7.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-nacos-discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<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-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
先引入这些依赖,后面使用到什么我们引入什么即可。
2、编写配置文件application.yml
server:
port: 8001
spring:
application:
name: payment-service
cloud:
nacos:
discovery:
server-addr: 139.198.33.126:8848 # 配置Nacos地址
username: nacos
password: nacos
注意:消费者的配置文件,可以加上
# 消费者要去访问的微服务名称(注册成功进去 nacos 的微服务提供者)
service-url:
nacos-user-service: http://payment-service
3、服务提供者编写测试方法,供服务消费者调用。需要注意的是Nacos
自带负载均衡机制,服务提供者可以搭建集群模式进行测试。
/**
* @description: PaymentController
* @date: 2022/2/26 12:26
*/
@Slf4j
@RestController
@RequestMapping(value = "/payment")
public class PaymentController {
@Value(value = "${server.port}")
private String serverPort;
@RequestMapping(value = "/getServerPort", method = RequestMethod.GET)
public R getServerPort() {
log.info("我是服务提供者");
return R.ok().put("port", serverPort);
}
}
4、主程序添加@EnableDiscoveryClient
注解,并启动
搭建模式如综上所述,我们的服务消费者cloud_order_8002/order-service
模块也是一样,如
5、消费者编写测试方法!去从注册中心消费生产者提供的服务。
配置类
/**
* @description: RestTemplateConfig
* @date: 2022/2/26 13:42
*/
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced // 一定不要忘记加这个注解
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
业务方法
/**
* @description: OrderController
* @date: 2022/2/26 13:44
*/
@RestController
@RequestMapping(value = "/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Value(value = "${service-url.nacos-user-service}")
private String paymentServerUrl;
@RequestMapping(value = "/test", method = RequestMethod.GET)
public R test() {
return R.ok().put("data", restTemplate.getForObject(paymentServerUrl + "/payment/getServerPort", R.class).get("port"));
}
}
6、重启消费者(8002
),进行测试
到这里我们只是完成了简单的一步,也算是刚刚入门!知识点补充:Nacos支持AP与CP模式的切换。
一般来说,如果不需要存储服务级别的信息且服务实例是通过Nacos-client
注册,并且能够保持心跳上报,那么就可以选择AP模式
。当前主流的服务如Spring Cloud
和Dubbo
服务,都用于AP模式,AP模式为了服务的可能性而减弱一致性,因此AP模式下只支持注册临时实例。
如果需要在服务级别编辑或者存储配置信息,那么CP是必须的。K8S服务和DNS服务则适用于CP模式
。CP模式下则支持注册持久化实例,此时则是以Raft
协议为集群运行模式,改模式下注册实例之前必须先注册服务,如果服务不存在,则返回错误。
切换方式
curl -X PUT 'nacos_server:8848/nacos/vi/ns/operator/switches?entry=serverMode&value=CP'
2.4、Nacos Config
依稀记得使用Spring Cloud Config
做服务配置中心的时候,需要集成GitHub
和RabbitMQ
、搭建Config服务端、搭建Config客户端等一堆东西,真的是非常麻烦!让我做技术选型绝对不选SpringCloud Config
。
而到了Nacos
这里可以让我们以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。也就是说Nacos
消除了配置变更时重新部署应用和服务的需要。让配置管理变得更加高效和敏捷,帮助我们在生产环境中管理配置变更和降低配置变更带来的风险。
还是基于上面实例改造,添加alibaba-nacos-config
依赖。
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-nacos-config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
1、更改配置文件在每个应用的 src/main/resources/bootstrap.yml
配置文件中配置 Nacos Config 元数据
Nacos
同Spring Cloud Config
一样,在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置之后,才能保证项目的正常启动。bootstrap.yml
配置文件会优先于application.yml
加载被读取!因此Nacos Config
元数据需要配在bootstrap.yml
文件中。
而Nacos Config
元数据必须要有Nacos server
的地址和应用名,例如:
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.application.name=example
这是因为spring.application.name
是构成 Nacos 配置管理 Data ID 字段(配置列表显示)的一部分。
在 Nacos Spring Cloud
中,Data ID 的完整格式如下:
${prefix}-${spring.profiles.active}.${file-extension}
prefix
默认为spring.application.name
的值,也可以通过配置项spring.cloud.nacos.config.prefix
来配置。spring.profiles.active
即为当前环境对应的profile
,详情可以参考 Spring Boot 文档。 注意:当 spring.profiles.active 为空时,对应的连接符-
也将不存在,dataId 的拼接格式变成${prefix}.${file-extension}
file-exetension
为配置内容的数据格式,可以通过配置项spring.cloud.nacos.config.file-extension
来配置。目前只支持properties
和yaml
类型。
综上所述,给出配置示例:
bootstrap.yml
server:
port: 8001
spring:
application:
name: payment-service
cloud:
nacos:
discovery:
server-addr: 139.198.33.126:8848
username: nacos
password: nacos
config:
server-addr: 139.198.33.126:8848
file-extension: yaml # 指定yaml格式的配置
application.yml
spring:
profiles:
active: dev
2、去Nacos
注册中心,配置列表添加配置
此配置叫数据集,可添加任何配置,可实现动态获取配置(后面讲解),注意bootstrap.yml
中file-extension
配置项,我们配的是yaml
,Data ID 中也要写yaml
,否则启动会报错。
3、修改有业务方法,测试是否能获取配置中心配置的文件内容
/**
* @description: PaymentController
* @date: 2022/2/26 12:26
*/
@Slf4j
@RefreshScope // 动态获取并刷新配置
@RestController
@RequestMapping(value = "/payment")
public class PaymentController {
@Value(value = "${server.port}")
private String serverPort;
@Value(value = "${config.info}")
private String configInfo;
@RequestMapping(value = "/getConfigInfo", method = RequestMethod.GET)
public R getConfigInfo() {
log.info("我是服务提供者");
Map<String, Object> data = new HashMap<>();
data.put("port", serverPort);
data.put("configInfo", configInfo);
return R.ok().put("data", data);
}
}
4、重新启动服务进行测试
注意启动时如果报这个错误:java.lang.IllegalArgumentException: Param 'serviceName' is illegal, serviceN
。可能是SpringCloud的版本太高了,不知道是啥原因没有读取到bootstrap.yml
文件,此时需要添加一个依赖就能解决:
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-bootstrap -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
访问业务方法
小结:我们发现可以正常返回我们在配置中心配置的内容,再次修改配置内容,然后重新访问,还是可以读到最新修改的配置。此时就可以实现动态修改配置文件!如果配置中心和当前应用的配置文件中都配置了相同的项,会优先使用配置中心的配置。
2.5、Nacos配置中心使用细节
2.5.1、引子
问题1:
实际开发中,通常一个系统会准备:
- dev开发环境
- test测试环境
- prodd生产环境
如何保证指定环境启动时服务能正确读取到Nacos
上相应环境的配置文件呢?
问题2:
一个大型分布式微服务系统会有很多微服务子项目,每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境等,那么怎么对这些微服务配置进行管理呢?
2.5.2、命名空间
用于进行租户粒度的配置隔离。不同的命名空间下,可以存在相同的 Group 或 Data ID 的配置。Namespace 的常用场景之一是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。
说白了就是做配置隔离,采用Namespace(默认public) + Group(默认DEFAULT_GROUP) + Data ID
的模式进行不同环境的配置区分,类似于Java里面的package + class + generic/泛型
。最外层的Namespace
是可以用于区分部署环境,而Group
和Data ID
逻辑上区分两个目标对象。
比如说我们现在有三个环境:开发、测试、生产环境,我们就可以创建三个Namespace
,不同的Namespace
之间是隔离的。
命名空间的第一个应用场景:环境隔离
# 1.默认是public(保留空间),默认新增的所有的配置都在public空间里
# 2.命名空间 --> 可以新建dev/test/prop空间
# 3.利用dev/test/prop空间来做环境的隔离
# 4.在 bootstrap.yml 配置文件里配置需要使用哪个命名空间下的配置
spring:
cloud:
nacos:
config:
namespace: 6e2f9961-21e7-45f8-ab5b-18d3cc065ec7 # 命名空间ID
如图
命名空间的第二个应用场景:微服务隔离
# 1.每一个微服务之间互相隔离配置,每一个微服务都创建自己的命名空间,只加载自己命名下所有的配置
# 2.命名空间 --> 新建命令空间(payment-service、order-service)
# 3.让微服务启动时,读自己的配置文件
# 4. 修改 bootstrap.yml 配置文件,指定微服务启动时读取的配置文件(payment-service)
spring:
cloud:
nacos:
config:
namespace: b30efa5c-6321-43c8-8e69-ebe026fecb75 # 命名空间ID
如图
2.5.3、配置集
所有配置的集合就叫配置集。例如,一个配置集可能包含了数据源、线程池、日志级别等配置项。
bootstrap.yml
# 1.配置集ID:类似文件名(Data Id)
# 2.配置分组:默认所有的配置集都属于 DEFAULT_GROUP 比如我们可以为获得进行分组,双十一、618、双十二等
spring:
cloud:
nacos:
config:
group: dev
总结:在我们这个项目中,我们为每个微服务创建自己的命名空间,利用配置分组区分环境(dev、test、prop),这是最常用的配置方式。例如:
bootstrap.yml
spring:
application:
name: order-service # 服务名
cloud:
nacos:
discovery:
server-addr: 139.198.35.12:8848
username: nacos
password: nacos
config:
server-addr: 139.198.35.12:8848 # 配置中心地址
file-extension: yaml # 指定yaml格式的配置
namespace: 6e2f9961-21e7-45f8-ab5b-18d3cc065ec7 # order-service微服务自己的命名空间
group: dev # 开发环境
如图
同时加载多个配置集
在实际开发中,我们不可能所有的配置都写在一个配置文件里,那样的话不便于维护并且比较繁琐。所以我们要拆分配置文件,比如数据库连接信息我们写在datasource.yaml
,mybatis配置相关写在mybatis.yaml
配置文件里等等。配置中心Nacos
也支持拆分操作!!
我们在配置中为order-service
新建多个配置文件(datasource.yaml、mybatis.yaml、other.yaml
)
最后在bootstrap.yml
中加载多个配置集
spring:
application:
name: payment-service
cloud:
nacos:
discovery:
server-addr: 139.198.35.12:8848
username: nacos
password: nacos
config:
server-addr: 139.198.35.12:8848
file-extension: yaml # 指定yaml格式的配置
namespace: 6e2f9961-21e7-45f8-ab5b-18d3cc065ec7 # 命名空间ID
group: dev
extension-configs:
- data-id: datasource.yaml
group: dev
refresh: true
- data-id: mybatis.yaml
group: dev
refresh: true
- data-id: other.yml
group: dev
refresh: true
或者是bootstrap.properties
# 服务名
spring.application.name=order-service
# 配置中心地址
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
# coupon微服务自己的命名空间
spring.cloud.nacos.config.namespace=b30efa5c-6321-43c8-8e69-ebe026fecb75
# spring.cloud.nacos.config.group=dev
spring.cloud.nacos.config.extension-configs[0].data-id=datasource.yaml
spring.cloud.nacos.config.extension-configs[0].group=dev
spring.cloud.nacos.config.extension-configs[0].refresh=true
spring.cloud.nacos.config.extension-configs[1].data-id=mybatis.yaml
spring.cloud.nacos.config.extension-configs[1].group=dev
spring.cloud.nacos.config.extension-configs[1].refresh=true
spring.cloud.nacos.config.extension-configs[2].data-id=other.yaml
spring.cloud.nacos.config.extension-configs[2].group=dev
spring.cloud.nacos.config.extension-configs[2].refresh=true
2.5.4、配置中心Nacos总结
- 任何配置文件,都可以放在配置中心中,只需要在
bootstrap.properties
中说明加载配置中心哪些配置文件即可。 - 可以使用
@Value
和@ConfigurationProperties
注解获取配置文件的信息。 - 需要添加
@RefreshScope
动态获取并刷新配置,并且配置中心的配置文件,会优先于写在当前应用的配置文件 - 更多关于Nacos的使用可查看官方文档:https://nacos.io/zh-cn/docs/what-is-nacos.html
3、Nacos集群与持久化
3.1、Nacos中的集群与持久化理论概述
Nacos
集群配置,也是比较重要的一项配置。在实际微服务架构中,我们不可能只有一个Nacos
服务站点!加上这台服务挂了,那么所有微服务调用链将崩溃!所以实际生产中至少要有3台服务站点(服务的注册与发现),才能保证整个应用的稳定。
官方集群部署说明:https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html
官方也明确说明了,生产环境推进使用Linux
系统做集群部署。
官网集群部署架构图:
数据存储/持久化的一些说明:
我们发现Nacos站点退出后重新登录,我们配置过的配置列表信息还在。这是因为Nacos
默认使用嵌入式数据库实现的数据存储,所以,如果启动多个默认配置下的Nacos
节点,数据存储是存在一致性问题的。为了解决这个问题,Nacos
采用了集中式存储的方式来支持集群化部署,目前只支持MySQL的存储。
根据官方文档得知,是Nacos 0.7
版本及之后的版本才增加了支持MySQL
数据源能力。整合要求
MySQL
版本要求5.6.5+
。- 初始化
MySQL
数据库,数据库初始化文件nacos-mysql.sql
- 修改
conf/application.properties
文件,增加支持MySQL
数据源配置(目前只支持MySQL)添加MySQL数据源的url、用户名和密码。
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://11.162.196.16:3306/nacos_devtest?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=nacos_devtest
db.password=youdontknow
再以单机模式启动nacos,nacos所有写嵌入式数据库的数据都写到了mysql
3.2、Nacos持久化切换配置
前面我们说了Nacos
默认使用嵌入式数据库(derby)实现的数据存储,这也是可以证明的,例如可在GitHub
上查看Nacos项目的pom.xml
文件。
也说了0.7
版本之后支持MySQL
数据源,那么如何进行切换呢?其实前面也已经说过了
- 初始化
mysql
数据库,数据库初始化文件:nacos-mysql.sql - 修改
conf/application.properties
文件,增加支持mysql数据源配置(目前只支持mysql),添加mysql数据源的url、用户名和密码。
由于我们是使用Docker
进行部署的,数据库初始化文件在容器上也能找得到,只是不叫nacos-mysql.sql
,而是叫schema.sql
。
1、这时只需一个命令就能复制容器内的文件,然后把复制出来的文件,去数据库执行一下即可完成第一步。
# 11caa57861de 是容器运行的进程ID
docker cp '11caa57861de':/home/nacos/conf/schema.sql /home/nacos/nacos-mysql.sql
这个sql根本不是MySQL的语法,还是给出真正的nacos-mysql.sql
drop table if exists 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 '2010-05-05 00:00:00' COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(20) 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' ROW_FORMAT=DYNAMIC;
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_aggr */
/******************************************/
drop table if exists 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='增加租户字段' ROW_FORMAT=DYNAMIC;
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_beta */
/******************************************/
drop table if exists 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 '2010-05-05 00:00:00' COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(20) 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' ROW_FORMAT=DYNAMIC;
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_tag */
/******************************************/
drop table if exists 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 '2010-05-05 00:00:00' COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(20) 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' ROW_FORMAT=DYNAMIC;
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_tags_relation */
/******************************************/
drop table if exists 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' ROW_FORMAT=DYNAMIC;
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = group_capacity */
/******************************************/
drop table if exists 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 '2010-05-05 00:00:00' COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表' ROW_FORMAT=DYNAMIC;
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = his_config_info */
/******************************************/
drop table if exists 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 '2010-05-05 00:00:00',
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00',
`src_user` text,
`src_ip` varchar(20) 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='多租户改造' ROW_FORMAT=DYNAMIC;
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = tenant_capacity */
/******************************************/
drop table if exists 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 '2010-05-05 00:00:00' COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表' ROW_FORMAT=DYNAMIC;
drop table if exists tenant_info;
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' ROW_FORMAT=DYNAMIC;
drop table if exists users;
CREATE TABLE users (
username varchar(50) NOT NULL PRIMARY KEY,
password varchar(500) NOT NULL,
enabled boolean NOT NULL
) ROW_FORMAT=DYNAMIC;
drop table if exists roles;
CREATE TABLE roles (
username varchar(50) NOT NULL,
role varchar(50) NOT NULL,
constraint uk_username_role UNIQUE (username,role)
) ROW_FORMAT=DYNAMIC;
drop table if exists permissions;
CREATE TABLE permissions (
role varchar(50) NOT NULL,
resource varchar(512) NOT NULL,
action varchar(8) NOT NULL,
constraint uk_role_permission UNIQUE (role,resource,action)
) ROW_FORMAT=DYNAMIC;
INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);
INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
2、修改conf/application.properties
文件,追加上以下配置
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://11.162.196.16:3306/nacos_devtest?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=nacos_devtest
db.password=youdontknow
重启docker即可完成Nacos
的持久化配置。在来测试下是否切换成功!我们在配置列表新建一条配置如下图
查询数据库
SELECT * FROM nacos.config_info
3.3、Nacos集群配置(重点)
前面所做的工作,都是为这一小节做铺垫,因为官方也明确说明了生产环境一定要集群部署!
部署之前我们需要准备:
- 1个
Nginx
- 3个
Nacos
注册中心(3个或3个以上Nacos节点才能构成集群) - 1个
MySQL
才能模拟真正生产环境上的部署。MySQL
数据库的配置请参照上一小节。每台都要配上。
1、Nacos
的集群配置cluster.conf
,这个文件配置IP地址与端口号即可,注意每一台Nacos
都要有这个文件。
cluster.conf
139.198.35.10:8848
139.198.34.44:8848
139.198.35.122:8848
注意这三个IP都要保证部署好了Nacos
站点,都能能正常访问。
2、修改Nginx
的配置,由它做负载均衡器
注意:Nacos 2.0
以后Nginx
不能配置http转发,而是要配置tcp转发。
修改代理
负载均衡修改
至此,我们的所有配置也算都完成了。需要注意的点就是3个Nacos
都要能稳定运行,并且都做了相同的持久化切换配置。
3、进行访问测试,因为我们的Nginx
监听的80端口,因此直接访问http://nginxIP地址/nacos
即可
OK,完美。Nacos
集群部署完美搞定!
3.4、微服务注册进Nacos集群
Nacos
集群我们已经搞定了,把我们的微服务注册进去集群中,这也是最后一步!其实在bootstrap.yml/.properties
文件中修改这个配置项即可:
spring.cloud.nacos.config.server-addr=nginx的ip地址:端口号
bootstrap.yml
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: 180.76.238.292:80
username: nacos
password: nacos
config:
server-addr: 180.76.238.292:80
file-extension: yaml
namespace: f8740c3d-a048-42a4-9227-6b75758de048
group: dev
4、Sentinel熔断与限流
说道Sentinel
也是非常重要的一个组件,配合Nacos
基本上成为了微服务分布式架构首选!
4.1、Sentinel概述
官网:https://github.com/alibaba/Sentinel/wiki/介绍
是什么?
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel
以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Hystrix
的痛点
需要我们开发者自己手工搭建监控平台,没有一套web可视化界面可以给我们进行更加细粒度的配置流控、速率控制、服务熔断、服务降级等并且配置方式篇复杂!
Sentinel
也是参照了Hystrix
的一些特性,它又有那些特性呢?
Sentinel
的开源生态
Sentinel
分为两个部分:
- 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
- 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
4.2、Sentinel的安装与运行
能部署在服务器上,我们坚决部署在云服务器上,并且还是测试与学习环境,对于安装与运行怎么简单怎么来!
# 1.下载镜像
docker pull bladex/sentinel-dashboard:1.7.0
# 2.创建容器
docker run --name sentinel -d -p 8858:8858 bladex/sentinel-dashboard:1.7.0
启动成功后,访问http://ip:8858/#/login
即可访问Sentinel
管理页面
注意用户与密码都是sentinel
Sentinel
使用 / 集成初体验。注意要确保Nacos
站点没有任何问题!
1、为工程添加GAV
坐标
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2、修改配置文件bootstrap.yml
spring:
application:
name: order-service
main:
allow-circular-references: true # 使用循环依赖 SpringBoot 2.6以后不推荐使用了
profiles:
active: dev
cloud:
nacos:
discovery:
server-addr: 121.411.57.179:8848
username: nacos
password: nacos
namespace: c6de5330-5d3f-4fc3-9305-4e9879dc99d1
group: dev
config:
server-addr: 121.411.57.179:8848
file-extension: yaml
namespace: c6de5330-5d3f-4fc3-9305-4e9879dc99d1
group: dev
sentinel: # Sentinel的配置
transport:
dashboard: 180.762.238.29:8858 # dashboard / 监控平台地址
port: 8719 # 服务与sentinel后台通信端口,默认是8719端口,假如被占用会自动从8719开始依次+1扫描,直到找到未被占用的端口
3、编写业务测试方法
/**
* @description: FlowLimitController
* @date: 2022/3/6 15:40
*/
@Slf4j
@RestController
@RequestMapping(value = "/sentinel")
public class FlowLimitController {
@RequestMapping(value = "/test", method = RequestMethod.GET)
public R test() {
log.info("sentinel is working");
return R.ok();
}
}
4、测试(注意服务器的安全组8858和8719端口一定要放开)
Sentinel
采用懒加载机制,需要提前访问一次,才能看到效果。另外如果实时监控没有出现数据,原因可能有如下两点:
1、IP设置的不对,这个IP如果没有指定是默认生成的
我们可以配置文件中指明这个IP地址,例如
spring:
cloud:
sentinel: # Sentinel的配置
transport:
dashboard: 180.762.238.29:8858 # dashboard / 监控平台地址
port: 8719
clientIp: 192.168.1.5 # 指明通信IP地址
2、客户端(我们自己搭建的服务)时间与Sentinel
应用所在的机器时间不一致,特别是是Docker
部署的Sentinel
最容易出现这个问题。此时我们需要修改Docker
的时区,与客户端保持一致。
# 1.查看日志是否报错
docker logs [CONTAINER ID / Sentinel dashboard 的容器ID]
[root@laizhenghua /]# docker logs a6288e32889b
2022-03-06 10:30:04.074 ERROR 1 --- [pool-2-thread-1] c.a.c.s.dashboard.metric.MetricFetcher : Failed to fetch metric from <http://192.168.1.5:8719/metric?startTime=1646562463000&endTime=1646562469000&refetch=false> (ConnectionException: Connection timed out)
# 好家伙,果然报错了
# 2.修改docker的时区
# 进入容器
[root@laizhenghua /]# docker exec -it a6288e32889b /bin/sh
/bladex/sentinel # cd /
/ #
/ # cd /etc
/etc # mv localtime localtime.bak
/etc # cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
/etc # date
Sun Mar 6 18:50:54 CST 2022
# 3.重启容器
[root@laizhenghua /]# docker restart a6288e32889b
如果还没有监控到,还是下个jar
包,在本地运行吧,docker
部署Sentinel
坑有点多。
4.3、流控规则
基本介绍(各种概念官方说的已经很清楚了)
流量控制(flow control),其原理是监控应用流量的 QPS(每秒请求数) 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
官网介绍地址:https://github.com/alibaba/Sentinel/wiki/流量控制
如何添加流控规则呢?有两种途径
第一种
第二种
例如我们配置一个1秒只允许访问一次的流控规则
快速访问http://127.0.0.1:8002/sentinel/test
即可看到效果。
这也是三种流控模式之一的直接模式也称快速失败模式,这种模式在API
达到限流条件时直接限流。除了这种模式外,Sentinel
还提供了关联、链路流控模式。
另外触发限流后,直接调用的是默认报错信息,对于我们开发者来说,看起来没啥问题,但是用户看到了这种报错肯定是不行的。是否能自定义报错信息呢?留个坑,后面(流控效果)再详细说明。
关联流控模式:当关联的资源达到阈值时,就限流自己
1、准备两个测试方法
/**
* @description: FlowLimitController
* @date: 2022/3/6 15:40
*/
@Slf4j
@RestController
@RequestMapping(value = "/sentinel")
public class FlowLimitController {
@RequestMapping(value = "/payment", method = RequestMethod.GET)
public R payment() {
log.info("支付完成后需要调订单服务生产订单");
return R.ok();
}
@RequestMapping(value = "/order", method = RequestMethod.GET)
public R order() {
log.info("订单已生成:" + UUID.randomUUID().toString());
return R.ok();
}
}
2、设置效果,当关联资源/sentinel/order
的QPS阈值超过1时,就限流/sentinel/payment
的REST访问地址,当关联资源到阈值后限制配置好的资源名。
3、测试效果。这里我们需要postman
模拟并发密集访问http://127.0.0.1:8002/sentinel/order
才能看到效果。因为payment
关联着order
,只有order
达到阈值才能触发payment
的限流。
postman
访问20次http://127.0.0.1:8002/sentinel/order
期间,快速访问http://127.0.0.1:8002/sentinel/payment
即可看到效果。
链路流控模式:适用于多个请求调用同一个微服务。只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)
1、现有3个服务地址,假设payment
和order
都要访问test
http://127.0.0.1:8002/sentinel/test
http://127.0.0.1:8002/sentinel/payment
http://127.0.0.1:8002/sentinel/order
2、当order
访问test
达到一定阈值后,我们想对order
进行限流,我们就可以这样配置
关于流控规则就说这么多,真正使用时还需要实际情况去分析。
4.4、流控效果
前面我们也知道了快速失败(Blocked by Sentinel (flow limiting)
),是默认流控效果(直接失败,抛出异常)。除了这种流控效果Sentinel
还提供了Warm Up和排队等待。
Warm Up
(预热):即预热 / 冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
计算公式:阈值除以coldFactor(冷加载因子)
(默认值为3)经过预热时长后才会达到阈值
coldFactor
为3,即请求QPS从(threshold / 3)开始,经多少预热时长才逐渐升值设定的QPS阈值?
例如:阈值为10 + 预热时长设置5秒,系统初始化的阈值为 10 / 3 约等于3,即阈值刚开始为3,然后经过了5秒后阈值慢慢升高恢复到10
频繁访问http://127.0.0.1:8002/sentinel/test
即可体验到效果。这种预热流控效果最常用的是秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来。
排队等待:匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER
)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
注意:匀速排队模式暂时不支持 QPS > 1000 的场景。
例如:阈值QPS = 1时,1秒通过一个请求,可以这样配置
4.5、降级规则
官方概念解释:https://github.com/alibaba/Sentinel/wiki/熔断降级