SpringCloud Alibaba入门简介
why会出现SpringCloud alibaba
SpringCloud alibaba带来了什么
功能
- 服务限流降级:默认支持Servlet、Feign、RestTemplate、Dubbo和RocketMQ限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级Metrics监控。
- 服务注册与发现:适配Spring Cloud服务注册与发现标准,默认集成Ribbon的支持。
- 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
- 消息驱动能力:基于Spring Cloud Stream为微服务应用构建消息驱动能力。
- 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于
Cron
表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有Worker(schedulerx-client)上执行。主要组件
SpringCloud alibaba学习资料获取
- —->>github官网;
- —->>官方参考手册;
-
SpringCloud Alibaba Nacos
服务注册和配置中心Nacos简介
是什么
一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
Nacos就是注册中心+配置中心的组合,等价于
Nacos=Eureka+Config+Bus
功能
替代Eureka做服务注册中心;
-
下载
- 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安装
直接解压即可(必须有jdk1.8以上的java环境);
单机版启动:
./startup.sh -m standalone
;Nacos作为服务注册中心演示
基于Nacos的服务提供者
Pom.xml
<dependencies>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.yaml
server:
port: 9001
spring:
application:
name: nacos-payment-provider
cloud:
nacos: #注册进nacos
discovery:
server-addr: 124.70.84.192:8848
#配置Nacos地址(千万不要写带上"http://")
management:
endpoints:
web:
exposure:
include: '*'
主启动类
@EnableDiscoveryClient @SpringBootApplication public class PaymentMain9001 { public static void main(String[] args) { SpringApplication.run(PaymentMain9001.class,args); } }
业务类
@RestController public class PaymentController { @Value("${server.port}") private String port; @GetMapping(value = "/payment/nacos/{id}") public String getPayment(@PathVariable("id") Integer id){ return "nacos registry, serverPort: "+ port+"\t id"+id; } }
测试
基于Nacos的服务消费者
pom.xml
<dependencies> <!--SpringCloud ailibaba nacos --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- 引入自己定义的api通用包,可以使用Payment支付Entity --> <dependency> <groupId>com.atguigu</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency> <!-- SpringBoot整合Web组件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--日常通用jar包配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
application.yaml
server: port: 83 spring: application: name: nacos-order-consumer cloud: nacos: discovery: server-addr: 124.70.84.192:8848 #消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者) service-url: #非必须,只是为了后面方便引用地址 nacos-user-service: http://nacos-payment-provider
nacos自带Ribbon
主启动类
@EnableDiscoveryClient @SpringBootApplication public class OrderNacosMain83 { public static void main(String[] args) { SpringApplication.run(OrderNacosMain83.class,args); } }
业务类
config
@Configuration public class ApplicationContextBean { @Bean @LoadBalanced public RestTemplate getRestTemplate() { return new RestTemplate(); } }
controller
@RestController public class OrderNacosController { @Autowired private RestTemplate restTemplate; @Value("${service-url.nacos-user-service}") private String serverURL; @GetMapping("/consumer/payment/nacos/{id}") public String paymentInfo(@PathVariable("id") Long id) { return restTemplate.getForObject(serverURL+"/payment/nacos/"+id,String.class); } }
测试
服务注册中心对比
Nacos全景图
Nacos和CAP
何时选择使用何种模式?
如果不需要存储服务级别的信息且服务实例是通过nacos-client注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如Spring cloud和Dubbo服务,都适用于AP模式,AP模式为了服务的可能性而减弱了一致性,因此AP模式下只支持注册临时实例。
- 如果需要在服务级别编辑或者存储配置信息,那么CP是必须,K8S服务和DNS服务则适用于CP模式。CP模式下则支持注册持久化实例,此时则是以Raft协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误。
AP模式转换为CP模式:
curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'
Nacos作为服务配置中心演示
这里演示windows版本nacos,因为linux版本需要先配数据库才能创建配置;
Nacos作为配置中心-基础配置
pom.xml
<dependencies> <!--nacos-config--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <!--nacos-discovery--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--web + actuator--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--一般基础配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
两个yaml配置文件
why配置两个
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格式的配置
application.yaml
spring: profiles: active: dev #表示开发环境
主启动类
@EnableDiscoveryClient @SpringBootApplication public class NacosConfigClientMain3377 { public static void main(String[] args) { SpringApplication.run(NacosConfigClientMain3377.class, args); } }
业务类
@RestController @RefreshScope //在控制器类加入@RefreshScope注解使当前类下的配置支持Nacos的动态刷新功能。 public class ConfigClientController { @Value("${config.info}") private String configInfo; @GetMapping("/config/info") public String getConfigInfo() { return configInfo; } }
在Nacos中添加配置信息
Nacos中的dataid的组成格式及与SpringBoot配置文件中的匹配规则(官网):
${prefix}**-**${spring.profiles.active}.${file-extension}
**prefix**
默认为spring.application.name
的值,也可以通过配置项spring.cloud.nacos.config.prefix
来配置。**spring.profiles.active**
即为当前环境对应的profile
,详情可以参考Spring Boot文档或者springboot2笔记。 注意:当 spring.profiles.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成**${prefix}.${file-extension}**
**file-exetension**
为配置内容的数据格式,可以通过配置项spring.cloud.nacos.config.file-extension
来配置。目前只支持properties和yaml类型。
总结
Nacos作为配置中心-分类配置
多环境多项目管理
问题1:
实际开发中,通常一个系统会准备:dev开发环境、test测试环境、prod生产环境。如何保证指定环境启动时服务能正确读取到Nacos上相应环境的配置文件呢?
问题2:
一个大型分布式微服务系统会有很多微服务子项目,每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境……那怎么对这些微服务配置进行管理呢?
Nacos的图形化管理界面
配置管理
命名空间
Namespace+Group+Data ID三者关系
1、是什么
类似Java里面的package名和类名,最外层的namespace是可以用于区分部署环境的,Group和DataID逻辑上区分两个目标对象。
2、三者情况:
默认情况: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),还可以尽量让同一个机房的微服务互相调用,以提升性能。
-
三种方案加载配置
DataID方案
直接修改
application.yaml
中的此处即可:spring: profiles: active: test #表示测试环境 #active: dev #表示开发环境
Group方案
1、在不同的分组里创建同一份配置文件
spring.profiles.active=info
:
2、在配置文件中的spring.cloud.nacos.config
下面设置一个属性**group**
即可:spring: application: name: nacos-config-client cloud: nacos: discovery: server-addr: localhost:8848 #Nacos服务注册中心地址 config: server-addr: localhost:8848 #Nacos作为配置中心地址 file-extension: yaml #指定yaml格式的配置 group: TEST_GROUP #在这里设置分组**********这里***** #group: DEV_GROUP
Namespace方案
1、新建
dev/test
的Namespace:
2、之后就可以在服务列表
里面选择命名空间来创建配置了;
3、yaml文件配置该命名空间ID即可(配置开发环境的):#********************application.yaml spring: application: name: nacos-config-client cloud: nacos: discovery: server-addr: localhost:8848 #Nacos服务注册中心地址 config: server-addr: localhost:8848 #Nacos作为配置中心地址 file-extension: yaml #指定yaml格式的配置 #group: TEST_GROUP #在这里设置分组 group: DEV_GROUP namespace: 96af6283-d9f9-4b18-ab2e-ed814a9b95e6 #配置命名空间ID**********************这里配置 #********************bootstrap.yaml spring: profiles: #active: test #表示测试环境 active: dev #表示开发环境 #active: info
Nacos集群和持久化配置(重要)
官网说明
—->>nacos集群官网说明
按照上述,我们需要mysql数据库;Nacos部署环境
Nacos定义为一个IDC内部应用组件,并非面向公网环境的产品,建议在内部隔离网络环境中部署,强烈不建议部署在公共网络环境。
Nacos支持三种部署模式
单机模式 - 用于测试和单机试用。
- 集群模式 - 用于生产环境,确保高可用。
- 多集群模式 - 用于多数据中心场景。
- —->>官网教程;
在0.7版本之前,在单机模式时nacos使用嵌入式数据库实现数据的存储,不方便观察数据存储的基本情况。0.7版本增加了支持mysql数据源能力,具体的操作步骤:
- 1.安装数据库,版本要求:5.6.5+
- 2.初始化mysql数据库,数据库初始化文件:
nacos-mysql.sql
; - 3.修改
conf/application.properties
文件,增加支持mysql数据源配置(目前只支持mysql),添加mysql数据源的url
、用户名
和密码
。Nacos持久化配置解释
Nacos默认自带的是嵌入式数据库derby
(—->>github官网源码配置文件),这样多台nacos就有多个单独的内嵌数据库,那么数据的一致性就无法保证,因此真实的生产环境不应该用内嵌的数据库,我们用mysql数据库;derby到mysql切换配置步骤
1、nacos-server-1.1.4\nacos\conf
目录下找到sql脚本nacos-mysql.sql
,在自己的数据库下执行这个脚本;
2、nacos-server-1.1.4\nacos\conf
目录下找到application.properties
进行配置:
3、之后重启nacos并登录nacos管理页面,然后创建一个配置,可以在spring.datasource.platform=mysql db.num=1 db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true db.user=root db.password=12345ssdlh
config_info
表中查到这条配置的信息即证配置成功,如下:
Linux版Nacos+MySQL生产环境配置
1个Nginx**+**3个nacos注册中心(版本1.1.4)**+**1个mysql
1、首先进行持久化(配置mysql)配置,如上;
2、Linux服务器上nacos的集群配置cluster.conf:
先将cluster.conf.example
拷贝一份为cluster.conf
,为了安全:[root@ecs-mcr conf]# pwd /opt/module/nacos/conf [root@ecs-mcr conf]# cp cluster.conf.example cluster.conf
vim编辑cluster.conf
里面的内容(把原来的注掉):
124.70.84.192:5555 124.70.84.192:3333 124.70.84.192:7777
这个IP不能写127.0.0.1;
3、编辑Nacos的启动脚本**startup.sh**
,使它能够接受不同的启动端口:
首先startup.sh
备份一份startup.sh.example
,为了安全:
[root@ecs-mcr bin]# pwd /opt/module/nacos/bin [root@ecs-mcr bin]# cp startup.sh startup.sh.example
vim配置startup.sh
里面的内容:
并在nacos的**application.properties**
中添加配置以下内容,设置固定IP:nacos.inetutils.ip-address=124.70.84.192
(服务器ip)
4、测试nacos伪集群:
[root@ecs-mcr bin]# ./startup.sh -m cluster -p 7777 [root@ecs-mcr bin]# ./startup.sh -m cluster -p 3333 [root@ecs-mcr bin]# ./startup.sh -m cluster -p 5555
看看是否开启够了三台:
[root@ecs-mcr bin]# ps -ef|grep nacos|grep -v grep|wc -l 3
5、Nginx的配置,由它作为负载均衡器:
修改nginx的配置文件nginx.conf
:
....
upstream cluster{
server 127.0.0.1:5555;
server 127.0.0.1:3333;
server 127.0.0.1:7777;
}
#虚拟主机1
server {
listen 80;
server_name localhost;
location / {
#proxy_pass http://192.168.80.132/;
#root /home/www/www;
#index index.html index.htm;
proxy_pass http://cluster; #最后有个分号,千万别忘
}
....
6、测试**nginx方向代理+nacos集群**
:
地址:http://124.70.84.192/nacos
,可以访问到nacos:
至此,nginx+nacos集群+mysql
配置成功
nginx+nacos集群+mysql
测试
spring:
application:
name: nacos-payment-provider
cloud:
nacos: #注册进nacos
discovery:
server-addr: 124.70.84.192:80
#配置Nacos集群的nginx反向代理地址
#即便是80端口也要写端口号,不写端口号默认转到8848端口
SpringCloud Alibaba Sentinel
实现熔断与限流
Sentinel简介
安装
- 下载地址:
[https://github.com/alibaba/Sentinel/releases](https://github.com/alibaba/Sentinel/releases)
; - 官网使用教程:
[--->>官网教程](https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_sentinel)
; - 运行,是个jar包,直接:
java -jar sentinel-dashboard-1.7.0.jar
; - 访问sentinel管理界面:
http://ip号:8080
; -
Sentinel分为两个部分:
核心库(Java 客户端)不依赖任何框架/库,能够运行于所有
Java
运行时环境,同时对Dubbo/Spring Cloud等框架也有较好的支持。控制台(Dashboard)基于Spring Boot开发,打包后可以直接运行,不需要额外的Tomcat等应用容器。
初始化演示工程
本次演示sentinel1.7.0版本;
-
服务模块
pom.xml
<dependencies> <!--SpringCloud ailibaba nacos --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--SpringCloud ailibaba sentinel-datasource-nacos后续做持久化用到--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> <!--SpringCloud ailibaba sentinel --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!--openfeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- SpringBoot整合Web组件+actuator --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--日常通用jar包配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>4.6.3</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
application.yaml
server: port: 8401 spring: application: name: cloudalibaba-sentinel-service cloud: nacos: discovery: #Nacos服务注册中心地址 server-addr: 124.70.84.192:80 sentinel: transport: #配置Sentinel dashboard地址 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); } }
业务类
@RestController public class FlowLimitController { @GetMapping(value = "/test1") public String test1(){return "test1";} @GetMapping(value = "/test2") public String test2(){return "test2";} }
测试
先启动
nacos
,再启动sentinel
,最后启动这个服务模块;
由于Sentinel采用的懒加载说明,我们执行一次访问即可http://localhost:8401/test2
流控规则
介绍
流控模式
直接(默认)
QPS与线程数的区别:
比如去一家银行办理业务;
- QPS就是每秒钟大于阈值的人(请求)去敲银行大门要办理业务,此时直接报错(此时还在银行门外);
线程数就是(假如阈值是1)每秒钟只有1个人(线程)能进到银行里面办理业务,大于一个进入直接报错;
现象
思想
直接调用默认报错信息,技术方面OK。but,是否应该有我们自己的后续处理?类似豪猪哥有个fallback的兜底方法?后面讲;
关联
当关联的资源达到阈值时,就限流自己【相当于别人惹事自己买单】
链路
流控效果
预热(Warm Up)
说明
公式:
阈值除以coldFactor(冷加载因子,默认值为3)
,经过预热时长后才会达到阈值;当流量突然增大的时候,我们常常会希望系统从空闲状态到繁忙状态的切换的时间长一些。即如果系统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步的增多,经过预期的时间以后,到达系统处理请求个数的最大值。Warm Up(冷启动,预热)模式就是为了实现这个目的的。
源码证明冷加载因子默认值为3:
com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController
-
配置说明
排队等待
匀速排队,阈值必须设置为QPS,—->>官方描述;
源码所在类: com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController
;降级规则
—->>官网介绍(官网针对的是Sentinel 1.8.0 及以上版本。1.8.0 版本对熔断降级特性进行了全新的改进升级);
Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例:调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响别的资源而导致级联错误。
- 当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出DegradeException)。
- Sentinel的断路器是没有半开状态的;
RT(平均响应时间,秒级)
- 平均响应时间超出阈值且在时间窗口内通过的请求>=5,两个条件同时满足后触发降级,窗口期过后关闭断路器;
- RT最大4900(更大的需要通过
-Dcsp.sentinel.statistic.max.rt=XXXX
才能生效);
异常比列(秒级)
QPS>=5
且异常比例(秒级统计)超过阈值
时触发降级;时间窗口结束后,关闭降级;
异常数(分钟级)
异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级;
降级策略实战
RT
Sentinel1.8.0版本(补)
1.8.0 版本对熔断降级特性进行了全新的改进升级,RT又平均响应时间变成了慢调用比例,如下:
官网原文:慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用RT则结束熔断,若大于设置的慢调用RT则会再次被熔断。
也就是说1.8.0版本有半开状态(也就是探测恢复状态(HALF-OPEN 状态))了;
异常比例
Sentinel1.8.0版本(补)
异常数
Sentinel1.8.0版本(补)
热点key限流
介绍
—->>官网介绍;
何为热点,热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作;
示例
兜底方法分为系统默认和客户自定义两种:
之前的case,限流出问题后,都是用sentinel系统默认的提示:
Blocked by Sentinel (flow limiting)
;使用
@SentinelResource
来进行自定义(HystrixCommand到@SentinelResource);controller
@GetMapping(value = "/testHotkey") @SentinelResource(value = "testHotkey",//这里value随便写 blockHandler = "deal_testHotkey" //如果某个资源违背了sentinel配的热点规则,就交由blockHandler指定的方法处理 ) public String testHotkey(//两个参数p1,p2都是可传可不传 @RequestParam(value = "p1",required = false)String p1, @RequestParam(value = "p1",required = false)String p2){ return "*-*-*-*-*-**-*testHotkey()"; } public String deal_testHotkey(String p1, String p2, BlockException e){ return "deal_testHotkey-----------/(ㄒoㄒ)/~~"; }
sentinel配置
@SentinelResource
注解必须配置blockHandler
参数,不然触发热点规则之后用户页面直接呈现不友好的error page页面;参数例外项配置
上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流,这是普通情况,那如果我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样呢?假如当p1的值等于5时,它的阈值可以达到200;
总结
@SentinelResource
处理的是Sentinel控制台配置的违规情况,有blockHandler
方法配置的兜底处理;运行出错该走异常走异常,@SentinelResource不管;系统规则
—->>官网介绍;
Sentinel系统自适应限流从整体维度对应用入口流量进行控制,结合应用的Load
、CPU使用率
、总体平均RT
、入口QPS
和并发线程数
等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。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达到阈值即触发系统保护。
@SentinelResource案例
Controller
新增控制层
RateLimitController
;@RestController public class RateLimitController { @GetMapping("/byResource") @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 服务不可用"); } }
注意:
@SentinelResource
注解不支持private()
方法;限流(根据资源名/URL地址)+后续处理
上面兜底方案面临的问题
系统默认的,没有体现我们自己的业务要求。
- 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
- 每个业务方法都添加一个兜底的,那代码膨胀加剧。
-
客户自定义限流处理逻辑
创建公共兜底类CustomerBlockHandler用于自定义限流处理逻辑
公共兜底类CustomerBlockHandler
public class CustomerBlockHandler { public static CommonResult handleException(BlockException exception){ return new CommonResult(40404,"自定义的限流处理信息......CustomerBlockHandler"); } }
使用此公共兜底类
@GetMapping("/byResource/one") @SentinelResource(value = "byResource1", blockHandlerClass = CustomerBlockHandler.class,//兜底方法所在类 blockHandler = "handleException")//所用的兜底类的兜底方法 public CommonResult byResource1(){ return new CommonResult(200,"按资源名称限流测试OK", new Payment(2020L,"serial001")); }
更多注解属性说明
Sentinel主要有三个核心Api:
SphU
定义资源;Tracer
定义统计;-
服务熔断功能
**sentinel**
整合ribbon+openFeign+fallback
;
启动nacos和sentinel;服务提供者9003/9004
新建
cloudalibaba-provider-payment9003/9004
两个一样,只有端口号不同,以9003为例;pom.xml
<dependencies> <!--SpringCloud ailibaba nacos --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity --> <groupId>com.atguigu</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency> <!-- SpringBoot整合Web组件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--日常通用jar包配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
application.yaml
server: port: 9003 spring: application: name: nacos-payment-provider cloud: nacos: discovery: server-addr: 124.70.84.192:80 #配置Nacos地址 management: endpoints: web: exposure: include: '*'
主启动类
@SpringBootApplication @EnableDiscoveryClient
控制层
@RestController 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")); } @GetMapping(value = "/paymentSQL/{id}") 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; } }
服务消费者84
pom.xml
<dependencies> <!--SpringCloud ailibaba nacos --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--SpringCloud ailibaba sentinel --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!-- 引入自己定义的api通用包,可以使用Payment支付Entity --> <dependency> <groupId>com.atguigu</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency> <!-- SpringBoot整合Web组件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--日常通用jar包配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
application.yaml
server: port: 84 spring: application: name: nacos-order-consumer cloud: nacos: discovery: server-addr: 124.70.84.192:80 sentinel: transport: dashboard: localhost:8080 #配置Sentinel dashboard地址 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口 port: 8719 #消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者) service-url: nacos-user-service: http://nacos-payment-provider
主启动类
@EnableDiscoveryClient @SpringBootApplication
业务类
配置类
@Configuration public class ApplicationContextConfig { @Bean @LoadBalanced public RestTemplate getRestTemplate() { return new RestTemplate(); } }
控制层
@RestController public class CircleBreakerController { public static final String SERVICE_URL="http://nacos-payment-provider"; @Resource private RestTemplate restTemplate; @RequestMapping("/consumer/fallback/{id}") @SentinelResource(value = "fallback", fallback="handlerFallback",//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||id<1) { 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); } public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) { Payment payment = new Payment(id,"null"); return new CommonResult<>(445, "无此流水: blockException "+blockException.getMessage(),payment); } }
总结
若
blockHandler
和fallback
都进行了配置并都出发了两者的规则时,则被限流降级而抛出BlockException
时只会进入blockHandler
处理逻辑。忽略属性(补充)
**@SentinelResource**
注解里的属性exceptionsToIgnore={IllegalArgumentException.class}
(以IllegalArgumentException异常为例);
使用OpenFeign
pom.xml新增OpenFeign的依赖
<!--SpringCloud openfeign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
激活Sentinel对Feign的支持
application.yaml
中增加激活Sentinel
对Feign
的支持;#激活Sentinel对Feign的支持 feign: sentinel: enabled: true
主启动类新增
@EnableFeignClients
@EnableDiscoveryClient @SpringBootApplication @EnableFeignClients//⭐⭐新增⭐⭐
业务类
Service
//⭐⭐⭐⭐⭐⭐接口⭐⭐⭐⭐⭐⭐ @FeignClient(value = "nacos-payment-provider", fallback=PaymentFallbackService.class)//调用中关闭9003服务提供者 public interface PaymentService { @GetMapping(value = "/paymentSQL/{id}") public CommonResult<Payment> paymentSQL(@PathVariable("id")Long id); } //⭐⭐⭐接口实现类兼兜底方法⭐⭐⭐ @Service public class PaymentFallbackService implements PaymentService { @Override public CommonResult<Payment> paymentSQL(Long id) { return new CommonResult<>(444,"服务降级返回",new Payment(id, "errorSerial..")); } }
controller
//⭐⭐⭐⭐⭐⭐OpenFeign⭐⭐⭐⭐⭐⭐ @Resource private PaymentService paymentService; @GetMapping(value = "/consumer/openfeign/{id}") public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) { if(id == 4) {throw new RuntimeException("没有该id");} return paymentService.paymentSQL(id); }
测试
首先正常访问,可以:
把服务提供者9004和9003关掉再次访问:
熔断框架比较
规则持久化
是什么
一旦我们重启应用,sentinel规则将消失,生产环境需要将配置规则进行持久化。将限流配置规则持久化进
Nacos
保存,只要刷新服务消费者某个rest地址,sentinel
控制台的流控规则就能看到,只要Nacos
里面的配置不删除,针对服务消费者上sentinel
上的流控规则持续有效;示例
pom.xml
<!--SpringCloud ailibaba sentinel-datasource-nacos后续做持久化用到--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>
application.yaml
spring: application: name: cloudalibaba-sentinel-service cloud: nacos: discovery: #Nacos服务注册中心地址 server-addr: 124.70.84.192:80 sentinel: transport: #配置Sentinel dashboard地址 dashboard: localhost:8080 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口 port: 8719 datasource: #⭐⭐⭐添加数据源配置⭐⭐⭐ ds1: nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} dataId: ${spring.application.name} groupId: DEFAULT_GROUP data-type: json rule-type: flow
nacos控制台新增配置
resource:资源名称;
- limitApp:来源应用;
- grade:阈值类型,0表示线程数,1表示QPS;
- count:单机阈值;
- strategy:流控模式,0表示直接,1表示关联,2表示链路;
- controlBehavior:流控效果,0表示快速失败,1表示
Warm Up
预热,2表示排队等待; -
测试
之后在模块重启时这个在
nacos
里创建的配置就会自动转化为sentinel
里面的流控规则,并且一直存在,就有持久性;总结
SpringCloud Alibaba Seata处理分布式事务
Seata简介
—->>官网;
Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
分布式事务处理过程的**1ID**
+**3组件**
模型: id-
**Transaction ID XID**
:全局唯一的事务ID;- 组件-
**Transaction Coordinator (TC)**
:事务协调者,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚; - 组件-
**Transaction Manager (TM)**
:事务管理器,控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议; - 组件-
**Resource Manager (RM)**
:资源管理器,控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚;分布式事务过程
TM
向TC
申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID
;XID
在微服务调用链路的上下文中传播;RM
向TC
注册分支事务,将其纳入XID
对应全局事务的管辖;TM
向TC
发起针对XID
的全局提交或回滚决议;-
下载安装
—->>下载地址;下载之后解压;
修改配置文件
seata/conf/file.conf
,首先将file.conf
备份一份以防改错;- 修改
file.conf
内的内容,主要修改:自定义事务组名称**+**事务日志存储模式为db**+**数据库连接信息
; - mysql5.7数据库新建库seata,进入该库并导入seata准备的sql文件(1.0版本之后,这个sql文件不再和服务端压缩一块,而是要单独下载其对应版本的source压缩包里面:
seata-1.0.0/script/server/db/mysql.sql
),导入成功如下: - 修改
seata/conf
目录下的registry.conf
配置文件: - 启动:
./seata-server.sh
;订单/库存/账户业务数据库准备
以下演示都需要先启动Nacos后启动Seata,保证两个都OK分布式事务业务说明
- 这里我们会创建三个服务,一个订单服务,一个库存服务,一个账户服务。
- 当用户下单时,会在订单服务中创建一个订单,
然后通过远程调用库存服务来扣减下单商品的库存
,再通过远程调用账户服务来扣减用户账户里面的余额
,最后在订单服务中修改订单状态为已完成
。 - 该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。
- 下订单—->扣库存—->减账户(余额);
创建相应的库及表
- 创建业务数据库:
- seata_order:存储订单的数据库;
- seata_storage:存储库存的数据库;
- seata_account:存储账户信息的数据库。
- 按照上述3库分别建对应业务表:
- seata_order库下建t_order表;
- seata_storage库下建t_storage表;
- seata_account库下建t_account表;
- 按照上述3库分别建对应的回滚日志表(同样在source压缩文件中,
seata-1.0.0/script/client/at/db/mysql.sql
),这个sql文件在上面三个库中每个执行一遍; - 最终效果:
-
订单/库存/账户业务微服务准备
业务需求:下订单->减库存->扣余额->改(订单)状态新建订单Order-Module
pom.xml
<dependencies> <!--nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--seata--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <exclusions> <exclusion> <artifactId>seata-all</artifactId> <groupId>io.seata</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> <version>1.0.0</version> </dependency> <!--feign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--web-actuator--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--mysql-druid--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.37</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>
application.yaml
server: port: 2001 spring: application: name: seata-order-service cloud: alibaba: seata: #自定义事务组名称需要与seata-server中的对应 tx-service-group: fsp_tx_group nacos: discovery: server-addr: localhost:8848 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://124.80.74.192:3306/seata_order username: root password: 12345ssdlh feign: hystrix: enabled: false logging: level: io: seata: info mybatis: mapperLocations: classpath:mapper/*.xml
fire.conf
0.9.0是直接复制seara里面fire.conf
的内容,1.0.0版本是复制fire.conf.example
的内容,之后做一些自己数据库的配置;transport { type = "TCP" server = "NIO" heartbeat = true thread-factory { boss-thread-prefix = "NettyBoss" worker-thread-prefix = "NettyServerNIOWorker" server-executor-thread-prefix = "NettyServerBizHandler" share-boss-worker = false client-selector-thread-prefix = "NettyClientSelector" client-selector-thread-size = 1 client-worker-thread-prefix = "NettyClientWorkerThread" boss-thread-size = 1 worker-thread-size = 8 } shutdown { wait = 3 } serialization = "seata" compressor = "none" } service { vgroup_mapping.fsp_tx_group = "default" #修改自定义事务组名称 default.grouplist = "127.0.0.1:8091" enableDegrade = false disable = false max.commit.retry.timeout = "-1" max.rollback.retry.timeout = "-1" disableGlobalTransaction = false } client { async.commit.buffer.limit = 10000 lock { retry.internal = 10 retry.times = 30 } report.retry.count = 5 tm.commit.retry.count = 1 tm.rollback.retry.count = 1 } store { mode = "db" file { dir = "sessionStore" max-branch-session-size = 16384 max-global-session-size = 512 file-write-buffer-cache-size = 16384 session.reload.read_size = 100 flush-disk-mode = async } db { datasource = "dbcp" db-type = "mysql" driver-class-name = "com.mysql.jdbc.Driver" url = "jdbc:mysql://124.70.84.192:3306/seata" user = "root" password = "12345ssdlh" min-conn = 1 max-conn = 3 global.table = "global_table" branch.table = "branch_table" lock-table = "lock_table" query-limit = 100 } } lock { mode = "remote" local { } remote { } } recovery { committing-retry-period = 1000 asyn-committing-retry-period = 1000 rollbacking-retry-period = 1000 timeout-retry-period = 1000 } transaction { undo.data.validation = true undo.log.serialization = "jackson" undo.log.save.days = 7 undo.log.delete.period = 86400000 undo.log.table = "undo_log" } metrics { enabled = false registry-type = "compact" exporter-list = "prometheus" exporter-prometheus-port = 9898 } support { spring { datasource.autoproxy = false } }
registry.conf
seata里面的registry.conf
里面的内容;registry { type = "nacos" nacos { serverAddr = "124.70.84.192:80" namespace = "" cluster = "default" } eureka { serviceUrl = "http://localhost:8761/eureka" application = "default" weight = "1" } redis { serverAddr = "localhost:6379" db = "0" } zk { cluster = "default" serverAddr = "127.0.0.1:2181" session.timeout = 6000 connect.timeout = 2000 } consul { cluster = "default" serverAddr = "127.0.0.1:8500" } etcd3 { cluster = "default" serverAddr = "http://localhost:2379" } sofa { serverAddr = "127.0.0.1:9603" application = "default" region = "DEFAULT_ZONE" datacenter = "DefaultDataCenter" cluster = "default" group = "SEATA_GROUP" addressWaitTime = "3000" } file { name = "file.conf" } } config { type = "file" nacos { serverAddr = "localhost" namespace = "" } consul { serverAddr = "127.0.0.1:8500" } apollo { app.id = "seata-server" apollo.meta = "http://192.168.1.204:8801" } zk { serverAddr = "127.0.0.1:2181" session.timeout = 6000 connect.timeout = 2000 } etcd3 { serverAddr = "http://localhost:2379" } file { name = "file.conf" } }
domain
就是javaBean类;CommonResult
@Data @AllArgsConstructor @NoArgsConstructor public class CommonResult<T> { private Integer code; private String message; private T data; public CommonResult(Integer code, String message) { this(code,message,null); } }
Order
@Data @AllArgsConstructor @NoArgsConstructor public class Order { private Long id; private Long userId; private Long productId; private Integer count; private BigDecimal money; private Integer status; //订单状态:0:创建中;1:已完结 }
Dao接口及实现
接口OrderDao
@Mapper public interface OrderDao { //1.新建订单 void create(Order order); //2.修改订单状态(0->1) void update(@Param("userId")Long userId,@Param("status")Integer status); }
映射文件OrderMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.atguigu.cloudalibaba.dao.OrderDao"> <resultMap id="BaseResultMap" type="com.atguigu.cloudalibaba.domain.Order"> <id column="id" property="id" jdbcType="BIGINT"/> <result column="user_id" property="userId" jdbcType="BIGINT"/> <result column="product_id" property="productId" jdbcType="BIGINT"/> <result column="count" property="count" jdbcType="INTEGER"/> <result column="money" property="money" jdbcType="DECIMAL"/> <result column="status" property="status" jdbcType="INTEGER"/> </resultMap> <!--void create(Order order);--> <insert id="create"> insert into t_order(id,user_id,product_id,count,money,status) values (null,#{userId},#{productId},#{count},#{money},0); </insert> <!--void update(@Param("userId")Long userId,@Param("status")Integer status);--> <update id="update"> update t_order set status = 1 where user_id=#{userId and status = #{status}; </update> </mapper>
Service接口及实现
版本变更太大,例子暂无法实现,参照官网:http://seata.io/zh-cn/;使用
在service业务层类上面加上注解:@GlobalTransactional(name = "create-order",rollbackFor = Exception.class)
-
name
是事务的名字,可以随意起,保持唯一性;rollbackFor = Exception.class
表示发生任何异常都进行回滚;seata原理
分布式事务的执行流程
TM
开启分布式事务(TM
向TC
注册全局事务记录);- 按业务场景,编排数据库、服务等事务内资源(
RM
向TC
汇报资源准备状态 ); TM
结束分布式事务,事务一阶段结束(TM
通知TC
提交/回滚分布式事务);- 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
TC
汇总事务信息,决定分布式事务是提交还是回滚;TC
通知所有RM
提交/回滚 资源,事务二阶段结束。
- 解析SQL语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,
- 执行“业务 SQL”更新业务数据,在业务数据更新之后,
- 其保存成“after image”,最后生成行锁。
以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。
二阶段提交
二阶段如是顺利提交的话:因为“业务 SQL”在一阶段已经提交至数据库,所以Seata框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。
二阶段回滚
二阶段回滚:
- 二阶段如果是回滚的话,Seata就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。
- 回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和“after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。