SpringCloud Alibaba 入门简介
能干嘛?
服务限流降级:默认支持 Servlet、Feign、RestTemplate、Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。
几乎可以把SpringCloud Netflix替掉了
Nacos
简介
为什么叫Nacos?前四个字母分别为Naming和Configuration的前两个字母,最后的s为Service。
能干什么?
替代Eureka做服务注册中心
替代Config做服务配置中心
对比
据说 Nacos 在阿里巴巴内部有超过 10 万的实例运行,已经过了类似双十一等各种大型流量的考验
安装Nacos
本地要有jdk8和maven
解压文件运行startup.cmd
看到logo就运行成功了
和Eureka一样,访问一下web管理页面就ok了,端口号8848
http://localhost:8848/nacos
默认的账号密码都是nacos
Nacos作为注册中心
Nacos工程父pom依赖引入
引入Spring Cloud Alibaba依赖
<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>
Nacos为服务端提供注册
新建cloudalibaba-provider-payment9001
新引入依赖
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
yml文件内容
主启动类不多赘述,主要就是一个SpringBoot启动注解和服务发现注解
Controller内容,很简单的一个接收请求返回端口号
启动测试
在Nacos的配置中心查看
注意:Nacos也是自带负载均衡,这里新建一个9002测试一下
偷懒的办法可以CV一份出来
测试一下,看一下web监控页
发起测试可以触发负载均衡,可以发起负载均衡的原因就是nacos里集成了Ribbon,Ribbon本身是支持负载均衡的
Nacos为消费端提供注册
新建cloudalibaba-consumer-nacos-order83
引入的pom和服务端是一样的也是nacos discovery
配置yml文件
主启动类和之前的一样,一个主启动类一个服务发现注解
因为我们是服务的消费者,要用负载均衡。由于Nacos依赖中是用Ribbon来实现的负载均衡,所以我们这里也是用Ribbon中的RestTemplate来实现负载均衡调用。
那么,要用RestTemplate就一定要有配置类,方便后续业务类调用RestTemplate进行注入
业务类
启动服务测试
服务入驻成功
83访问9001/9002,轮询负载OK
服务注册中心对比
Nacos是可以根据开发者的需求进行CP或者AP模式的选择,在高可用或者高一致性的两者间反复横跳
如何切换?
C是所有节点在同一时间看到的数据是一致的;而A的定义是所有的请求都会收到响应。
何时选择使用何种模式?
一般来说,
如果不需要存储服务级别的信息且服务实例是通过nacos-client注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如 Spring cloud 和 Dubbo 服务,都适用于AP模式,AP模式为了服务的可能性而减弱了一致性,因此AP模式下只支持注册临时实例。
如果需要在服务级别编辑或者存储配置信息,那么 CP 是必须,K8S服务和DNS服务则适用于CP模式。
CP模式下则支持注册持久化实例,此时则是以 Raft 协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误。
切换命令
curl -X PUT ‘$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP’
Nacos作为配置中心
替代了之前Bus+Config的yml配置,解耦
Nacos config 依赖
<!--nacos-config-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
新建配置中心cloudalibaba-config-nacos-client3377
yml文件,注意这里要配置一个application,还有一个bootstrap
为什么要配置两个?
Nacos同Springcloud-config一样,在项目初始化时,要保证先从配置中心进行配置拉取,
拉取配置之后,才能保证项目的正常启动。
springboot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application
application.yml的内容
spring:
profiles:
active: dev # 表示开发环境
bootstrap.yml的内容
# nacos配置
server:
port: 3377
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos服务注册中心地址
config:
server-addr: localhost:8848 #Nacos作为配置中心地址
file-extension: yaml #指定yaml格式的配置
业务类
注意,Spring Cloud config原生的@RefreshScope
也是可以用来触发Nacos的自动刷新功能的
业务类代码:
Nacos中的匹配规则,同步配置中心的文件
最后公式:
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
回到Nacos配置中心创建新文件
新建一个配置文件,就可以完成配置中心同步的功能关于上面的公式总结:
yml与yaml的小坑
会因为yaml的问题启动失败,这里最好是改一下
回到Nacos里,删了原有的yml后缀(Nacos不让直接改名),重建一个yaml的结尾,符合咱们的配置规则
启动测试,接通端口,测试通过成功读取内容
修改一下再发布,就可以直接读取到了,真爽真香,不用手动去配置
Nacos命名空间和DataId三者之间的关系
问题1:
实际开发中,通常一个系统会准备
dev开发环境
test测试环境
prod生产环境。
如何保证指定环境启动时服务能正确读取到Nacos上相应环境的配置文件呢?
问题2:
一个大型分布式微服务系统会有很多微服务子项目,
每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境…
那怎么对这些微服务配置进行管理呢?
复习一下Nacos操作
配置管理
命名空间
Namespace+Group+Data ID 前言
1 是什么
类似Java里面的package名和类名
最外层的namespace是可以用于区分部署环境的,Group和DataID逻辑上区分两个目标对象。
2 三者情况
默认情况:
Namespace=public,Group=DEFAULT_GROUP, 默认Cluster是DEFAULT
Nacos默认的命名空间是public,Namespace主要用来实现隔离。
比方说我们现在有三个环境:开发、测试、生产环境,我们就可以创建三个Namespace,不同的Namespace之间是隔离的。
Group默认是DEFAULT_GROUP,Group可以把不同的微服务划分到同一个分组里面去
Service就是微服务;一个Service可以包含多个Cluster(集群),Nacos默认Cluster是DEFAULT,Cluster是对指定微服务的一个虚拟划分。
比方说为了容灾,将Service微服务分别部署在了杭州机房和广州机房,
这时就可以给杭州机房的Service微服务起一个集群名称(HZ),
给广州机房的Service微服务起一个集群名称(GZ),还可以尽量让同一个机房的微服务互相调用,以提升性能。
最后是Instance,就是微服务的实例。
深入DataID方案
指定spring.profile.active和配置文件的DataID来使不同环境下读取不同的配置
说白了就是更改application.yml的文件,根据环境更改名字
默认空间+默认分组+新建dev和test两个DataID
新建dev配置DataID
新建test配置DataID
由于我们已经更改好了active环境,此时的active是test,那么再用上面的公式去套
$ {spring.application.name}-$ {spring.profiles.active}.$ {spring.cloud.nacos.config.file-extension}
就会得到这样的名字:{nacos-config-client}-{test}.{yaml} 注意,这里中括号是为了分割的更清楚,实际上是没有的!
深入Group方案
新建group
新建多个配置文件加入不同分组
如何读取分组下的文件
上面的是读取TEST_GROUP分组的,下面是读取DEV_GROUP分组的,按需要配置就行
测试接口,成功读取信息
深入Namespace方案
新建命名空间
回到服务管理-服务列表查看
按照域名配置填写,在命名空间下可以分组,分分支(测试、开发、上线…)。这样就可以把空间区分开
应用到工程yml中进行设置,主要都是在Bootstrap.yml进行配置,根据想同步的namespace的命名空间ID进行定位
这样就读取dev空间下的文件
新建一个配置进行测试
测试一下,可以读到yaml文件内容
Nacos集群和持久化配置
集群架构分析
官网架构图,VIP相当于一个虚拟IP(Virtual IP)进行负载均衡
翻译一下这个图
重点说明
持久化配置切换
windows版本Nacos
之所以可以不用连接数据库就可以存之前说的命名空间,yml文件…这些东西都是依靠他本身内嵌的数据库derby
Nacos默认自带的是嵌入式数据库derby其他数据库的话只支持MySQL
derby到mysql切换配置步骤
nacos-server-1.1.4\nacos\conf目录下找到sql脚本,到数据库里运行一下生成数据库
nacos-server-1.1.4\nacos\conf目录下找到application.properties,进入并编辑
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=123456
有个小坑,启动失败是MySQL8.0,空指针报错,找了很多方法无果,这里更换成5.5就一切正常,尚未解决,因此这里只用5.5做演示
这样就可以切换成MySQL数据库作存储了
此时重启Nacos,再次进入web管理界面,就会发现之前创建的配置文件都不在了
尝试着建立一条新的yml文件,在数据库中就会发现存入的痕迹
不难发现已经存进来了
Linux版本Nacos
直接解压就行,和windows版本差不多
Nacos集群配置
先把数据库从Derby切换到MySQL,运行一下SQL
找到properties配置文件更改一下数据库
和上面的配置内容一样,粘一下就行
Linux服务器上nacos的集群配置cluster.conf
先把作为集群的端口号梳理出来
复制一份文件
进行编辑
思考一下为什么要改脚本
/mynacos/nacos/bin 目录下有startup.sh
平时单机版的启动,都是./startup.sh即可。
但是
集群启动,我们希望可以类似其它软件的shell命令,传递不同的端口号启动不同的nacos实例。
命令:./startup.sh -p 3333 表示启动端口号为3333的nacos服务器实例,和上一步的cluster.conf配置的一致。
编辑Nacos的启动脚本startup.sh,使它能够接受不同的启动端口
直接右键编辑脚本,修改完就可以让nacos带着端口号启动了
带着端口号成功启动
配置Nginx负载均衡,请求先到Nginx,再通过Nginx负载均衡打到微服务上
修改nginx的配置文件
upStream cluster是Nginx负载均衡的ip映射配置upstream
按照配置启动
完整的架构图
Sentinel服务熔断与限流
一句话解释,之前我们讲解过的Hystrix,现在是阿里版
快速起步
Sentinel是一个jar包,下载下来之后直接命令运行jar包即可
和Nacos一样,直接登陆web界面,端口号8080 http://localhost:8080
账户密码都一样均为sentinel
访问页面
Sentinel依赖
<!--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>
新建工程cloudalibaba-sentinel-service8401
yml
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
#Nacos服务注册中心地址,8401注册到8080
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard地址,8080监控8401
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
management:
endpoints:
web:
exposure:
include: '*'
主启动类还是那样,注意加上服务暴露注解
Controller业务层
@RestController
public class FlowLimitController
{
@GetMapping("/testA")
public String testA()
{
return "------testA";
}
@GetMapping("/testB")
public String testB()
{
return "------testB";
}
}
启动服务,注意:Nacos和Sentinel都要启动
此时进入Sentinel的管理页面是什么都没有的
因为Sentinel采用的懒加载,这个服务不执行一次请求是不会对其进行监控的
发起一次请求
再次进入Sentinel的控制页面,就可以发现服务已经进来了
流量控制
这个页面上直接可以对流量进行控制
每个参数的详细解释
流控模式(直接)
表示1秒钟内查询1次就是OK,若超过次数1,就直接-快速失败,报默认错误
测试一下,1s内多次点击触发限流
这样根据自己接口的性能,自己定一个合适的QPS量
流控模式(关联)
别人惹事我买单,当关联的资源达到阈值时,就限流自己。当与A关联的资源B达到阀值后,就限流A自己。
现实中的例子:支付接口达到上限了,就让下订单的接口限流。
如何设置
当关联资源/testB的qps阀值超过1时,就限流/testA的Rest访问地址,当关联资源到阈值后限制配置好的资源名
B有事的话就限流A
采用Jmeter对B接口进行大量访问,此时A是被B关联的,B流量一起来,A就会被限流
流控模式(链路)
阈值统计时,只统计从指定资源进入当前资源的请求,是对请求来源的限流
只记录指定路上的流量,指定资源从入口资源进来的流量,如果达到阈值,就进行限流,api级别的限流
阈值统计时,只统计从指定资源进入当前资源的请求,是对请求来源的限流
流控效果
快速失败
这个是默认的,给你返回信息
预热
慢慢给系统放流量,让系统有个准备
公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值
举例:
默认 coldFactor 为 3,即请求QPS从(threshold / 3) 开始,经多少预热时长才逐渐升至设定的 QPS 阈值。
案例,阀值为10+预热时长设置5秒。
系统初始化的阀值为10 / 3 约等于3,即阀值刚开始为3;然后过了5秒后阀值才慢慢升高恢复到10
启动测试结果
一开始的时候会报限流错误
但是过一会之后就能扛得住正常访问了
相当于给系统一个缓冲时间,典型的场景就是秒杀系统的预热
秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。
排队等待
排队处理,挨个处理请求
注意,排队等待只支持QPS模式,多线程模式是不支持的
匀速排队,让请求以均匀的速度通过,阀值类型必须设成QPS,否则无效。
设置含义:/testA每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒。
启动测试,批量访问
在控制台可以发现请求每秒过来一个,挨个处理
Sentinel熔断降级
RT(平均响应时间,秒级)
平均响应时间 超出阈值 且 在时间窗口内通过的请求>=5,两个条件同时满足后触发降级
窗口期过后关闭断路器
RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)
异常比列(秒级)
QPS >= 5 且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
异常数(分钟级)
异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。
当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出DegradeException)。
与Hystrix的区别
之前学习豪猪哥的时候有一个熔断器半开状态(Half Open),指的是豪猪哥在服务熔断后尝试重新连接时会有半开状态,当一定数量的请求打过来并且成功的时候,才会一点一点让服务重新连接上来
多次错误,然后慢慢正确,发现刚开始不满足条件,就算是正确的访问地址也不能进行,也就是触发了熔断器,他需要慢慢地开启,此时处于半开状态(尝试),需要一些正常的访问通过,才会恢复正常
那么Sentinel的断路器呢?
Sentinel的断路器是没有半开状态的
降级策略
RT降级规则(平均响应时间)
思路架构图(响应速度慢了就会认为接口不行了,快到上限了,开启熔断器触发降级)
新加入一个接口作为延迟处理,通过线程睡眠实现一下接口的延迟,来模拟响应时间超限触发降级
在Sentinel的管理界面对接口进行一下RT规则配置
进行流量测试
按照上述配置,
永远一秒钟打进来10个线程(大于5个了)调用testC,我们希望200毫秒处理完本次任务,
如果超过200毫秒还没处理完,在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了
后续我停止jmeter,没有这么大的访问量了,断路器关闭(保险丝恢复),微服务恢复OK
异常比例
按照请求的异常数量来确定是否要开启降级
引入一个测试接口,人为造一个异常
为该接口配置一个异常比例的降级规则
启动测试
单独访问一次,必然来一次报错一次(int age = 10/0),调一次错一次;
开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了。
断路器开启(保险丝跳闸),微服务不可用了,不再报错error而是服务降级了。
如果是一次一次访问都是报500,就不友好了
异常数
这里的异常数是时间比较长的,一分钟之内达到的异常数量达到一定额数后,便会触发熔断器降级
时间窗口一定要大于等于60秒。
异常数是按照分钟统计的
引入测试接口
http://localhost:8401/testE,第一次访问绝对报错,因为除数不能为零, 我们看到error窗口,但是达到5次报错后,进入熔断后降级。
热点Key限流
引言
何为热点?
热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作
复习一下
兜底方法
分为系统默认和客户自定义,两种
之前的case,限流出问题后,都是用sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
我们能不能自定?类似hystrix,某个方法出问题了,就找对应的兜底降级方法?
结论
从HystrixCommand 到@SentinelResource
Sentinel热点key限流(上)
主要是针对访问时的某个参数的进行限流
主要注解:@SentinelResource(value = "唯一名称,一般取API名字",blockHandler = "降级的方法")
Controller层代码
如果不写blockHandler会怎么样
web端热点配置
API的参数计数是从0开始的
对应解释
也就是说,如果进行测试,只会针对
http://localhost:8401/testHotKey?param1=xxx这种访问进行限流(每秒最多一次,超过的情况就会触发限流方法) 对于这种
http://localhost:8401/testHotKey?param1=xxx¶m2=xxx的情况是不限流的,因为我只规定对key1进行限流
限流模式只支持QPS模式,固定写死了。(这才叫热点)
@SentinelResource注解的方法参数索引,0代表第一个参数,1代表第二个参数,以此类推
单机阀值以及统计窗口时长表示在此窗口时间超过阀值就限流。
上面的抓图就是第一个参数有值的话,1秒的QPS为1,超过就限流,限流后调用dealHandler_testHotKey支持方法。
测试一下
访问http://localhost:8401/testHotKey?param1=xxx
此时快速点击访问超过了每秒1次的QPS,就会触发限流的兜底方法
如果不写兜底方法就会前台报500,删去兜底的blockHandler
再次快速访问就报500
Sentinel热点key限流(下)
配置内容为参数例外项,也就是下面的高级选项
特殊情况下对热点key解除限流比如key为一个特殊值时,限流规则又是另外一种
对某一个特殊的key扩容开小灶
点击添加规则按钮
详细解释
启动测试
热点参数的注意点,参数必须是基本类型或者String
特殊情况
@SentinelResource
处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;
RuntimeException
int age = 10/0,这个是java运行时报出的运行时异常RunTimeException,@SentinelResource不管
总结
@SentinelResource主管配置出错,运行出错该走异常走异常
Sentinel系统规则
在整个系统的最外围提供限流
各项配置参数说明
@SentinelResource注解
之前提到的降级方法,如果不写,达到阈值会返回限流的提示
面临的问题
1 返回的限流提示是系统默认的,没有体现我们自己的业务要求。
2 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
3 每个业务方法都添加一个兜底的,那代码膨胀加剧。
4 全局统一的处理方法没有体现。
自定义限流逻辑处理
为了解决上述的每一个方法都要一个的限流处理类,我们自定义一个统一的处理类CustomerBlockHandler
降级处理类写好了怎么调用呢?
@SentinelResource(value = “唯一名称”, blockHandlerClass = 自定义处理类.class,blockHandler = “处理方法名称”)
重启测试
Sentinel控制台配置(在簇点链路中配置)
快速点击,超过了1的QPS,可以看到降级走的是我们自定义的处理方法
进一步说明
Sentinel服务熔断Ribbon系列
Sentinel整合ribbon+openFeign+fallback
服务熔断无配置默认情况
Sentinel注解后面什么都没有,只有一个唯一的名字
**什么都没配就代表着没有熔断也没有降级。**一旦报错就是500的页面,不友好
通过Ribbon去调用另一个微服务9004的服务
此时测试异常报错,id=4的情况
服务熔断配置fallback(降级兜底处理)
指向一个兜底的fallback程序
测试一下,这种报错返回就比较友好
服务熔断配置blockFallback
因为配了blockHandler,所以Sentinel里面也要配置一下,随便一个降级规则都是可以的
异常超过2次后,断路器打开,断电跳闸,系统被保护(这里如果是异常只有一次出现,那么这一次的异常就是报500的不友好界面)
fallback和blockHandler的区别
fallback是针对方法出现异常了,则会进入fallback方法。blockhandler是针对流控设置,超出规则,则会进入blockhandler方法。具体的区别可以参照这篇文章
fallback和blockHandler都配置
先写代码,把两个都配上
设置一下限流规则
进行测试:
如果同时触发了限流+异常,那么会走限流的处理方法。也就是限流要优先于异常处理的出现
快速访问id=4的异常情况,此时就会出现限流的提示
若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑
忽略属性(不再使用降级兜底)
结果就是再触发异常的话不在用兜底服务了
Sentinel服务熔断Feign系列
复习一下Feign,和之前的内容一样
引入的依赖
<!--SpringCloud openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
修改yml配置文件
激活Sentinel对Feign的支持
feign:
sentinel:
enabled: true
业务类
带@FeignClient注解的业务接口
主启动类加入注解
controller调用
测试84调用9003,此时故意关闭9003微服务提供者,看84消费侧自动降级,不会被耗死
熔断框架比较
规则持久化
出现的问题,Sentinel一旦重启,之前配置的各种限流规则就都不存在了,如果每次都要重新配置就非常麻烦,所以我们要对配置好的规则进行持久化处理。
具体思路
将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台
的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效
修改cloudalibaba-sentinel-service8401
加入持久化配置依赖
<!--SpringCloud ailibaba sentinel-datasource-nacos -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
yml添加Nacos数据源配置
进入nacos
添加Nacos业务规则配置(点加号添加配置)
解析一下配置的json
测试启动8401后刷新sentinel发现业务规则有了
此时重启sentinel就会发现配置规则仍然存在,超过QPS限流等操作都是好用的,已经存入nacos,配置持久化就是成功的。
Seata处理分布式事务
分布式事务问题背景
单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。
一个业务场景下订单:要调支付服务,库存服务,银行账户的增减服务,三个服务处在不同的地方不同的机房
不同的服务器,数据库一个事务下包含了三个异地的服务器调度,就容易出现问题,也叫作分布式的事务问题。此时Seata就横空出现成为了分布式事务问题的解决方案。
一句话概括:一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题
Seata术语
分布式事务处理过程的一ID+三组件模型
处理过程
TM是老板,TC是打工人,RM是任务
快速起步
加注解
下载Seata并解压
修改conf目录下的file.conf配置文件
主要修改:自定义事务组名称+事务日志存储模式为db+数据库连接信息
自定义事务组名称:
修改事务日志存储模式为db:
指定了db的存储模式还不算完,因为要连数据库了所以就要提供账号密码(我们选择的版本是5.7)
建一个Seata库,运行Seata官方的SQL文件,完成建表
修改seata-server-0.9.0\seata\conf目录下的registry.conf配置文件,将Seata进行注册
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
serverAddr = "localhost:8848"
namespace = ""
cluster = "default"
}
目的是:指明注册中心为nacos,及修改nacos连接信息
启动测试,一定先启动nacos,在启动seata,用脚本启动
Seata实战
业务说明
这里我们会创建三个服务,一个订单服务,一个库存服务,一个账户服务。
当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,
再通过远程调用账户服务来扣减用户账户里面的余额,
最后在订单服务中修改订单状态为已完成。
该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。
下订单—>扣库存—>减账户(余额)
准备订单业务数据库
按照上述3库分别建对应的回滚日志表
各自的库里拉入\seata-server-0.9.0\seata\conf目录下的db_undo_log.sql
最终效果
Java侧编码
回顾一下业务需求
下订单->减库存->扣余额->改(订单)状态
三个模块下的业务逻辑
库存模块
账户模块
订单模块
订单模块中主要的微服务调用逻辑,这里只讲关键的服务调用,其他业务逻辑代码不过多赘述
两个远程Feign调用的接口
2002库存接口远程调用
2003账户接口远程调用
测试
模拟出现的问题
这么多的远程微服务调用,如果中间有一个报错了停下了,那么整个就乱掉了,就会出现扣了钱订单没更新的类似问题,举个例子,高延时报错
AccountServiceImpl添加超时模拟
再次请求http://localhost:2001/order/create?userId=1&productId=1&count=10&mnotallow=100
就会出现数据不一致的问题
再或者这种情况
此时为了解决这种情况就要加入注解@GlobalTransactional
加入@GlobalTransactional
在多个微服务调用逻辑的最上方加入@GlobalTransactional注解,
加入这个注解的就是事务的发起方
这个事务下调用的远程微服务就是事务的参与方
重启测试,这个时候再出现错误就会发现,数据插不进来遇见报错回滚了
此时延时结束后,整个事务都结束了才会提交上来,事务不结束不提交。
Seata原理解析
AT模式如何做到对业务的无侵入
AT也就是Seata的付费高性能版本
一阶段拦截
在一阶段,Seata 会拦截“业务 SQL”,
1 解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,
2 执行“业务 SQL”更新业务数据,在业务数据更新之后,
3 其保存成“after image”,最后生成行锁。
以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。
二阶段提交
二阶段如是顺利提交的话,
因为“业务 SQL”在一阶段已经提交至数据库,所以Seata框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。
二阶段回滚
二阶段回滚:
二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。
回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,
如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。
补充