spring官网:https://spring.io/projects/spring-cloud-alibaba
英文官网:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_cloud_oss
中文官网:https://github.com/alibaba/spring-cloud-alibaba/blob/2.2.x/README-zh.md
a)为什么出现?
Spring Cloud Netflix项目进入维护模式,意味着 Spring Cloud 团队将不会再向模块添加新功能,新组件功能将以其他替代平代替的方式实现
b)能干嘛?
服务限流降级:默认支持 Servlet、Feign、RestTemplate、Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。
c)怎么玩?
在父工程中引入
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.2.8.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
然后在子工程中引入依赖就行了~
d)一张图来了解一下nacos有多牛逼
nacos支持AP和CP的切换
C是所有节点在同一时间看到的数据是一致的;而A的定义是所有的请求都会收到响应。
官方文档:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_nacos_discovery
a)是什么?
用一句话来简单的介绍一下:Nacos = Eureka+Config +Bus
替代Eureka做服务注册中心
替代config做服务配置中心
b)下载
官方网址:https://nacos.io/zh-cn/
注:下载的版本最好与自己框架中的对应
查看版本:https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
先去官网下载,解压后直接运行bin目录下的startup.cmd
在cmd中输入:startup.cmd -m standalone
运行成功后直接访问http://localhost:8848/nacos,如果能访问到下面的界面,恭喜你,安装成功~
(1)新建cloudalibaba-provider-payment9001
(2)改pom
主要是这个spring-cloud-starter-alibaba-nacos-discovery,也是从官方文档中学到的
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
server:
port: 9001
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址
management:
endpoints:
web:
exposure:
include: '*'
(4)主启动
注意加注解L:@EnableDiscoveryClient,这个也是从官网得知的
(5)业务类
@RestController
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@GetMapping("/payment/nacos/{id}")
public String getPayment(@PathVariable("id") Integer id){
return "nacos registry,serverPort"+serverPort+"\t"+"id:"+id;
}
}
(6)为了后面负载均衡的测试,我们模仿9001再新建一个9002
如果不想重复劳动,可以按照下图的流程建立一个虚拟映射
(6)测试
首先启动nacos注册中心,然后启动9001,我们在服务列表中看到了我们的9001和9002
(1)新建cloudalibaba-consumer-nacos-order83
(2)改pom
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.javalearn.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
(3)写yml
server:
port: 83
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
nacos-user-service: http://nacos-payment-provider
(4)主启动
记得加@EnableDiscoveryClient
(5)既然集成了ribbon,那么就需要一个配置类来注入RestTemplate
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
(6)业务类
@RestController
@Slf4j
public class OrderNacosController {
@Resource
private RestTemplate restTemplate;
@Value("${service-url.nacos-user-service}")//读取我们配置文件中写的service
private String serverURL;
@GetMapping("/consumer/payment/nacos/{id}")
public String getPaymentInfo(@PathVariable("id") Integer id){
return restTemplate.getForObject(serverURL+"/payment/nacos/"+id,String.class);
}
}
(7)测试
启动nacos,启动9001,9002,与83
浏览器输入网址:http://localhost:83/consumer/payment/nacos/13
我们发现是轮询访问9001和9002
在之前的学习中,我们是使用config和bus,来作为服务的配置中心,现在我们也可以使用nacos来代替他。
(1)新建cloudalibaba-config-nacos-client3377
(2)改pom
主要的依赖:
spring-cloud-starter-alibaba-nacos-config
spring-cloud-starter-alibaba-nacos-discovery
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
(3)写yml
这里有两个配置文件:application.yml和bootstrap.yml
Nacos同springcloud-config一样,在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置之后,才能保证项目的正常启动,bootstrap优先级高于application
bootstrap
# 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 #在8848中可以读取yaml格式的文件
# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
application,这两个配置加起来,就是我们的配置中心3377,要去8848上拉什么环境的配置文件~
spring:
profiles:
active: dev # 表示开发环境
(4)主启动
注意加上注解:@EnableDiscoveryClient
(5)业务类:可以读取配置文件中的info内容
官网上告诉了我们,使用springcloud原生注解@RefreshScope实现配置自动更新。
@RestController
@RefreshScope //在控制器类加入@RefreshScope注解使当前类下的配置支持Nacos的动态刷新功能。
public class ConfigClientController
{
@Value("${config.info}")
private String configInfo;
@GetMapping("/config/info")
public String getConfigInfo() {
return configInfo;
}
}
(6)Nacos中的匹配规则
我们先看看官网的介绍
最后我们得到公式:
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
按照这个公式,我们刚刚的yml中,可以得出我们配置文件的名称
(7)新增配置
点击配置列表中的+号
点击提交,配置成功~
注意你的文件后缀,一定要和file-extension这个属性一样,如果你的属性是yaml,就不能写yml!
(8)测试
启动3377,输入网址:http://localhost:3377/config/info
成功~
自带动态刷新:修改下Nacos中的yaml配置文件,再次调用查看配置的接口,就会发现配置已经刷新
在完成了基础配置后,现在还存在这一些多环境多项目管理的问题:
问题1: 实际开发中,通常一个系统会准备多个环境,如何保证指定环境启动时服务能正确读取到Nacos上相应环境的配置文件呢?
问题2: 一个大型分布式微服务系统会有很多微服务子项目,每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境…那怎么对这些微服务配置进行管理呢?
b)Namespace+Group+Data ID三者关系
其实就类似于java中的package名和类名
最外层的namespace是可以用于区分部署环境的,Group和DataID逻辑上区分两个目标对象。
Nacos默认的命名空间是public,Namespace主要用来实现隔离。
比方说我们现在有三个环境:开发、测试、生产环境,我们就可以创建三个Namespace,不同的Namespace之间是隔离的。
Group默认是DEFAULT_GROUP,Group可以把不同的微服务划分到同一个分组里面去
Service就是微服务;一个Service可以包含多个Cluster(集群),Nacos默认Cluster是DEFAULT,Cluster是对指定微服务的一个虚拟划分。
c)DataID配置
首先现在我们的nacos中准备两份配置文件,由于我们并没有创建命名空间,所以这两个配置文件都在我们默认的public中
通过spring.profile.active属性就能进行多环境下配置文件的读取~
再来回忆一下公式:${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
d)Group分组方案
我们继续在nacos中新建一个配置文件,这次指定他的组别
同样的方式我们再新建一个test环境的
如何用程序获取?
在config下增加一条group的配置即可。可配置为DEV_GROUP或TEST_GROUP
e) Namespace空间方案
namespace是三者中最大的!默认是public,是不可以删除的!
接下来我们新建两个命名空间
接下来回到服务列表,就可以在三个命名空间中切换了
怎么在程序中去读取这个命名空间的配置文件呢?
复制这个自动生成的id
在你的config标签下加一个namespace即可
其余的DATAID与GROUP与我们之前的完全一致,至此,我们的nacos的配置中心结束~
官网:https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html
官网上的架构图实在是太难懂了,下面用一张图来概括
下图是重点说明的内容:
a)Nacos默认自带的是嵌入式数据库derby
在官网的pom.xml可以找到
b)derby到mysql切换配置步骤
1、nacos-server-1.1.4\nacos\conf目录下找到sql脚本,把这个脚本复制黏贴,到你的mysql中执行即可。
执行完后见下图,不要做任何修改,直接复制粘贴!
2、nacos-server-1.1.4\nacos\conf目录下找到application.properties
加上下面的内容,这些也都是在官网上找到的,mysql8.0的话需要加上时区
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/你的数据库名字?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=
db.password=
3、重启nacos,测试一下
可以看到是个全新的空记录界面,以前是记录进derby,现在是记录进mysql了~,可以自己试一下
a)首先去官网下载好linux版本的nacos,下载解压,找到下面的文件,之后就可以在里面启动nacos了
b)sql脚本在哪?
也是一样的,在linux中执行SQL脚本,出现下图内容即可
c)application.properties 配置
位置:
注意改之前先做好备份,修改配置文件,增加内容和windows上的一致!
d)Linux服务器上nacos的集群配置cluster.conf
位置也是conf,还是一样的改之前先备份:cp cluster.conf.example cluster.conf
在这个文件中,配置三个
你的ip : 端口号
这个IP不能写127.0.0.1,必须是Linux命令hostname -i能够识别的IP
e)编辑Nacos的启动脚本startup.sh,使它能够接受不同的启动端口
/mynacos/nacos/bin 目录下有startup.sh
修改前先备份,下面是我们的修改内容
1.4版本版本注意:由于p已经存在,所以我们需要自拟一个
并且更高的版本可能需要复制三份nacos文件夹分别启动三个端口,参考下面的文章:
https://blog.csdn.net/weixin_41929941/article/details/121276265?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166243140416782417024570%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=166243140416782417024570&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-9-121276265-null-null.142v46new_blog_pos_by_title&utm_term=nacos1.4.2%E9%9B%86%E7%BE%A4%E9%85%8D%E7%BD%AE&spm=1018.2226.3001.4187
f)Nginx的配置,由它作为负载均衡器
g)依次启动三台nacos和nginx
启动完后输入这个命令应该有三台
然后用你刚刚的修改的配置文件启动nginx,最终的架构图
并且我们的本地微服务也可以通过1111注册进集群中~
官网:https://github.com/alibaba/Sentinel
中文:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
a)Hystrix用sentinel对比
sentinel一句话:类似于之前我们学习过的Hystrix
b)去哪下?
https://github.com/alibaba/Sentinel/releases
本次学习的版本:1.7.0
c)能干嘛?
d)怎么玩?
https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_sentinel
e)运行及访问
注:java环境ok,且8080端口不能被占用
在jar包的文件夹下,使用cmd运行:java -jar sentinel-dashboard-1.7.0.jar,无需解压
浏览器访问localhost:8080,安装成功~
默认的用户名密码都是sentinel
注,如果启动报错,检查一下自己的java是不是java8,如果不是,去官网下载,然后加上set Path
a)启动Nacos8848
b)新建cloudalibaba-sentinel-service8401被监控
c)写pom
主要是spring-cloud-starter-alibaba-sentinel
还有一个后续持久化要用到的:sentinel-datasource-nacos
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>4.6.3version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
project>
d)写yml
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
#Nacos服务注册中心地址
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard地址
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
management:
endpoints:
web:
exposure:
include: '*'
e)主启动
加上注解@EnableDiscoveryClient
f)controller
@RestController
public class FlowLimitController
{
@GetMapping("/testA")
public String testA()
{
return "------testA";
}
@GetMapping("/testB")
public String testB()
{
return "------testB";
}
}
g)启动测试。
注意先启动nacos和sentinel,启动8401
注意,sentinel使用的是懒加载,我们现在去sentinel的web界面中,是空空如也的,我们需要先执行一次访问
8401入驻8080成功~
随便发几个请求来看看界面
那么接下来我们就依次来看一看左边菜单栏的几个选项分别有什么用
a)基本介绍
菜单栏中的第三个
b)直接模式(默认)
QPS直接->快速失败
首先先新增一个流控规则
然后我们试试狂点请求,超过一秒一次的阈值后,就被限流了,
我们发现,直接调用默认报错信息,技术上是可以的,但是后续处理应该由我们自己处理,这里先留个伏笔~
c)关联模式
当关联的资源达到阈值时,就限流自己
举个例子:当与A关联的资源B达到阀值后,就限流A自己,B惹事,A挂了,比如说,支付功能坏了,就限流订单功能
接下来我们来进行一个设置
当关联资源/testB的qps阀值超过1时,就限流/testA的Rest访问地址,当关联资源到阈值后限制配置好的资源名
接下来我们用postman来模拟并发密集访问testB,结果发现testA挂了,等B结束后,A才能正常访问
d)链路模式
链路模式:只针对从指定链路访问到本资源的请求做统计,判断是否超过阈值。
参考了这位大佬的文章:https://blog.csdn.net/kuyongganggang/article/details/123653538?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166247055116782248558009%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=166247055116782248558009&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-1-123653538-null-null.142v47body_digest,201v2control&utm_term=sentinel%E7%9A%84%E9%93%BE%E8%B7%AF%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4187
e)流控效果-warm up
前面我们已经知道了系统默认的快速失败,接下来我们来了解一下预热模式
默认coldFactor(冷加载因子)为3,即请求 QPS 从 阈值/ 3 开始,经预热时长逐渐升至设定的 QPS 阈值。
应用:如秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。
f)流控效果-排队等待
匀速排队,让请求以均匀的速度通过,阀值类型必须设成QPS,否则无效。
应用:对于时延不是很敏感的业务可以使用
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误,在一定的请求窗口之后,会尝试恢复服务。
a)简介
b)RT
我们首先设置一个testD每次都会等待一秒钟
@GetMapping("/testD")
public String testD()
{
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
log.info("testD 测试RT");
return "------testD";
}
这个配置的意思:如果一秒钟内进来的线程大于5个,且超过200毫秒还没处理完,那么触发熔断,即微服务不可用了。
c)异常比例(秒级)
接下来我们加上一段模拟异常的代码
@GetMapping("/testD")
public String testD()
{
log.info("testD 测试异常比例");
int age = 10/0;
return "------testD";
}
配置sentinel
这个意思是,如果每秒的线程数大于5,且异常比例大于20%,触发熔断
d)异常数(分钟级)
测试代码
@GetMapping("/testE")
public String testE()
{
log.info("testE 测试异常数");
int age = 10/0;
return "------testE 测试异常数";
}
配置sentinel
配置意思:一分钟内异常数超过5次,触发熔断
热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
我们来回顾一下我们曾经学的Hystrix,有系统默认的和客户自定义的两种兜底方法,那么我们可不可以类似Hystrix,自定义兜底方法呢?
结论: 从HystrixCommand 到@SentinelResource,那么接下来,就动手实现一下
a)代码
在8401中新写一个方法
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
@RequestParam(value = "p2",required = false) String p2){
return "----testHotKey";
}
//这个是兜底方法
public String deal_testHotKey(String p1, String p2, BlockException e){
// sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
return "deal_testHotKey,失败";
}
}
在sentinel中配置
这个意思是:方法testHotKey里面第一个参数(在我们的方法中就是p1参数,参数名为p2的不会被限流)只要QPS超过每秒1次,马上降级处理,并且我们通过blockHandler 自定义了降级处理的方法,就不是系统默认的降级方法了
b)参数例外项
上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流,但是,如果我们希望假如当p1的值等于5时,它的阈值可以达到200,那么该怎么设置呢?
注意不要忘记点添加!
这样以后,我们就完成了我们想要的效果,这个就类似于VIP用户特殊通道~
我们之前的限流,都是针对于某个具体的请求的,而系统规则,是针对于所有请求的,也就是整个系统。
a)按资源名称限流+后续处理
这个在前面的热点规则中其实已经涉及到了,就小小的回顾一下
首先在8401中新写一个controller
@RestController
public class RateLimitController
{
@GetMapping("/byResource")
@SentinelResource(value = "byResource",blockHandler = "handleException")
public CommonResult byResource()
{
return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001"));
}
public CommonResult handleException(BlockException exception)
{
return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
}
}
接下来在sentinel中进行配置,资源名写注解的value值即可,表示每秒一次的阈值,如果qps超过1,那么兜底方法就是我们blockHandler 指定的方法
注:如果没有指定兜底方法,会直接返回500错误界面
注:sentinel只管配置时的出错,处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;
但int age = 10/0,这个是java运行时报出的运行时异常RunTimeException,@SentinelResource不管,会直接报出500的错误页面
b)按照Url地址限流+后续处理
通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息
首先我们在刚刚的controller中新增一个方法
@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl",blockHandler = "handleException")
public CommonResult byUrl()
{
return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002"));
}
新增配置
经过测试:用url来实现限流,如果异常,会出现系统默认的兜底方法,@SentinelResource中的blockHandler失效
上述两个兜底方案的问题:
1 系统默认的,没有体现我们自己的业务要求。
2 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
3 每个业务方法都添加一个兜底的,那代码膨胀加剧。
4 全局统一的处理方法没有体现。
c)客户自定义限流处理逻辑
1、创建CustomerBlockHandler类用于自定义限流处理逻辑
public class CustomerBlockHandler {
//一定要是static,CommonResult是我们在第一部分笔记中就已经写过的返回给前端的
public static CommonResult handlerException(BlockException exception){
return new CommonResult(4444,"按客户自定义,global exception",new Payment(2020L,"serial---1"));
}
public static CommonResult handlerException2(BlockException exception){
return new CommonResult(4444,"按客户自定义,global exception",new Payment(2020L,"serial---2"));
}
}
2、在我们的controller中,可以自由的选择这两个兜底方法
3、进行配置,注意不能使用url限流!
经过测试,当qps大于1时,会走我们在CustomerBlockHandler 定义的handlerException2方法
d)更多注解属性说明
sentinel也可以用代码定义,但不推荐,详情可以见官网。
浅浅的回忆一下降级和熔断的关系:调用失败会触发降级,而降级会调用fallback方法,但无论如何降级的流程一定会调用正常方法再调用fallbcak方法,假如单位时间内调用失败次数过多,也就是降级次数过多,则触发熔断,熔断以后就会跳过正常的方法直接执行fallback方法
我们要实现的大体架构
a)新建服务提供者9003和9004
还是我们那老五步,引入nacos的依赖即可,注意区分两个module配置文件中的端口号,并注册进nacos中即可
b)提供者中模拟数据库的业务类
@RestController
public class PaymentController
{
@Value("${server.port}")
private String serverPort;
public static HashMap<Long, Payment> hashMap = new HashMap<>();
static
{
hashMap.put(1L,new Payment(1L,"28a8c1e3bc2742d8848569891fb42181"));
hashMap.put(2L,new Payment(2L,"bba8c1e3bc2742d8848569891ac32182"));
hashMap.put(3L,new Payment(3L,"6ua8c1e3bc2742d8848569891xt92183"));
}
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id)
{
Payment payment = hashMap.get(id);
CommonResult<Payment> result = new CommonResult(200,"from mysql,serverPort: "+serverPort,payment);
return result;
}
}
c)新建消费者84
老五步,引入nacos和sentinel的依赖,让sentinel去监控我们的84消费者
修改配置文件
server:
port: 84
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard地址
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
nacos-user-service: http://nacos-payment-provider
注意既然是ribbon,那么就需要配置类
@Configuration
public class ApplicationContextConfig
{
@Bean
@LoadBalanced
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
}
业务类的基础代码,经过测试,轮询功能正常
@RestController
@Slf4j
public class CircleBreakerController {
public static final String SERVICE_URL = "http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@GetMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback")//这里还没有配置
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL+"/paymentSQL/"+id, CommonResult.class,id);
if (id == 4){
throw new IllegalArgumentException("非法参数异常!");
}else if (result.getData()==null){
throw new NullPointerException("没有对应记录,空指针异常!");
}
return result;
}
}
当然啦,我们现在没有做任何配置,如果java业务代码出现异常,就会导致直接给前端返回一个不友好的500error页面
d)只配置fallback
这样以后,84的业务代码出现异常,就会返回给我们前端自己定义的兜底方法啦~
e)只配置blockHandler
那么我就只会处理sentinel配置规则的违规啦,java业务类的异常就无人处理了。
f)fallback和blockHandler都配置
那么我们业务代码异常和违反sentinel配置的请求都可以处理,但是注意,后者的优先级高于前者。
1、首先修改84的pom,引入feign,注feign一般用在消费侧
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
2、然后修改yml,激活sentinel对feign的支持,之前我们使用feign和Hystrix的时候也做过类似的配置
# 激活Sentinel对Feign的支持
feign:
sentinel:
enabled: true
3、如果需要用feign,主启动类不要忘记记上注解:@EnableFeignClients
4、业务类,带@FeignClient注解的业务接口
@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService {
@GetMapping("/paymentSQL/{id}")//转发到消费者9003中对应这个地址的方法
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
5、业务类的降级服务实现类
@Component
public class PaymentFallbackService implements PaymentService{
@Override
public CommonResult<Payment> paymentSQL(Long id) {
return new CommonResult<>(444,"服务降级返回,PaymentFallbackService",new Payment(id,"error"));
}
}
6、在消费者84的controller中添加方法
@Resource
private PaymentService paymentService;
@GetMapping("/consumer/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){
return paymentService.paymentSQL(id);
}
测试84调用9003,此时故意关闭9003微服务提供者,发现84消费侧调用了降级方法,不会被耗死,sentinel配置openfeign成功~
我们现在遇到的问题:一旦我们重启应用,sentinel规则将消失,生产环境需要将配置规则进行持久化
1、怎么持久化?
将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效
2、配置步骤
a)修改8401的pom,添加下面的依赖,这个依赖是我们实现持久化的关键
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
dependency>
b)修改8401的yml
添加nacos数据源的配置
spring:
cloud:
sentinel:
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
c)添加nacos业务规则配置
resource:资源名称;
limitApp:来源应用;
grade:阈值类型,0表示线程数,1表示QPS;
count:单机阈值;
strategy:流控模式,0表示直接,1表示关联,2表示链路;
controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
clusterMode:是否集群。
d)启动8401后刷新sentinel发现业务规则有了
其实就是借用nacos的持久化,来储存sentinel的配置,停机后,发现流控规则没有了,但是重启后,流控规则就又出现了~
官网:http://seata.io/zh-cn/
首先先了解一下分布式事务问题的由来:一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,此时,如果每个服务内部的数据一致性由本地事务来保证,就会产生分布式事务问题
a)是什么?
Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
b)一ID+三组件模型
Transaction ID XID:全局唯一的事务ID
TC:事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;
TM:控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
RM:控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚
c)去哪下?
下载网址:https://seata.io/zh-cn/blog/download.html
本笔记学习版本:0.9.0
e)下载后进入conf文件夹,修改file.conf配置,注意改前先备份
1、指定事务组名称
2、在db模块中配置自己的数据库连接信息,并且在数据库中新建一个seata库
f)、在数据库中建表
建表的sql语句,conf中会有一个db_store.sql文件,高版本点击readme.md,找到里面蓝色的对应db模式的地址,粘贴到数据库中运行即可
-- the table to store GlobalSession data
drop table if exists `global_table`;
create table `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_gmt_modified_status` (`gmt_modified`, `status`),
key `idx_transaction_id` (`transaction_id`)
);
-- the table to store BranchSession data
drop table if exists `branch_table`;
create table `branch_table` (
`branch_id` bigint not null,
`xid` varchar(128) not null,
`transaction_id` bigint ,
`resource_group_id` varchar(32),
`resource_id` varchar(256) ,
`lock_key` varchar(128) ,
`branch_type` varchar(8) ,
`status` tinyint,
`client_id` varchar(64),
`application_data` varchar(2000),
`gmt_create` datetime,
`gmt_modified` datetime,
primary key (`branch_id`),
key `idx_xid` (`xid`)
);
-- the table to store lock data
drop table if exists `lock_table`;
create table `lock_table` (
`row_key` varchar(128) not null,
`xid` varchar(96),
`transaction_id` long ,
`branch_id` long,
`resource_id` varchar(256) ,
`table_name` varchar(32) ,
`pk` varchar(36) ,
`gmt_create` datetime ,
`gmt_modified` datetime,
primary key(`row_key`)
);
h)启动seata
运行bin目录下的seata-server.bat。
出现下面字段表示seata启动成功。seata启动日志在C:\Users\admin\logs\seata文件夹下。
注:mysql8版本的,需要将nacoslib中的5版本jar包删除,然后自己放一个自己版本的mysql进去
a)业务说明
这里我们会创建三个服务,一个订单服务,一个库存服务,一个账户服务。
当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,
再通过远程调用账户服务来扣减用户账户里面的余额,
最后在订单服务中修改订单状态为已完成。
该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。
b)创建业务数据库
c)seata_order库下建t_order表
CREATE TABLE t_order (
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`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 '金额',
`status` INT(1) DEFAULT NULL COMMENT '订单状态:0:创建中;1:已完结'
) ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
SELECT * FROM t_order;
d)seata_storage库下建t_storage
CREATE TABLE t_storage (
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`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 '剩余库存'
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO seata_storage.t_storage(`id`, `product_id`, `total`, `used`, `residue`)
VALUES ('1', '1', '100', '0', '100');
SELECT * FROM t_storage;
e)seata_account库下建t_account
CREATE TABLE t_account (
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id',
`user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
`total` DECIMAL(10,0) DEFAULT NULL COMMENT '总额度',
`used` DECIMAL(10,0) DEFAULT NULL COMMENT '已用余额',
`residue` DECIMAL(10,0) DEFAULT '0' COMMENT '剩余可用额度'
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO seata_account.t_account(`id`, `user_id`, `total`, `used`, `residue`) VALUES ('1', '1', '1000', '0', '1000');
SELECT * FROM t_account;
f)按照上述3库分别建对应的回滚日志表
我的版本在conf文件夹中会有一个db_undo_log.sql,如果是高版本,在readme.md中client的网址里,也是一样的,粘贴sql然后运行即可
-- the table to store seata xid data
-- 0.7.0+ add context
-- you must to init this sql for you business databese. the seata server not need it.
-- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
drop table `undo_log`;
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
a)新建订单Order-Module
b)改pom
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-seataartifactId>
<exclusions>
<exclusion>
<artifactId>seata-allartifactId>
<groupId>io.seatagroupId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>io.seatagroupId>
<artifactId>seata-allartifactId>
<version>0.9.0version>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.21version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.10version>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.0.0version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
dependencies>
c)写yml
server:
port: 2001
spring:
application:
name: seata-order-service
cloud:
alibaba:
seata:
#自定义事务组名称需要与file.cinf中的seata-server中的对应
tx-service-group: fsp_tx_group
nacos:
discovery:
server-addr: localhost:8848
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=utf-8&useSSL=false&nullCatalogMeansCurrent=true&serverTimezone=UTC
username: root
password:
feign:
hystrix:
enabled: false
logging:
level:
io:
seata: info
mybatis:
mapperLocations: classpath:mapper/*.xml
d)把file.conf和registry.conf粘贴到resources中
d)实体类包domain
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order
{
private Long id;
private Long userId;
private Long productId;
private Integer count;
private BigDecimal money;
/**
* 订单状态:0:创建中;1:已完结
*/
private Integer status;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T>
{
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message)
{
this(code,message,null);
}
}
e)dao接口及实现
@Mapper
public interface OrderDao {
//1、新建订单
void create(Order order);
//2、修改订单状态,从0改为1
void update(@Param("userId") Long userId,@Param("statud") Integer status);
}
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.javalearn.springcloud.dao.OrderDao">
<resultMap id="BaseResultMap" type="com.javalearn.springcloud.domain.Order">
<id property="id" column="id" jdbcType="BIGINT">id>
<result property="userId" column="user_id" jdbcType="BIGINT">result>
<result property="productId" column="product_id" jdbcType="BIGINT">result>
<result property="count" column="count" jdbcType="INTEGER">result>
<result property="money" column="money" jdbcType="DECIMAL">result>
<result property="status" column="status" jdbcType="INTEGER">result>
resultMap>
<insert id="create">
insert into t_order (id,user_id,product_id,count,money,status)
values (null,#{userId},#{productId},#{count},#{money},0);
insert>
<update id="update">
update t_order set status = 1
where user_id = #{userId} and statud = #{status};
update>
mapper>
f)service
其中,两个service是通过feign去调用另外两个微服务的
public interface OrderService {
void create(Order order);
}
//订单微服务去调用库存微服务
@FeignClient(value = "seata-storage-service")
public interface StorageService {
@PostMapping("/storage/decrease")
CommonResult decrease(@RequestParam("produceId") Long produceId,@RequestParam("count") Integer count);
}
//订单微服务去调用账户微服务
@FeignClient(value = "seata-account-service")
public interface AccountService {
@PostMapping("/account/decrease")
CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}
订单微服务的实现类
/**
* 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
* 简单说:
* 下订单->减库存->减余额->改状态
*/
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Resource
private OrderDao orderDao;
@Resource
private AccountService accountService;
@Resource
private StorageService storageService;
@Override
public void create(Order order) {
log.info("----->开始新建订单");
orderDao.create(order);
log.info("----->订单微服务开始调用库存,做扣减");
storageService.decrease(order.getProductId(),order.getCount());
log.info("----->订单微服务开始调用库存,做扣减end");
log.info("----->订单微服务开始调用账户,扣减money");
accountService.decrease(order.getUserId(),order.getMoney());
log.info("----->订单微服务开始调用账户,扣减end");
//修改订单状态,从0到1,1代表已完成
log.info("----->修改订单状态开始");
orderDao.update(order.getUserId(),0);
log.info("----->修改订单状态结束");
log.info("下订单结束了~");
}
}
g)controller
@RestController
public class OrderController {
@Resource
private OrderService orderService;
@GetMapping("/order/create")
public CommonResult create(Order order){
orderService.create(order);
return new CommonResult(200,"订单创建成功");
}
}
h)config配置类
指定mybatis的mapper扫描包
@Configuration
@MapperScan({"com.javalearn.springcloud.dao"})
public class MyBatisConfig {
}
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
/**
* @auther zzyy
* @create 2019-12-11 16:58
* 使用Seata对数据源进行代理
*/
@Configuration
public class DataSourceProxyConfig {
@Value("${mybatis.mapperLocations}")
private String mapperLocations;
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource(){
return new DruidDataSource();
}
@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return sqlSessionFactoryBean.getObject();
}
}
i)主启动
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源的自动创建
public class SeataOrderMainApp2001
{
public static void main(String[] args)
{
SpringApplication.run(SeataOrderMainApp2001.class, args);
}
}
a)改pom
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-seataartifactId>
<exclusions>
<exclusion>
<artifactId>seata-allartifactId>
<groupId>io.seatagroupId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>io.seatagroupId>
<artifactId>seata-allartifactId>
<version>0.9.0version>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.0.0version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.21version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.10version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
dependencies>
b)写yml
server:
port: 2002
spring:
application:
name: seata-storage-service
cloud:
alibaba:
seata:
tx-service-group: fsp_tx_group
nacos:
discovery:
server-addr: localhost:8848
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_storage?useUnicode=true&characterEncoding=utf-8&useSSL=false&nullCatalogMeansCurrent=true&serverTimezone=UTC
username: root
password:
logging:
level:
io:
seata: info
mybatis:
mapperLocations: classpath:mapper/*.xml
c)粘贴file和registry,和订单一样
d)domain实体类
import lombok.Data;
@Data
public class Storage {
private Long id;
/**
* 产品id
*/
private Long productId;
/**
* 总库存
*/
private Integer total;
/**
* 已用库存
*/
private Integer used;
/**
* 剩余库存
*/
private Integer residue;
}
e)dao
@Mapper
public interface StorageDao {
/**
* 扣减库存
*/
void decrease(@Param("productId") Long productId, @Param("count") Integer count);
}
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.javalearn.springcloud.dao.StorageDao">
<resultMap id="BaseResultMap" type="com.javalearn.springcloud.domian.Storage">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="product_id" property="productId" jdbcType="BIGINT"/>
<result column="total" property="total" jdbcType="INTEGER"/>
<result column="used" property="used" jdbcType="INTEGER"/>
<result column="residue" property="residue" jdbcType="INTEGER"/>
resultMap>
<update id="decrease">
UPDATE t_storage
SET used = used + #{count},
residue = residue - #{count}
WHERE product_id = #{productId}
update>
mapper>
f)service
public interface StorageService {
/**
* 扣减库存
*/
void decrease(Long productId, Integer count);
}
import com.javalearn.springcloud.dao.StorageDao;
import com.javalearn.springcloud.service.StorageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Resource;
public class StorageServiceImpl implements StorageService {
private static final Logger LOGGER = LoggerFactory.getLogger(StorageServiceImpl.class);
@Resource
private StorageDao storageDao;
/**
* 扣减库存
*/
@Override
public void decrease(Long productId, Integer count) {
LOGGER.info("------->storage-service中扣减库存开始");
storageDao.decrease(productId,count);
LOGGER.info("------->storage-service中扣减库存结束");
}
}
g)controller
@RestController
public class StorageController {
@Autowired
private StorageService storageService;
/**
* 扣减库存
*/
@RequestMapping("/storage/decrease")
public CommonResult decrease(Long productId, Integer count) {
storageService.decrease(productId, count);
return new CommonResult(200,"扣减库存成功!");
}
}
剩下的主启动,config与之前完全一致
和之前两个完全一样,实现的业务是根据用户的id,去扣除账户的money
a)超时异常,没加@GlobalTransactional
b)超时异常,添加@GlobalTransactional
还记得吗,我们是通过订单服务中的create方法,来开始调用其他两个微服务的,那么我们只需要在这个方法上,加上@GlobalTransactional注解~
经过测试,只要三个微服务中,有一个出现异常,就会实现回滚,事务功能实现成功~
博主在学习到时候,本来是使用的较新的1.4.0版本,但无奈与老师教程的0.9.0相差过大,自己尝试着配了半天没成功,所以以后回来填这个坑