八、Spring Cloud Stream
- 构建消息驱动微服务的框架
- 应用程序通过生产者(outputs)、消费者(inputs)与Spring Cloud Stream中binder对象交互
- 通过在配置文件中进行配置来binding,而Spring Cloud Stream的binder对象负责与消息中间件交互
- 类似jpa,屏蔽底层消息中间件的差异,程序员主要操作Spring Cloud Stream即可
- 不需要管底层是kafka还是rabbitMq,屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型
1、如何屏蔽差异(Rabbit MQ、KafKa)
- 通过定义帮定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离
- Binder:INPUT对应消费者,OUTPUT对应生产者
2、处理架构
3、通信模式
- 遵循发布-订阅模式
- Topic主题进行广播:在Rabbit中是exchange、kafka中是Topic
4、业务流程
5、常用注解及API
6、实操
(1)pom依赖
- 生产者与消费者添加的依赖相同
<!-- cloud stream 相关依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
(2)yml配置
spring:
cloud:
stream:
binders: # 配置要绑定的mq的服务信息
streamRabbit: # 自定义的名称,用于binding整合
type: rabbit # mq类型,rabbitmq / kafka
environment: # mq环境配置信息
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
outPutOneRabbit: # 自定义的名称,用于binding整合
type: rabbit # mq类型,rabbitmq / kafka
environment: # mq环境配置信息
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
outPutTwoRabbit: # 自定义的名称,用于binding整合
type: rabbit # mq类型,rabbitmq / kafka
environment: # mq环境配置信息
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 服务整合处理
output: # 一个通道的名称,默认为output,可自定义
destination: streamRabbitExchange # rabbitmq中使用的exchange
content-type: application/json # 消息类型
binder: streamRabbit # 设置要绑定的消息服务的具体设置,与binders中的一一对应
myOutPutOne: # 一个通道的名称,默认为output,可自定义
destination: streamRabbitOneExchange # rabbitmq中使用的exchange
content-type: application/json # 消息类型
binder: outPutOneRabbit # 设置要绑定的消息服务的具体设置,与binders中的一一对应
myOutPutTwo: # 一个通道的名称,默认为output,可自定义
destination: streamRabbitOneExchange # rabbitmq中使用的exchange
content-type: application/json # 消息类型
binder: outPutTwoRabbit # 设置要绑定的消息服务的具体设置,与binders中的一一对应
input: # 一个通道的名称,默认为output,可自定义
destination: streamRabbitExchange # rabbitmq中使用的exchange
content-type: application/json # 消息类型
binder: streamRabbit # 设置要绑定的消息服务的具体设置,与binders中的一一对应
group: groupOne
myInputOne: # 一个通道的名称,默认为output,可自定义
destination: streamRabbitOneExchange # rabbitmq中使用的exchange
content-type: application/json # 消息类型
binder: outPutOneRabbit # 设置要绑定的消息服务的具体设置,与binders中的一一对应
group: groupInputOne
myInputTwo: # 一个通道的名称,默认为output,可自定义
destination: streamRabbitTwoExchange # rabbitmq中使用的exchange
content-type: application/json # 消息类型
binder: outPutTwoRabbit # 设置要绑定的消息服务的具体设置,与binders中的一一对应
group: groupInputTwo
default-binder: streamRabbit
(3)配置解析
- binders:配置要绑定的mq的服务信息
- streamRabbit:自定义的名称,需要保持唯一性,在bindings中整合
- type:mq类型
- environment:mq环境配置
- 配置了多个mq时,需要指定一个默认的mq(default-binder),否则会报异常
- bindings:mq服务整合
- output:默认的生产者消息发送通道
- input:默认的消费者消息接受通道
- myOutPutOne、myOutPutOne:自定义的生产者消息发送通道
- myInputOne、myInputTwo:自定义的消费者消息消费通道
- destination:rabbitmq中的exchange
- content-type:消息数据类型
- binder:绑定一个mq,即binders下的streamRabbit
- group:消费者分组,绑定了同一个mq的消费者,如果不指定分组,多个消费者会重复接受消息,且指定分组后,如果消费者宕机,消费者重新上线后可继续接收未消费的消息;
(4)自定义生产、消费通道
① 生产者
- 是一个接口
- 每个通道定义一个方法:MessageChannel 类型
- 方法上加@Output注解,value为通道名称
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;
import org.springframework.stereotype.Component;
/**
* @Description
* @Author xzkui
* @Date 2021/9/14 16:21
**/
public interface MyOutPutConfig {
String myOutPutOne = "myOutPutOne";
String myOutPutTwo = "myOutPutTwo";
/**
* 自定义输出
* @return
*/
@Output(value = myOutPutOne)
MessageChannel myOutPutOne();
/**
* 自定义输出
* @return
*/
@Output(value = myOutPutTwo)
MessageChannel myOutPutTwo();
}
② 消费者
- 是一个接口
- 每个通道定义一个方法:SubscribableChannel 类型
- 方法加@Input注解,value为通道名称
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.stereotype.Component;
/**
* @Description
* @Author xzkui
* @Date 2021/9/14 16:23
**/
public interface MyInputConfig {
String myInputOne = "myInputOne";
String myInputTwo = "myInputTwo";
/**
* 自定义接收
* @return
*/
@Input(value = myInputOne)
SubscribableChannel myInputOne();
/**
* 自定义接收
* @return
*/
@Input(value = myInputTwo)
SubscribableChannel myInputTwo();
}
(5)生产者消息发送
- @EnableBinding 绑定自定义的通道
import com.atguigu.springcloud.config.MyOutPutConfig;
import com.atguigu.springcloud.service.IMessageSendMyOutPut;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @Description
* @Author xzkui
* @Date 2021/9/14 17:01
**/
@EnableBinding(MyOutPutConfig.class)
@Slf4j
public class MessageSendMyOutPutImpl implements IMessageSendMyOutPut {
@Autowired
private MyOutPutConfig outPutConfig;
@Override
public String sendMsgByOutPutOne() {
String now = "outPutOne ==> " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
Message<String> message = MessageBuilder.withPayload(now).build();
outPutConfig.myOutPutOne().send(message);
return now;
}
@Override
public String sendMsgByOutPutTwo() {
String now = "outPutTwo ==> " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
Message<String> message = MessageBuilder.withPayload(now).build();
outPutConfig.myOutPutOne().send(message);
return now;
}
}
(6)消费者消息接收
import com.atguigu.springcloud.config.MyInputConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
/**
* @Description
* @Author xzkui
* @Date 2021/9/14 17:04
**/
@Component
@Slf4j
@EnableBinding(MyInputConfig.class)
public class ReceiveMyInput {
@Value("${server.port}")
private String serverPost;
/**
* 自定义消息接收
* @param message
*/
@StreamListener(MyInputConfig.myInputOne)
public void getMsgByInputOne(Message<String> message){
log.info(message.getPayload() + " <== inputOne ==>" + serverPost);
}
/**
* 自定义消息接收
* @param message
*/
@StreamListener(MyInputConfig.myInputTwo)
public void getMsgByInputTwo(String message){
log.info(message + " <== inputTwo ==>" + serverPost);
}
}
九、链路追踪:Spring Cloud Sleuth
- 用于追踪每个请求的整体链路
- 需要安装zipkin:可直接运行jar包,使用web页面查看链路数据,默认地址为: localhost:9411
- 项目中如何使用:
- 在需要被监控的微服务中添加依赖:
<!--包含了sleuth+zipkin--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency>
- yml中添加配置
spring: application: name: cloud-order-service zipkin: base-url: http://localhost:9411 sleuth: sampler: #采样率值介于0到1之间,1则表示全部采集 probability: 1
- 调用接口之后可在web界面查看到链路信息
十、Spring Cloud Alibaba — Nacos
1、是什么
- 阿里巴巴开源产品,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台
- 是服务注册和配置中心的组合
2、安装
- 下载地址:github
- 下载解压后进入bin目录,windows运行
start.cmd
,linux运行start.sh
- Nacos默认端口为8848,
localhost:8848/nacos
,账号密码默认都为nacos
3、在微服务中使用nacos(作为服务注册中心)
3.1 服务提供
- Spring Cloud Alibaba 依赖
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- Nacos依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- yml配置文件
server:
port: 9001
spring:
application:
name: cloud-alibaba-nacos-provider
cloud:
nacos:
discovery:
# nacos 地址
server-addr: localhost:8848
management:
endpoints:
web:
exposure:
include: '*'
- 启动类添加注解
@EnableDiscoveryClient
3.2 服务消费
- pom文件添加的依赖和服务提供方一样
- yml配置文件
server:
port: 83
spring:
application:
name: cloud-albaba-nacos-consumer
cloud:
nacos:
discovery:
server-addr: localhost:1111
# 消费者使用RestTemplate远程调用的服务提供方地址
service:
provider-url: http://cloud-alibaba-nacos-provider
- 启动类添加注解
@EnableDiscoveryClient
- 负载均衡,因为Nacos集成了Ribbon所以Nacos也支持负载均衡,需要添加配置类开启
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @create 2022-01-14
*/
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
4、Nacos与其他服务注册对比
- Nacos 既支持CP,也支持AP,通过
curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'
切换CP、AP模式
5、在微服务中使用Nacos(作为配置中心)
- 和Spring Cloud Config 一样,在项目初始化时,要保证先从配置中心进行配置拉取,只有在拉取配置之后,才能保证项目的正常启动
- Spring Boot 中配置文件的加载存在优先级顺序,
bootstrap
优先级高于application
5.1、如何使用
- 作为配置中心所需依赖
<!--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>
- bootstrap.yml
server:
port: 3377
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848#服务注册中心地址
config:
server-addr: localhost:8848 #配置中心地址
file-extension: yaml #指定yaml格式的配置
# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
# nacos-config-client-dev.yaml
# nacos-config-client-test.yaml ----> config.info
- application.yml
spring:
profiles:
active: dev
- 启动类添加注解
@EnableDiscoveryClient
- controller:在类级别上添加
@RefreshScope
注解,用于自动刷新
5.2、配置文件命名规则(dataid)
${spring.application.name}-${spring.profiles.active}-${spring.cloud.nacos.config-file-extension}
- ${spring.application.name}:微服务应用名称
- ${spring.profiles.active}:开发环境(dev、test、prod)
- ${spring.cloud.nacos.config-file-extension}:文件类型(yaml、properties)
5.3、自动刷新
- Nacos支持Bus总线,在Nacos的web页面修改配置文件后,会自动发送命令更新所有客户端
- 需要在controller添加类级别注解
@RefreshScope
5.4、分类配置
NameSpace + Group + DataID
,类似Java中的package + class + method
-
NameSpace:命名空间,默认为
public
-
Group:分组,在Nacos的web界面新建配置文件时创建,默认为
DEFAULT_GROUP
-
DataID:配置文件名称,根据命名规则命名
6、Nacos集群及持久化配置
6.1 切换Nacos自带数据库为我们自己的MySQL数据库
- 在Nacos的conf目录下找到application.properties配置文件,并修改配置
# spring
server.contextPath=/nacos
server.servlet.contextPath=/nacos
# nacos端口,可根据实际情况进行修改
server.port=8848
# nacos.cmdb.dumpTaskInterval=3600
# nacos.cmdb.eventTaskInterval=10
# nacos.cmdb.labelTaskInterval=300
# nacos.cmdb.loadDataAtStart=false
# metrics for prometheus
#management.endpoints.web.exposure.include=*
# metrics for elastic search
management.metrics.export.elastic.enabled=false
#management.metrics.export.elastic.host=http://localhost:9200
# metrics for influx
management.metrics.export.influx.enabled=false
#management.metrics.export.influx.db=springboot
#management.metrics.export.influx.uri=http://localhost:8086
#management.metrics.export.influx.auto-create-db=true
#management.metrics.export.influx.consistency=one
#management.metrics.export.influx.compressed=true
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D %{User-Agent}i
# default current work dir
server.tomcat.basedir=
## spring security config
### turn off security
#spring.security.enabled=false
#management.security=false
#security.basic.enabled=false
#nacos.security.ignore.urls=/**
nacos.security.ignore.urls=/,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/login,/v1/console/health/**,/v1/cs/**,/v1/ns/**,/v1/cmdb/**,/actuator/**,/v1/console/server/**
# nacos.naming.distro.taskDispatchPeriod=200
# nacos.naming.distro.batchSyncKeyCount=1000
# nacos.naming.distro.syncRetryDelay=5000
# nacos.naming.data.warmup=true
# nacos.naming.expireInstance=true
nacos.istio.mcp.server.enabled=false
# 将nacos内嵌的derby数据库切换为自己的mysql数据库
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://localhost:3306/nacos_config?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&autoReconnect=true&failOverReadOnly=false
db.user=root
db.password=123456
6.2、集群部署
- VIP:虚拟IP(Virtual IP)
- Nacos默认有自带的嵌入式数据库(derby),如果是集群模式,需要使用MySQL(目前只支持MySQL)集中式存储配置信息
- 新建MySQL数据库(nacos-config)在conf目录下找到nacos-mysql.sql,复制sql脚本在nacos-config数据库运行
- 拷贝三份Nacos,并根据6.1修改端口号以及数据库(数据库需要一致)
- 找到Nacos安装目录下的cluster.conf,并添加Nacos集群的ip
192.168.182.1:8848
192.168.182.1:8849
192.168.182.1:8850
- 在bin目录下找到start.cmd,修改
MODE
为cluster
,表示集群模式,单体模式时,MODE
值为standalone
- 使用Nginx作为VIP,添加配置
- 集群部署验证
1、先启动Nacos
2、启动Nginx
3、用浏览器访问:localhost:1111/nacos
4、可以进入nacos的web界面则表示成功
十一、Spring Cloud Alibaba — Sentinel
1、是什么
- 实现服务熔断和限流
2、运行Sentinel
- 下载jar包,使用
java -jar xxxx.jar
运行 - 访问sentinel,端口默认为8080,
localhost:8080
,账号密码默认为sentinel
3、微服务整合Sentinel
- Spring Cloud Alibaba的依赖‘
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 需要的依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- yml配置文件
server:
port: 8401
spring:
application:
name: cloud-alibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:1111
sentinel:
transport:
dashboard: localhost:8080
port: 8719
management:
endpoints:
web:
exposure:
include: '*'
- 启动类添加注解
@EnableDiscoveryClient
4、微服务整合完成并启动后,在sentinel的web界面控制台还看不到微服务的任务信息,因为sentinel是懒加载,所以需要访问微服务中的某个接口后,才能在sentinel的web界面控制台看到微服务的信息
4.1、流控规则
流量限制控制规则
- 资源名:唯一名称,默认请求路径
- 针对来源:可以针对调用者进行限流,填写微服务名称;默认是default(不区分来源)
- 阈值类型:
- QPS(每秒钟的请求数量):当调用该api的QPS达到
单机阈值
设定的数量时,进行限流 - 线程数:当调用该api的线程数量达到
单机阈值
设定的数量后进行限流
- QPS(每秒钟的请求数量):当调用该api的QPS达到
- 是否集群
- 流控模式:
- 直接:api达到限流条件时直接进行限流(没得商量)
- 关联:关联的资源达到阈值时,就限流自己(选择该项时,需要指定关联的资源–请求路径)
- 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值就进行限流)【api级别的针对来源】,当选择该项时需要指定入口资源
- 流控效果:
- 快速失败:直接失败,抛出异常
- Warm up(预热):比如设置的单机阈值是10,开启了Warm up 后,实际上默认的阈值为 单机阈值 / 3(即 10 / 3 = 3),当该资源达到了阈值后,经过设定的
预热时长(秒)
后单机阈值才会慢慢提升到我们填写的阈值;这样做的目的是平常某些系统可能QPS不高,但是某些时间段可能或瞬间有很多的访问量,如果不设置预热,瞬间的高并发可能会导致系统崩溃的问题 - 排队等待:匀速排队,让请求以均匀的速度通过,阈值类型必需设置为QPS,否则无效;当达到了限流条件后,新的请求需要等待设定的
超时时间(毫秒)
以后,才能正常访问;
4.2、降级规则
熔断降级,
(1) sentinel 会在调用链路中某个资源出现不稳定状态时(例如调用超时或者异常比例升高),对这个资源进行限制,让请求快速失败,避免影响到其他的资源而导致级联错误;
(2)当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认是抛出DegradeException);
(3)Sentinel的断路器没有半开状态
,半开状态是指系统会自动检测请求是否有异常,如果没有则关闭断路器回复使用,如果有则打开断路器使请求不可用
- 资源名:请求路径
- 降级策略:
- RT(ms):平均响应时间,秒级;平均响应时间
超出阈值
且在时间窗口内通过的请求>=5
,两个条件同时满足后触发降级;窗口期过后关闭断路器;RT最大可设置为4900ms
(更大的需要通过设置-Dcsp.sentinel.statistic.max.rt=xxxx
才能生效) - 异常比例(0.0 ~ 1.0):
QPS >= 5
且异常比例(秒级统计)超过阈值
时,触发降级;时间窗口结束后关闭降级) - 异常数:异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后关闭降级;
如果选择了异常数,那么时间窗口必需大于60,因为异常数是以分钟为单位进行统计的,如果小于60,则结束熔断后可能会导致再次进入熔断状态
- RT(ms):平均响应时间,秒级;平均响应时间
- 时间窗口:降级时间间隔(秒),在时间窗口时间内,对该资源的调动都会自动熔断。
4.3、热点规则
(1)如果该请求携带了指定索引位置的参数,并达到了阈值,则触发降级;
(2)如果该请求没有携带指定索引位置的参数,则不会触发降级;
(3)如果设置了参数例外项,且请求携带的参数是指定索引位置的参数,如果参数值和设置的一致,那么阈值将会调整为参数例外项中设置的阈值;
- 资源名:请求路径
- 限流模式:固定为QPS
- 参数索引:请求接口的参数索引,从0开始
- 单机阈值:接口每秒内的请求次数阈值,达到设定的值后降级
- 统计窗口时长:
- 参数例外项:
- 参数类型:int、double、long、float、char、byte、java.lang.String
- 参数值:指定索引参数的参数值
- 限流阈值:新的阈值
4.4、系统规则
系统自适应限流,从整体维度对一个用入口进行限流
- 除了CPU使用率的阈值为[0,1]的小数外,其余的都是[0, ~) 的正整数
- LOAD(自适应,仅对Linux/Unix-like机器生效):系统的load1作为启发指标,进行自适应系统保护。当系统load1超过设定的其法制,且系统当前的并发线程超过估算的系统容量时才会触发系统保护(BBR阶段)。系统容量由系统的 maxQps * minRt估算得出。设定的参考值一般是 CPU cores * 2.5;
- 平均RT:当单台机器上所有入口流量的平均RT达到阈值即触发系统保护,单位是ms
- 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护
- 入口QPS:当单台机器上所有入口流量的QPS达到阈值即触发系统保护
- CPU使用率(1.5.0+的版本):当系统的CPU使用率超过阈值即触发系统保护[0.0, 1.0]
4.5、@SentinelResource注解
用于配置降级等功能
注:1.6.0 之前的版本 fallback 函数只针对降级异常(DegradeException)进行处理,不能针对业务异常进行处理
- 常用属性
- value:必填;属性值可作为资源名(尽量不要和接口注解名称一致)
- blockHandlerClass:指定降级方法所在类
- blockHandler:指定降级方法,必需是public方法,返回类型、参数类型和原方法一致,在最后需要添加一个额外的参数(BlockException);默认和原方法在同一个类,不在则需要指定blockHandlerClass,且该方法必须为static方法
- fallbackClass:指定fallback方法所在类
- fallback:指定fallback方法,用于在抛出异常后提供fallback逻辑处理,可以针对除exceptionToIgnore 里面排除的所有类型的异常;返回类型、参数列表和原方法一致,也可以加一个Throwable类型参数,用于接收对应的异常;默认和原方法在用一个类中,不在则需要用fallbackClass指定方法所在类,且方法必需是static方法
- defaultFallback (since 1.6.0):默认的fallback方法名,用于通用的fallback;作用与fallback类似,不同的是指定的方法参数列表需要为空,或者可以加一个Throwablw类型参数;当fallback和defaultFallBack同时配置,只有fallback会生效
- exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
- 特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandler、fallback 和 defaultFallback,则被限流降级时会将 BlockException 直接抛出。
十二、Spring Cloud Alibaba — Seata
分布式事务解决方案
1、相关概念
- Transaction ID XID:全局唯一的事务ID
- 三组件:
- TC - 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚
- TM - 事务管理器:定义全局事务的范围:开始全局事务、提交、回滚全局事务
- RM - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
- 流程:
- TM 向 TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID
- XID 在微服务调用链上下文中传播
- RM 向 TC 注册分支事务,将其纳入 XID 对应的全局事务的管辖
- TM 向 TC 发起针对XID的全局提交或混滚决议
- TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求
2、安装
2.1、修改配置文件
conf/file.conf,修改两处
(1)找到service节点,可修改如图红框中的值
(2)找到store 节点
修改mode为db,随后找到db节点,填写自己的数据库配置信息
conf/registry.conf,修改一处
找到registry节点,根据实际需求选择注册中心
2.2、创建seata需要的数据库
新建数据库:seata,执行conf/db_store.sql 的脚本创建相关数据表
3、运行
进入bin目录,运行seata-server.bat
4、微服务集成seata
- Spring Cloud Alibaba的依赖‘
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 需要的依赖
<!--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>0.9.0</version>
</dependency>
- yml配置文件
server:
port: 2002
spring:
application:
name: cloud-alibaba-seata-storage
cloud:
alibaba:
seata:
# 自定义事务组名称需要与seata-server中的对应,我们之前在seata的配置文件中配置的名字
tx-service-group: xzk_tx_group
nacos:
discovery:
server-addr: localhost:8848
- 微服务除了自己的业务表外,还需要一张seata的回滚日志表,执行 conf/db_undo_log.sql
-- the table to store seata xid data
-- 0.7.0+ add context
-- you must to init this sql for you business databese. the seata server not need it.
-- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
drop table if exists `undo_log`;
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
- 将2.1 中修改过的配置文件放到微服务项目中
- 分布式事务如何生效
- 需要在事务发起方添加注解
@GlobalTransactional(name = "global-transaction-name")
- 在分布式事务中,事务参与方不要使用try{} catch(){} 去捕获异常信息,可以在事务的发起方使用try{} catch(){} 进行捕获,如果在非事务发起方进行捕获,那么事务发起方将无法正确捕获异常和进行事务回滚;
- 需要在事务发起方添加注解
5、seata原理
- TM 开启分布式事务(TM 向 TC注册全局事务记录)
- 按业务场景,编排数据库、服务等事务资源(RM 向 TC汇报资源准备状态)
- TM 结束分布式事务,事务一阶段结束(TM 通知 TC 提交/回滚分布式事务)
- TC 汇总事务信息,决定分布式事务是提交还是回滚
- TC通知所有 RM 提交/回滚资源,事务二阶段结束
5.1、第一阶段
在一阶段,Seata会拦截
业务SQL
- 以下操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性
- 解析SQL语义,找到
业务SQL
要更新的业务数据,在业务数据被更新前,将其保存成before image
- 执行
业务SQL
更新业务数据 - 业务数据更新后,将更新后的数据保存为
after image
- 生成行锁
- 解析SQL语义,找到
5.2、第二阶段
(1)提交
如果是顺利提交,因为
业务SQL
在第一阶段已经提交至数据库中,所以seata框架只需将第一阶段保存的快照数据和行锁删掉,完成数据清理即可
(2)回滚
如果是回滚,Seata就需要回滚第一阶段已经执行的
业务SQL
,并还原业务数据,回滚方式便是用before image
还原业务数据;但在还原前首先校验脏写,对比数据库当前业务数据
和after image
,如果两份数据完全一致就说明没有脏写,可以还原业务数据,反之就说明有脏写,需要人工处理此处的
脏写
:在回滚之前,其他请求已经成功修改过这条数据,那么当前就不能回滚了