springCloud-Alibaba
Spring-Cloud-Alibaba
Nacos(服务注册和配置中心)
一、安装
官网:https://nacos.io/zh-cn/
刚下完是集群模式,所以直接启动会显示"nacos is starting with cluster"
可以在bin/startup.cmd文件中修改:
set MODE="standalone"
修改完成后即可启动成功,在浏览器输入:
http://localhost:8848/nacos
即可进入nacos管理界面:
二、作为服务注册中心
以下配置均可在官网文档找到
1.创建两个module(端口:4001,4002)作为provider集群
pom
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>
yml
# 基本配置 server:port: 4001 # 集群时,注意端口号不一致spring:application:name: payment-alibaba-providercloud:nacos:discovery:server-addr: localhost:8848 management:endpoints:web:exposure:include: "*"
主启动类:添加注解:
@EnableDiscoveryClient
controller
@RestController public class PaymentController {@Value("${spring.application.name}")private String name;@Value("${server.port}")private String port;// 返回服务名称和当前实例的端口号,便于测试负载均衡@GetMapping("/get/nacos/{id}")public String getNacos(@PathVariable(value = "id")int id){return "get id from nacos: " + name + "----port:" + port;} }
完成后即可在管理界面看到(注意需要先启动nacos,在启动微服务):
2.创建一个module(端口:81)作为consumer
pom,同上
yml,同上(注意微服务名称和端口)
主启动类,同上
controller
- 支持Restful,所以需要注入RestTemplate(代码略);因为nacos包含了ribbon,所以支持负载均衡,使用时与ribbon一样添加``@LoadBalanced`注解即可
@RestController public class ConsumerController {// 此对象为官网使用方法,下面注释的方式配合该对象,可以不用添加@LoadBalanced注解也能开启负载均衡 // @Autowired // private LoadBalancerClient loadBalancerClient;@Autowiredprivate RestTemplate restTemplate;@Value("${provider.service-url}")private String providerName;// @GetMapping("consumer/get/nacos/{id}") // public String getNacos(@PathVariable(value = "id")int id){// ServiceInstance serviceInstance = loadBalancerClient.choose(providerName); // return restTemplate.getForObject("http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + // " /get/nacos/" + id , String.class); // }@GetMapping("consumer/get/nacos/{id}")public String getNacos(@PathVariable(value = "id")int id){return restTemplate.getForObject("http://"+providerName+"/get/nacos/"+id,String.class);}}
Nacos支持AP和CP的切换,Eureka只支持AP
三、作为配置中心
1.快速入门
新建module(端口2001)
pom
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>
yml(application、bootstrap), springCloud也是这样
bootstrap:
server:port: 2001spring:application:name: config-center-alibabacloud:nacos:discovery:server-addr: localhost:8848 # 服务注册config:server-addr: localhost:8848 # nacos作为配置中地址file-extension: yaml # 指定配置文件类型
application:
spring:profiles:active: dev # 环境,与bootstrap中的配置可以结合起来
主启动类,注意添加EableDiscoverClient注解
nacos配置中心创建配置文件:
Data ID需要注意规范:
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
即:微服务名称-环境.配置文件类型====》对应案例:config-center-alibaba-dev.yaml
测试:
@RestController @RefreshScope public class ConfigController {@Value("${config.info}")private String info;@GetMapping("/get/config")public String getInfo(){return info;} }
访问:http://localhost:2001/get/info 即可得到配置信息
Nacos具有自动的动态更新配置的功能,在controller上添加
@RefreshScope
注解即可,不需要和springcloud一样通过bus发送广播更新
tips:docker 安装nacos
安装docker(不作解释)
docker search nacos
docker pull nacos/nacos-server # 默认最新版
docker images # 查看是否安装成功
docker run -d -p 8848:8848 --env MODE=standalone --name nacos nacos/nacos-server
http://IP+8848/nacos 登录
注意修改微服务中的配置文件的ip
2.分类配置
NameSpace、Group、Data Id
相当于java中包和类的关系
新建命名空间和配置文件:
微服务服务配置:
server:port: 2001spring:application:name: config-center-alibabacloud:nacos:discovery:server-addr: localhost:8848 # 服务注册config:server-addr: localhost:8848 # nacos作为配置中地址file-extension: yaml # 指定配置文件类型group: DEFAULT_GROUP # 指定分组namespace: ca6f1c91-fb7e-4a46-bf91-7f210bcef489 # 指定命名空间
四、Nacos集群和持久化
1.准备:
Nacos集群需要使用mysql:因为Nacos自带有小型的嵌入式数据库,所以在在集群的时候每个nacos都会有自己的数据库,这样就会导致数据的不一致,所以要切换到mysql将其统一持久到一个mysql数据库中。(目前只支持mysql)
在windows中配置mysql为数据源:
在ncaos中的conf文件夹下有:
nacos-mysql.sql文件:里面是数据库建表的语句,直接cv创建即可(数据库名称自定义)
application.properties文件修改如下(只需要将注解打开即可,注意修改数据库名称和密码,docker环境下就不需要修改这个文件了
):
spring.datasource.platform=mysql### Count of DB:
db.num=1### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=root
配置完成后,启动naco并登录管理界面,就会看到原来的配置文件不见了,再新建的配置文件信息可以再MySQL的自建的数据库的config_info表中看到,即配置成功。
在mysql版本过高可能会出现建表失败(时间的错误)
2.naocs集群配置(这里使用docker)
在上面已经下载nacos的docker版,只需在linux中配置mysql即可(如上,这里也是使用docker的mysql)。
创建容器命令(需要三份分别为:8846、8847、8848):
docker run -d \
-e PREFER_HOST_MODE=hostname \
-e MODE=cluster \
-e NACOS_APPLICATION_PORT=8847 \
-e NACOS_SERVERS="192.168.1.118:8846 192.168.1.118:8847 192.168.1.118:8848" \
-e SPRING_DATASOURCE_PLATFORM=mysql \
-e MYSQL_SERVICE_HOST=182.168.1.118 \
-e MYSQL_SERVICE_PORT=3307 \
-e MYSQL_SERVICE_USER=root \
-e MYSQL_SERVICE_PASSWORD=123456 \
-e MYSQL_SERVICE_DB_NAME=nacos_config \
-e NACOS_SERVER_IP=192.168.1.118 \
-p 8847:8847 \
--name my-nacos8847 \
nacos/nacos-server:1.4.2
简单解释:
- 配置模式为:集群
- nacos服务端口:8846
- 集群的IP:3个
- 使用数据库为mysql,
在这里配置后,不需要修改application.properties文件了,将会自动绑定数据库
- 数据库信息
- 宿主机端口映射
**此时可以作个小测试:**
浏览器分别访问这三个nacos,在其中一个里面添加配置文件,刷新其他两个nacos管理界面,会出现同样的配置文件,即完成集群,且配置文件已经持久到mysql数据库中,可在数据库中查看。
注意:在登录管理界面时,可能出现默认账户密码错误,可以通过查看数据库中user表中是否有数据,大概率时没有导致的,只需要添加即可(密码是加密后的密码)
3.nginx配置(使用docker)
这里没有开启nginx的高可用
创建nginx容器:
docker run \
--name=nginx-MASTER \
-d -p 8000:80 \
-v $PWD/nginx.conf:/etc/nginx/nginx.conf:ro \
-v $PWD/conf.d:/etc/nginx/conf.d \
nginx
修改配置文件:
upstream cluster{ # nacos集群列表server 192.168.1.118:8846;server 192.168.1.118:8847;server 192.168.1.118:8848;
}
# server下
listen 80; # nginx访问端口,docker映射为81:80
location / {proxy_pass http://cluster;
}
如上配置启动nginx,nacos;
访问:
http://192.168.1.118:81/nacos/#/lodin/
即可到nacos登录界面,且已经集群;在实际开发中,只能暴露nginx的虚拟ip,而nacos的IP都是不公开的。
遇到的坑:完成上述集群后,最后一步,需要注册微服务。但是始终无法注册进入nacos。调用服务注册api:
/nacos/v1/ns/instance
总会出现:server is DOWN now, please try again later!等字样,在java控制台中报错500或503.
查阅资料、官方文档、github上的issues,尝试过以下方法:
- 修改docker中的nacos版本:latest、2.0.0、1.4.1
- 设置linux的hostname即IP
- 删除nacos里面的data中的protocol文件夹
- 修改alibaba的依赖版本等
最终使用1.4.1版本使用/nacos/v1/ns/instance访问成功,并注册成功。具体原因还是没找到。个人认为是版本问题。在github上,官方说是由于(1)前置Filter拦截。(2)旧的jRaft的data数据会干扰本次jRaft的选举。总之还是使用RELEASE版本比较好
官网问题描述:https://github.com/alibaba/nacos/issues/5346
Sentinel(熔断与限流)
sentinal官网:
https://sentinelguard.io/zh-cn/
https://github.com/alibaba/Sentinel
功能:
下载:https://github.com/alibaba/Sentinel/releases/tag/1.8.1
下载的是一个jar包,所以世直接使用java -jar xxxx 直接运行即可,在浏览器访问http://localhost:8080可以看到dashboard(用户名密码默认为sentinel)
一、入门:对服务的监控
新建module
添加pom
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency>
配置yml
server:port: 4003spring:application:name: alibaba-sentinel-servicecloud:nacos:discovery:server-addr: localhost:8848# sentinel监控该服务sentinel:transport:dashboard: localhost:8080port: 8719management:endpoints:web:exposure:include: "*"
创建主启动类
创建controller(随意创建mapping)
测试:在nacos中注册成功;sentinel中没有:因为sentinel使用的是懒加载,当进行一次访问该服务时就会加载
二、流控规则
流控规则添加:
解释:
资源名:微服务中的Rest地址
针对来源:
阈值类型:QPS:访问的数量;线程数:服务端的线程数量
单机阈值:QPS的请求阈值或线程数的数量
是否集群:使用集群
流控模式:
直接:达到限流条件时,直接限流自己
关联:A关联B,B达到设定阈值后,限流A自己
如下:test1关联test2,当test2的请求QPS大于2时,test1就会被限流
链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就可以限流)
流控效果
- 快速关闭:超过阈值直接返回错误
- Warm up:预热,一个系统平时访问量很低,突然之间访问量暴增,超过阈值会使系统崩溃;使用预热留空会在设定时间内,将阈值提到上限;正常情况的阈值为:阈值上限/3 (3为预热因子,默认为3)
- 排队等待:匀速排队(
RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER
)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。
三、降级规则
1.慢调用比例策略
慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例
大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
如图:对于/get/test3这个请求资源,满足如下规则触发熔断:
- 在1000ms的时间内,最少需要有5次请求
- 在1000ms内,超出RT的请求(即慢调用请求)的比例为1(100%)
就会触发2s的熔断。
**测试:**使用JMETER测试,给定每秒钟10个请求,controller中的处理时间为1s(超过RT,所以阈值为100%),此时使用浏览器访问该资源,会出现错误 Blocked by Sentinel (flow limiting)
@GetMapping("/get/test3")public String test3(){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}return "-----test3----";}
2.异常比例
异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例
大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
如图,对于test4资源:
设置的比例阈值为0.5(50%),即:
- 当1s内,最少有5次请求
- 且,异常的比例为50%以上
则触发熔断。这里需要注意:这里的异常
指的是业务异常
,对 Sentinel 限流降级本身的异常(BlockException
)不生效
@GetMapping("/get/test4")public String test4(){int a=1/0; // 错误率100%return "-----test4----";}
3.异常数
异常数 (ERROR_COUNT):当单位统计时长内的异常数目
超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
异常数是按分钟为单位计数
如图:当窗口其内请求异常数超过10个就会触发熔断。
四、热点规则
**官网解释:**何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
- 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
- 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。
1.初步使用
业务代码:
这里使用到了新的标签@SentinelResource
,该注解只适用于sentinel配置的异常,如服务限流、熔断、热点限流等;后台的程序异常使用,如:RuntimeException等
@GetMapping("/hotKeyTest")@SentinelResource(value = "hotKeyTest", blockHandler = "deal_hotKeyTest")public String hotKeyTest(@RequestParam(value = "p1", required = false)String p1,@RequestParam(value = "p2", required = false)String p2){return "-----hotKeyTest----";}// blockHandler异常处理的方法public String deal_hotKeyTest(String p1, String p2, BlockException blockException){return "deal_hotKeyTest, request error";}
普通Sentinel配置:
解释:资源名为hotKeyTest,而不是"/hotKeyTest";仅支持QPS的限流方式;参数索引为0,即第一个参数为热点参数;阈值:即统计时长内的请求。
所以,对于上面的热点规则,当在1s内超过一次请求带有参数p1则会触发异常,调用blockHandler
方法;如果不带参数p1则不会触发。
参数例外项配置:
当某个热点参数的值为某个特定的值的时候,给与使用特定的规则,如下:
热点参数为p1,当p1的值为java.String类型的"zhangsan"的时候,允许其阈值为5,即可以在1s内访问5次,而其他带有p1参数的请求只能1s访问1次。
五、系统规则
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 达到阈值即触发系统保护。
如图:
当设置系统规则为入口QPS后,系统下的任何一个资源的访问大于1,都会被限流。
六、@SentinelResource
注解包含以下属性:
value
:资源名称,必需项(不能为空)entryType
:entry 类型,可选项(默认为 EntryType.OUT)blockHandler
/blockHandlerClass
: blockHandler 对应处理BlockException的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class`对象,注意对应的函数必需为 static 函数,否则无法解析。fallback/fallbackClass
:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable`类型的参数用于接收对应的异常。
- fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
defaultFallback
(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要为空,或者可以额外多一个 Throwable`类型的参数用于接收对应的异常。
- defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass为对应的类的 Class对象,注意对应的函数必需为 static 函数,否则无法解析。
exceptionsToIgnore
(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException
时只会进入 blockHandler
处理逻辑。若未配置 blockHandler
、fallback
和 defaultFallback
,则被限流降级时会将 BlockException
直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException
)。
1.blockHandler / blockHandlerClass的使用
该属性用于BlockException异常的处理,即服务限流、降级相关的异常。
当出现这些异常的时候会使用指定的异常处理方法进行处理。配置如下(为了防止代码膨胀,将处理方法置于其他专门的类中):
controller:
@GetMapping("/byResource/globalHandler")@SentinelResource(value = "globalHandler" , // 资源名称blockHandlerClass = CustomerBlockHandler.class, // 指定异常处理所在类的classblockHandler = "globalHandler1") // 异常处理方法名称public CommonResult globalHandler(){return new CommonResult(200,"request /byResource/globalHandler success");}
异常处理类:
// 异常处理类
public class CustomerBlockHandler {// 异常处理方法1public static CommonResult globalHandler1(BlockException e){return new CommonResult(444, "error by globalHandler1");}// 异常处理方法2public static CommonResult globalHandler2(BlockException e){return new CommonResult(444, "error by globalHandler2");}// 异常处理方法3public static CommonResult globalHandler3(BlockException e){return new CommonResult(444, "error by globalHandler3");}
}
注意:异常处理方法必须是public static修饰的,否则无法解析
因此,当对资源"globalHandler"限流的时候。超过阈值时就会进入"globalHandler1"方法并返回。
2.fallback / fallbackClass的使用
fallback用于处理程序运行时抛出的异常,测试如下:
@RestController
public class ConsumerController {@Autowiredprivate RestTemplate restTemplate;@Value("${provider.service-url}")private String providerName;// 调用微服务查询结果@GetMapping("/consumer/get/payment/{id}")// fallback可以处理运行(业务)的异常// @SentinelResource(value = "fallback",fallback = "fallback_method")// blockHandler不能处理运行异常,只能处理sentinel的配置异常// @SentinelResource(value = "fallback",blockHandler = "blockHandle_method")// 同时可以处理sentinel配置异常,也可以处理业务异常@SentinelResource(value = "fallback",fallback = "fallback_method",blockHandler = "blockHandle_method")public CommonResult getPayment(@PathVariable(value = "id")Long id){CommonResult res = restTemplate.getForObject("http://" + providerName + "/get/payment/" + id, CommonResult.class);Object data = res.getData();if(data != null){return res;}else{throw new NullPointerException("用户不存在");}}// 再次强调:fallback函数和blockHandle函数的格式必须严格遵守官网要求public static CommonResult fallback_method(@PathVariable(value = "id")Long id,Throwable e){return new CommonResult(444,"请求失败:id为:" + id + " 的用户不存在 ");}// 再次强调:fallback函数和blockHandle函数的格式必须严格遵守官网要求public static CommonResult blockHandle_method(@PathVariable(value = "id")Long id, BlockException e){return new CommonResult(444,"请求失败:被限流限流了");}}
上述代码对单独使用"fallback",单独使用"blockHandler",同时使用二者进行了测试,结果为:
- 单独使用fallback,只能处理查询运行时抛出的异常
- 单独使用blockHandler,只能处理sentinel配置后违规的异常
- 同时使用,则所有异常都能处理,且当同时又两种异常发生时,会优先使用blockHandler的异常处理。
对于fallback,也可以将异常处理方法置于一个单独的类中,操作与1.一样。
3.defaultFallback
即:使用了@SentinelResource注解后,不添加fallback,则使用默认的方法;操作同上。
4.exceptionsToIgnore
忽略异常,即:当抛出的异常为指定的以场数时,不再做处理,将按照原样抛出;指定的参数为.class
七、服务熔断
Sentinel+OpenFeign实现服务熔断
POM添加依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
修改yml配置
# 开启sentinel与feign的结合 feign:sentinel:enabled: true
主启动类添加注解
@EnableFeignClients
添加OpenFeign接口和实现
接口:
@Component @FeignClient(value = "payment-alibaba-provider", fallback = FeignServiceImpl.class) public interface FeignService {@GetMapping("/get/payment/{id}")public CommonResult getNacos(@PathVariable(value = "id")int id); }
实现:
@Component public class FeignServiceImpl implements FeignService {@Overridepublic CommonResult getNacos(int id) {return new CommonResult(444,"请求失败:id为:" + id + " 的用户不存在 ");} }
Controller
@Autowiredprivate FeignService feignService;@GetMapping("/consumer/get/byFeign/{id}")public CommonResult getByFeign(@PathVariable(value = "id")int id){return feignService.getNacos(id);}
测试:在断开服务后,consumer访问服务将会被openFeign的实现类中的方法处理。
八、Sentinel规则持久化
…
Seata(处理分布式事务)
分布式事务问题:
微服务的开发需要用到多个数据库保存不同的数据信息,也有许多不同的微服务调用,所以就需要事务来控制数据的一致性,所有需要使用到分布式事务。
一、Seata的基本概念
官网:https://seata.io/zh-cn/
Seata有3个基本组成部分:
- **事务协调器(TC):**维护全局事务和分支事务的状态,驱动全局提交或回滚。
- **事务管理器TM:**定义全局事务的范围:开始全局事务,提交或回滚全局事务。
- **资源管理器(RM):**管理分支事务正在处理的资源,与TC进行对话以注册分支事务并报告分支事务的状态,并驱动分支事务的提交或回滚。
seata由一个XID+三个管理器组成
Seata管理的分布式事务的典型生命周期:
- TM要求TC开始一项新的全局事务。TC生成代表全局事务的XID。
- XID通过微服务的调用链传播。
- RM将本地事务注册为XID到TC的相应全局事务的分支。
- TM要求TC提交或回退相应的XID全局事务。
- TC驱动XID对应的全局事务下的所有分支事务,以完成分支的提交或回滚。
二、seata的配置与启动
下载地址:https://github.com/seata/seata/releases/tag/v1.4.2
1.配置文件修改
seata\seata-server-1.4.2\conf\file.conf
1. 修改数据库 mode = "db"2. 设置数据库连接 driverClassName = "com.mysql.cj.jdbc.Driver"url = "jdbc:mysql://localhost:3306/seata?rewriteBatchedStatements=true&serverTimezone=UTC&useSSL=false"user = "root"password = "root"3. 添加设置,以下代码原来的文件中没有,需添加,指定事务组;重点关注"admin-tx_group"的值 service {#transaction service group mappingvgroupMapping.my_test_tx_group = "admin-tx_group"#only support when registry.type=file, please don't set multiple addressesdefault.grouplist = "127.0.0.1:8091"#degrade, current not supportenableDegrade = false#disable seatadisableGlobalTransaction = false }
这里需要注意驱动的使用:本次使用的版本是1.4.2,在lib目录下由jdbc目录,将里面的8.0的驱动拖到lib目录下,并按照上述配置修改即可(不这样操作会导致无法连接数据库而启动失败)
seata\seata-server-1.4.2\conf\registry.conf
1.registry.type=nacos 2.registry.nacos.serverAddr="localhost:8848" #此处为nacos地址端口
创建数据库
创建数据库,名称为上述配置中的名称(任意)。
创建表:sql脚本地址:https://github.com/seata/seata/tree/develop/script/server/db
创建完成后:
测试:
1.启动nacos
2.启动seata
3.注册成功
三、案例准备
如图:当用户下单时,会在订单服务(order)创建一个订单,然后通过远程调用库存服务器来扣除库存;
再通过远程调用账户服务来扣除用户账户余额;
最后再订单服务中修改订单的状态为已完成。
1.创建数据库
order
create table t_order(id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,user_id BIGINT(11) DEFAULT NULL,product_id BIGINT(11) DEFAULT NULL,count INT(11) DEFAULT NULL,money DECIMAL(11,0) DEFAULT NULL,status INT(1) DEFAULT NULL COMMENT '0:订单创建中 1:已完结' )ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;-- 日志表 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,PRIMARY KEY (`id`),UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
storage
create table t_storage(id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,product_id BIGINT(11) DEFAULT NULL,total INT(11) DEFAULT NULL,used INT(11) DEFAULT NULL COMMENT '已用库存' ,residue INT(11) DEFAULT NULL COMMENT '剩余' )ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;insert into seata_storage.t_storage(id,product_id,total,used,residue) values (1,1,100,0,100);select * from seata_storage.t_storage-- 日志表 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,PRIMARY KEY (`id`),UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
account
create table t_account(id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,user_id BIGINT(11) DEFAULT NULL,total DECIMAL(11) DEFAULT NULL COMMENT '总额度度',used DECIMAL(11) DEFAULT NULL COMMENT '已用额度' ,residue DECIMAL(11) DEFAULT NULL COMMENT '剩余可用额度' )ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;insert into t_account(id,user_id,total,used,residue) values (1,1,10000,0,10000);select * from t_account-- 日志表 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,PRIMARY KEY (`id`),UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
日志表创建sql脚本:https://github.com/seata/seata-samples/blob/master/ha/src/main/resources/sql/undo_log.sql
2.order-Module创建
1.添加pom
<dependencies><!--feign组件--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--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><groupId>io.seata</groupId><artifactId>seata-all</artifactId></exclusion></exclusions></dependency><dependency><groupId>io.seata</groupId><artifactId>seata-all</artifactId><version>1.4.2</version></dependency><!--spring boot 组件--><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><!--mybatis--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId></dependency><!--jdbc--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.5</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>
2.创建yml
server:port: 8011spring:application:name: seata-order-servicecloud:alibaba:seata:tx-service-group: admin-tx_group # 需要与seata的服务端设置的事务组名称一致service:vgroup-mapping:admin-tx_group: default #key与上面的tx-service-group的值对应nacos:discovery:server-addr: localhost:8848datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/seata_orderpassword: rootusername: rootfeign:hystrix:enabled: truemybatis: # 整合mybatistype-aliases-package: com.zjj.cloud01.domian # 别名所在包mapperLocations: classpath:mapper/*.xml
3.添加seata配置文件的conf
直接将seata的file.conf和registry.conf放入resources目录下即可
cong.file
# 添加如下配置信息, service {#transaction service group mappingvgroupMapping.admin-tx_group = "default"#only support when registry.type=file, please don't set multiple addressesdefault.grouplist = "127.0.0.1:8091"#degrade, current not supportenableDegrade = false#disable seatadisableGlobalTransaction = false }
tips:这里需要注意,当前使用的1.4.2本版seata。在下载下来后,所提供的file.config文件中不存在”service“这个配置项,这个配置项主要用于定义事务组的名称。在使用时,服务端的seata定义了事务组的名称(
详见本章"二"配置的详细内容
),而客户端需要配置与其一致的事务组名称(如上添加的配置项)。如不配置,可能会出现提示:can not get cluster name in registry config ‘…’ please make sure registry config correct 的错误registry.conf
主要修改注册类型为nacos,修改地址和ip
4.创建domain包
- 创建order的实体类:Order
- 创建结果返回类:CommonResult
5.创建mapper
@Component
@Mapper
public interface OrderMapper {// 添加订单void createOrder(Order order);// 修改订单状态void updateOrderStatus(@Param(value = "userId")Long userId, @Param(value = "status")Integer status);}
<mapper namespace="com.zjj.cloud01.mapper.OrderMapper"><insert id="createOrder" parameterType="com.zjj.cloud01.domain.Order">insert into t_order values (null,#{userId},#{productId},#{count},#{money},0)</insert><update id="updateOrderStatus">update t_order set status = 1 where user_id = #{userId} and status = #{status}</update></mapper>
6.创建service
OrderServiceImpl
public class OrderServiceImpl implements OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate AccountService accountService;@Autowiredprivate StorageService storageService;@Overridepublic void createOrder(Order order) {log.info("开始添加订单================");orderMapper.createOrder(order);log.info("开始扣减库存=================");storageService.decrease(order.getProductId(),order.getCount());log.info("扣减库存结束================");log.info("开始扣减余额==================");accountService.decrease(order.getUserId(),order.getMoney());log.info("扣减余额结束================");log.info("开始修改订单的状态=============");orderMapper.updateOrderStatus(order.getUserId(), 0);log.info("修改订单的状态结束================");log.info("添加订单结束================");} }
AccountService
@FeignClient(value = "seata-account-service") public interface AccountService {@PostMapping("/account/decrease")CommonResult decrease(@RequestParam(value = "userId")Long userId, @RequestParam(value = "money")BigDecimal money); }
StorageService
@FeignClient(value = "seata-storage-service") public interface StorageService {@PostMapping("/storage/decrease")CommonResult decrease(@RequestParam(value = "productId")Long productId, @RequestParam(value = "count")Integer count); }
7.创建controller
@RestController
public class OrderController {@Autowiredprivate OrderService orderService;@GetMapping("order/create")public CommonResult createOrder(Order order){orderService.createOrder(order);return new CommonResult(200,"添加订单成功~");}
}
8.创建主启动类
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) // 取消数据源的自动创建
@EnableDiscoveryClient
@EnableFeignClients
public class SeataOrderMain8011 {public static void main(String[] args) {SpringApplication.run(SeataOrderMain8011.class,args);}
}
9.创建config
MybatisConfig
@Configuration @MapperScan({"com.zjj.cloud01.mapper.OrderMapper"}) public class MyBatisConfig {}
DateSourceProxyConfig
// 使用Seata对数据源管理 @Configuration public class DateSourceProxyConfig {@Value("${mybatis.mapperLocations}")private String mapperLocations;@Bean@ConfigurationProperties(prefix = "spring.datasource")public DataSource druidDataSource(){return new DruidDataSource();}@Beanpublic DataSourceProxy dataSourceProxy(DataSource dataSource){return new DataSourceProxy(dataSource);}@Beanpublic SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception {SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dataSourceProxy);sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());return sqlSessionFactoryBean.getObject();} }
3.storage-Module创建
步骤同上,只需要修改业务逻辑即可(修改库存操作)
4.account-创建
步骤同上,只需要修改业务逻辑即可(修改用户余额操作)
以上步骤全部完成后,测试:访问order,传入订单信息,将会相继调用微服务并修改数据库。
四、案例实践
当正常访问时,一切安好。
1.异常:
情况:当订单创建后(order表新建记录,状态为0),后面的任何一个环节出现故障,都会导致数据的不一致。
实例:修改AccountServiceImpl,为其设置超时(睡眠20s)
@Overridepublic void updateAccount(Long userId, BigDecimal money) {log.info("扣款开始===========");try {Thread.sleep(20000);} catch (InterruptedException e) {e.printStackTrace();}accountMapper.updateAccount(userId,money);log.info("扣款完成===========");}
结果:当执行到account时,由于时间超时,所以在order端就会报错而不会执行下面的修改状态操作,最终导致用户余额减少,而订单状态处于未处理状态(数据不一致)。
2.解决
在OrderServiceImpl方法上添加@GlobalTransactional
注解即可(在入口添加)
name属性值任取,rollbackFor表示何种情况回滚
@Override@GlobalTransactional(name = "order-transaction", rollbackFor = Exception.class)public void createOrder(Order order) {log.info("开始添加订单================");orderMapper.createOrder(order);log.info("开始扣减库存=================");storageService.decrease(order.getProductId(),order.getCount());log.info("扣减库存结束================");log.info("开始扣减余额==================");accountService.decrease(order.getUserId(),order.getMoney());log.info("扣减余额结束================");log.info("开始修改订单的状态=============");orderMapper.updateOrderStatus(order.getUserId(), 0);log.info("修改订单的状态结束================");log.info("添加订单结束================");}
五、seata原理
ccountServiceImpl,为其设置超时(睡眠20s)
@Overridepublic void updateAccount(Long userId, BigDecimal money) {log.info("扣款开始===========");try {Thread.sleep(20000);} catch (InterruptedException e) {e.printStackTrace();}accountMapper.updateAccount(userId,money);log.info("扣款完成===========");}
结果:当执行到account时,由于时间超时,所以在order端就会报错而不会执行下面的修改状态操作,最终导致用户余额减少,而订单状态处于未处理状态(数据不一致)。
[外链图片转存中…(img-FZGm2hHG-1620911972626)]
2.解决
在OrderServiceImpl方法上添加@GlobalTransactional
注解即可(在入口添加)
name属性值任取,rollbackFor表示何种情况回滚
@Override@GlobalTransactional(name = "order-transaction", rollbackFor = Exception.class)public void createOrder(Order order) {log.info("开始添加订单================");orderMapper.createOrder(order);log.info("开始扣减库存=================");storageService.decrease(order.getProductId(),order.getCount());log.info("扣减库存结束================");log.info("开始扣减余额==================");accountService.decrease(order.getUserId(),order.getMoney());log.info("扣减余额结束================");log.info("开始修改订单的状态=============");orderMapper.updateOrderStatus(order.getUserId(), 0);log.info("修改订单的状态结束================");log.info("添加订单结束================");}
springCloud-Alibaba相关推荐
- SpringCloud Alibaba微服务实战(七) - 路由网关(Gateway)全局过滤
说在前面 全局过滤器作用于所有的路由,不需要单独配置,我们可以用它来实现很多统一化处理的业务需求,比如权限认证,IP 访问限制,监控,限流等等. 创建路由网关(Gateway)启动服务cloud-ac ...
- SpringCloud Alibaba微服务实战(五) - Sentinel实现限流熔断
什么是Sentinel? 请查看文章:SpringCloud Alibaba微服务实战(一) - 基础环境搭建 构建服务消费者cloud-sentinel进行服务调用 服务创建请查看文章:Spring ...
- SpringCloud Alibaba微服务实战(四) - Nacos Config 配置中心
说在前面 Nacos 是阿里巴巴开源的一个更易于构建云原生应用的动态服务发现.配置管理和服务管理平台.Nacos Config就是一个类似于SpringCloud Config的配置中心. 一.启动N ...
- SpringCloud Alibaba微服务实战(三) - Nacos服务创建消费者(Feign)
什么是Feign Feign 是一个声明式的伪 Http 客户端,它使得写 Http 客户端变得更简单.使用 Feign,只需要创建一个接口并注解.它具有可插拔的注解特性,可使用 Feign 注解和 ...
- SpringCloud Alibaba微服务实战(二) - Nacos服务注册与restTemplate消费
说在前面 基础环境搭建,理论,请看上一篇,在这就不扯理论了,直接上代码. 项目结构 代码实现 第一步:在父pom的项目中引入dependencyManagement 在引入父pom之前咱们先来回顾下d ...
- SpringCloud Alibaba微服务实战(一) - 基础环境搭建
说在前面 Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案.此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来 ...
- 给老板解释解释,为什么要用SpringCloud alibaba作为微服务开发框架???
什么是微服务 提到微服务不得不提Martin Fowler在2014年3月25日发表的文章 Microservices,里面给出了微服务的定义.后续国内所有关于微服务的介绍都是基于这篇文章的翻译,或加 ...
- SpringCloud Alibaba Sentinel 流量控制规则介绍与配置
概述:流量控制(flow control),其原理是sentinel断路器通过监控应用服务调用的QPS或调用并发线程数来实现调用控制.当QPS或线程数达到配置的阈值时,进行响应的服务降级功能,从而到达 ...
- SpringCloud Alibaba Sentinel 项目基础环境搭建
本示例项目采用在maven父工程SpringCloud下面创建子module(sentinel_order_service_rest)形式. 文件清单列表: 父工程pom.xml 子工程pom.xml ...
- 为什么要用SpringCloud alibaba作为微服务开发框架?
作者 | 飘渺Jam 责编 | 欧阳姝黎 什么是微服务 提到微服务不得不提Martin Fowler在2014年3月25日发表的文章 Microservices,里面给出了微服务的定义.后续 ...
最新文章
- [收集] Web服务相关的, 介绍框架(framework)类的论文
- python第三方开发软件_python开发者的必备工具(一)
- 51nod 1092 回文字符串
- 计算机系统的层次结构是缺一不可的吗,第1章 计算机组成与结构绪论.ppt
- nginx 同一个IP上配置多个HTTPS主机
- PHP支付接口教程,详解微信支付(二)
- 【Python CheckiO 题解】Between Markers (simplified)
- C#中判断服务器图片是否存在
- 计算机考试策略,计算机等级考试应试的策略.doc
- jQuery调用或获取iframe中的方法或控件值
- Vue 中的计算属性,方法,监听器
- 如何深入学习Android系统
- 标准电阻阻值速查表及由来
- 计算机图形学概论论文5000字,关于计算机图形学探究的论文
- 软件设计模式概念与意义
- Metasploit Framework —— Exploit
- 办公族如何防治鼠标手?
- CentOS 可以ping通IP和域名,但打开火狐却上不了网
- e^x导数为什么是e^x
- SOHO中国高管建“老鼠仓”吸钱 大企成空壳谁之责?
热门文章
- 顶尖高校【中国科学院大学】公布3届毕业生深造及就业情况!
- 正负±5V(-5V到+5V)转0-3.3V简单电路实现(其他输入输出值只需稍微更改)
- 如何查看小方侦测云存储_杭州雄迈信息技术有限公司
- Perconadb加载tokudb引擎时的问题
- [分享]英语面试问答大全
- android 动画方式,Android Activity进出动画三种方法
- linux重启was控制台,was控制台打不开了 详细
- 来了!8M/S+速度,Pdown复活!
- javascript网页设计大作业: HTML期末学生大作业 基于HTML+CSS+JavaScript通用的后台管理系统ui框架模板
- UnityShader入门精要——消融效果