接着SpringCloud--Alibaba入门(Nacos+Sentinel),上次使用了SpringCloudAlibaba中的Nacos和Sentinel,其中Nacos作为微服务的核心,不仅仅拥有服务注册中心、服务发现,还有配置中心的功能,并且自带Ribbon;Sentinel为保障整体微服务架构的高可用,拥有流控、熔断等功能,剩下还有OpenFeign、GateWay、Seata。
一、OpenFeign
Netflix也是使用的OpenFeign,所以用法是一致的
创建项目,作为服务调用者:
1.基本使用
1.1 依赖
除了nacos服务发现依赖外,还需要OpenFeign依赖,OpenFeign是SpringCloud中的组件,需要注意和SpringBoot的版本关系:
Spring Cloud Alibaba Version | Spring Cloud Version | Spring Boot Version |
---|---|---|
2021.0.1.0 | Spring Cloud 2021.0.1 | 2.6.3 |
2.2.7.RELEASE | Spring Cloud Hoxton.SR12 | 2.3.12.RELEASE |
2021.1 | Spring Cloud 2020.0.1 | 2.4.2 |
2.2.6.RELEASE | Spring Cloud Hoxton.SR9 | 2.3.2.RELEASE |
2.1.4.RELEASE | Spring Cloud Greenwich.SR6 | 2.1.13.RELEASE |
2.2.1.RELEASE | Spring Cloud Hoxton.SR3 | 2.2.5.RELEASE |
2.2.0.RELEASE | Spring Cloud Hoxton.RELEASE | 2.2.X.RELEASE |
2.1.2.RELEASE | Spring Cloud Greenwich | 2.1.X.RELEASE |
2.0.4.RELEASE(停止维护,建议升级) | Spring Cloud Finchley | 2.0.X.RELEASE |
1.5.1.RELEASE(停止维护,建议升级) | Spring Cloud Edgware | 1.5.X.RELEASE |
父项目SpringBoot版本为2.3.12.RELEASE
,所以导入SpringCloud的Hoxton.SR12
依赖:
spring-boot-starter-parent
org.springframework.boot
2.3.12.RELEASE
...
Hoxton.SR12
...
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
子项目导入OpenFeign依赖:
org.springframework.boot
spring-boot-starter-web
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.cloud
spring-cloud-starter-openfeign
1.2 yml配置
yml配置还是和一般的消费者相同,需要指定nacos地址
server:
port: 9002
spring:
application:
name: openfeign-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
management:
endpoints:
web:
exposure:
include: '*'
1.3 远程调用接口
之前使用过openFeign了,所以这边也不多做介绍,openFeign需要定义一个接口来调用提供者的http接口,定义方式和调用的接口方法相同
这是想要调用的远程服务接口:
使用@FeignClient
注解指定服务名,按照远程服务的http接口编写openFeign接口:
@Component
@FeignClient("provider")
public interface DemoFeignClient {
@RequestMapping("/demo")
public String demo();
}
1.4 编写controller
注入DemoFeignClient ,并调用远程服务:
@RestController
public class FeignDemoController {
@Autowired
private DemoFeignClient demoFeignClient;
@RequestMapping("/feignDemo")
public String feignDemo() {
return demoFeignClient.demo();
}
}
1.5 启动服务
定义启动类,并添加@EnableFeignClients
注解:
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OpenFeignConsumer9002Application {
public static void main(String[] args) {
SpringApplication.run(OpenFeignConsumer9002Application.class, args);
}
}
启动消费者和提供者服务:
由于OpenFeign也集成了Ribbon,自带负载均衡效果:
2.服务降级
Ribbon本身就支持服务降级,当调用达到超时时间时,会自动失败,默认超时时间为1s
。Sentinel则是支持更细粒度的资源控制,至于项目中使用Sentinel还是OpenFeign自带的Ribbon,看具体的需求
2.1 定义一个远程接口
远程接口中模拟超时,睡眠3s后再返回:
@RequestMapping("/timeout")
public String timeout() throws InterruptedException {
Thread.sleep(3000);
return "timeout";
}
2.2 定义openFeign接口方法
远程调用timeout
接口:
@Component
@FeignClient(value = "provider")
public interface DemoFeignClient {
@RequestMapping("/demo")
public String demo();
@RequestMapping("/timeout")
public String timeout();
}
2.3 配置超时时间
超时默认1s,我们也可以通过yml配置自定义超时时间:
#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 2000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 2000
2.4 controller层中调用
@RequestMapping("/feignTimeout")
public String feignTimeout() {
return demoFeignClient.timeout();
}
访问效果,出现500错误:
后台抛出的异常为SocketTimeoutException
,如果项目中使用,需要使用SpringMVC的异常处理方法来手动处理该异常:
3.整合Sentinel
之前Netflix中使用Hystrix,当服务降级发生,openFeign可以使用@FeignClient
注解的fallback
属性指定服务降级的异常处理,让请求返回的更友好,原因是Hystrix和openFeign已经整合了。同样的Sentinel与openFeign配合使用,也进行了整合
3.1 依赖
加入Sentinel的依赖:
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
3.2 yml配置
告知openFeign支持Sentinel:
# 开启sentinel
feign:
sentinel:
enabled: true
3.3 降级处理类
使用fallback
属性指定自定义的服务降级处理类
:
@Component
@FeignClient(value = "provider",fallback = DemoFeignClientFallback.class)
public interface DemoFeignClient {
@RequestMapping("/demo")
public String demo();
@RequestMapping("/timeout")
public String timeout();
}
服务降级处理类
实现openFeign接口:
@Component
public class DemoFeignClientFallback implements DemoFeignClient {
@Override
public String demo() {
return "发生服务降级!";
}
@Override
public String timeout() {
return "发生服务降级!";
}
}
此时再对接口进行访问:
注:@FeignClient的fallback和@SentinelResource的fallback效果相同,只能处理其他异常,不能处理限流异常(BlockException)
二、GateWay
GateWay就是网关,用于提供对外的统一接口,以防止暴露内部服务地址,相比于Zuul,GateWay拥有更高的性能,底层由Netty框架实现
GateWay的执行流程和过滤器Fliter相同,采用责任链模式,在请求前和请求后都可以对数据进行处理
GateWay核心:
- 路由:表示一条反向代理的规则,由ID、目标uri、断言、过滤器等组成
- 断言:http请求中资源的匹配规则,如:请求路径、请求头、请求参数等是否匹配
- Filter:请求前和请求后的特殊处理,如:对请求头修改、返回结果修改
1.使用GateWay
创建项目:
1.1 依赖
导入nacos和gateway依赖,注意不需要web依赖:
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.cloud
spring-cloud-starter-gateway
1.2 yml配置
除了服务发现外,还需要配置gateway相关,主要是针对路由规则进行配置:
server:
port: 8050
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
routes: # 路由规则,可配置多个
- id: provider # 定义一个id,一般与服务提供者的服务名相同
uri: http://localhost:8001/provider
predicates: # 断言,匹配路径用
- Path=/** # ant匹配符,表示所有目录下的所有路径
1.3 启动服务
@SpringBootApplication
@EnableDiscoveryClient
public class GateWay8050Application {
public static void main(String[] args) {
SpringApplication.run(GateWay8050Application.class, args);
}
}
访问GateWay反向代理的接口:
2.路由规则:配置类方式
除了在yml中配置,配置类也可以进行路由规则的配置,注入Spring容器,返回一个RouteLocator
对象,方法入参为 RouteLocatorBuilder
:
@Configuration
public class GateWayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
// 构建多个路由routes
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
// 具体路由地址
routes.route("consumer", r -> r.path("/consumer/**").uri("http://localhost:9001/consumer")).build();
// 返回所有路由规则
return routes.build();
}
}
由于使用/**
,会优先从yml配置的路由规则的服务进行调用,会出现404找不到资源,所以加上前缀consumer
并为该服务配置context-path
:
server:
port: 9001
servlet:
context-path: /consumer
启动后调用:
注:该负载均衡效果是消费者调用时Ribbon实现的
3.自动路由
除了手动配置路由规则,GateWay还支持自动路由,由于GateWay本身就会注册到Nacos中,它会根据Nacos中注册的服务名进行匹配,并自带负载均衡,自动路由默认是关闭的,需要yml中配置:
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: true #标识作为服务提供者注册到nacos中
此时路由规则就不需要写了,启动后,通过服务名
进行访问:
4.手动负载均衡
自动路由是根据服务名
进行访问的,并自带负载均衡,但此时对外暴露服务名
并不合适,想要在调用接口时去除服务名
,并且带负载均衡效果,就需要进行手动配置路由规则,路由的uri
使用lb
协议:
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: true #标识作为服务提供者注册到nacos中
routes: # 路由规则,可配置多个
- id: provider # 定义一个id,一般与服务提供者的服务名相同
uri: lb://provider # 表示使用负载均衡协议,后面跟上服务名
predicates: # 断言,匹配路径用
- Path=/** # ant匹配符,表示所有目录下的所有路径
此时访问就不需要带上服务名
了,并且也是有负载均衡的效果:
5.断言
断言的种类有:
种类 | 描述 |
---|---|
After | 匹配在指定日期时间之后发生的请求。 |
Before | 匹配在指定日期之前发生的请求。 |
Between | 需要指定两个日期参数,设定一个时间区间,匹配此时间区间内的请求。 |
Cookie | 需要指定两个参数,分别为name和regexp(正则表达式),也可以理解Key和Value,匹配具有给定名称且其值与正则表达式匹配的Cookie。 |
Header | 需要两个参数header和regexp(正则表达式),也可以理解为Key和Value,匹配请求携带信息。 |
Host | 匹配当前请求是否来自于设置的主机。 |
Method | 可以设置一个或多个参数,匹配HTTP请求,比如GET、POST |
Path | 匹配指定路径下的请求,可以是多个用逗号分隔 |
Query | 需要指定一个或者多个参数,一个必须参数和一个可选的正则表达式,匹配请求中是否包含第一个参数,如果有两个参数,则匹配请求中第一个参数的值是否符合正则表达式。 |
RemoteAddr | 匹配指定IP或IP段,符合条件转发。 |
Weight | 需要两个参数group和weight(int),实现了路由权重功能,按照路由权重选择同一个分组中的路由 |
断言的使用主要看官网文档即可:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories
6.过滤器
GateWay的过滤器和Servlet的过滤器与SpringBoot的拦截器差不多,都能够针对请求前和请求后进行处理。
官方提供的GateWay很多,使用yml配置即可,针对一些简单业务场景使用
官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
自定义过滤器,需要实现GlobalFilter
和Ordered
接口,Ordered
接口用于设置过滤器的优先级:
public class CustomGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 请求前
System.out.println("请求前...");
MultiValueMap queryParams = exchange.getRequest().getQueryParams();
System.out.println("请求参数:" + queryParams);
Mono filter = chain.filter(exchange);
// 其他过滤器执行后的请求结果
System.out.println("请求后...");
ServerHttpResponse response = exchange.getResponse();
if (!queryParams.containsKey("token")) {
response.setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return response.setComplete();
}
System.out.println(response.getStatusCode());
return filter;
}
@Override
public int getOrder() {
return 0;
}
}
注入到Spring容器中:
@Configuration
public class FilterConfig {
@Bean
public CustomGateWayFilter providerCustomGateWayFilter() {
return new CustomGateWayFilter();
}
}
日志打印:
三、分布式事务
分布式事务理论分别为CAP定律
和BASE理论
1.CAP
CAP由三个英文单词首字母组成,分别为:Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性)
- C(一致性):在分布式系统中,所有数据备份是否在同一时刻都是相同的
- A(可用性):在集群某个节点故障后,整体能否响应客户端的读写请求
- P(分区容错性):由通信带来的时限要求。在时限达到后,还未达成数据一致性,那么必须在C和A之间做出选择,要么快速响应客户端,要么保证达成数据一致后再响应客户端,后者可能永远无法响应
CAP定律证实:分布式事务由于网络等原因出现P后,只能够做到CP或AP,无法做到三者同时满足
2.BASE
BASE理论是基于CAP定律发展而来的,由于CAP的强一致性太苛刻了,BASE理论核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
BASE理论由三个英文短语组成:
- Basically Available(基本可用):
1.响应时间上,允许有一定时间的延迟
2.系统功能上,允许服务降级的发生 - Soft state(软状态):
允许系统中的数据存在中间状态,并这种状态不影响整体可用性。即允许数据副本同步过程有一定的延迟 - Eventually consistent(最终一致性):
所有的数据副本,在经过一段时间同步后达到相同
四、Seata
Seata是提供处理分布式事务解决方案的一个组件,官网:https://seata.io/zh-cn/docs/overview/what-is-seata.html,首先来了解下什么是2PC
1.2PC
2PC就是两阶段提交协议,将整个分布式事务分为两个阶段:
- P(准备阶段):由事务管理器通知每个事务成员执行本地事务,并形成Undo/Redo日志,Undo日志是记录修改前的日志,用于回滚操作,Redo日志是记录修改后的日志,用于提交操作
- C(提交阶段):如果某个事务成员响应超时或失败,那么事务管理器通知每个成员回滚,如果全员通过,那么通知提交;由每个事务成员根据事务管理器的具体通知执行提交还是回滚,并释放资源
2.Seata三大角色
Seata也是基于2PC协议,并在2PC基础上进一步抽象,将分布式事务种的成员分为三个角色:
- TC(Transaction Coordinator)- 事务协调者:
维护全局和分支事务状态,通知各成员全局事务的提交或回滚 - TM(Transaction Manager)- 事务管理器:
发起全局准备事务、全局提交或回滚事务,交由TC处理 - RM(Resource Manager)- 资源管理器:
分支事务具体参与者,注册和通知TC分支事务状态,TM也是RM
3.Seata支持的模式
模式 | 描述 | 侵入性 |
---|---|---|
AT | 使用最多的模式,基于2PC协议,准备阶段生成undo日志,根据提交阶段提交/回滚,删除undo或使用undo回滚 | 无 |
XA | 基于数据库的XA协议实现2PC,只有全局事务完成,数据才会真正更新到数据库表中,强调事务一致性时使用 | 无 |
TCC | 自定义准备阶段,以及提交/回滚阶段的行为,可以针对不仅仅是数据库资源的分布式事务 | 高 |
Saga | 单个事务失败后,可以补偿前面的成功者,具体补偿行为由自定义业务处理,针对长事务时使用 | 高 |
4.Seata安装
下载最新版本1.5.2:https://github.com/seata/seata/releases
4.1 修改配置
来到conf目录下:
1.5.2版本所有的配置都整合进了application.yml
,1.4.2版本对registry.conf和file.conf的配置方式就不适用了,配置的例子可以看application.example.yml
,我这边就是将application.example.yml
内容复制进application.yml
,并做了一些删减,主要是针对配置中心、服务注册中心、存储方式进行配置:
server:
port: 7091
console:
user:
username: aruba
password: aruba
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
extend:
logstash-appender:
destination: 127.0.0.1:4560
kafka-appender:
bootstrap-servers: 127.0.0.1:9092
topic: logback_to_logstash
seata:
# 配置中心配置
config:
# support: nacos 、 consul 、 apollo 、 zk 、 etcd3
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace:
group: SEATA_GROUP
username: nacos
password: nacos
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key: ""
#secret-key: ""
# 配置中心的对应配置的DataID
data-id: seataServer.properties
# 服务注册中心配置
registry:
# support: nacos 、 eureka 、 redis 、 zk 、 consul 、 etcd3 、 sofa
type: nacos
preferred-networks: 30.240.*
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace:
cluster: default
username: nacos
password: nacos
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key: ""
#secret-key: ""
# 对外服务配置
server:
service-port: 8091 #If not configured, the default is '${server.port} + 1000'
max-commit-retry-timeout: -1
max-rollback-retry-timeout: -1
rollback-retry-timeout-unlock-enable: false
enable-check-auth: true
enable-parallel-request-handle: true
retry-dead-threshold: 130000
xaer-nota-retry-timeout: 60000
recovery:
handle-all-session-period: 1000
undo:
log-save-days: 7
log-delete-period: 86400000
session:
branch-async-queue-size: 5000 #branch async remove queue size
enable-branch-async-remove: false #enable to asynchronous remove branchSession
# 存储模式配置
store:
# support: file 、 db 、 redis
mode: db
session:
mode: db
lock:
mode: db
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/seata?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
user: root
password: root
min-conn: 5
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
query-limit: 100
max-wait: 5000
metrics:
enabled: false
registry-type: compact
exporter-list: prometheus
exporter-prometheus-port: 9898
transport:
rpc-tc-request-timeout: 30000
enable-tc-server-batch-send-response: false
shutdown:
wait: 3
thread-factory:
boss-thread-prefix: NettyBoss
worker-thread-prefix: NettyServerNIOWorker
boss-thread-size: 1
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
配置完后,来到bin目录下,执行seata-server.bat
批处理文件,此时可能会输出错误日志,原因是连接不到对应的数据库,后面我们会进行配置:
当Nacos种出现seata-server
的服务,说明seata启动成功了:
4.2 存储方式
上面我们针对seata的存储方式进行了配置,使用的是db
,数据库为mysql
,现在需要创建相应的数据库和表
官方提供了建表语句:https://github.com/seata/seata/tree/develop/script/server/db
执行完后:
Seata默认使用的数据库驱动为mysql5
版本,想要使用其他的数据库版本,需要自己手动导入jdbc的jar包,官方提供了mysql5和mysql8,我这边使用的是mysql8,所以只要删除mysql5的jdbc即可,来到lib/jdbc
目录下:
此时启动seata后,不会报数据库连接异常了
4.3 配置中心
既然是SpringCloudAlibaba,推荐使用Nacos作为配置中心,此外Seata也支持其他的配置中心,下面是我们配置的Nacos作为配置中心的配置:
根据该配置在Nacos配置中心中,新建seataServer.properties
配置,可配置的内容官网也提供了,其实配置项和上面yml中的配置项差不多:https://github.com/seata/seata/blob/develop/script/config-center/config.txt
根据提供内容进行简单的修改,主要针对jdbc配置:
#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html
#Transport configuration, for client and server
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none
#Transaction routing rules configuration, only for the client
#配置事务组,可以标识为机房地址
service.vgroupMapping.default_tx_group=default
#If you use a registry, you can ignore it
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
#Transaction rule configuration, only for the client
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h
#Log rule configuration, for client and server
log.exceptionRate=100
#Transaction storage configuration, only for the server. The file, DB, and redis configuration values are optional.
store.mode=file
store.lock.mode=file
store.session.mode=file
#Used for password encryption
#store.publicKey=
#If `store.mode,store.lock.mode,store.session.mode` are not equal to `file`, you can remove the configuration block.
store.file.dir=file_store/data
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.sessionReloadReadSize=100
#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
#jdbc的配置
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
store.db.user=root
store.db.password=root
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
#These configurations are required if the `store mode` is `redis`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `redis`, you can remove the configuration block.
store.redis.mode=single
store.redis.single.host=127.0.0.1
store.redis.single.port=6379
#store.redis.sentinel.masterName=
#store.redis.sentinel.sentinelHosts=
store.redis.maxConn=10
store.redis.minConn=1
store.redis.maxTotal=100
store.redis.database=0
#store.redis.password=
store.redis.queryLimit=100
#Transaction rule configuration, only for the server
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
server.xaerNotaRetryTimeout=60000
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false
server.enableParallelRequestHandle=false
#Metrics configuration, only for the server
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
创建完成,以后就可以通过Nacos配置中心动态的改变配置了:
5.Seata-AT
AT是使用最广泛的模式,Seata-AT模式会自动生成事务的二阶段提交和回滚操作,具体流程为:
- 第一阶段
1.TM发起准备事务,执行本地事务并远程调用RM方法
2.TC拦截业务SQL,并解析SQL,在数据更新前,在TM/RM本地undo_log
表中生成对应的Undo日志
3.TM/RM执行本地事务,数据更新,并形成行锁,防止其他事务修改 - 第二阶段
根据第一阶段是否有本地事务执行失败,如果有,发起回滚,TM/RM根据Undo日志执行反向更新操作;如果都执行成功,发起提交,TM/RM删除Undo日志和释放资源
5.1 表设计
设计两张表:订单表(order_tbl)和库存表(stock_tbl)
stock表中插入一条数据:
5.2 创建项目
分别创建order模块与stock模块:
5.3 yml配置
两个模块只是端口和服务名不同,所以就只贴出一个:
server:
port: 8201
spring:
application:
name: seata-order
cloud:
discovery:
server-addr: 127.0.0.1:8848
alibaba:
seata:
tx-service-group: default_tx_group
datasource:
url: jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
seata:
tx-service-group: default_tx_group # 事务组名称,要和服务端对应
service:
vgroup-mapping:
default_tx_group: default # key是事务组名称 value要和服务端的机房名称保持一致
management:
endpoint:
web:
exposure:
include:'*'
mybatis:
mapper-locations: classpath:mybatis/*.xml #指定映射文件路径
type-aliases-package: com.aruba.bean
需要注意的是,我们先前为seata-server
配置的事务组,要与客户端yml配置对应上:
5.4 @GlobalTransactional
其他的CURD操作就不展示了,在Service层相应方法上使用@GlobalTransactional
注解开启分布式全局事务:
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockClient stockClient;
@Override
@GlobalTransactional// 开启分布式事务
public void addOrder() {
Order order = new Order(null, 1, "新增订单");
orderMapper.addOrder(order);
stockClient.reduceStock(1);
}
}
StockClient
是使用OpenFeign远程调用stock
模块的方法,其方法中抛出一个异常:
@RestController
public class StockController {
@Autowired
private StockService stockService;
@GetMapping("/reduceStock")
public String reduceStock(@RequestParam("id") Integer id) {
int i = 1 / 0;
System.out.println("请求来了");
stockService.reduceStock(id);
return "减库存成功";
}
}
启动服务后,经过测试,远程服务调用失败,数据库中的数据最终都回滚到了最初状态
其他模式
由于篇幅原因,其他模式暂时不做展示,另外三种模式使用的也不多,可以通过官方Demo进行参考:https://github.com/seata/seata-samples
项目地址:
https://gitee.com/aruba/spring-cloud-alibaba-study.git