SpringCloud学习第一季-4

目录

16.SpringCloud Alibaba Nacos服务注册和配置中心

SpringCloud Alibaba简介

1. 为什么出现 SpringCloud Alibaba

2. SpringCloud Alibaba带来了什么

2.1 能干什么

2.2 去哪里下载

2.3 怎么玩

3. 学习资料的获取

17.SpringCloud Alibaba Nacos服务注册和配置中心

一、 Nacos简介

1 是什么

2 能干什么

3 下载地址

4 各注册中心比较

二、 安装并运行Nacos

三、 Nacos作为服务注册中心演示

1 基于Nacos的服务提供者

1.1 建moudle

1.2 pom

1.3 yml

1.4 主启动类

1.5 业务类

1.6 测试

1.7 相同配置再新建一个module 9002

2 基于Nacos的服务消费者

2.1 新建module cloudalibaba-consumer-nacos-order83

2.2 pom

2.3 yml

2.4 主启动类

2.5 业务类

2.6 测试

3. 服务注册中心对比

3.1 Nacos与CAP

3.2 CP与AP的切换

四、 Nacos作为服务配置中心演示

1. Nacos作为配置中心——基础配置

1.1 建module cloudalibaba-config-nacos-client3377

1.2 pom

1.3 yml

1.4 主启动类

1.5 业务类

1.6 在Nacos中添加配置信息

1.7 测试

2. Nacos作为配置中心——分类配置

2.1 分布式开发中的多环境多项目管理问题

2.2 Nacos的图形化管理界面

2.3 Namespace+Group+Data ID三者关系?为什么这么设计?

3. 三种方案加载配置

3.1 DataID方案

3.2 Group方案

1新建一个配置文件,添加到RPOD_GROUP分组

2新建一个配置文件,添加到TEST_GROUP分组

3在config下增加一条group的配置即可。可配置为PROD_GROUP或TEST_GROUP

4测试:

3.3 namesapce方案

1.新建Namesapce

2.回到服务管理-服务列表查看

3.在这两个新建的namespace中分别新建配置文件

4.修改3377的yml文件

5.测试

3.4 总结

4. Nacos集群和持久化配置(重点)

4.1 官网说明

4.2 Nacos嵌入式数据库derby切换到mysql-windows单机版derby到mysql切换步骤

1.首先在nacos安装目录的conf目录下找到一个名为nacos-mysql.sql的sql脚本

2.在conf目录下找到application.properties

3.重新启动nacos,可以看到是个全新的空记录界面,以前是记录进derby

4.在naocs新建配置时,会自动保存到mysql数据库中

(踩坑)1.1.4版本nacos使用外部数据库Mysql8 (这种方式我也亲自试了OK)

5.修改nacos源码(可行)

4.3 Linux版Nacos+MySQL生产环境配置

4.3.1 Nacos 下载Linux版

4.3.2 集群配置步骤(重点)

4.4 微服务cloudalibaba-provider-payment9002启动注册进nacos集群

18.SpringCloud Alibaba Sentinel实现熔断与限流


16.SpringCloud Alibaba Nacos服务注册和配置中心

SpringCloud Alibaba简介

1. 为什么出现 SpringCloud Alibaba

Spring Cloud Netflix项目进入维护模式
Spring Cloud Greenwich.RC1 available now
进入维护模式意味着Spring Cloud Netflix 将不再开发新的组件
我们都知道Spring Cloud 版本迭代算是比较快的,因而出现了很多重大ISSUE都还来不及Fix就又推另一个Release了。进入维护模式意思就是目前一直以后一段时间Spring Cloud Netflix提供的服务和功能就这么多了,不在开发新的组件和功能了。以后将以维护和Merge分支Full Request为主
新组件功能将以其他替代平代替的方式实现
 

2. SpringCloud Alibaba带来了什么

官网

2.1 能干什么

服务限流降级:默认支持 Servlet、Feign、RestTemplate、Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。

服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。

分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。

消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。

阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。

分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。

2.2 去哪里下载

https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md
之前在父工程的pom文件中已经引入了alibaba的依赖

2.3 怎么玩

3. 学习资料的获取

官网:Spring Cloud Alibaba
英文:
https://github.com/alibaba/spring-cloud-alibaba
Spring Cloud Alibaba Reference Documentation
中文:https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md

17.SpringCloud Alibaba Nacos服务注册和配置中心

对应着我们前面学的:Eureka/Consul/Zookeeper(服务注册) Config+Bus(配置中心)

一、 Nacos简介

Nacos——Naming Configuration Service

1 是什么

●一个更易于构建云原生应用的动态服务发现、配置管理和服务的管理平台。
●Nacos:Dynamic Naming and Configuration Service
●Nacos就是注册中心 + 配置中心的组合, Nacos = Eureka + Config + Bus

2 能干什么

替代Eureka做服务注册中心;替代Config做服务配置中心

3 下载地址

https://github.com/alibaba/Nacos
官网文档:Redirecting to: https://nacos.io/
Spring Cloud Alibaba Reference Documentation

4 各注册中心比较

二、 安装并运行Nacos

本地需要准备java8+maven环境。
官网下载:https://github.com/alibaba/nacos/releases
老师安装的1.1.4,我这里使用的最新版2.0.3
解压安装包,运行bin目录下的startup.cmd
默认的是集群模式,我们需要单机启动cmd中输入:
startup.cmd -m standalone(nacos单机模式启动命令) 即可通过单机模式启动

命令运行成功后直接访问http://localhost:8848/nacos 默认账号密码都是nacos

启动成功。

三、 Nacos作为服务注册中心演示

官网手册

1 基于Nacos的服务提供者

1.1 建moudle

cloudalibaba-provider-payment9001

1.2 pom

父工程中需要引入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>

本模块pom

<dependencies><!--SpringCloud ailibaba nacos --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!-- SpringBoot整合Web组件 --><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><!--日常通用jar包配置--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></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>
1.3 yml

按照官网上的说明配置。

server:  port: 9001  spring:  application:  name: nacos-payment-provider  cloud:  nacos:  discovery:  server-addr: localhost:8848 #配置Nacos地址  management:  endpoints:  web:  exposure:  include: '*'
1.4 主启动类
package com.springcloud.alibaba;  import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;  @SpringBootApplication  
@EnableDiscoveryClient  
public class PaymentMain9001 {  public static void main(String[] args) {  SpringApplication.run(PaymentMain9001.class,args);  }  }
1.5 业务类
package com.springcloud.alibaba.controller;  import org.springframework.beans.factory.annotation.Value;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.PathVariable;  
import org.springframework.web.bind.annotation.RestController;  @RestController  
public class PaymentController {  @Value("${server.port}")  private String serverPort;  @GetMapping(value = "/payment/nacos/{id}")  public String getPayment(@PathVariable("id") Integer id)  {  return "nacos registry, serverPort: "+ serverPort+"\t id"+id;  }  
}
1.6 测试

这里不像eureka还要写注册中心微服务,直接安装打开nacos即可。
启动9001,查看nacos控制台,注册成功

访问http://localhost:9001/payment/nacos/1
 


nacos服务注册中心+服务提供者9001都OK了

1.7 相同配置再新建一个module 9002

步骤与9001一致,就是端口号改成9002
这里有一个简单的方法,虚拟映射一个和9001配置相同的9011微服务模块,端口号是9011


控制台服务名称

现在有两个支付模块,9001和9002。

2 基于Nacos的服务消费者

Nacos自带负载均衡

2.1 新建module cloudalibaba-consumer-nacos-order83
2.2 pom
<dependencies>  <!--SpringCloud ailibaba nacos -->  <dependency>  <groupId>com.alibaba.cloud</groupId>  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>  </dependency>    <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->  <dependency>  <groupId>org.xu.springcloud</groupId>  <artifactId>cloud-api-commons</artifactId>  <version>1.0.0</version>  </dependency>    <!-- SpringBoot整合Web组件 -->  <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>    <!--日常通用jar包配置-->  <dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-devtools</artifactId>  <scope>runtime</scope>  <optional>true</optional>  </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>

nacos自带负载均衡


Ribbon:支持负载均衡,自带RestTemplate

2.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
2.4 主启动类
package com.springcloud.alibaba;  import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;  @SpringBootApplication  
@EnableDiscoveryClient  
public class OrderNacosMain83 {  public static void main(String[] args)  {  SpringApplication.run(OrderNacosMain83.class,args);  }  }
2.5 业务类
package com.springcloud.alibaba.controller;  import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.PathVariable;  
import org.springframework.web.bind.annotation.RestController;  
import org.springframework.web.client.RestTemplate;  import javax.annotation.Resource;  @RestController  
public class OrderNacosController {  @Resource  private RestTemplate restTemplate;  @Value("${service-url.nacos-user-service}")  private String serverURL;  @GetMapping("/consumer/payment/nacos/{id}")  public String paymentInfo(@PathVariable("id") Long id){  return restTemplate.getForObject(serverURL+"/payment/nacos/"+id,String.class);  }  }

配置类

package com.springcloud.alibaba.config;  import org.springframework.cloud.client.loadbalancer.LoadBalanced;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.web.client.RestTemplate;  @Configuration  
public class ApplicationContextBean {  @Bean  @LoadBalanced //使用@LoadBalanced注解赋予RestTemplate负载均衡的能力  RestTemplate getRestTemplate(){  return new RestTemplate();  }  }
2.6 测试

启动9001、9002、83
nacos控制台


测试链接:http://localhost:83/consumer/payment/nacos/1
发现9001与9002交替出现,即轮询负载均衡。

可能出现的问题:
当消费者通过http://nacos-payment-provider去调用微服务时抛出UnknowHostException:未知主机名异常。
这是因为nacos-payment-provider对应两个微服务实例,他不知道用哪个微服务,所以报错。
解决:加上负载均衡注解 @LoadBalanced

3. 服务注册中心对比

3.1 Nacos与CAP

Nacos可以在CP与AP之间切换

3.2 CP与AP的切换

C(consistency):所有节点在同一时间看到的数据是一致的,强一致性;
A(availability):的定义是所有的请求都会收到响应,最起码有一个兜底回复,高可用性。

何时选择使用何种模式?
AP:
一般来说,如果不需要存储服务级别的信息且服务实例是通过nacos-client注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如 Spring cloud 和 Dubbo 服务,都适用于AP模式,AP模式为了服务的可能性而减弱了一致性,因此AP模式下只支持注册临时实例。
CP:
如果需要在服务级别编辑或者存储配置信息,那么 CP 是必须,K8S服务和DNS服务则适用于CP模式。
CP模式下则支持注册持久化实例,此时则是以 Raft 协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误。

怎么切换:
curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'

四、 Nacos作为服务配置中心演示

以前我们将所有的配置信息写到了GitHub上,用Config+Bus来进行自动刷新和动态的更新。
现在我们可以直接把配置文件写进Nacos,然后再用Nacos做类似于config这样的功能,直接从Nacos上抓取我们的配置信息。

1. Nacos作为配置中心——基础配置

主要是添加一个:nacos-config依赖

com.alibaba.cloudspring-cloud-starter-alibaba-nacos-config

1.1 建module cloudalibaba-config-nacos-client3377
1.2 pom
<dependencies><!--nacos-config--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><!--nacos-discovery--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!--web + actuator--><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><!--一般基础配置--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></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>
1.3 yml

这里需要配置两个,一个bootstrap和一个application。
原因:Nacos同springcloud-config一样,在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置之后,才能保证项目的正常启动。
springboot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application。
全局的放在:bootstrap.yml 自己的放在:application.yml
bootstrap:
微服务端口:3377,服务名nacos-config-client,注册进nacos:localhost:8848 作为配置客户端

# 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.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}

application:

spring:  profiles:  active: dev # 表示开发环境

bootstrap + application 就表示我要去配置中心找名为dev.yaml的文件。

1.4 主启动类
package com.springcloud.alibaba;  import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;  @SpringBootApplication  
@EnableDiscoveryClient  
public class NacosConfigClientMain3377 {  public static void main(String[] args) {  SpringApplication.run(NacosConfigClientMain3377.class,args);  }  
}
1.5 业务类

通过Spring Cloud原生注解@RefreshScope 实现配置自动更新

package com.springcloud.alibaba.controller;  import org.springframework.beans.factory.annotation.Value;  
import org.springframework.cloud.context.config.annotation.RefreshScope;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.RestController;  @RestController  
@RefreshScope //在控制器类加入@RefreshScope注解使当前类下的配置支持Nacos的动态刷新功能。  
public class ConfigClientController {  @Value("${config.info}")  private String configInfo;  @GetMapping("/config/info")  public String getConfigInfo(){  return configInfo;  }  }
1.6 在Nacos中添加配置信息

Nacos中的匹配规则
理论:
●Nacos中的dataid的组成格式与SpringBoot配置文件中的匹配规则
官网:Nacos 融合 Spring Cloud,成为注册配置中心 | Nacos 官网

注意nacos只识别yaml,不支持yml。
最终公式:{spring.application.name}-{spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
结果: nacos-config-client-dev.yaml
公式图解说明

实操
配置新增:
 


自己新建一个配置
 

1.7 测试

启动3377
发送请求:http://localhost:3377/config/info

测试成功
自带动态刷新:修改nacos中的yaml配置文件,再次调用查看配置,发现配置刷新了。

2. Nacos作为配置中心——分类配置

2.1 分布式开发中的多环境多项目管理问题

●问题1:
实际开发中,通常一个系统会准备
dev开发环境
test测试环境
prod生产环境。
如何保证指定环境启动时服务能正确读取到Nacos上相应环境的配置文件呢?
●问题2:
一个大型分布式微服务系统会有很多微服务子项目,
每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境......
那怎么对这些微服务配置进行管理呢?

2.2 Nacos的图形化管理界面

配置管理:

命名空间:

2.3 Namespace+Group+Data ID三者关系?为什么这么设计?

类似Java里面的package名和类名,最外层的namespace是可以用于区分部署环境的,Group和DataID逻辑上区分两个目标对象。
三者情况

默认情况:
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,就是微服务的实例。

3. 三种方案加载配置

3.1 DataID方案

指定spring.profile.active和配置文件的DataID来使不同环境下读取不同的配置
默认空间+默认分组+新建dev和test两个DataID
●dev配置DataID,上一讲配置过
●新建test配置DataID

这里命名空间是默认的public,Group也是默认的。

通过spring.profile.active属性就能进行多环境下配置文件的读取

重启3377 测试http://localhost:3377/config/info,成功读取到test配置下的config.info

3.2 Group方案

默认Group是DEFAULT_GROUP,现在通过Group实现环境分区

1新建一个配置文件,添加到RPOD_GROUP分组

2新建一个配置文件,添加到TEST_GROUP分组

界面就可以看到我们配置的文件了

3在config下增加一条group的配置即可。可配置为PROD_GROUP或TEST_GROUP

4测试:

3.3 namesapce方案
1.新建Namesapce

2.回到服务管理-服务列表查看

3.在这两个新建的namespace中分别新建配置文件

4.修改3377的yml文件

bootstrap:

5.测试

3.4 总结

DataID方案是在默认namesapce和默认Group下,创建两个不同的DataID。
Group方案是在默认namespace下,新建两个DataID相同的配置文件,通过指定不同的分组来读取不同的配置。
Namespace方案,是相同的Group,相同的DataID,创建并指定不同的namespace来读取不同配置。

4. Nacos集群和持久化配置(重点)

我们之前在学eureka的时候,配置了两个eureka注册中心微服务。学Nacos时,不用我们单独新建注册中心微服务模块了,直接安装使用即可,很方便。
但是:如果这个注册中心挂了怎么办?我们目前就开了一个Nacos程序,也没有配置集群。显然,实际情况中不可能只有一个Nacos注册中心,因此需要用到nacos集群。
集群官网文档

4.1 官网说明

1.架构图:

这里vip表示virtual ip(虚拟IP)

2.要将配置持久化到数据库中:MySQL 不用nacos内嵌的数据库

3.通俗易懂的Nacos集群架构图

4.重点说明:
默认Nacos使用嵌入式数据库实现数据的存储,我们重启Nacos后,以前的配置文件不会消失。
但是,如果启动多个默认配置下的Nacos节点,数据存储是存在一致性问题的。每个nacos都有自己独立的嵌入式数据库,存放的数据不一致。
为了解决这个问题,Nacos采用了集中式存储的方式来支持集群化部署,目前只支持MySQL的存储。
nacos支持的三种部署模式:
●单机模式:用于测试和单机使用
●集群模式:用于生产环境,确保高可用
●多集群模式:用于多数据中心场景

4.2 Nacos嵌入式数据库derby切换到mysql-windows单机版derby到mysql切换步骤

Nacos默认自带的是嵌入式数据库derby,那么如果做集群时每个nacos都自带一个derby,那么就有三个存储配置稳健的数据库,显然数据的统一性存在问题。

1.首先在nacos安装目录的conf目录下找到一个名为nacos-mysql.sql的sql脚本

然后执行nacos-mysql.sql脚本
注意:数据库nacos_config需要自己创建
新建完数据库以后打开这个文件复制sql执行一下。
nacos-mysql.sql(10 KB)

2.在conf目录下找到application.properties

到这一步的时候请再次确认一下你的nacos版本号是多少,我因为自己没注意,


每次启动的时候都可以看到,或者登录web页面的时候,我这里用的版本竟然是2.0.3,所以不能按照视频里的来,配置写法根本就不一样,看下图解释

3.重新启动nacos,可以看到是个全新的空记录界面,以前是记录进derby

在bin目录下的cmd 输入 startup.cmd -m standalone 执行,这里还是单机模式,只不过数据库从derby迁移到了mysql

可以看到,重启之后之前的配置都没了,说明迁移成功。

4.在naocs新建配置时,会自动保存到mysql数据库中

到数据库中查看一下有没有生产新的数据

(踩坑)1.1.4版本nacos使用外部数据库Mysql8 (这种方式我也亲自试了OK)

后面Seata章节由于Nacos、sentinel、seata存在版本对应关系,因此需要安装1.1.4版本nacos。
我在安装nacos1.1.4配置MYSQL8的application.properties后,报错:

尝试了如下方法:
1在nacos\plugins\mysql(自己创建)文件夹下放mysql8对应版本的jar包(不管用)
2修改application.properties文件名为bootstrap.properties:可以正常启动nacos,但是数据库并没有从derby切换为mysql
3添加时区:

一般我在使用mysql8的时候都会添加上时区,仍然不管用。

5.修改nacos源码(可行)

找到nacos官网 Nacos 快速开始 | Nacos 官网
点击

然后一直翻,找到1.1.4
修改nacos源码
下载nacos1.1.4源码,修改父工程pom的mysql-connector-java的版本,将5.1.34改成自己mysql对应的版本
 


这里一改,naming模块下的com.alibaba.nacos.naming.healthcheck.MysqlHealthCheckProcessor就会报错 ,

把import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;改为import com.mysql.cj.jdbc.MysqlDataSource;
 


这里我注释的一定要删掉(这里我注释掉主要是为了区分),不然会报错:


然后在源码根目录下打开cmd,使用maven打包(实际我是直接在idea里面打包的)
执行:mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U

打包好的nacos存放在根目录\distribution\target目录下 ,当前项目目录下去找

这个压缩包就相当于我们从官网上下的。然后复制到你想解压的地方,解压。
随后,配置conf\application.properties里面的数据库:

serverTimezone=UTC不要忘记了。(每次配置mysql8,都要带上这个设置)
然后启动nacos:

启动成功,我们试一下,能不能存到数据库中,新建一个配置,然后查看数据库中是否生成!

4.3 Linux版Nacos+MySQL生产环境配置

一定要先把环境准备好! centos7/Ubuntu/ + maven(3.2+)+mysql(5.6.5+)+JDK1.8 一站式部署环境配置地址: Linux服务器服务搭建及项目部署超详细 - 哩个啷个波 - 博客园
预计需要1个Nginx+3个nacos注册中心+1个mysql

4.3.1 Nacos 下载Linux版

下载nacos-linux:https://github.com/alibaba/nacos/releases/tag/2.0.3 我放在了/usr/local 下
解压: tar -zxvf nacos-server-2.0.3.tar.gz
cp -r nacos /mynacos/ 递归拷贝nacos 文件夹到mynacos文件夹下 (我这里没有执行这步)

4.3.2 集群配置步骤(重点)

(1) Linux服务器上mysql数据库配置
跟windows一样,linxu的nacos 在 /nacos/conf 目录下有一个nacos-mysql.sql的sql脚本
我是通过SQLyog客户端远程连接,然后执行。
安装MySQL可以参考我这里的文章: Linux服务器服务搭建及项目部署超详细 - 哩个啷个波 - 博客园
直接使用连接远程的方式去执行创建mysql的表。表创建成功以后我们就可以去Linux服务器上面的MySQL去验证这些表是否已经创建好了。
 


(2) application.properties 配置
/usr/local/nacos/conf 下,修改内容:数据库密码自己要配置自己的并且要配置正确

spring.datasource.platform=mysqldb.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

配置的时候注意一下几个地方,但是如果你的nacos版本是2.0以上,我自己用的那么这个配置文件中是不需要手动写或者复制的,在注释里面是可以找到的,只需要打开注释就可以了。

(3) Linux服务器上nacos的集群配置cluster.conf
梳理出3台nacos集器的不同服务端口号 3333 4444 5555

cp cluster.conf.example cluster.conf  
vim cluster.conf  

查找自己的ip地址 或者使用命令ip addr

(4) 编辑Nacos的启动脚本startup.sh,使它能够接受不同的启动端口
/usr/local/nacos/bin 目录下有startup.sh,平时单机版的启动,都是./startup.sh即可。
集群启动,我们希望可以类似其它软件的shell命令,传递不同的端口号启动不同的nacos实例。
命令:./startup.sh -p 3333 表示启动端口号为3333的nacos服务器实例,和上一步的cluster.conf配置的一致。
注意:2.0版本的-p被占用了,可以用大写P或者其他字母,刚改完浏览器会有延迟,过一会才会好。
●1.1.4 版本修改前后对比方式: 下面的信息一定要配置对 PORT=$OPTARG这里是大写O,可不是0,我写错了第一次的时候 ,下面是-Dserver.port=${PORT}

|

●2.0.3版本修改前后对比

修改前

修改后

如果防火墙没关,记得将3333,4444,5555端口号设置可访问
最终只能启动一个nacos,尝试各种办法未解决,更换nacos1.1.4后没有问题。
(番外)nacos linux踩坑必看
最开始我用的nacos2.0.3,到集群那就出现了问题,只能开一个nacos,剩下两个开起来就报错。
尝试了各种方法,改JVM配置等等,还是不行。最后选择使用跟视频里面一样的nacos版本1.1.4
使用1.1.4的时候数据库的配置 /usr/local/mynacos/nacos/conf/ (1.1.4的安装路径) application.properties 文件使用mysql8+ 需要加上时区 2.0.3 不需要

cluster.conf 还是跟之前一样配置;
startup.sh 跟老师一样的改法,然后需要将JVM的配置修改,因为我的虚拟机只有4G内存,所以我把JVM配置调整为如下:

重启方式:
 


 


上面启动完以后一定要去日志表里面看一下有没有报错信息ERROR,日志启动的可能比较慢,耐心等待一下,没有的话用 ps -ef|grep nacos 看一下服务是不是正常启动着

然后分别执行开启3333、4444、5555三个nacos,成功开启nacos集群

使用nacos2+的朋友可以尝试开三台虚拟机,参考官方手册配置集群nacos官方手册
注意:用nacos1.4以上的,貌似就用不了这个模拟集群的方法了,老实复制3个nacos文件夹再逐个设置端口号启动 同一台机启动多个nacos的问题

(5) Nginx的配置,由它作为负载均衡器
Nginx基础
/usr/local/nginx/conf 下nginx.conf文件为默认配置文件 启动nginx通过-c可以指定配置文件启动。我这里将原来的配置文件拷贝为nginx_nacos.conf

启动nginx,cd到/usr/local/nginx/sbin下 ,执行:
./nginx -c /usr/local/nginx/conf/nginx_nacos.conf
别忘了防火墙开启1111端口的访问:
firewall-cmd --add-port=1111/tcp --permanent
firewall-cmd --reload

(6) 测试
测试通过nginx访问nacos,成功登陆

然后我们新建一个配置文件:

成功存到Linux的数据库中!

4.4 微服务cloudalibaba-provider-payment9002启动注册进nacos集群

修改9002的application.yml文件。

做监控需要把这个全部暴露出来

18.SpringCloud Alibaba Sentinel实现熔断与限流

一、Sentinel

https://github.com/alibaba/Sentinel 中文
Sentinel 是轻量级的流量控制、熔断降级Java库;功能类似于Hystrix
Web端界面特点:采用的是懒加载模式,服务必须被请求一次后才能在界面中看到,这点需要注意了


下载地址


怎么玩:
入门文档
服务使用中的各种问题:服务雪崩、服务降级、服务熔断、服务限流

二、安装Sentinel控制台

Sentinel分为两个部分:
●核心库(Java客户端)不依赖任何框架/库,能够云星宇所有Java运行时环境,同时对Dubbo/Spring Cloud等框架也有较好的支持——后台;
●控制台(Dashboard)基于Spring Boot开发,打包后可以直接运行,不需要额外的Tomcat等应用容器——前台 8080

安装步骤
下载到本地sentinel-dashboard-1.8.2.jar
运行命令:
前提需要Java8,且8080端口不能被占用;java -jar sentinel-dashboard-1.8.2.jar (Sentinel启动命令)

访问 localhost:8080,账号密码均为sentinel

三、初始化演示工程

3.1 启动Nacos8848

3.2 新建Module cloudalibaba-sentinel-service8401

3.2.1 pom

以后基本上nacos 跟sentinel一起配置

<?xml version="1.0" encoding="UTF-8"?>  
<project xmlns="http://maven.apache.org/POM/4.0.0"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  <parent>        <artifactId>springcloud2020</artifactId>  <groupId>org.xu.springcloud</groupId>  <version>1.0.0</version>  </parent>    <modelVersion>4.0.0</modelVersion>  <artifactId>cloudalibaba-sentinel-service8401</artifactId>  <properties>        <maven.compiler.source>8</maven.compiler.source>  <maven.compiler.target>8</maven.compiler.target>  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  </properties>  <dependencies>        <!--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>        <!-- SpringBoot整合Web组件+actuator -->  <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>        <!--日常通用jar包配置-->  <dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-devtools</artifactId>  <scope>runtime</scope>  <optional>true</optional>  </dependency>        <dependency>            <groupId>cn.hutool</groupId>  <artifactId>hutool-all</artifactId>  <version>4.6.3</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>  </project>
3.2.2 yaml

spring.cloud.sentinel.transport.port 端口配置会在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互。
比如 Sentinel 控制台添加了1个限流规则,会把规则数据push给这个Http Server接收,Http Server再将规则注册到Sentinel中。
spring.cloud.sentinel.transport.port:指定与Sentinel控制台交互的端口,应用本地会启动一个占用该端口的Http Server

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: '*'
3.2.3 主启动类
package com.cloudalibaba.sentinel;  import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;  @EnableDiscoveryClient  
@SpringBootApplication  
public class MainApp8401 {  public static void main(String[] args) {  SpringApplication.run(MainApp8401.class, args);  }  }
3.2.4 业务类

流量控制controller:FlowLimitController

package com.cloudalibaba.sentinel.controller;  import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.RestController;  @RestController  
public class FlowLimitController {  @GetMapping("/testA")  public String testA()  {  return "------testA";  }  @GetMapping("/testB")  public String testB()  {  return "------testB";  }  }

3.3 测试

启动Sentinel8080 java -jar sentinel-dashboard-1.8.2.jar、启动微服务8401
查看Sentinel控制台,发现什么也没有。

原因:Sentinel采用懒加载机制

执行一下:http://localhost:8401/testA

sentinel8080正在监控微服务8401

四、流控规则

流量限制控制规则,分为:流控模式和流控效果

各选项含义:

4.1 流控模式

流控模式有三种:直接、关联、链路

4.1.1 直接(默认)+快速失败(默认)
(1) QPS直接快速失败

QPS:query per second,每秒钟的请求数量,当调用该api的QPS达到阈值时,进行限流。
下面设置表示1秒钟内查询一次就是OK,若QPS>1,就直接-快速失败,报默认错误

编辑好以后返回界面可以看到自己的设置
 


测试一下,当/testA的访问超过1次/s是,页面报错。被Sentinel限流,还能继续请求只要QPS<=1。


小结:表示1秒钟内请求次数大于1,就直接快速失败,报默认错误

直接调用默认的报错信息在技术上是OK的,但是是否应该有自定义的后续处理?应该有类似Hystrix的fallback的兜底方法。

(2) 线程数直接快速失败

当调用该api的线程数达到阈值的时候,进行限流。
与QPS直接快速失败不同的是,QPS情况下限制的是流量,比如银行的人流量只能是1人/s,也就是说每次只能一个人进入银行办理业务;而线程数就好比银行只有一个窗口开放,一群人都可以进入银行,但是每次只能处理一个人的业务。

演示效果:
先修改一下8401的业务类

然后重启8401,测试/testA,最好用两个浏览器访问,效果更明显

4.1.2 关联

当关联的资源达到阈值时,就限流自己。比如当与A关联的资源B达到阈值后,就限流A自己。
支付接口达到阈值,限流下订单的接口。

  1. 配置
    设置效果:当关联资源/testB的qps阀值超过1时,就限流/testA的Rest访问地址,当关联资源到阈值后限制配置好的资源名

  1. 测试
    单独访问testB成功。

postman模拟并发密集访问testB
先创建一个集合,名字自己随便取。

然后将创建的访问/testB的请求保存在创建的集合中
设定集合运行参数,20个线程,每次间隔0.3s访问一次(QPS>1),执行:

然后再访问/testA,发现被限流,等postman执行完毕,testA又可以访问了

4.1.3 链路

需要测试链路的话,springcloud 阿里巴巴版本需要2.1.1.RELEASE以上,在父工程的pom中修改,不要直接在子module的pom中修改,版本有对应关系,不然报错。

●Sentinel从1.6.3版本开始,Sentinel Web Filter 默认收敛所有的URL入口的Context,因此链路限流不生效
●1.7.0版本开始,官方在CommomFilter中引入了WEB_CONTEXT_UNIFY这个init parameter,用于控制是否收敛context,将其配置为false即可根据不同的URL进行链路限流
●Spring Cloud Alibaba 在2.1.1.RELEASE版本后,可以通过配置spring.cloud.sentinel.web-context-unify=false关闭


https://github.com/alibaba/Sentinel/issues/1313

测试
启动8401,给/testA设置链路+快速失败流控规则:

这里入口资源就是簇点链路中,资源名称的上一级。
访问http://localhost:8401/linktestA ,多次刷新出现限流

,但是这种情况我个人觉得跟直接快速失败区别不大,只直接是监控/testA资源,而链路是监控/testA的资源入口sentinel_web_servlet_context。

然后我又参看了其他博客,流控模式——链路。增加了FlowLimitService 修改了controller

分别通过/linktetA 和 /linktestB都是message的入口,然后设置/linktestA入口的流量限制,发现不起作用。。。

4.2 流控效果

快速失败在上面的流控模式演示过了,他是默认的流控效果,直接失败,抛出异常。源码:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController

4.2.1 warm up 预热

官网 源码:com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController
公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值

配置图片说明

测试
5秒之前得阈值不是10,5秒时间过了以后才能达到设定的阈值,得有前戏才行,不能一上来就。。。
 


狂点请求,可以看通过的QPS逐渐增加,最开始会报错限流,之后就可以抗住10/s的QPS了
**应用场景:**秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。

4.2.2 排队等待

官网 源码:com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController

匀速排队,让请求以均匀的速度通过,阀值类型必须设成QPS,否则无效。

/testA的QPS最大为1,超过的话就排队等待,等待的超时时间为20000ms。

修改一下业务代码,把线程名打印出来以验证是否排队。


postman配置成20个请求,延时300毫秒


测试
postman:
遍历20次,耗时时间接近20,说明每秒请求限制为1个。

也可以监测idea控制台输入语句看看频率是多少。但是我发现Sentinel配置的话要检查一下配置是不是还在,刷新页面以后看看是不是就消失了。

可以看到刚好满足1s一个请求,说明请求的执行进行了排队。

五、降级规则(熔断规则)

官网
老版本的Sentinel的断路器是没有半开状态的,半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用。具体可以参考Hystrix。(在Hystrix中 快照时间窗口是值 阈值检测时间 ,而休眠时间窗口是指 断路器从开启到半开状态间隔的时间)
新版本的Sentinel加入了半开状态!

5.1 降级策略

Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。
当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。

5.1.1 慢调用比例,RT(平均响应时间,秒级)

老版本:
 


新版本:
●慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。(跟豪猪科类似)

实战测试
业务类中加一个rest 接口,以用于测试:

@GetMapping("/testD")
public String testD()
{//暂停几秒钟线程try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }log.info("testD 测试RT");return "------testD";
}

访问没有问题

编辑熔断规则:
在1000ms的统计时间内,总请求数(超过5次)中有80%的请求最大RT超过了200ms,那么触发熔断机制,熔断2s。

jmeter压测:
设置线程数10个,响应时间1秒,
 


添加取样器,HTTP请求
 


添加请求路径

永远一秒钟打进来10个线程(大于5个了)调用testD,我们希望200毫秒处理完本次任务,如果超过200毫秒还没处理完,在未来s秒钟的时间内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了。
testD被熔断了


从实时监控也可以看到,看看什么时间熔断的。
后续我停止jmeter,没有这么大的访问量了,断路器半开到关闭(保险丝恢复),微服务恢复OK。

5.1.2 异常比例

新版本:
●异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。

实战测试
修改业务类:

@GetMapping("/testD")
public String testD()
{log.info("testD 测试RT");int age = 10/0;return "------testD";
}

编辑熔断规则:
 


1000ms统计时长内,大于5次的请求中超过80%的请求出现异常,则熔断2s。

jmeter压测:
单独访问一次,必然来一次报错一次(int age = 10/0),调一次错一次;开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了。断路器开启(保险丝跳闸),微服务不可用了,不再报错error而是服务降级了。

testD被熔断。
停掉jemeter后过2s。报/zero错误,因为业务类中有个10/0。

5.1.3 异常数

老版本:
 


时间窗口一定要大于等于60秒。
新版本
●异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

实战测试
修改业务类:

@GetMapping("/testE")
public String testE()
{log.info("testE 测试异常比例");int age = 10/0;return "------testE 测试异常比例";
}

编辑熔断规则:

手动测试:


这里我测试有bug,虽然熔断了但是熔断时长不是我配置的5s,大约是一分钟,统计时长也不是1s,好像也是一分钟,同时没达到最小请求数,只达到3次异常就直接熔断了。虽然我用的新版本,但是逻辑好像跟老版本的一样?

六、热点key限流

6.1 基本介绍

官网
何为热点:热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作。比如:
●商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
●用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
热点参数限制会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限制。热点参数限流可以看作是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

Sentinel利用LRU策略统计最近最常访问的热电参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。

6.2 基本使用

兜底防范分为系统默认和客户自定义;两种,根据之前的case,都是使用sentinel系统默认的提示:Blocked by Sentinel (flow limiting)。那我们能不能自定义兜底方法呢?类似hystrix,某个方法出问题了,就找对应的兜底降级方法?
类似于@HystrixCommand, 引入@SentinelResource注解。
热点规则共有资源名、限流模式(只支持QPS模式)、参数索引、单机阈值、统计窗口时长、是否集群6种参数,还有一些高级选项,用到时会详细介绍。这里会用到注解中的value作为资源名,兜底方法会在后面详细介绍@SentinelResource注解详解

注意:
资源名:唯一路径,默认为请求路径。此处必须是 @SentinelResource 注解的 value 属性值,配置@GetMapping 的请求路径无效)

6.2.1 测试方法

还是在8401的controller中,加入热点测试方法。

@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "dealHandler_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1, @RequestParam(value = "p2",required = false) String p2){return "------testHotKey";
}
public String dealHandler_testHotKey(String p1,String p2,BlockException exception)
{return "-----dealHandler_testHotKey";
}

注解@SentinelResource(value = "testHotKey", blockHandler = "del_testHotKey")
分析:
●其中 value = "testHotKey" 是一个标识(Sentinel资源名),与rest的/testHotKey对应,这里value的值可以任意写,但是我们约定与rest地址一致,唯一区别是没有/
●blockHandler = "del_testHotKey" 则表示如果违背了Sentinel中配置的流控规则,就会调用我们自己的兜底方法del_testHotKey

6.2.2 配置热点key限流规则

绑定testHotKey资源,把testHotKey对应的第一个参数作为热点key进行监控。设定热点限流规则:当该资源的访问QPS超过1次/s的时候,产生限流并执行自定义的del_testHotKey兜底方法。
简而言之:方法testHotKey里面第一个参数只要QPS超过每秒1次,马上降级处理。

6.2.3 测试

1访问http://localhost:8401/testHotKey?p1=a&p2=b
1次/s正常显示,迅速点击两次,触发热点限流,执行自定义兜底方法:

2仅传入参数p2没有任何影响: http://localhost:8401/testHotKey?p2=b


3现在开两个访问,一个通过jmeter压测http://localhost:8401/testHotKey?p1=a&p2=b,另外再单独使用浏览器访问http://localhost:8401/testHotKey?p2=b,发现只带参数p2访问没有任何影响。

4不配置blockeHandler(兜底方法)

触发热点限流降级会出现error page,对用户不友好。

6.3 参数例外项

上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流
特例情况:我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样,比如当p1的值等于5时,它的阈值可以达到200。

测试
狂点http://localhost:8401/testHotKey?p1=5&p2=b 没有限流

6.4 其他

手动添加一个异常:


测试直接错误页面。

要注意: Sentinel它只管你有没有触发它的限流规则,也可以说只管这个web交互页面(控制台)里面的东西。 配置类的东西Sentinel可以管,java异常的错误我不管。
@SentinelResource

处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;

RuntimeException

int age = 10/0,这个是java运行时报出的运行时异常RunTimeException,@SentinelResource不管

总结:

@SentinelResource主管配置出错,运行出错该走异常走异常

七、系统规则(系统自适应限流)
官网
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。
系统规则支持以下的模式:
●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

不管是/testA还是/testB 只要QPS > 1 整个系统就不能用。
这个粒度太粗,就相当于一个窗口人很多,整个银行就不接待人了,不太建议使用。

八、@SentinelResource 注解详解

8.1 按资源名称限流+后续处理

启动nacos+sentinel

8.1.1 修改8401

(1) pom
引入我们自定义的公共api jar包

        <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity --><groupId>com.atguigu.springcloud</groupId><artifactId>cloud-api-commons</artifactId><version>${project.version}</version></dependency>

(2) 业务类

package com.cloudalibaba.sentinel.controller;  import com.alibaba.csp.sentinel.annotation.SentinelResource;  
import com.alibaba.csp.sentinel.slots.block.BlockException;  
import com.java.cloud.common.CommonResult;  
import com.java.cloud.entities.Payment;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.RestController;  @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 服务不可用");  }  }
8.1.2 配置流控规则——按资源名称添加流控规则

8.1.3 测试

自测:


触发流控规则:

8.1.4 问题

如果我们重启8401会发现之前配置的一些规则都没有了。难道每次重启服务器都要重新配置一遍规则吗?规则如何进行持久化?

8.2 按照Url地址限流+后续处理

通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息

8.2.1 修改controller
 @GetMapping("/rateLimit/byUrl")@SentinelResource(value = "byUrl")public CommonResult byUrl(){return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002"));}
8.2.2 设置流控规则及测试

先自测,没有问题:

按rest URI设置流控规则

触发流控:


这个没有自定义的兜底的方法,返回Sentinel自带的限流处理结果。

8.3 总结以及面临的问题

不管是@GetMapping(rest url)还是 @SentinelResource,只要是唯一的,就可以作为流控规则的资源名称。如果没有自定义自己的兜底方法,那么就使用系统自带的。

问题:
●依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。如果都用系统默认的,就没有体现我们自己的业务要求。
●如果每个业务方法/API接口都添加一个兜底的,那代码膨胀加剧。
●全局统一的处理方法没有体现。

8.4 客户自定义限流处理逻辑

为了解决代码耦合与膨胀的问题

8.4.1 创建CustomerBlockHandler类用于自定义限流处理逻辑

在CustomerBlockHandler类中统一的处理限流提示、服务降级的说明等等。。

package com.cloudalibaba.sentinel.myHandler;  import com.alibaba.csp.sentinel.slots.block.BlockException;  
import com.java.cloud.common.CommonResult;  public class CustomerBlockHandler {  public static CommonResult handleException(BlockException exception){  return new CommonResult(2020,"自定义的限流处理信息......CustomerBlockHandler------1");  }  public static CommonResult handleException2(BlockException exception){  return new CommonResult(2020,"自定义的限流处理信息......CustomerBlockHandler------2");  }  }
8.4.2 修改RateLimitController,使用自定义处理逻辑类
/**  * 自定义通用的限流处理逻辑,  blockHandlerClass = CustomerBlockHandler.class  blockHandler = handleException2 上述配置:找CustomerBlockHandler类里的handleException2方法进行兜底处理  */  
/**  * 自定义通用的限流处理逻辑  */  
@GetMapping("/rateLimit/customerBlockHandler")  
@SentinelResource(value = "customerBlockHandler",  blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handleException2")  
public CommonResult customerBlockHandler()  
{  return new CommonResult(200,"按客户自定义限流处理逻辑");  
}
8.4.3 测试

启动8401,先测试一次http://localhost:8401/rateLimit/customerBlockHandler

设置流控规则:


触发流控,看是否是我们自定义提示:


自定义提示出来了!

8.4.4 结构说明

这样就实现了兜底方法与业务方法的解耦。

8.5 更多属性说明

注解支持文档

九、服务熔断

主要内容:
●sentinel分别整合ribbon+openFeign以及设置fallback
●熔断框架比较

9.1 Ribbon系列

nacos中整合了Ribbon,所以直接使用nacos就行。启动nacos和Sentinel。

9.1.1 服务提供者9003/9004

新建cloudalibaba-provider-payment9003/9004两个一样的做法

(1) pom

2020版和springcloud和nacos记得引入spring-cloud-starter-loadbalancer依赖

<?xml version="1.0" encoding="UTF-8"?>  
<project xmlns="http://maven.apache.org/POM/4.0.0"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  <parent>        <artifactId>springcloud2020</artifactId>  <groupId>org.xu.springcloud</groupId>  <version>1.0.0</version>  </parent>    <modelVersion>4.0.0</modelVersion>  <artifactId>cloudalibaba-provider-payment9003</artifactId>  <properties>        <maven.compiler.source>8</maven.compiler.source>  <maven.compiler.target>8</maven.compiler.target>  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  </properties>  <dependencies>        <!--SpringCloud ailibaba nacos -->  <dependency>  <groupId>com.alibaba.cloud</groupId>  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>  </dependency>        <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->  <groupId>org.xu.springcloud</groupId>  <artifactId>cloud-api-commons</artifactId>  <version>1.0.0</version>  </dependency>        <!-- SpringBoot整合Web组件 -->  <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>        <!--日常通用jar包配置-->  <dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-devtools</artifactId>  <scope>runtime</scope>  <optional>true</optional>  </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>  </project>
(2) yml

9004 别忘了改端口号

 
server:port: 9003spring:application:name: nacos-payment-providercloud:nacos:discovery:server-addr: localhost:8848 #配置Nacos地址management:endpoints:web:exposure:include: '*'
(3) 主启动类
package com.java.cloud;  import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  @SpringBootApplication 
@EnableDiscoveryClient
public class PaymentMain9003 {  public static void main(String[] args) {  SpringApplication.run(PaymentMain9003.class,args);  }  
}
(4) 业务类

这里图方便,就没有连接数据库。

package com.java.cloud.controller;  import com.java.cloud.common.CommonResult;  
import com.java.cloud.entities.Payment;  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.web.bind.annotation.PathVariable;  
import org.springframework.web.bind.annotation.RestController;  import java.util.HashMap;  @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,"565123155216156315153131"));  hashMap.put(2L,new Payment(2L,"565123155216156315153131"));  hashMap.put(3L,new Payment(3L,"565123155216156315153131"));  }  public CommonResult<Payment> paymentSql(@PathVariable("id") Long id){  Payment payment = hashMap.get(id);  CommonResult<Payment> commonResult = new CommonResult<>(200,"from mysql,serPort:"+serverPort,payment);  return commonResult;  }  }
(5) 测试

http://localhost:9003/paymentSQL/1

http://localhost:9004/paymentSQL/1

9.1.2 服务消费者84

新建cloudalibaba-consumer-nacos-order84

(1) pom
<?xml version="1.0" encoding="UTF-8"?>  
<project xmlns="http://maven.apache.org/POM/4.0.0"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  <parent>        <artifactId>springcloud2020</artifactId>  <groupId>org.xu.springcloud</groupId>  <version>1.0.0</version>  </parent>    <modelVersion>4.0.0</modelVersion>  <artifactId>cloudalibaba-consumer-nacos-order84</artifactId>  <properties>        <maven.compiler.source>8</maven.compiler.source>  <maven.compiler.target>8</maven.compiler.target>  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  </properties>  <dependencies>        <!--SpringCloud ailibaba nacos -->  <dependency>  <groupId>com.alibaba.cloud</groupId>  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>  </dependency>        <!--SpringCloud ailibaba sentinel -->  <dependency>  <groupId>com.alibaba.cloud</groupId>  <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>  </dependency>        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->  <dependency>  <groupId>org.xu.springcloud</groupId>  <artifactId>cloud-api-commons</artifactId>  <version>1.0.0</version>  </dependency>        <!-- SpringBoot整合Web组件 -->  <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>        <!--日常通用jar包配置-->  <dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-devtools</artifactId>  <scope>runtime</scope>  <optional>true</optional>  </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>  
</project>
(2) yml
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
(3) 主启动类
package com.java.cloud;  import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  @SpringBootApplication  
@EnableDiscoveryClient
public class OrderNacosMain84 {  public static void main(String[] args) {  SpringApplication.run(OrderNacosMain84.class,args);  }  
}
(4) 业务类

因为用的Ribbon,需要使用其提供的RestTemplate

package com.java.cloud.config;  import org.springframework.cloud.client.loadbalancer.LoadBalanced;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.web.client.RestTemplate;  @Configuration  
public class ApplicationContextConfig {  @Bean  @LoadBalanced    public RestTemplate getRestTemplate()  {  return new RestTemplate();  }  }

业务类:

package com.java.cloud.controller;  import com.alibaba.csp.sentinel.annotation.SentinelResource;  
import com.java.cloud.common.CommonResult;  
import com.java.cloud.entities.Payment;  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.PathVariable;  
import org.springframework.web.bind.annotation.RestController;  
import org.springframework.web.client.RestTemplate;  import javax.annotation.Resource;  @RestController  
@Slf4j  
public class CircleBreakerController {  public static final String SERVECE_URL = "http://nacos-payment-provider";  @Resource  private RestTemplate restTemplate;  @GetMapping("/consumer/fallback/{id}")  @SentinelResource(value = "fallback")  public CommonResult<Payment> fallback(@PathVariable Long id) throws IllegalAccessException {  CommonResult forObject = restTemplate.getForObject(SERVECE_URL + "/paymentSql/"+id, CommonResult.class, id);  if(id==4){  throw new IllegalAccessException("IllegalAccessException,非法参数异常");  }else if(forObject.getData()==null){  throw new NullPointerException("NullPointerException,空指针异常,没有获取到对应id的数据");  }  return forObject;  }  }
(5) 测试

负载均衡实现。

9.1.3 fallback 和 blockHandler

加深@SentinelResource(value = "xxx", fallback = "fffff", blockHandler = "bbbbbb")注解理解:
fallback管运行异常,blockHandler管配置违规。

fallback对应服务降级,就是服务出错了应该怎么办(需要有个兜底方法);
blockHandler对应服务熔断,就是我现在服务不可用,我应该怎么办,怎么给客户一个户提示(同样需要一个兜底方法)

9.1.4 差异化配置

这里改的业务类代码均是消费者84端的业务类代码,不要搞错了。
降级是服务业务代码出现错误的兜底,熔断是服务不可用。降级是兜底方法,熔断是对服务保护时间窗口期服务不可用。

(1) 没有任何配置

前面我们的84消费端,@SentinelResource里面只配置了value,fallback和blockHandler都没有配置,该情况下我们测试一下http://localhost:84/consumer/fallback/4

出现了错误页面,error page对客户不友好,所以我们需要有兜底方法。

(2) 只配置fallback

fallback对应服务降级,就是服务可以正常访问,但是业务逻辑出现错误,需要降级兜底。

访问http://localhost:84/consumer/fallback/4,可以看到业务异常

(3) 只配置blockHandler

blockHandler对应服务熔断,当前sentinel配置已经违规(RT数过多、异常过多),服务熔断后不可用,需要给客户提示,进行一个熔断的兜底。

配置sentinel

访问:http://localhost:84/consumer/fallback/5

如果异常数达到规定的标准就会触发熔断,比如我们设置的异常数是2,那么请求两次以后就会报以下异常
 


这里还是跟之前一样的bug,很迷。

(4) fallback和blockHandler都配置

同时有降级跟熔断的兜底方法,当降级达到sentinel配置规则后,触发熔断。

配置sentinel
删除之前的熔断规则(这里不是删除,因为每次重启以后就没有了),配置流控:

没有触发限流时,我们触发业务异常http://localhost:84/consumer/fallback/4,会被降级方法fallback兜底:

触发限流时,我们仍然访问可以触发业务异常的连接,此时服务已经被限流(可以理解为服务不可用即熔断),此时触发的是限流(熔断blockHandler):
 


也就是说,同时配置fallback:处理业务异常(微服务自身异常,服务降级)和blockHandler:处理触发sentinel配置(微服务不可用,服务熔断)时。在没有违反sentinel规则时,出现业务异常(降级)走fallback方法;违反了sentinel规则时,直接微服务不可用(熔断),走blockHandler指定的自定义方法。

(5) 异常忽略属性

可以选择性的配置当某些异常发生时,不触发fallback的兜底方法。

测试一下,访问:http://localhost:84/consumer/fallback/4

直接报错误页面,没有了降级兜底方法。
其他异常不受影响:

当然,触发流控之后,仍然通过blockHandler指定的方法进行熔断兜底。

测试代码:

package com.java.cloud.controller;  import com.alibaba.csp.sentinel.annotation.SentinelResource;  
import com.alibaba.csp.sentinel.slots.block.BlockException;  
import com.java.cloud.common.CommonResult;  
import com.java.cloud.entities.Payment;  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.PathVariable;  
import org.springframework.web.bind.annotation.RestController;  
import org.springframework.web.client.RestTemplate;  import javax.annotation.Resource;  @RestController  
@Slf4j  
public class CircleBreakerController {  public static final String SERVECE_URL = "http://nacos-payment-provider";  @Resource  private RestTemplate restTemplate;  @GetMapping("/consumer/fallback/{id}")  
//    @SentinelResource(value = "fallback",fallback = "handlerFallback")  
//    @SentinelResource(value = "fallback",blockHandler = "blockHandler")  @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler",  exceptionsToIgnore = {IllegalAccessException.class})  public CommonResult<Payment> fallback(@PathVariable Long id) throws IllegalAccessException {  CommonResult forObject = restTemplate.getForObject(SERVECE_URL + "/paymentSql/"+id, CommonResult.class, id);  if(id==4){  throw new IllegalAccessException("IllegalAccessException,非法参数异常");  }else if(forObject.getData()==null){  throw new NullPointerException("NullPointerException,空指针异常,没有获取到对应id的数据");  }  return forObject;  }  public CommonResult handlerFallback(@PathVariable  Long id,Throwable e) {  Payment payment = new Payment(id,"null");  return new CommonResult<>(444,"兜底异常handlerFallback,exception内容  "+e.getMessage(),payment);  }  public CommonResult blockHandler(@PathVariable  Long id, BlockException blockException) {  Payment payment = new Payment(id,"null");  return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException  "+blockException.getMessage(),payment);  }  
}

9.2 Feign系列

9.2.1 修改84模块

修改84模块,Feign组件一般是在消费侧。

(1) pom

pom 加入feign的依赖

<!--SpringCloud openfeign --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
(2) yml

激活Sentinel对Feign的支持

(3) 业务类

后续84的controller不找restTemplate(Ribbon),不是restTemplate去调用payment微服务中的接口。而是通过调用PaymentFeignService,service再去调用payment微服务中的端口。

Feign需要定义一个业务逻辑(service)接口+ @FeignClient注解以调用服务提供者。
新建PaymentFeignService interface:

package com.java.cloud.service;  import com.java.cloud.common.CommonResult;  
import com.java.cloud.entities.Payment;  
import com.java.cloud.service.impl.PaymentFallbackService;  
import org.springframework.cloud.openfeign.FeignClient;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.PathVariable;  
/**  * @auther zzyy  * @create 2019-12-10 17:17  * 使用 fallback 方式是无法获取异常信息的,  * 如果想要获取异常信息,可以使用 fallbackFactory参数  */  
@FeignClient(value="nacos-payment-provider",fallback = PaymentFallbackService.class)  
public interface PaymentFeignService {  @GetMapping(value = "/paymentSql/{id}")  public CommonResult<Payment> paymentSql(@PathVariable("id") Long id);  }

调用失败的兜底方法:

package com.java.cloud.service.impl;  import com.java.cloud.common.CommonResult;  
import com.java.cloud.entities.Payment;  
import com.java.cloud.service.PaymentFeignService;  
import org.springframework.stereotype.Component;  @Component  
public class PaymentFallbackService implements PaymentFeignService {  @Override  public CommonResult<Payment> paymentSql(Long id) {  return new CommonResult<>(444,"服务降级返回,没有该流水信息",new Payment(id, "errorSerial......"));  }  
}

84端口controller加入openFeign的接口:

@Resource  
private PaymentFeignService paymentFeignService;  @GetMapping("/consumer/openFeign/{id}")  
public CommonResult<Payment> paymentSql(@PathVariable("id") Long id){  if(id == 4){  throw new RuntimeException("没有该id");  }  return paymentFeignService.paymentSql(id);  
}
(4) 主启动类

加上@EnableFeignClient注解开启OpenFeign

package com.java.cloud;  import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;  
import org.springframework.cloud.openfeign.EnableFeignClients;  @EnableDiscoveryClient  
@SpringBootApplication  
@EnableFeignClients  
public class OrderNacosMain84 {  public static void main(String[] args) {  SpringApplication.run(OrderNacosMain84.class,args);  }  
}
(5) 测试

启动9003、9004、84
访问:http://localhost:84/consumer/paymentSQL/1
9003、9004负载均衡

关闭9003、9004微服务提供者,看到84消费者自动执行降级兜底方法。

如果yaml没有配置Sentinel对Feign的支持,就不会执行降级方法,而是直接报错误页面。

9.3 熔断框架比较

十、持久化规则

前面我们微服务新增的限流规则后,微服务关闭后就会丢失,当时配置都限流规则都是临时的。 将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则 就能看到。只要nacos里面的配置不删除,针对8401上的sentinel上的流控规则就持续存在。 (也可以持久化到文件,redis,数据库等)

案例——修改8401已完成持久化设置

(1) pom

导入持久化所需依赖

<!--SpringCloud ailibaba sentinel-datasource-nacos -->
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-datasource-nacos</artifactId></dependency>

(2) yaml

添加nacos数据源配置

 
server:port: 8401spring:application:name: cloudalibaba-sentinel-servicecloud:nacos:discovery:server-addr: localhost:8848 #Nacos服务注册中心地址sentinel:transport:dashboard: localhost:8080 #配置Sentinel dashboard地址port: 8719datasource:ds1:nacos:server-addr: localhost:8848dataId: cloudalibaba-sentinel-servicegroupId: DEFAULT_GROUPdata-type: jsonrule-type: flowmanagement:endpoints:web:exposure:include: '*'feign:sentinel:enabled: true # 激活Sentinel对Feign的支持

(3) 添加nacos业务规则配置

我们将sentinel的流控配置保存在nacos中,因为nacos的配置持久化在了数据库中。

[{"resource": "/rateLimit/byUrl","limitApp": "default","grade": 1,"count": 1,"strategy": 0,"controlBehavior": 0,"clusterMode": false}
]

resource:资源名称;
limitApp:来源应用;
grade:阈值类型,0表示线程数,1表示QPS;
count:单机阈值;
strategy:流控模式,0表示直接,1表示关联,2表示链路;
controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
clusterMode:是否集群。

注意:这里如果要配nacos的命名空间(public、dev、test)的话,应该是配namespace的id,不是名称

(4) 测试
启动8401,访问8401任意接口,刷新Sentinel。可以看到Sentinel中加载了通过nacos持久化的规则配置文件。
 

关掉8401后发现流控规则没有了。
 


再次启动8401查看sentinel,访问几次8401后流控规则又出现了。
查看数据库,发现规则持久化到数据库中了。

19. SpringCloud Alibaba Seata处理分布式事务

分布式事务问题

只要用到分布式,必然会提及分布式的事务。
在分布式之前,一切组件全都在一台机器上。
在使用分布式之后,单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源。
业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。

一句话:一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题。

一、Seata简介与安装

Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。 官网

1.1 相关术语

一个典型的分布式事务过程,可以用分布式处理过程的一ID+三组件模型来描述。
一ID(全局唯一的事务ID):Transaction ID XID,在这个事务ID下的所有事务会被统一控制
三组件:
●Transaction Coordinator (TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;(Server端,为单独服务器部署)
●Transaction Manager (TM):事务管理器,控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
●Resource Manager (RM):资源管理器,控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚
●Seata分TC、TM和RM三个角色,TC(Server端)为单独服务端部署,TM和RM(Client端)由业务系统集成(微服务)。

1.2 典型的分布式控制事务流程

1.TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;
2.XID 在微服务调用链路的上下文中传播;(也就是在多个TM,RM中传播)
3.RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
4.TM 向 TC 发起针对 XID 的全局提交或回滚决议;
5.TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。

1.3 Seata-Server的下载与配置

我这里下载了0.9.0版本跟1.4.2版本(配了半天没配好,后面再填坑),差别还是蛮大的。
github地址:https://github.com/seata/seata/releases
官网地址:Apache Seata

1.3.1 修改file.conf文件

解压到指定目录并修改conf目录下的file.conf配置文件。
1.备份原始file.conf文件。

* a 主要修改:自定义事务组名称+事务日志存储模式为db+数据库连接信息。

2.修改file.conf文件

* a service模块(1.4.2里面没有这个模块,需要自己加)

* b store模块


注意:上面配置文件中的driver-class-name,如果你的mysql是8.0以后的版本,记得名称修改成:com.mysql.cj.jdbc.Driver,同时url里面要配置上时区

1.3.2 数据库中建库建表

数据库新建库seata,建表db_store.sql在\seata-server-0.9.0\seata\conf目录里面

1.3.3 修改seata-server-0.9.0\seata\conf目录下的registry.conf配置文件

目的是:指明注册中心为nacos,及修改nacos连接信息

1.3.4 启动nacos和seata (先启动nacos)

seata-server-0.9.0\seata\bin\seata-server.bat
启动失败,报错:

解决:0.9.0默认的mysql是5.1.30版本,将lib文件夹下mysql-connector-java-5.1.30.jar删除,替换成自己mysql版本的jar包,我的是mysql-connector-java-8.0.22.jar。
再次启动:

出现这些提示信息代表seata启动成功。
nacos中成功注册了seate:

**Seata 1.4.2版本填坑---nacos作为seata的注册/配置中心

seata1.2.0 Seata1.4.0+nacos
Seata0.9.0版本不支持集群,生产环境下需要使用1.0.0以上版本。我们这里配置seata的最新版本1.4.2,下载后文件目录如下图所示:

0. 启动nacos

启动nacos,新建一个命名空间seata用于存放seata的配置信息。


注意这里的命名空间ID,后面会用到。这里不新建也可以,seata使用的是public。
我们使用nacos充当seata的注册中心和配置中心!

1. 修改配置文件
①进入conf文件夹,修改file.conf文件

1.4.2版本可以参考file.conf.example(server端)和conf文件夹下README-zh.md中的client端配置
总共需要修改的地方:我这里用seate_1_4_2数据库来对应seata1.4.2版本

②修改conf\registry.conf文件

思考:这里我们把seata-server端的config设置为了nacos,那么是不是第一步的file.conf文件就不再需要了。因为直接从nacos读取配置?

2. 将配置导入到nacos
① 准备nacos-config.sh脚本

在conf文件夹下,需要有个nacos-config.sh文件,这个文件1.4.2版本没有。README-zh.md文件中访问config-center超链接(https://github.com/seata/seata/tree/develop/script/config-center),nacos文件夹下:

用这个可以直接下当前页的文件github-directory-downloader

② config.txt准备及修改

在conf目录下还需要一个config.txt文件,1.4.2版本同样没有,还是去README里面的config-center超链接。

config.txt需要放在conf的上级目录下。

修改config.txt文件中的内容,主要是下面这几项:

改为使用db存储:

注意这里store.db.url中数据库的名字就是我们之后需要新建的数据库的名字。

相比于其他版本,1.4.2这里多了个distributedLockTable。
整个config.txt文件中,store.publicKey、store.redis.sentinel.masterName、store.redis.sentinel.sentinelHosts、store.redis.password四个属性默认都是空的

。所以后面在将config.txt文件中的配置注册到nacos的时候,会出现四个失败项。

③ 导入seata相应的配置项到Nacos

config.txt就是seata各种详细的配置,执行nacos-config.sh即可将这些配置导入到nacos。这样就不需要将file.conf和registry.conf放到我们的项目中了,需要什么配置就直接从nacos中读取。(这句话是参考博客Seata 1.4.0 + nacos配置和使用,超详细_seata config.txt-CSDN博客,我觉不完全对,后面registry.conf里的配置项虽然不需要.conf文件配置,但是需要在yml或properties文件中配置,而file.conf可以直接在nacos中读取)
导入配置:

然后在git bash界面输入:

注:h表示nacos的地址,p表示端口号,g表示配置的分组,t表示命名空间的ID,u跟w表示nacos的账户密码。如果没有设置命名空间,而且都是默认选项直接 sh nacos-config.sh -h localhost就行。

可以看到共98项,导入失败4项,就是上面没有值的那四项(不影响,如果用到直接在nacos里面新建配置即可)

可以看到,nacos的seata命名空间中已经导入了配置项。(seata命名空间是我自己创建的,可以按自己的需求创建,不创建默认的就是public。)

3. 数据库中建库建表

我们先创建数据库seata1_4_2(数据库要与config.txt中db设置那里对应),数据库的建表语句在README文件的server连接中:

然后执行mysql.sql(1.4.2多了个distributed_lock表,和一些插入语句):

,这四张表跟config.txt文件中的配置对应。

4. 启动seata

运行bin目录下的seata-server.bat。
出现下面字段表示seata启动成功。seata启动日志在C:\Users\admin\logs\seata文件夹下。

nacos中在seata命名空间内也成功注册,注意这里服务名对应的是registry.conf文件中nacos下面application的值。0.9.0版本好像设置不了这个,默认是serverAddr。

二、订单/库存/账户业务数据库准备

以下演示都需要先启动Nacos后启动Seata,保证两个都OK。Seata没启动报错no available server to connect。

2.1 分布式事务业务说明

这里我们会创建三个服务,一个订单服务,一个库存服务,一个账户服务。
当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,再通过远程调用账户服务来扣减用户账户里面的余额,最后在订单服务中修改订单状态为已完成。
该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。
下订单--->扣库存--->减账户(余额)

2.2 创建业务数据库与表

1. 创建业务数据库

●seata_order:存储订单的数据库;
●seata_storage:存储库存的数据库;
●seata_account:存储账户信息的数据库。

CREATE DATABASE seata_order;CREATE DATABASE seata_storage;CREATE DATABASE seata_account;
2. 按照上述3库分别创建对应业务表

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;

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;

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;
3. 按照上述3库分别建对应的回滚日志表

订单-库存-账户3个库下都需要建各自的回滚日志表,\seata-server-0.9.0\seata\conf目录下的db_undo_log.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;

1.4.2版本的在README_ZH文件中的client:

注意:0.9版本跟1.4.2版本的undo_log表的属性有差别,1.4.2版本没有id跟ext这两个属性,个人觉得使用上没有影响
这里的话我还是按0.9.0版本来建表。

最终效果: 这里展示了1.4.2版本跟0.9版本,实际中用一个就行,别的版本一样。

三、订单/库存/账户业务微服务准备

业务需求:下订单->减库存->扣余额->改(订单)状态

版本对应关系——很重要
注意:由于seata0.9.0版本跟1.0之后的版本(支持yml、properties配置)区别巨大,这里使用0.9.0版本(跟视频一致),其版本对应关系见版本说明。(seata0.9.0 + nacos 1.1.4 + sentinel 1.7.0 + SpringCloud Alibaba 2.1.1RELEASE)前面用的各组件版本得对应上(头疼)

3.1 新建订单Order-Module——seata-order-service2001

新建seata-order-service2001

(1) pom
<dependencies><!--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><artifactId>seata-all</artifactId><groupId>io.seata</groupId></exclusion></exclusions></dependency><dependency><groupId>io.seata</groupId><artifactId>seata-all</artifactId><version>0.9.0</version></dependency><!--feign--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--web-actuator--><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><!--mysql-druid--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.37</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.10</version></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.0.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies>
  1. Nacos 服务发现:用于服务注册和发现,使得微服务可以相互通信。
  1. Seata 分布式事务:用于实现分布式事务管理,确保多个微服务之间的数据一致性。
  1. Feign 客户端:用于简化 HTTP 客户端调用,实现声明式 REST 调用。
  1. Web 和 Actuator:用于构建 Web 应用程序,并提供了 Actuator 端点,用于监控和管理应用程序。
  1. MySQL 驱动:用于连接 MySQL 数据库。
  1. Druid 数据库连接池:用于优化数据库连接管理。
  1. MyBatis 集成:用于简化 MyBatis 的使用,实现 ORM(对象关系映射)。
  1. Spring Boot 测试:用于编写和运行单元测试。
  1. Lombok 插件:用于减少样板代码,如 getter、setter、toString 等。
(2) application.yml

这里配置的是我们自己微服务的数据源

server:  port: 2001  # 指定服务运行的端口为2001  spring:  application:  name: seata-order-service  # 指定应用程序的名称为seata-order-service  cloud:  alibaba:  seata:  tx-service-group: tx_group  # 指定Seata事务分组名称  #自定义事务组名称需要与seata-server中的对应  nacos:  discovery:  server-addr: localhost:8848  # 指定Nacos服务发现地址  datasource:  driver-class-name: com.mysql.cj.jdbc.Driver  # 指定MySQL数据库驱动类  url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8  # 指定MySQL数据库连接URL  username: root  # 指定MySQL数据库用户名  password: root  # 指定MySQL数据库密码  feign:  hystrix:  enabled: false  # 禁用Feign的Hystrix支持  logging:  level:  io:  seata: info  # 设置Seata的日志级别为info  mybatis:  mapper-locations: classpath:mapper/*.xml  # 指定MyBatis的Mapper XML文件位置
(3) file.conf

程序中依赖的是 seata-all,对应于 *.conf 文件,所以需要在resource新建.conf文件,高版本的支持yml、properties配置。这里仅仅是seata-order-service2001模块的file.conf(配置2001的分布式事务),seata软件那里配置的是总控file.conf。

transport {  # tcp udt unix-domain-socket  type = "TCP"  #NIO NATIVE  server = "NIO"  #enable heartbeat  heartbeat = true  #thread factory for netty  thread-factory {  boss-thread-prefix = "NettyBoss"  worker-thread-prefix = "NettyServerNIOWorker"  server-executor-thread-prefix = "NettyServerBizHandler"  share-boss-worker = false  client-selector-thread-prefix = "NettyClientSelector"  client-selector-thread-size = 1  client-worker-thread-prefix = "NettyClientWorkerThread"  # netty boss thread size,will not be used for UDT  boss-thread-size = 1  #auto default pin or 8  worker-thread-size = 8  }  shutdown {  # when destroy server, wait seconds  wait = 3  }  serialization = "seata"  compressor = "none"  
}  service {  vgroup_mapping.xu_group = "default" #修改自定义事务组名称  default.grouplist = "127.0.0.1:8091"  enableDegrade = false  disable = false  max.commit.retry.timeout = "-1"  max.rollback.retry.timeout = "-1"  disableGlobalTransaction = false  
}  client {  async.commit.buffer.limit = 10000  lock {  retry.internal = 10  retry.times = 30  }  report.retry.count = 5  tm.commit.retry.count = 1  tm.rollback.retry.count = 1  
}  ## transaction log store  
store {  ## store mode: file、db  mode = "db"  ## file store  file {  dir = "sessionStore"  # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions  max-branch-session-size = 16384  # globe session size , if exceeded throws exceptions  max-global-session-size = 512  # file buffer size , if exceeded allocate new buffer  file-write-buffer-cache-size = 16384  # when recover batch read size  session.reload.read_size = 100  # async, sync  flush-disk-mode = async  }  ## database store  db {  ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.  datasource = "dbcp"  ## mysql/oracle/h2/oceanbase etc.  db-type = "mysql"  #driver-class-name = "com.mysql.jdbc.Driver"  driver-class-name = "com.mysql.cj.jdbc.Driver"  url = "jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8"  user = "root"  password = "root"  min-conn = 1  max-conn = 3  global.table = "global_table"  branch.table = "branch_table"  lock-table = "lock_table"  query-limit = 100  }  
}  
lock {  ## the lock store mode: local、remote  mode = "remote"  local {  ## store locks in user's database  }  remote {  ## store locks in the seata's server  }  
}  
recovery {  #schedule committing retry period in milliseconds  committing-retry-period = 1000  #schedule asyn committing retry period in milliseconds  asyn-committing-retry-period = 1000  #schedule rollbacking retry period in milliseconds  rollbacking-retry-period = 1000  #schedule timeout retry period in milliseconds  timeout-retry-period = 1000  
}  transaction {  undo.data.validation = true  undo.log.serialization = "jackson"  undo.log.save.days = 7  #schedule delete expired undo_log in milliseconds  undo.log.delete.period = 86400000  undo.log.table = "undo_log"  
}  ## metrics settings  
metrics {  enabled = false  registry-type = "compact"  # multi exporters use comma divided  exporter-list = "prometheus"  exporter-prometheus-port = 9898  
}  support {  ## spring  spring {  # auto proxy the DataSource bean  datasource.autoproxy = false  }  
}

注意修改这两处:

几个配置文件对应关系

(4) registry.conf

指明注册到nacos中:

registry {# file 、nacos 、eureka、redis、zk、consul、etcd3、sofatype = "nacos"nacos {serverAddr = "localhost:8848"namespace = ""cluster = "default"}eureka {serviceUrl = "http://localhost:8761/eureka"application = "default"weight = "1"}redis {serverAddr = "localhost:6379"db = "0"}zk {cluster = "default"serverAddr = "127.0.0.1:2181"session.timeout = 6000connect.timeout = 2000}consul {cluster = "default"serverAddr = "127.0.0.1:8500"}etcd3 {cluster = "default"serverAddr = "http://localhost:2379"}sofa {serverAddr = "127.0.0.1:9603"application = "default"region = "DEFAULT_ZONE"datacenter = "DefaultDataCenter"cluster = "default"group = "SEATA_GROUP"addressWaitTime = "3000"}file {name = "file.conf"}
}config {# file、nacos 、apollo、zk、consul、etcd3type = "file"nacos {serverAddr = "localhost"namespace = ""}consul {serverAddr = "127.0.0.1:8500"}apollo {app.id = "seata-server"apollo.meta = "http://192.168.1.204:8801"}zk {serverAddr = "127.0.0.1:2181"session.timeout = 6000connect.timeout = 2000}etcd3 {serverAddr = "http://localhost:2379"}file {name = "file.conf"}
}
(5) domain

domain 就是entity(pojo,bean),对应数据库的表,不同公司习惯不一样。
新建Order类与CommonResult类

Order

package com.java.springcloud.domain;  import lombok.AllArgsConstructor;  
import lombok.Data;  
import lombok.NoArgsConstructor;  import java.math.BigDecimal;  @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;  
}

CommonResult

package com.java.springcloud.domain;  import lombok.AllArgsConstructor;  
import lombok.Data;  
import lombok.NoArgsConstructor;  @Data  
@NoArgsConstructor  
@AllArgsConstructor  
public class CommonResult<T> {  private Integer code;  private String message;  private T data;  /**  * 这种构造函数重载的方式常用于简化对象的创建过程。  * 例如,在创建CommonResult对象时,如果不需要提供data参数,  * 可以直接使用只接受code和message的构造函数,这样可以减少代码的复杂度和冗余。  * @param code  * @param message  */  public CommonResult(Integer code, String message){  this(code,message,null);  }  }
(6) Dao接口及实现(SQL映射文件)

dao中至少要有两个方法,一个是创建订单,一个是修改订单状态

OrderDao

package com.java.springcloud.dao;  import com.java.springcloud.domain.Order;  
import org.apache.ibatis.annotations.Mapper;  
import org.apache.ibatis.annotations.Param;  @Mapper  
public interface OrderDao {  /**  * 创建订单  * @param order  */  void create(Order order);  /**  * 修改订单状态  */  void update(@Param("userId")Long userId,@Param("status")Integer  status);  }

OrderMapper.xml
resources文件夹下新建mapper文件夹后添加OrderMapper.xml。完成dao的具体实现。

<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >  <mapper namespace="com.java.springcloud.dao.OrderDao">  <resultMap id="BaseResultMap" type="com.java.springcloud.domain.Order">  <id column="id" property="id" jdbcType="BIGINT"/>  <result column="user_id" property="userId" jdbcType="BIGINT"/>  <result column="product_id" property="productId" jdbcType="BIGINT"/>  <result column="count" property="count" jdbcType="INTEGER"/>  <result column="money" property="money" jdbcType="DECIMAL"/>  <result column="status" property="status" jdbcType="INTEGER"/>  </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 status = #{status};    </update>  
</mapper>
(7) Service接口及实现

Order2001驱动自己,外加调用库存和账户:共3个service

OrderService

package com.java.springcloud.service;  import com.java.springcloud.domain.Order;  
import org.springframework.cloud.openfeign.EnableFeignClients;  public interface OrderService {  void create(Order order);  
}

StorageService

package com.java.springcloud.service;  import org.springframework.cloud.openfeign.EnableFeignClients;  
import org.springframework.cloud.openfeign.FeignClient;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestParam;  @FeignClient(value = "seata-storage-service")  
public interface StorageService {  /**  * 修改库存  */  @PostMapping("/storage/decrease")  void decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);  }

AccountService

package com.java.springcloud.service;  import org.springframework.cloud.openfeign.FeignClient;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestParam;  import java.math.BigDecimal;  @FeignClient(value="seata-account-service")  
public interface AccountService {  /**  * 修改库存  * 必须使用RequestParam注解,不然启动就会报错  */  @PostMapping("/account/decrease")  void decrease(@RequestParam("userId") Long userId, @RequestParam("money")BigDecimal money);  }

OrderServiceImpl

package com.java.springcloud.service.impl;  import com.java.springcloud.dao.OrderDao;  
import com.java.springcloud.domain.Order;  
import com.java.springcloud.service.AccountService;  
import com.java.springcloud.service.OrderService;  
import com.java.springcloud.service.StorageService;  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.stereotype.Service;  import javax.annotation.Resource;  @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("新建订单,用户id是{}",order.getUserId());  orderDao.create(order);  //远程调用库存服务扣减库存  log.info("调用库存服务,扣减库存-开始");  storageService.decrease(order.getUserId(),order.getCount());  log.info("调用库存服务,扣减库存-结束");  //远程调用账户服务扣减余额  log.info("调用账户服务,扣减金额-开始");  accountService.decrease(order.getUserId(),order.getMoney());  //修改订单状态为已完成  log.info("调用账户服务,扣减金额-结束");  orderDao.update(order.getUserId(),0);  log.info("新建订单结束,用户id是{}",order.getUserId());  }  
}
(8) controller
package com.java.springcloud.controller;  import com.java.springcloud.domain.CommonResult;  
import com.java.springcloud.domain.Order;  
import com.java.springcloud.service.OrderService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.RestController;  @RestController  
public class OrderController  
{  @Autowired  private OrderService orderService;  @GetMapping("/order/create")  public CommonResult create(Order order){  orderService.create(order);  return new CommonResult(200,"订单创建成功");  }  }
(9) config

MyBatisConfig
mybatis配置类,绑定实现文件OrderMapper.xml与Dao接口

package com.java.springcloud.config;  import org.mybatis.spring.annotation.MapperScan;  
import org.springframework.context.annotation.Configuration;  @Configuration  
@MapperScan({"com.java.springcloud.dao"})  
public class MyBatisConfig {  
}

DataSourceProxyConfig

package com.java.springcloud.config;  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;  @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();  }  }

DataSouce的包是sql下的,DataSourceProxy是seata下的,不要搞错了。

(10) 主启动类
package com.java.springcloud;  import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;  
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;  
import org.springframework.cloud.openfeign.EnableFeignClients;  @EnableDiscoveryClient  
@EnableFeignClients  
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源的自动创建  
public class SeataOrderMainApp2001 {  public static void main(String[] args) {  SpringApplication.run(SeataOrderMainApp2001.class, args);  }  
}

启动测试
先启动nacos-1.1.4和seata-0.9.0,再启动2001。
2001启动成功,成功注册到nacos中

测试nacos-2.0.3和seata-0.9.0,再启动2001 也可以启动成功

3.2 新建库存Storage-Module——seata-storage-service2002

(1) pom
<dependencies><!--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><artifactId>seata-all</artifactId><groupId>io.seata</groupId></exclusion></exclusions></dependency><dependency><groupId>io.seata</groupId><artifactId>seata-all</artifactId><version>0.9.0</version></dependency><!--feign--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.0.0</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.37</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.10</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies>
(2) application.yml

用到的数据库跟上面的是不一样的,我们就是为了测试分布式数据库的

server:  port: 2002  spring:  application:  name: seata-storage-service  cloud:  alibaba:  seata:  tx-service-group: xu_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&serverTimezone=GMT%2B8&useInformationSchema=false  # 指定MySQL数据库连接URL  username: root  password: root  logging:  level:  io:  seata: info  mybatis:  mapperLocations: classpath:mapper/*.xml
(3) file.conf & registry.conf

跟2001的一模一样

(4) domain

Storage

package com.java.springcloud.domain;  import lombok.AllArgsConstructor;  
import lombok.Data;  
import lombok.NoArgsConstructor;  @Data  
@NoArgsConstructor  
@AllArgsConstructor  
public class Storage  
{  private Long id;  /**  * 产品id  */    private Long productId;  /**  * 总库存  */  private Integer total;  /**  * 已用库存  */  private Integer used;  /**  * 剩余库存  */  private Integer residue;  }

CommonResult

package com.java.springcloud.domain;  import lombok.AllArgsConstructor;  
import lombok.Data;  
import lombok.NoArgsConstructor;  @Data  
@NoArgsConstructor  
@AllArgsConstructor  
public class CommonResult<T> {  private Integer code;  private String message;  private T data;  /**  * 这种构造函数重载的方式常用于简化对象的创建过程。  * 例如,在创建CommonResult对象时,如果不需要提供data参数,  * 可以直接使用只接受code和message的构造函数,这样可以减少代码的复杂度和冗余。  * @param code  * @param message  */    public CommonResult(Integer code, String message){  this(code,message,null);  }  }
(5) Dao接口及实现(SQL映射文件)

StorageDao

package com.java.springcloud.dao;  import org.apache.ibatis.annotations.Mapper;  
import org.apache.ibatis.annotations.Param;  @Mapper  
public interface StorageDao {  /**  * 扣减库存  */  void decrease(@Param("productId") Long productId, @Param("count") Integer count);  }

StorageMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >  <mapper namespace="com.java.springcloud.dao.StorageDao">  <resultMap id="BaseResultMap" type="com.java.springcloud.domain.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>
(6) Service接口及实现

StorageService

package com.java.springcloud.service;  import org.springframework.cloud.openfeign.FeignClient;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestParam;  public interface StorageService {  /**  * 修改库存  */  @PostMapping("/storage/decrease")  void decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);  }

StorageServiceImpl

package com.java.springcloud.service.impl;  import com.java.springcloud.dao.StorageDao;  
import com.java.springcloud.service.StorageService;  
import org.apache.ibatis.annotations.Param;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Service;  @Service  
public class StorageServiceImpl implements StorageService {  @Autowired  private StorageDao storageDao;  @Override  public void decrease(@Param("productId") Long productId, @Param("count") Integer count) {  storageDao.decrease(productId,count);  }  
}
(7) Controller
package com.java.springcloud.controller;  import com.java.springcloud.domain.CommonResult;  
import com.java.springcloud.service.StorageService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RestController;  @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,"扣减库存成功!");  }  }
(8) config配置

与2001的一模一样

package com.java.springcloud.config;  import org.mybatis.spring.annotation.MapperScan;  
import org.springframework.context.annotation.Configuration;  @Configuration  
@MapperScan({"com.java.springcloud.dao"})  
public class MyBatisConfig {  
}
package com.java.springcloud.config;  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;  @Configuration  
public class DataSourceProxyConfig {  @Value("${mybatis.mapper-locations}")  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();  }  }
(9) 主启动类
package com.java.springcloud;  import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;  
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;  
import org.springframework.cloud.openfeign.EnableFeignClients;  @EnableFeignClients  
@EnableDiscoveryClient  
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})  
public class SeataStorageServiceApplication2002 {  public static void main(String[] args) {  SpringApplication.run(SeataStorageServiceApplication2002.class, args);  }  
}

启动测试
启动nacos、seata、2002;启动成功,注册进nacos。

3.3 新建账户Account-Module——seata-account-service2003

(1) pom
<?xml version="1.0" encoding="UTF-8"?>  
<project xmlns="http://maven.apache.org/POM/4.0.0"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  <parent>        <artifactId>springcloud2020</artifactId>  <groupId>org.xu.springcloud</groupId>  <version>1.0.0</version>  </parent>    <modelVersion>4.0.0</modelVersion>  <artifactId>seata-account-service2003</artifactId>  <properties>        <maven.compiler.source>8</maven.compiler.source>  <maven.compiler.target>8</maven.compiler.target>  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  </properties>    <dependencies>        <!--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>                    <artifactId>seata-all</artifactId>  <groupId>io.seata</groupId>  </exclusion>            </exclusions>        </dependency>        <dependency>            <groupId>io.seata</groupId>  <artifactId>seata-all</artifactId>  <version>0.9.0</version>  </dependency>        <!--feign-->  <dependency>  <groupId>org.springframework.cloud</groupId>  <artifactId>spring-cloud-starter-openfeign</artifactId>  </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-web</artifactId>  </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-test</artifactId>  <scope>test</scope>  </dependency>        <dependency>            <groupId>org.mybatis.spring.boot</groupId>  <artifactId>mybatis-spring-boot-starter</artifactId>  <version>2.0.0</version>  </dependency>        <dependency>            <groupId>mysql</groupId>  <artifactId>mysql-connector-java</artifactId>  <version>8.0.18</version>  </dependency>        <dependency>            <groupId>com.alibaba</groupId>  <artifactId>druid-spring-boot-starter</artifactId>  <version>1.1.10</version>  </dependency>        <dependency>            <groupId>org.projectlombok</groupId>  <artifactId>lombok</artifactId>  <optional>true</optional>  </dependency>    </dependencies>  
</project>
(2) application.yml
server:  port: 2003  spring:  application:  name: seata-account-service  cloud:  alibaba:  seata:  tx-service-group: xu_group  nacos:  discovery:  server-addr: localhost:8848  datasource:  driver-class-name: com.mysql.cj.jdbc.Driver  url: jdbc:mysql://localhost:3306/seata_account?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8&useInformationSchema=false  # 指定MySQL数据库连接URL  username: root  password: root  feign:  hystrix:  enabled: false  logging:  level:  io:  seata: info  mybatis:  mapperLocations: classpath:mapper/*.xml
(3) file.conf & registry.conf

跟2001一模一样

(4) domain

Account

package com.java.springcloud.domain;  import lombok.AllArgsConstructor;  
import lombok.Data;  
import lombok.NoArgsConstructor;  import java.math.BigDecimal;  @Data  
@NoArgsConstructor  
@AllArgsConstructor  
public class Account {  private Long id;  /**  * 用户id  */    private Long userId;  /**  * 总额度  */  private BigDecimal total;  /**  * 已用额度  */  private BigDecimal used;  /**  * 剩余额度  */  private BigDecimal residue;  }

CommonResult

(5) Dao接口及实现

AccountDao

package com.java.springcloud.dao;  import org.apache.ibatis.annotations.Mapper;  
import org.apache.ibatis.annotations.Param;  import java.math.BigDecimal;  @Mapper  
public interface AccountDao {  void decrease(@Param("userId") Long userId, @Param("money") BigDecimal money);  
}

AccountMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >  <mapper namespace="com.java.springcloud.dao.AccountDao">  <resultMap id="BaseResultMap" type="com.java.springcloud.domain.Account">  <id column="id" property="id" jdbcType="BIGINT"/>  <result column="user_id" property="userId" jdbcType="BIGINT"/>  <result column="total" property="total" jdbcType="DECIMAL"/>  <result column="used" property="used" jdbcType="DECIMAL"/>  <result column="residue" property="residue" jdbcType="DECIMAL"/>  </resultMap>  <update id="decrease">  UPDATE t_account  SET            residue = residue - #{money},used = used + #{money}        WHERE            user_id = #{userId};    </update>  </mapper>
(6) Service接口及实现

AccountService

package com.java.springcloud.service;  import org.springframework.web.bind.annotation.RequestParam;  import java.math.BigDecimal;  public interface AccountService {  void decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);  }

AccountServiceImpl

package com.java.springcloud.service.impl;  import com.java.springcloud.dao.AccountDao;  
import com.java.springcloud.service.AccountService;  
import org.springframework.stereotype.Service;  import javax.annotation.Resource;  
import java.math.BigDecimal;  @Service  
public class AccountServiceImpl implements AccountService {  @Resource  private AccountDao accountDao;  @Override  public void decrease(Long userId, BigDecimal money) {  accountDao.decrease(userId,money);  }  
}
(7) Controller
package com.java.springcloud.controller;  import com.java.springcloud.domain.CommonResult;  
import com.java.springcloud.service.AccountService;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestParam;  
import org.springframework.web.bind.annotation.RestController;  import javax.annotation.Resource;  
import java.math.BigDecimal;  
import java.util.concurrent.TimeUnit;  @RestController  
public class AccountController {  @Resource  private AccountService accountService;  @PostMapping("/account/decrease")  public CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money) {  try {  TimeUnit.MILLISECONDS.sleep(2000);  } catch (InterruptedException e) {  throw new RuntimeException(e);  }  accountService.decrease(userId, money);  return new CommonResult(200, "扣减账户余额成功!");  }  }
(8) config配置

和2001一模一样

(9) 主启动类
package com.java.springcloud;  import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;  
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;  
import org.springframework.cloud.openfeign.EnableFeignClients;  @EnableDiscoveryClient  
@EnableFeignClients  
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})  
public class SeataAccountMainApp2003 {  public static void main(String[] args) {  SpringApplication.run(SeataAccountMainApp2003.class,args);  }  
}

启动测试
启动nacos、seata、2003;启动成功,成功注册进nacos

填坑高版本seata1.4.2client配置

seata1.2.0 Seata1.4.0+nacos
使用seata1.4.2 各个微服务整体上的代码是差不多的,区别的地方在于1.4.2支持yml、properties文件里配置client端的seata,不再需要file.conf/registry.conf文件。同时支持@EnableAutoDataSourceProxy注解开启数据源的自动代理(不需要手动配置数据源)

① 修改父工程版本控制

seata高版本各微服务可以直接通过yml、properties来配置seata,不需要在微服务中加入file.conf和registry.conf文件。

注意版本对应关系,这里要使用SpringCloud Hoxton.SR9+SpringCloud Alibaba 2.2.6.RELEASE+Spring Boot 2.3.2RELEASE+Nacos 1.4.2(我用的2.0.3)+Seata 1.3.0(我用的1.4.2)。
修改父工程的依赖版本,主要是让springboot、SpringCloud、SpringCloud alibaba版本对应。

② 修改微服务模块seata依赖pom

官网上对seata依赖是这么描述的:

官网推荐依赖配置方式:

但是经过我测试发现,还是需要排除掉spring-cloud-starter-alibaba-seata里面的seata-all

实际上我有微服务只配置了seata-spring-boot-starter依赖,spring-cloud-starter-alibaba-seata没有配置,并不影响正常使用。

③ 修改微服务application.yml

直接将seata的相关配置,配置到application.yml文件中,几个微服务的yml类似:

注意对应关系:

④ 修改主启动类和DataSourceProxyConfig类

主启动类
主启动类加上@EnableAutoDataSourceProxy注解,这里以storage的微服务为例:

配置类
使用@EnableAutoDataSourceProxy注解后,不再需要DataSourceProxyConfig配置数据源代理。强行写会报错。如果需要自己配置数据源代理的话,在application.yml中设置seata.enable-auto-data-source-proxy为false,主启动类上去掉@EnableAutoDataSourceProxy注解即可。

参考博客
1Seata1.4.2+Nacos搭建使用
2Seata1.4.2整合SpringCloud H——Seata安装与搭建
3最详细实际项目中seata踩坑、部署教程以及多链路调用_seata lock.retrytimes-CSDN博客
4spring cloud使用nacos和seata(windows环境)
5SEATA配合nacos使用

四、测试

Seata全局事务怎么使用
Spring提供的本地事务:@Transactional
Seata提供的全局事务:@GlobalTransactional

4.0 数据库初始情况

下订单->减库存->扣余额->改(订单)状态

4.1 测试正常下单

启动nacos、seata、2001、2002、2003;
测试:http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100

报错
java.sql.SQLException:Failed to fetch schema of t_order
Connector/J 5.0.0以后的版本有一个名为useInformationSchema的数据库连接参数,Connector/J 在mysql8.0中默认配置连接属性useInformationSchema为true,使查询table信息时更为有效。用户依然可以配置useInformationSchema为false,但是在8.0.3及其之后的版本中,由于不能支持早期的特性,某些数据字典的查询可能会失败。
useInformationSchema配置为false的时候,也可能会造成REMARKS信息(对应数据库中各字段的comment)的缺失。

在各微服务的application.yml 文件的spring.datasource.url 后面加上&useInformationSchema=false设置useInformationSchema为false,即可解决该问题。 参考:jdbc中useInformationSchema属性分析 - 简书

再次测试
访问成功

数据库情况:

4.2 测试超时异常:不加@GlobalTransactional

AccountServiceImpl添加超时:

我们使用的是Openfeign,默认超时时长是1s,这里我们延迟30s。

报错超时异常:


数据库情况:

当库存和账户金额扣减后,订单状态并没有设置为已经完成,没有从零改为1;而且由于feign的重试机制,账户余额还有可能被多次扣减。

4.3 测试超时异常:加@GlobalTransactional

OrderServiceImpl添加@GlobalTransactional注解,注意改注解只能用在方法上!

这个注解加在哪个业务类上呢? 谁是最开始的发起者就加在谁上面
 


●name:给定全局事务实例的名称,随便取,唯一即可
●rollbackFor:当发生什么样的异常时,进行回滚
●noRollbackFor:发生什么样的异常不进行回滚。

测试:
依然超时异常 ,


重点是数据库有没有发生变化:

我们发现数据库中的数据根本就没有变化,记录都添加不进来,说明回滚成功!

4.4 小结

做好配置后,我们只需要使用一个 @GlobalTransactional(name = "lsp-create-order", rollbackFor = Exception.class) 放在业务的入口,即可实现控制全局的事务。注意该注解只能放在方法上。

五、补充说明

Seata:Simple Extensible Autonomous Transaction Architecture,简单可扩展自治事务框架
0.9不支持集群,生产环境请使用1.0以上的版本。

5.0 undo_log表的作用

模块内方法也可以加@Transactional注解,如果一个模块的事务提交了,Seata会把提交了哪些数据记录到undo_log表中,如果这时TC通知全局事务回滚,那么RM就从undo_log表中获取之前修改了哪些资源,并根据这个表回滚。(有待考证)

5.1 再看TC/TM/RM三大组件

TC:seata服务器; (我们电脑上启动的seata )
TM:事物的发起者,业务的入口。 哪个微服务使用了@GlobalTransactional哪个就是TM
RM:事务的参与者,一个数据库就是一个RM。

分布式事务的执行流程:
1TM 开启分布式事务(TM 向 TC 注册全局事务记录);
2按业务场景,编排数据库、服务等事务内资源(RM 向 TC 汇报资源准备状态 );
3TM 结束分布式事务,事务一阶段结束(TM 通知 TC 提交/回滚分布式事务);
4TC 汇总事务信息,决定分布式事务是提交还是回滚;
5TC 通知所有 RM 提交/回滚 资源,事务二阶段结束。

5.2 AT模式(默认)如何做到对业务的无侵入

Seata有四大模式:AT(默认)、TCC、SAGA、XA。(阿里云上的AT叫做GTS,收费)
AT模式
AT模式两阶段提交协议的演变:
●一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
●二阶段:
○提交异步化,非常快速地完成。
○回滚通过一阶段的回滚日志进行反向补偿(前面insert,后面回滚时就delete)。

每个数据库除了自身存储数据的表以外,都会有一个事务回滚表:undo_log
Seata库中存在:branch_table\global_table\lock_table\distributed_lock(高版本才有)这样一些表

5.2.1 一阶段加载

在一阶段,Seata 会拦截“业务 SQL”,
1 解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”(前置镜像)
2 执行“业务 SQL”更新业务数据,在业务数据更新之后,
3 其保存成“after image”,最后生成行锁。
以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。

5.2.2 二阶段提交

因为“业务 SQL”在一阶段已经提交至数据库,二阶段如果顺利提交的话,那么Seata框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

5.2.3 二阶段回滚

二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。
回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”。如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。

5.3 debug查看流程

最开是seata库中的三张表是没有数据的

2003打上断点,debug启动

访问http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100。


此时seata库中的三个表都是有数据的:

看一下branch_table,记录了各个RM的信息,分别对应order、storage、account三个微服务

可以看到xid跟global_table中的xid一致。

再看global_table:

查看lock_table:
 


rollback_info是JSON字符串,存储了beforeimage、afterimage:

查看seata_storage库中的undo_log表的roobal_info信息,可以看到beforeimage和afterimage分别保存了修改前后的信息。

debug放行,seata库中表中的中间数据和undo_log表的数据都删除了。(我的seata_account表的undo_log中没有被删除,等了半天也没有。)异步任务阶段的分支提交请求将异步和批量地删除相应的undo_log记录。
发现account2003微服务的日志跟2001和2002都不一样

5.4 整体流程图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/91925.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/91925.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式开发学习———Linux环境下数据结构学习(五)

折半查找&#xff08;二分查找&#xff09;适用于已排序的数组&#xff0c;通过不断缩小查找范围定位目标值。int binarySearch(int arr[], int size, int target) {int left 0, right size - 1;while (left < right) {int mid left (right - left) / 2;if (arr[mid] t…

(一)React +Ts(vite创建项目/useState/Props/Interface)

文章目录 项目地址 一、React基础 1.1 vite创建 1. 创建项目 2. 安装项目所需环境 1.2 jsx 1. 三元表达式 1.3 基础 1. 创建第一个组件 2. 安装boostrap 3. 插件常用命令 4. map 二、组件 2.1 useState 1. useState 2. 使用 3.更新对象 4. 更新数组(增,删,改) 5. 使用immer…

网关和BFF是如何演化的

BFF&#xff08;Backend For Frontend&#xff09;:对返回的数据结构进行聚合、裁剪、透传等适配逻辑。 适用于API网关之后的数据聚合、裁剪与透传简化客户端逻辑&#xff0c;减少网络开销敏感数据过滤 BFF逻辑层 架构没有最好&#xff0c;要看是否满足当前的业务场景。 业务的…

SQL中的WITH语句(公共表表达式CTE)解释

SQL中的WITH语句&#xff08;公共表表达式CTE&#xff09; WITH语句&#xff0c;也称为公共表表达式&#xff08;Common Table Expression&#xff0c;CTE&#xff09;&#xff0c;是SQL中一种强大的功能&#xff0c;它允许你创建临时结果集&#xff0c;这些结果集可以在后续的…

服务器地域选择指南:深度分析北京/上海/广州节点对网站速度的影响

更多云服务器知识&#xff0c;尽在hostol.com你准备开一个覆盖全国的线上零食店&#xff0c;现在万事俱备&#xff0c;只差一个核心问题没解决&#xff1a;你唯一的那个总仓库&#xff0c;应该建在哪里&#xff1f;是建在哈尔滨&#xff0c;让南方的朋友下单后&#xff0c;一包…

桶排序-Java实现

桶排序是一种分配式排序算法&#xff0c;将元素分到有限数量的桶里&#xff0c;每个桶再单独排序&#xff08;比如用插入排序&#xff09;&#xff0c;最后依次把各个桶中的元素取出来即完成排序。 时间复杂度&#xff1a;最佳 O(n) | 平均 O(n n/k k) | 最差 O(n) 空间复杂…

oracle知识

这里写自定义目录标题Oracle常用的数据类型&#xff1a;Oracle实操&#xff1a;创建数据表Oracle约束建表的时候设置约束&#xff1a;表创建后添加添加约束&#xff1a;Oracle常用的数据类型&#xff1a; Oracle实操&#xff1a;创建数据表 Oracle约束 建表的时候设置约束&…

超级人工智能+无人机操控系统,振兴乡村经济的加速器,(申请专利应用),严禁抄袭!

无人机边缘智能系统&#xff1a;山林珍稀资源探测的完整架构与实战指南本人设计的多模态边缘AI系统已在秦岭山区完成实地验证&#xff0c;对7种高价值食用菌识别准确率达94.3%&#xff0c;定位误差小于0.8米一、前沿技术融合的商业化机遇根据Gartner 2025年技术成熟度曲线分析&…

用腾讯地图写一个逆地址解析(很详细)

首先说明以下代码适合有前端基础知识的同学。以下是css和html部分<!DOCTYPE html><html lang"zh-CN"><!-- lang是用来申明语言类型&#xff0c;这里申明为中文&#xff08;zh&#xff09;中国大陆&#xff08;CN&#xff09;补充中文繁体为zh-TW --&g…

在 Vue3+Vite+TypeScript 项目中使用 svg 文件并支持自定义样式

参考文档&#xff1a;vite-svg-loader 安装与配置 安装插件 pnpm add vite-svg-loader -D配置 // vite.config.ts import svgLoader from vite-svg-loaderexport default defineConfig({plugins: [vue(),svgLoader({defaultImport: component})] })使用 <script setup …

ShimetaPi M4-R1:国产高性能嵌入式平台的异构计算架构与OpenHarmony生态实践

在全球化芯片供应链波动及树莓派等硬件持续涨价的背景下&#xff0c;ShimetaPi M4-R1 作为全栈国产化嵌入式开发平台&#xff0c;以 高性能异构计算架构 和 开源鸿蒙原生支持 为核心突破点&#xff0c;填补了中高端边缘设备开发的国产方案空白。其基于瑞芯微 RK3568B2 的硬件设…

zookeeper分布式锁 -- 读锁和写锁实现方式

读锁和写锁读锁: 是共享锁,读锁与读锁是可以兼容的,所以同时有多个请求都可以持有写锁: 是独占锁,写锁与任何锁都互斥,所以只有一个请求持有,这个请求释放写锁其他请求才能持有一旦持有写锁,说明数据在发送变化就不能读了,自然一个请求就不能出现读锁和写锁共存的情况总结: 读锁…

第二篇:Linux 文件系统操作:从基础到进阶

目录 一、文件与目录管理基础 创建文件 创建目录 目录结构查看 二、链接文件深入理解 创建软链接 创建硬链接 核心区别对比 三、文件压缩与解压缩全攻略 1、压缩命令对比 2、解压缩命令 3、三种压缩方式性能对比 4、通用解压技巧 四、文件查找与搜索 1、按文件名…

哔哩哔哩招游戏内容产品运营

游戏内容产品运营【2026届】&#xff08;岗位信息已获jobleap.cn授权转发到csdn&#xff09;哔哩哔哩集团 上海收录时间&#xff1a; 2025年08月01日职位描述1、负责研究B站游戏创作者的创作过程、动机及遇到的问题&#xff0c;产出研究报告&#xff1b; 2、结合用研分析和相关…

谈谈Flutter中的Key

目录 前言 一、什么是Key 1.StatelessWidget 2.StatefulWidget 3.加入Key后的效果 二、什么时候应该使用 Key&#xff1f; 1.Flutter判断widget的逻辑 1.Flutter判断组件身份的规则 1.Widget的类型&#xff08;runtimeType&#xff09;相同 2. Key相同&#xff08;ke…

重生之我在暑假学习微服务第八天《OpenFeign篇》

个人主页&#xff1a;VON文章所属专栏&#xff1a;微服务 微服务系列文章 重生之我在暑假学习微服务第一天《MybatisPlus-上篇》重生之我在暑假学习微服务第二天《MybatisPlus-下篇》重生之我在暑假学习微服务第三天《Docker-上篇》重生之我在暑假学习微服务第四天《Docker-下篇…

风光储综合能源系统双层优化规划设计【MATLAB模型实现】

本模型基于双层优化框架&#xff0c;利用KKT条件、大M法、对偶理论求解&#xff0c;专注于综合能源系统&#xff08;微电网&#xff09;多电源容量优化配置的模型介绍。代码采用CPLEX求解器&#xff0c;注释详尽&#xff0c;非常适合新手学习该类问题的建模与求解思路。 模型总…

雪花算法重复id问题

原理解析 雪花算法实现简单、适配性强&#xff0c;无论是电商订单、日志追踪还是分布式存储&#xff0c;都能满足 “唯一、有序、高效、可扩展” 的核心需求&#xff0c;因此成为分布式ID主流选择。雪花算法生成的ID是一个64位的整数&#xff0c;由多段不同意义的数字拼接而成&…

MQTT 入门教程:三步从 Docker 部署到 Java 客户端实现

在物联网&#xff08;IoT&#xff09;与边缘计算快速发展的今天&#xff0c;设备间的高效通信成为核心需求。MQTT 作为一种轻量级的发布 / 订阅模式协议&#xff0c;凭借其低带宽占用、强稳定性和灵活的消息路由能力&#xff0c;已成为物联网通信的事实标准。无论是智能家居的设…

公网服务器上Nginx或者Openresty如何屏蔽IP直接扫描

0x01 背景云服务器很多时候为了通信需要设置公网访问&#xff0c;但是网络当中存在很多的扫描器&#xff0c;无时无刻在扫描&#xff0c;当80,443端口暴露时&#xff0c;成了这些扫描IP的攻击对象&#xff0c;无时无刻收到威胁。0x02 扫描攻击方式1.直接通过公网IP地址进行一些…