Spring Cloud Alibaba
官网:https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md
由于Netflix进入维护模式,阿里巴巴推出SpringCloud Alibaba一系列的框架,解决分布式微服务架构的问题。
版本对应关系:
Spring Cloud Alibaba | Spring Cloud | Spring Boot |
---|---|---|
2022.0.0* | Spring Cloud 2022.0.0 | 3.0.2 |
2022.0.0-RC2 | Spring Cloud 2022.0.0 | 3.0.2 |
2022.0.0-RC1 | Spring Cloud 2022.0.0 | 3.0.0 |
2021.0.5.0* | Spring Cloud 2021.0.5 | 2.6.13 |
2021.0.4.0* | Spring Cloud 2021.0.4 | 2.6.11 |
2021.0.1.0* | Spring Cloud 2021.0.1 | 2.6.3 |
2021.1 | Spring Cloud 2020.0.1 | 2.4.2 |
服务注册和配置中心
Nacos
官网地址:https://nacos.io/zh-cn/index.html
下载地址:https://github.com/alibaba/Nacos
文档地址:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_nacos_discovery
启动nacos
# 解压安装包,然后在bin目录下的运行,单机启动
startup.cmd -m standalone
# 启动成功,运行
http://localhost:8848/nacos
用户名、密码:nacos
# window上关闭服务器
shutdown.cmd
# linux进行启动
# sh startup.sh -m standalone
# linux进行访问
curl -X POST 'http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=nacos.naming.serviceName&ip=20.18.7.10&port=8080'
# linux上关闭服务器
sh shutdown.sh
注册中心配置:
步骤1:引包:
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
步骤2:改配置文件
server:
port: 80
spring:
application:
name: nacos-order
cloud:
nacos:
discovery:
server-addr: localhost:8848
步骤3:启动服务
# 访问地址,会发现该服务已注册上nacos上
http://localhost:8848/nacos
nacos它是支持AP和CP模式,默认是AP
K8S服务和DNS服务则适用于CP模式、切换CP模式:
curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'
配置中心配置:
步骤1:引包:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
步骤2:改配置文件
server:
port: 80
spring:
application:
name: nacos-order
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848 # Nacos作为配置中心地址
file-extension: yaml # 指定yaml格式的配置
shared-configs:
# refresh为true,代表使用@RefreshScope注解,更改配置文件,会实时更新。
- {dataId: xxx.yml,refresh: true}
步骤3:在nacos创建对应的配置文件
Data Id :服务名-版本号-文件后缀 例:nacos-order-dev-yaml
# 具体的配置文件在该文件里配置
步骤4:启动服务
# 启动服务,根据不同环境配置启动
java -Dspring.profiles.active=dev -jar xxx.jar
# 访问地址,会发现该服务已注册上nacos上,然后我们更改配置文件,对应的服务也刷新更改
http://localhost:8848/nacos
保护阈值
注册上的nacos的服务实例,默认都是临时实例,客户端会每隔5秒发送一次心跳检测,服务端如果没有收到客户端的心跳,会把该实例标记不健康的服务,如果30秒没有收到,就会删除该实例。
在使用中,保护阈值可以设置一个0-1的比例,例子:如果有100个服务:20健康服务/80个不健康服务;正常情况,我们只能访问到这20健康服务,但在高并发的请求下,一下访问到这20健康服务,会导致这20台健康服务挂掉,导致全部都不能访问,所有当我们设置保护阈值的值,这100个服务都打开,nginx的负载均衡会随机访问,肯能访问80不健康的服务,也有肯能访问到20健康的服务,把错误推给客户端。当我们保护阈值设置0.5,就是有百分20的概率访问。
命名空间多环境多状态配置
Namespace+Group+Data ID的关系
相当于java的包名、类名、方法,默认是Namespace=public,Group=DEFAULT_GROUP,默认Cluster是DEFAULT,我们可以自己配置,配置更加细节、不同环境、不同的场景、不同用户访问情况的配置文件。
历史版本回退
nacos还提供历史版本的回退,在配置管理下有历史版本,输入对应Data ID和Group,查询最近更改的记录
监控查询
在配置管理下有监控查询,它负责查询,该配置在哪台机器上使用
集群和持久化配置
单机配置
步骤1:数据库配置
# 创建数据库 (nacos_config)
CREATE DATABASE nacos_config;
USE nacos_config;
# 找到初始化脚本,在nacos下conf目录下找到nacos-mysql.sql,执行初始化SQL脚本
步骤2:更改配置文件
# 配置文件:在nacos下conf的application.properties文件(切换数据库)
# 文件里的内容:在最下面,添加-切换数据库配置内容
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
db.user=root
db.password=数据库密码
# 也可以在该文件,进行修改端口
server.port=8848
步骤3:重启nacos服务
# 重启:startup.cmd -m standalone
# 这样我们在访问:http://localhost:8848/nacos
# 会是重新的配置,每次配置会保存到数据库上
Linux版Nacos集群配置
步骤1:下载对应的Linux版Nacos包,上传到服务器上,并解压到安装目录
步骤2:同单机配置一样,完成单机配置的步骤1、步骤2
步骤3:修改cluster.conf文件
# 我们配置3台Nacos机器的ip地址,
# 注意:一定是具体的IP地址,可通过该命令查看:hostname -i 能够识别的IP
192.168.11.64:3333
192.168.11.64:4444
192.168.11.64:5555
# 如果不是同一个ip地址,分别在不同的机器,改IP地址,省略第4步骤
# 启动,使用默认启动,./startup.sh 分别在其他机器上进行启动
步骤4:修改startup.sh文件
# 里面的内容要修改
while getopts ":m:f:s:p" opt
do
case $opt in
m)
MODE=$OPTARG;;
f)
...
s)
SERVER=$OPTARG;;
p)
PORT=OPTARG;;
?)
...
# start
echo "$JAVA ${JAVA_OPT} ...." > ...
nohup $JAVA -Dserver.port=${PORT} ${JAVA_OPT} nacos...
...
步骤5:修改nginx的配置
# 我们通过nginx进行负载均衡,对外访问nginx,然后nginx负载均衡到这3台Nacos服务
# nginx的负载均衡配置
upstream cluster{
server 192.168.11.64:3333;
server 192.168.11.64:4444;
server 192.168.11.64:5555;
}
server {
listen 8848;
server_name localhost;
location / {
proxy_pass http://cluster;
}
...
}
步骤6:启动ngin
# 在nginx的sbin目录下,启动nginx
./nginx -c /usr/nginx的配置文件路径地址
步骤7:启动Nacos
# 在Nacos的bin目录下,进行启动,这2台Nacos服务
./startup.sh -p 3333 # 启动其中一台
./startup.sh -p 4444
./startup.sh -p 5555
# 可通过Nacos页面,在集群管理这里,查看节点信息
步骤8:更改我们项目中的配置
# 启动这些服务成功,我们访问nginx的地址,通过负载均衡配置转发到Nacos服务
# 然后我们项目中的,配置服务,连接nginx的地址
spring:
application:
name: xxx
cloud:
nacos:
discovery:
# 配置Nacos地址
#server-addr: localhost:8848
# 换成nginx的1111端口,做集群
server-addr: 192.168.11.64:1111
# 如果我们不使用nginx ,可以多个IP,逗号分隔,但官方建议使用nginx
# server-addr: 192.168.11.64:3333,192.168.11.64:4444,192.168.11.64:5555
熔断与限流
Sentinel
它可以解决服务雪崩、服务降级、服务熔断、服务限流;
中文:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
下载:https://github.com/alibaba/Sentinel/releases
版本说明:https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
运行命令:
# 启动
java -jar sentinel-dashboard-1.8.6.jar
# 访问sentinel管理界面,登录账号密码均为sentinel
http://localhost:8080
# 或者启动
# -Dproject.name:指定本服务的名称 -Dcsp.sentinel.dashboard.serve:指定sentinel控制台的地址
# -Dsentinel.dashboard.auth.username:登录用户名 -Dsentinel.dashboard.auth.password:密码
java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -Dsentinel.dashboard.auth.username=sentinel -Dsentinel.dashboard.auth.password=123456 -jar sentinel-dashboard-1.8.6.jar
# 访问sentinel管理界面
http://localhost:8888
服务端需要注册上Sentinel
步骤1: 引包
<!--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>
步骤2:修改配置文件
spring:
application:
name: sentinel-service
cloud:
nacos:
discovery:
# Nacos服务注册中心地址
server-addr: localhost:8848
sentinel:
transport:
# 配置Sentinel dashboard地址
dashboard: localhost:8080
# 默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
步骤3:启动服务
# 启动服务,并访问几个接口
# 登录sentinel管理界面
http://localhost:8080
实时监控:
可监控该服务的接口,请求频率。响应时间等信息;
流控规则
解释字段
- 资源名:唯一名称,默认请求路径
- 针对来源:可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
- 阈值类型/单机值
- QPS(每秒钟的请求数量):当调用该api就QPS达到阈值的时候,进行限流
- 线程数.当调用该api的线程数达到阈值的时候,进行限流
- 是否集群:不需要集群
- 流控模式:
- 直接:api达到限流条件时,直接限流。分为QPS和线程数
- 关联:当关联的资到阈值时,就限流自己。别人惹事,自己买单
- 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【api级别的针对来源
- 流控效果:
- 快速失败:直接抛异常
- warm up:根据codeFactor(冷加载因子,默认3)的值,从阈值codeFactor,经过预热时长,才达到设置的QPS阈值
- 排队等待:匀速排毒,让请求以匀速通过,阈值类型必须设置为QPS,否则无效
例子:
请求该路径:http://xxxx/t1/testA,当它的QPS达到5的时候,就会直接失败,返回:Blocked by Sentinel (flow limiting)
注意:QPS和线程数的区别,就是QPS(每秒请求数),当每秒的请求数达到阈值就会报错;线程数代表,设置了3个线程,就3个工作线程,当大量请求,分别去找这3个工作线程,如果3个都在工作,没有空余线程,会报错。
流控模式
一. 直接(默认),直接就是当前的资源名。
二. 关联
当我们 请求接口:/testA ,正常
当我们 请求接口:/testB ,正常
当我们 大量的请求:/testB 接口 的时候,再去访问 http://localhost:8401/testA ,此时的testA会进入服务降级.
也就是,当请求testB,请求的阈值是设置的值,就会影响到testA的接口访问。
三 链路:
访问:/testC - 单独访问 正常
访问:/testD - 单独访问 正常
但如果:配置了链路,多次访问/testC正常,但/testD多次访问,会进入流控。
流控效果
一. 快速失败
直接抛出异常:抛出异常:Blocked by Sentinel (flow limiting)
二. 预热(WarmUp)
选择这个,需要设置预热时长,默认是3,它是经过一段时间才逐渐升至设定的 QPS 阈值
例子:阀值为10+预热时长设置5秒,它就是10/3 约等于3,即阀值刚开始为3;然后过了5秒后阀值才慢慢升高恢复到10
场景:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。
三. 排队等待
匀速排队:设置排队等待必须在阀值类型是QPS模式下,设置排队等待,可设置超时时间,单位毫秒。
它就是当QPS超过设置的阀值,就进行排队等待,后期会慢慢执行,如果超时了等待时间,就不在排队。
降级规则
官网:https://github.com/alibaba/Sentinel/wiki/%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7
一. RT(平均响应时间,秒级)
- 平均响应时间超出阈值且在时间窗口内通过的请求>=5,两个条件同时满足后触发降级
- 窗口期过后关闭断路器
- RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)
二. 异常比列(秒级)
- QPS >= 5且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级,就是该接口出现异常,大于设置的异常比例,如:0.2,就是百分之20,并且QPS请求>=5,才会触发熔断
三. 异常数(分钟级)
- 异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
Sentinel 的断路器是没有半开状态的,
半开状态就是,没有异常访问正常方法,有异常访问其他方法。
热点key限流
热点即经常访问的数据,比如某些参数是某个值,进行限流操作,其他参数不进行限流。
案例:
// 正常逻辑 - 执行
@GetMapping("/testD")
@SentinelResource(value = "testD", blockHandler = "dealTestHotKey")
public String testD(@RequestParam(value = "p1", required = false) String p1,
@RequestParam(value = "p2", required = false) String p2) {
return "------testD -- 正常逻辑返回" ;
}
// 触发-降级的方法
public String dealTestHotKey(String p1, String p2, BlockException exception) {
return "-----dealTestHotKey 服务降级方法";
}
案例:资源名,与@SentinelResource里的value对应,参数索引,代表第几个参数,图中是参数1,单机阈值是2,代表每秒QPS达到2个就会触发该限流方法。会执行代码中降级里的方法。
http://localhost:8401/t3/testD?p1=a # 会触发
http://localhost:8401/t3/testD?p2=ss&p1=a # 会触发
http://localhost:8401/t3/testD?p2=a # 不会触发
参数例外项
当我们的参数值是24的时候,阈值是1,代表是24,QPS是1会触发限流;当参数值是25,阈值是5,QPS是5会触发限流里的方法逻辑。
http://localhost:8401/t3/testD?p2=ss&p1=24 # 连续点击2次,会触发
http://localhost:8401/t3/testD?p2=ss&p1=24 # 多次频繁点击,会触发
授权规则
在某种场景,可以根据调用接口来源判断是否允许执行本次请求,可以提供白名单与黑名单两种授权类型。
// 配置黑名单
// 访问:http://localhost:8401/r1/test6?serverName=t1 会被授权,限制
// 访问:http://localhost:8401/r1/test6?serverName=t2 会被授权,限制
// 访问:http://localhost:8401/r1/test6?serverName=t22 不会被限制
系统规则
这个是配置整体服务的,
- Load自适应:需要在Linux上配置,这个是自适应系统保护,当系统的并发线程超过估算的系统容量会触发保护机制,系统容量计算:最大的QPS * 最小的Rt 估算出来,一般设置几核CPU*2.5
- CPU,当系统的CPU超过设置的值会触发保护机制,取值0-1,如果是0.8,就是当CPU使用率超过80%会触发
- 平均RT:当单台机器上所有的入口流量的平均RT达到阈值会触发,单位毫秒
- 入口QPS:当单机上所有入口流量QPS达到阈值,就会触发
SentinelResource
方式1:
// 该注解,使用,既可以用url进行配置,也可以根据配置名称
@GetMapping("/testD")
@SentinelResource(value = "testD", blockHandler = "testHotKey")
public String testD(@RequestParam(value = "p1", required = false) String p1,
@RequestParam(value = "p2", required = false) String p2) {
return "------testD -- 正常逻辑返回" ;
}
// 触发-降级的方法
public String testHotKey(String p1, String p2, BlockException exception) {
return "-----testD dealTestHotKey 服务降级方法";
}
方式2:
// 正常逻辑 - 执行 , 熔断限流,方法在该CustomerBlockHandler下的handleException2方法
@GetMapping("/testE")
@SentinelResource(value = "testE",
blockHandlerClass = CustomerBlockHandler.class,
blockHandler = "handleException2")
public String testD() {
return "------testE -- 正常逻辑返回" ;
}
// CustomerBlockHandler类 (自定义限流处理逻辑)
public class CustomerBlockHandler {
public static String handleException(BlockException exception) {
return "自定义的限流处理信息......限流方法1";
}
public static String handleException2(BlockException exception) {
return "自定义的限流处理信息......限流方法2";
}
}
配置持久化
每次重启项目不再需要重新配置
步骤1:引包
<!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
步骤2:修改配置文件
spring:
application:
name: sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
# 配置Sentinel dashboard地址
dashboard: localhost:8888
port: 8080
# sentinel持久化
datasource:
dsl-flow:
nacos:
server-addr: localhost:8848
# nacos 的 Data ID
data-id: ${spring.application.name}
group-id: DEFAULT_GROUP
data-type: json
rule-type: flow
当Gateway使用了流控配置的持久化,那么sentinel中datasource配置下的rule-type参数值就得是gw_flow, 不能是flow
如果配置的是flow
,那么在sentinle中就不会显示处Gateway的流控配置,但是不影响功能。
如果配置的是gw_flow
,才会在sentinel中显示Gateway的流控配置,同时也不影响功能。
步骤3:配置nacos文件
添加对应的文件,例子:
// 流控配置
[
{
"resource":"fallback", // 资源名
"limitApp":"default", // 针对来源,若为 default 则不区分调用来源
"grade":1, // 限流阈值类型(1:QPS; 0:并发线程数)
"count":1, // 阈值
"strategy":0, // 流控模式(0:直接; 1:关联; 2:链路)
"controlBehavior":0, // 流控效果 (0:快速失败; 1:Warm Up(预热模式); 2:排队等待)
"clusterMode":false, // 是否是集群模式
"warmUpPeriodSec": 10, // 预热时间(秒,预热模式需要此参数)
"maxQueueingTimeMs": 500, // 超时时间(排队等待模式需要此参数)
"refResource": "rrr" // 关联资源、入口资源(关联、链路模式)
}
]
// 熔断降级配置
[
{
"resource": "fallback",
"grade": 0, // 熔断策略,支持慢调用比例(0),异常比例(1),异常数(2)策略
"count": 1000, // 慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用,单位ms);异常比例/异常数模式下为对应的阈值
"slowRatioThreshold": 0.1, // 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入)
"minRequestAmount": 10, //熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断
"timeWindow": 10, // 熔断时长,单位为 s
"statIntervalMs": 1000 // 统计时长(单位为 ms),如 60*1000 代表分钟级
}
]
// 热点参数配置
[
{
"resource": "/test1",
"grade": 1, // 限流模式(QPS 模式,不可更改)
"paramIdx": 0, // 参数索引
"count": 13, // 单机阈值
"durationInSec": 6, // 统计窗口时长
"clusterMode": false, // 是否集群 默认false
"controlBehavior": 0, // 流控效果(支持快速失败和匀速排队模式)
"limitApp": "default",
// 高级选项
"paramFlowItemList": [{
"classType": "int", // 参数类型
"count": 222, // 限流阈值
"object": "2" // 参数值
}]
}
]
// 系统规则配置
[
{
"avgRt": 1, // RT
"highestCpuUsage": -1, // CPU 使用率
"highestSystemLoad": -1, // LOAD
"maxThread": -1, // 线程数
"qps": -1, // 入口 QPS
"count": 55, // 阈值,在CPU使用率中是百分比
}
]
// 授权规则配置
[
{
"resource": "sentinel_spring_web_context",
"limitApp": "/test",
"strategy": 0 // 授权类型(0代表白名单;1代表黑名单。)
}
]
分布式事务
Seata
是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
官网地址:https://seata.apache.org/zh-cn/
文档:https://seata.apache.org/zh-cn/docs/overview/what-is-seata/
下载:https://github.com/apache/incubator-seata/tags
术语
- TC 事务协调者
- TM 事务管理器
- RM 资源管理器
分布式事务过程
- Transaction ID XID:全局唯一的事务ID
- 三大组件:Transaction Coordinator (TC) - 事务协调者、TM (Transaction Manager) - 事务管理器、RM (Resource Manager) - 资源管理器
过程:
- TM向TC申请开启一个全局事务,创建成功并生成一个全局唯一的XID;
- XID在微服务调用链路的上下文中传播
- RM向TC注册分支事务,将其纳入XID对应全局事务的管辖
- TM向TC发起针对XID的全局提交或回滚决议
- TC调度XID下管辖的全部分支事务完成提交或回滚要求
各事务模式
-
XA 模式 (强一致)
第一阶段提交,执行SQL脚本,事务并没有提交; 第二阶段提交,没有问题,进行事务的提交,否则进行回滚。 缺点:第一阶段,需要对数据库资源进行锁定,到第二阶段,才进行释放资源,性能较差; 优点:数据强一致
-
AT 模式 (弱一致)
第一阶段提交,执行SQL脚本前,会对该表增加全局锁,进行锁定状态,记录快照(undo_log表)记录操作前后的数据记录,然后SQL执行完,提交事务,进行解锁; 第二阶段提交,没有问题,进行删除快照(undo_log表)该条数据历史记录;有问题进行回滚,根据快照(undo_log表)进行恢复数据,再删除该表的快照记录,如果快照记录与现在实际数据不一致,需要抛出异常,人工处理; 优点:第一阶段,事务就提交,释放资源,性能比XA模式好 缺点:第二阶段前,是软状态,等第二阶段完成,是数据最终一致性,存在如果全脚事务和普通事务同时操作该行数据,避免不了事务的一致性问题。
-
TCC 模式(弱一致)
第一阶段提交,执行SQL脚本,进入Try方法里,会记录表进行资源锁定,为冻结数据,并提交事务; 第二阶段提交,没有问题,会进入 Confirm 方法里,删除冻结数据,失败的话,进入Cancel方法里,进行手动回滚数据; 优点:跟AT模式一样,是性能最好的,不依赖数据库, 缺点:有代码侵入,需要人为编写try、Confirm和Cancel接口,还需要做接口幂等性问题。
-
Saga 模式 (最终一致)
第一阶段提交,执行sql脚本,提交事务; 第二阶段提交,没有问题,不做处理,有问题,进行手动回滚,补偿数据; 优点:它性能最高,适合长事务解决方案、或者第3方的接口; 缺点,会出现脏数据、没有做到事务的隔离性;
安装
步骤1:在nacos控制台添加新的命名空间
命名空间:seata,并记录命名空间ID,后面要用。
步骤2:修改seata配置文件
修改seata/conf/application.yml
文件
server:
port: 7091
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
console:
user:
username: seata
password: seata
seata:
# 配置中心
config:
# support: nacos 、 consul 、 apollo 、 zk 、 etcd3
type: nacos
nacos:
server-addr: 127.0.0.1:8848
# 命名空间id
namespace: 6344c52c-ec84-49f0-b38b-bdce31aa5e68
group: SEATA_GROUP
username: nacos
password: nacos
# seataServer的配置文件
data-id: seataServer.properties
#context-path:
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key:
#secret-key:
# 注册中心
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: DEFAULT_GROUP
# 对应的命名空间,在nacos中配置,没有就空
# namespace: 6344c52c-ec84-49f0-b38b-bdce31aa5e68
# 指定注册至nacos注册中心的集群名,该服务部署在上海,就是SH,北京就是BJ,一个名字而已
cluster: BJ
username: nacos
password: nacos
store:
# support: file 、 db 、 redis
mode: db
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
user: root
password: 数据库密码
min-conn: 10
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
query-limit: 1000
max-wait: 5000
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
步骤3:创建数据表
注意:不同版本的数据库不相同,需要去官网找对应的SQL脚本,脚本目录:xxx\seata\script\server\db里。
-- 在一个seata单独的数据库上创建
-- 全局事务
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
-- 分支事务
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
-- the table to store lock data 全局锁
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(128),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_status` (`status`),
KEY `idx_branch_id` (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
CREATE TABLE IF NOT EXISTS `distributed_lock`
(
`lock_key` CHAR(20) NOT NULL,
`lock_value` VARCHAR(20) NOT NULL,
`expire` BIGINT,
primary key (`lock_key`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
步骤4:下载服务端的Seata,进行修改配置文件
在nacos创建seataServer.properties的配置文件,如果上面配置了命名空间、分组需要保持一致;
把配置文件config.txt加载到nacos上,原文件在seata/script/config-center/config.txt
... 以下内容要改
#Transaction storage configuration, only for the server. The file, db, and redis configuration values are optional.
store.mode=db
store.lock.mode=file
store.session.mode=file
#Used for password encryption
... 数据库改成自己的seata
#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.
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
store.db.user=root
store.db.password=数据库密码
步骤5:加载到nacos上
还需要在nacos上添加Data ID:service.vgroupMapping.default_tx_group配置文件,配置事务组:
分组:SEATA_GROUP、值:default
如果创建集群,可多创建多个配置配置,该端口号和分组配置等,客户端这边可以动态同步,就实现集群部署。
步骤6:回滚日志表,需要在每个微服务创建对应的表
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
步骤7:启动
# 启动
# 双击运行/bin/seata-server.bat
# 启动成功后,会在nacos中看到seata-server服务(事务管理器服务)
# 然后登陆seata可视化界面:http://127.0.0.1:7091
# 默认用户名和密码:seata
步骤8:客户端配置
# 引包
<!--seata依赖需要排除掉,用了什么版本的seata就用那个版本的依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.6.1</version>
</dependency>
更改yml配置
# 分布式事务配置
seata:
enabled: true
# 该服务注入到seata的应用程序,需要唯一
application-id: ${spring.application.name}
# 事务的模式,默认是 AT模式 、XA模式 TCC魔术(需要注释掉)
data-source-proxy-mode: AT
# 事务组,根据这个获取tc服务端cluster名称,默认是 default_tx_group
tx-service-group: yyybj
service:
vgroup-mapping:
# key是事务分组名称 value要和服务端的机房名称保持一致,与cluster名称一致,还需要在nacos上配置
yyybj: BJ
# 注册中心
registry:
type: nacos
nacos:
# Seata服务名(应与seata-服务端的实际注册的服务名一致)
application: seata-server
server-addr: 127.0.0.1:8848
# nacos 命令空间
# namespace: 6344c52c-ec84-49f0-b38b-bdce31aa5e68
group: DEFAULT_GROUP
# 配置中心
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
# nacos 命令空间
namespace: 6344c52c-ec84-49f0-b38b-bdce31aa5e68
group: SEATA_GROUP
2.0版本实操案例
步骤1:创建数据表
注意:不同版本的数据库不相同,需要去官网找对应的SQL脚本,脚本目录:xxx\seata\script\server\db里。
global_table、branch_table、lock_table、distributed_lock // 这4张表
步骤2:更改seata的配置文件
目录:seata\2.0.0\conf\application.yml
server:
port: 7091
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${log.home:${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 客户端 登录用户名和密码
console:
user:
username: seata
password: seata
seata:
# 配置中心
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace:
group: SEATA_GROUP #后续自己在nacos里面新建,不想新建SEATA_GROUP,就写DEFAULT_GROUP
username: nacos
password: nacos
# 注册中心
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP #后续自己在nacos里面新建,不想新建SEATA_GROUP,就写DEFAULT_GROUP
namespace:
cluster: default
username: nacos
password: nacos
# 全局事务的存在位置
store:
mode: db
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
user: 数据库用户名
password: 数据库密码
min-conn: 10
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
query-limit: 1000
max-wait: 5000
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login,/metadata/v1/**
步骤3:启动nacos
startup.cmd -m standalone
步骤4:启动Seata
在目录:seata\2.0.0\bin
执行:seata-server.bat 命令
步骤5:验证
访问 nacos 客户端,seata是否注册成功
地址:http://localhost:8848/nacos
访问 seata 客户端
地址:http://localhost:7091/#/login
步骤6:准备3个测试数据库
create database seata_a;
create database seata_b;
create database seata_c;
// 回滚日志表,需要在每个微服务创建对应的库中,创建回滚日志表
// 但是AT模式下,需要创建,如果其他模式,不需要创建该表
```sql
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
```
--- 分别创建对应的自己的库中
CREATE TABLE `t_axx` (
`id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
`user_id` bigint(11) DEFAULT NULL COMMENT '用户id',
`total` int DEFAULT NULL COMMENT '总额度',
`used` int DEFAULT NULL COMMENT '已用余额',
`residue` int DEFAULT '0' COMMENT '还有金额',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 初始值,代表1号用户有100总金额,已使用0,还有100金额
INSERT INTO `t_axx` (`id`, `user_id`, `total`, `used`, `residue`) VALUES (1, 1, 1000, 0, 1000);
CREATE TABLE `t_bxx` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`user_id` bigint(11) DEFAULT NULL COMMENT '用户id',
`product_id` bigint(11) DEFAULT NULL COMMENT '产品id',
`count` int(11) DEFAULT NULL COMMENT '数量',
`money` decimal(11,0) DEFAULT NULL COMMENT '金额',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `t_cxx` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`product_id` bigint(11) DEFAULT NULL COMMENT '产品id',
`total` int(11) DEFAULT NULL COMMENT '总库存',
`used` int(11) DEFAULT NULL COMMENT '已用库存',
`residue` int(11) DEFAULT NULL COMMENT '剩余库存',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 初始值,代表1号产品有100库存,已用0,剩余100
INSERT INTO `t_cxx` (`id`,`product_id`,`total`, `used`, `residue`) VALUES (1, 1, 100, 0, 100);
步骤7:服务准备
// 需要创建3个微服务,具体代码已分享在码云上,Spring Cloud Alibaba系列。//
// 编写对应的代码,开启全局事务注解,在调用者处增加
@GlobalTransactional(name = "bxx-create", rollbackFor = Exception.class)
步骤8:登录Seata客户端
用户名密码默认都是Seata,可查看事务的信息,全局锁信息。
网关
Gateway
它为微服务架构提供有效的统一的API路由管理方式,底层使用了Netty通讯框架。可以做反向代理、鉴权、流量控制、熔断、日志监控、防止SQL注入、防止Web攻击、证书处理。
它与Zuul的区别:
Zuul是基于IO的API网关,基于Servlet 2. 5使用阻塞架构。Spring Cloud Gateway它是使用非阻塞API。
三大核心:
- 路由:Route(如果断言正确,匹配该路由)
- 断言:Predicate(请求与断言是否匹配)
- 过滤:Filter(进行细化的控制)
创建新的服务当网关
步骤1:引包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
步骤2:修改yml
server:
port: 9527
spring:
application:
name: cloud-gateway
# 网关配置
cloud:
gateway:
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: cloud1_provider_routh # 路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 # 匹配后提供服务的路由地址(第一版,可以写固定的)
uri: lb://cloud1-provider # 匹配后提供服务的路由地址,与注册中心的服务名一致
predicates:
- Path=/t2/test1/** # 断言,路径相匹配的进行路由
- id: cloud1_pxx_routh
uri: lb://cloud1-pxx
predicates:
- Path=/pxx/test1/**
# 断言,当在这个时间之前可以使用该路由
- After=2022-03-08T23:14:51.718+08:00[Asia/Shanghai]
# 断言,当在这个时间之后可以使用该路由
- Before=2022-03-08T23:14:51.718+08:00[Asia/Shanghai]
# 断言 在这个范围之内
- Between=2022-02-02T17:45:06.206+08:00[Asia/Shanghai],2023-03-25T18:59:06.206+08:00[Asia/Shanghai]
# 断言,必须携带该Cookie,不带该Cookie会爆404
# 携带Cookie,
# 访问:curl http://localhost:9527/pxx/test1/xxxx --cookie “username=yan”
- Cookie=username,yan
# 断言,请求头要有X-Request-Id属性并且值为整数的正则表达式,否则会404,
# 请求 curl http://localhost:9527/pxx/xxx -H “X-Request-Id:-123”
- Header=X-Request-Id, \d+
# 断言,请求域名,必须以goodyan.com结尾,
# 请求 curl http://localhost:9527/pxx/xxx -H “Host: java.goodyan.com”
- Host=**.goodyan.com
# 断言,必须是get请求
- Method=GET
# 断言,要有参数名username并且值还要是整数才能路由
- Query=username, \d+
# 过滤器,路由过滤器只能指定路由进行使用,可用于修改进入的HTTP请求和返回的HTTP响应
filters:
# 过滤器工厂会在匹配的请求头加上一对请求头,名称为X-Request-Id值为1024
- AddRequestParameter=X-Request-Id,1024
步骤3:进行测试
// 访问服务端机器1:http://localhost:8001/t2/test1/6
// 访问服务端机器2:http://localhost:8003/t2/test1/6
// 访问网关地址: http://localhost:9527/t2/test1/6
自定义断言
// 当满足自己设置的断言规则,才可以访问该服务
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {
public MyRoutePredicateFactory() {
super(MyRoutePredicateFactory.Config.class);
}
// 这个Config类就是我们的路由断言规则,重要
@Validated
public static class Config {
@Setter
@Getter
@NotEmpty
private String userType; // yml配置的会员等级
}
// 如果不配置这个,需要yml这里,这么配置
// - name: My
// args:
// userType: diamond
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("userType");
}
@Override
public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config) {
return new Predicate<ServerWebExchange>() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
// 检查request的参数里面,userType是否为指定的值,符合配置就通过
// 访问:http://localhost:xxx/xx/get/1 接口 失败
// 访问:http://localhost:xxx/xx/get/1?userType=xxx 接口 失败
// 访问:http://localhost:xxx/xx/get/1?userType=gold 接口 成功
String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");
if (userType == null) {
return false;
}
// 如果说参数存在,与config的数据进行比较,一致会放行,可以访问
if (userType.equalsIgnoreCase(config.getUserType())) {
return true;
}
return false;
}
};
}
}
自定义全局过滤器
// 给每个服务通过网关进行请求,增加耗时时间
@Component
@Slf4j
public class MyGlobalFilter implements GlobalFilter, Ordered {
//开始调用方法的时间
public static final String BEGIN_VISIT_TIME = "begin_visit_time";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1 先记录下访问接口的开始时间
exchange.getAttributes().put(BEGIN_VISIT_TIME, System.currentTimeMillis());
// 2 返回统计的各个结果给后台
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
Long beginVisitTime = exchange.getAttribute(BEGIN_VISIT_TIME);
if (beginVisitTime != null) {
log.info("访问接口主机:" + exchange.getRequest().getURI().getHost());
log.info("访问接口端口:" + exchange.getRequest().getURI().getPort());
log.info("访问接口URL:" + exchange.getRequest().getURI().getPath());
log.info("访问接口URL后面参数:" + exchange.getRequest().getURI().getRawQuery());
log.info("访问接口时长" + (System.currentTimeMillis() - beginVisitTime) + "毫秒");
log.info("============分割线==========================");
}
}));
}
// 数字越小,优先级越高
@Override
public int getOrder() {
return 0;
}
}
自定义单一过滤器
// 给某些服务指定配置,需要特殊参数,才可以访问
@Component
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {
public MyGatewayFilterFactory() {
super(MyGatewayFilterFactory.Config.class);
}
@Override
public GatewayFilter apply(MyGatewayFilterFactory.Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 得到请求头
ServerHttpRequest request = exchange.getRequest();
System.out.println("进入了自定义网关过滤器 - yml中配置的status:" + config.getStatus());
// 如果参数yy,进行放行,没有返回 - 回应头
if (request.getQueryParams().containsKey("yy")) {
return chain.filter(exchange);
} else {
exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
return exchange.getResponse().setComplete();
}
}
};
}
// 如果不配置这个,需要yml这里,需要全写,参考 断言的
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("status");
}
public static class Config {
@Getter
@Setter
private String status; //设定一个状态值/标志位,它等于多少,匹配和才可以访问
}
}
对应的代码可在SpringCloud案例查看,分享在:gitee