SpringCloud Alibaba
SpringCloud Alibaba
一、入门简介
1、why会出现SpringCloud alibaba
1.1、Spring Cloud Netflix项目进入维护模式
1.2、Spring Cloud Netflix Projects Entering Maintenance Mode
1.2.1、什么是维护模式
将模块置于维护模式,意味着 Spring Cloud 团队将不会再向模块添加新功能。 我们将修复 block 级别的 bug 以及安全问题,我们也会考虑并审查社区的小型 pull request。
1.2.2、进入维护模式意味着什么呢?
进入维护模式意味着
Spring Cloud Netflix 将不再开发新的组件 我们都知道Spring Cloud 版本迭代算是比较快的,因而出现了很多重大ISSUE都还来不及Fix就又推另一个Release了。进入维护模式意思就是目前一直以后一段时间Spring Cloud Netflix提供的服务和功能就这么多了,不在开发新的组件和功能了。以后将以维护和Merge分支Full Request为主
新组件功能将以其他替代平代替的方式实现
2、SpringCloud alibaba带来了什么
2.1、是什么
官网:
Spring Cloud Alibaba 参考文档 (spring-cloud-alibaba-group.github.io)
spring-cloud-alibaba/README-zh.md at 2021.x · alibaba/spring-cloud-alibaba · GitHub
诞生: 2018.10.31,Spring Cloud Alibaba 正式入驻了 Spring Cloud 官方孵化器,并在 Maven 中央库发布了第一个版本。
2.2、能干嘛
- 服务限流降级:默认支持 Servlet、Feign、RestTemplate、Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
- 服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
- 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
- 消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
- 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
- 分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。
2.3、去哪下载
spring-cloud-alibaba/README-zh.md at 2021.x · alibaba/spring-cloud-alibaba · GitHub
2.4、怎么玩
3、SpringCloud alibaba学习资料获取
3.1、官网
Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。
依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。
SpringCloud Alibaba进入了SpringCloud官方孵化器,而且毕业了
3.2、英文版
spring-cloud-alibaba-group.github.io/github-page…
3.3、中文版
spring-cloud-alibaba/README-zh.md at 2021.x · alibaba/spring-cloud-alibaba · GitHub
二、SpringCloud Alibaba Nacos服务注册和配置中心
1、Nacos简介
1.1、为什么叫Nacos
前四个字母分别为Naming和Configuration的前两个字母,最后的s为Service。
1.2、是什么
一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
Nacos: Dynamic Naming and Configuration Service
Nacos就是注册中心 + 配置中心的组合
等价于:Nacos = Eureka+Config +Bus
1.3、能干嘛
替代Eureka做服务注册中心
替代Config做服务配置中心
1.4、去哪下
官网文档:
spring-cloud-alibaba-group.github.io/github-page…
1.5、各种注册中心比较
据说 Nacos 在阿里巴巴内部有超过 10 万的实例运行,已经过了类似双十一等各种大型流量的考验
2、安装运行Nacos
本地Java8+Maven环境已经OK
2.1、先从官网下载Nacos:github.com/alibaba/nac…
2.2、解压安装包,直接运行bin目录下的startup.cmd
startup.cmd -m standalone
新版的需要修改配置application.properties
使用以下程序生成秘钥:
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class SecretKeyGenerator {
public static SecretKey generate() {
try {
// 使用AES算法生成256位的SecretKey
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(256);
return keyGenerator.generateKey();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
public static void main(String[] args) {
SecretKey secretKey = generate();
System.out.println(Base64.getEncoder().encodeToString(secretKey.getEncoded()));
}
}
搜索并添加到配置文件中:
nacos.core.auth.plugin.nacos.token.secret.key=
2.3、登录
默认账号密码都是:nacos
2.4、结果页面:
3、Nacos作为服务注册中心演示
3.1、官网文档
Spring Cloud Alibaba Reference Documentation (spring-cloud-alibaba-group.github.io)
3.2、基于Nacos的服务提供者
3.2.1、新建cloudalibaba-provider-payment9001
3.2.2、改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>cloud2020</artifactId>
<groupId>com.lxg.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloudalibaba-provider-payment9001</artifactId>
<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>
</project>
3.2.3、改yml
server:
port: 9001
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址
management:
endpoints:
web:
exposure:
include: '*'
3.2.4、主启动类
package com.lxg.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @auther xiaolin
* @creatr 2023/3/21 15:00
*/
@EnableDiscoveryClient
@SpringBootApplication
public class PaymentMain9001
{
public static void main(String[] args) {
SpringApplication.run(PaymentMain9001.class, args);
}
}
3.2.5、业务类
package com.lxg.springcloud.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;
/**
* @auther xiaolin
* @creatr 2023/3/21 15:00
*/
@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;
}
}
3.2.6、测试
http://localhost:9001/payment/nacos/1
nacos服务注册中心+服务提供者9001都OK了
3.2.7、新建9002
新建cloudalibaba-provider-payment9002
与9001完全一致,只需要更改端口号即可
或者取巧不想新建重复体力劳动,直接拷贝虚拟端口映射:
3.3、基于Nacos的服务消费者
3.3.1、新建cloudalibaba-consumer-nacos-order83模块
3.3.2、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>cloud2020</artifactId>
<groupId>com.lxg.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloudalibaba-consumer-nacos-order83</artifactId>
<dependencies>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.lxg.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</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>
注意:老版的nacos自带ribbon负载均衡,但是新版的已经没有,需要加入loadbalancer依赖
3.3.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
3.3.4、主启动类
package com.lxg.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @auther xiaolin
* @creatr 2023/3/22 15:36
*/
@EnableDiscoveryClient
@SpringBootApplication
public class OrderNacosMain83
{
public static void main(String[] args)
{
SpringApplication.run(OrderNacosMain83.class,args);
}
}
3.3.5、业务类
ApplicationContextBean:
package com.lxg.springcloud.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;
/**
* @auther xiaolin
* @creatr 2023/3/22 15:38
*/
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
}
OrderNacosController
package com.lxg.springcloud.controller;
import lombok.extern.slf4j.Slf4j;
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;
/**
* @auther xiaolin
* @creatr 2023/3/22 15:42
*/
@RestController
@Slf4j
public class OrderNacosController {
@Value("${service-url.nacos-user-service}")
private String serverURL;
@Resource
private RestTemplate restTemplate;
@GetMapping(value = "/consumer/payment/nacos/{id}")
public String paymentInfo(@PathVariable("id") Long id){
return restTemplate.getForObject(serverURL+"/payment/nacos/"+id,String.class);
}
}
3.3.6、测试
localhost:83/consumer/payment/nacos/13
实现了负载均衡
3.4、服务注册中心对比
3.4.1、各种注册中心的对比
Nacos全景图所示
Nacos和CAP
Nacos 支持AP和CP模式的切换:
C是所有节点在同一时间看到的数据是一致的;而A的定义是所有的请求都会收到响应。
何时选择使用何种模式?
一般来说,
如果不需要存储服务级别的信息且服务实例是通过nacos-client注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如 Spring cloud 和 Dubbo 服务,都适用于AP模式,AP模式为了服务的可能性而减弱了一致性,因此AP模式下只支持注册临时实例。
如果需要在服务级别编辑或者存储配置信息,那么 CP 是必须,K8S服务和DNS服务则适用于CP模式。
CP模式下则支持注册持久化实例,此时则是以 Raft 协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误。
curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'
4、Nacos作为服务配置中心演示
4.1、Nacos作为配置中心-基础配置
4.1.1、新建cloudalibaba-config-nacos-client3377
4.1.2、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>cloud2020</artifactId>
<groupId>com.lxg.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloudalibaba-config-nacos-client3377</artifactId>
<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>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
</dependencies>
</project>
注意,使用了bootstrap.yml就别忘记引入bootstrap依赖
4.1.3、yml文件
why配置两个:
Nacos同springcloud-config一样,在项目初始化时,要保证先从配置中心进行配置拉取,
拉取配置之后,才能保证项目的正常启动。
springboot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application
优先级高先加载,正常情况应该是被覆盖的,但是bootstrap是不会被application覆盖的
bootstrap.yml:
# nacos配置
server:
port: 3377
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos服务注册中心地址
config:
server-addr: localhost:8848 #Nacos作为配置中心地址
file-extension: yaml #指定yaml格式的配置
# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
# nacos-config-client-dev.yaml
application.yml:
spring:
profiles:
active: dev # 表示开发环境
4.1.4、主启动类
package com.lxg.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @auther xiaolin
* @creatr 2023/3/22 23:05
*/
@EnableDiscoveryClient
@SpringBootApplication
public class NacosConfigClientMain3377
{
public static void main(String[] args) {
SpringApplication.run(NacosConfigClientMain3377.class, args);
}
}
4.1.5、业务类
ConfigClientController:
package com.lxg.springcloud.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;
/**
* @auther xiaolin
* @creatr 2023/3/22 23:05
*/
@RestController
@RefreshScope //在控制器类加入@RefreshScope注解使当前类下的配置支持Nacos的动态刷新功能。
public class ConfigClientController
{
@Value("${config.info}")
private String configInfo;
@GetMapping("/config/info")
public String getConfigInfo() {
return configInfo;
}
}
@RefreshScope:
4.1.6、在nacos中添加配置信息
Nacos中的匹配规则:
Nacos中的dataid的组成格式及与SpringBoot配置文件中的匹配规则
官网:
最后公式:
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
配置新增:
nacos-config-dev.yaml
Nacos界面配置对应
设置DataId
公式:spring.application.name−{spring.application.name}-spring.application.name−{spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
prefix 默认为 spring.application.name 的值
spring.profile.active 即为当前环境对应的 profile,可以通过配置项 spring.profile.active 来配置。
file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置
小总结说明:
历史配置:
Nacos会记录配置文件的历史版本默认保留30天,此外还有一键回滚功能,回滚操作将会触发配置更新
回滚:
4.1.7、测试
启动前需要在nacos客户端-配置管理-配置管理栏目下有对应的yaml配置文件
运行cloud-config-nacos-client3377的主启动类
调用接口查看配置信息:http://localhost:3377/config/info
自带动态刷新:
修改下Nacos中的yaml配置文件,再次调用查看配置的接口,就会发现配置已经刷新
4.2、Nacos作为配置中心-分类配置
4.2.1、问题
多环境多项目管理
问题1: 实际开发中,通常一个系统会准备 dev开发环境 test测试环境 prod生产环境。 如何保证指定环境启动时服务能正确读取到Nacos上相应环境的配置文件呢?
问题2: 一个大型分布式微服务系统会有很多微服务子项目, 每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境...... 那怎么对这些微服务配置进行管理呢?
4.2.2、Nacos的图形化管理界面
配置管理
命名空间
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,就是微服务的实例。
4.2.3、案例
三种方案加载配置
-
DataID方案
指定spring.profile.active和配置文件的DataID来使不同环境下读取不同的配置
默认空间+默认分组+新建dev和test两个DataID:
新建dev配置DataID:
新建test配置DataID:
通过spring.profile.active属性就能进行多环境下配置文件的读取:
测试:
http://localhost:3377/config/info
配置是什么就加载什么
-
Group方案
通过Group实现环境区分
新建group
在nacos图形界面控制台上面新建配置文件DataID:
bootstrap+application:
在config下增加一条group的配置即可。 可配置为DEV_GROUP或TEST_GROUP
-
Namespace方案
新建dev/test的Namespace:
回到服务管理-服务列表查看:
按照域名配置填写:
YML:
bootstrap:
# nacos配置 server: port: 3377 spring: application: name: nacos-config-client cloud: nacos: discovery: server-addr: localhost:8848 #Nacos服务注册中心地址 config: server-addr: localhost:8848 #Nacos作为配置中心地址 file-extension: yaml #指定yaml格式的配置 group: DEV_GROUP namespace: b4f3cb1a-30bc-4230-92a6-3c81497217f7 # ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension} # nacos-config-client-dev.yaml
application:
# Nacos注册配置,application.yml spring: profiles: #active: test active: dev #active: info
5、Nacos集群和持久化配置(重要)
5.1、官网说明
5.1.1、官网架构图
5.1.2、上图官网翻译,真实情况
即VIP就是虚拟ip配置nginx进行访问
5.1.3、说明
默认Nacos使用嵌入式数据库实现数据的存储。(如Nacos关掉后再打开,里面的配置依然存在)但是因为这样,如果启动多个默认配置下的Nacos节点,数据存储是存在一致性问题的。 为了解决这个问题,Nacos采用了集中式存储的方式来支持集群化部署,目前只支持MySQL的存储。
按照上述,我们需要mysql数据库
5.2、Nacos持久化配置解释
5.2.1、Nacos默认自带的是嵌入式数据库derby
观察到pom文件引入了derby
5.2.2、derby到mysql切换配置步骤
-
nacos安装目录conf目录下找到sql脚本
新版是mysql-schema.sql,执行脚本(新版需要自建数据库)
-
nacos安装目录conf目录下找到application.properties
添加配置
spring.datasource.platform=mysql db.num=1 db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true db.user=root db.password=123456
5.2.3、启动
启动Nacos,可以看到是个全新的空记录界面,以前是记录进derby
5.2.4、新建配置文件
已经保存进mysql数据库
5.3、Linux版 Nacos+MySQL生产环境配置
这个章节一堆坑!!!!!!
若是三台机器搭建需要每一台都按照如下配置
5.3.1、首先安装好mysql
Linux 安装Mysql 详细教程(图文教程)-CSDN博客
5.3.2、安装jdk(1.8+)
Linux 安装JDK详细步骤_linux 安装 jdk_Charge8的博客-CSDN博客
5.3.3、安装Nacos
Release 2.2.1 (Mar 17th, 2023) · alibaba/nacos · GitHub
5.3.4、Linux服务器上mysql数据库配置
SQL脚本在哪里:
复制sql语句执行即可
5.3.5、application.properties 配置
更改数据存放位置:
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=123456
新版的需要修改配置application.properties!!!!!!!!
nacos.core.auth.plugin.nacos.token.secret.key=
报错改一下午!!!!
key可以通过以上的类进行生成:[tip](# 2.2、解压安装包,直接运行bin目录下的startup.cmd)
5.3.6、需要更改机器的hostname
hostname -i 查看主机名(不能是127.0.0.1)
如果是,需要修改:
vi /etc/hostname
直接写名字
如:
centos01
继续 vi /etc/hosts
主机映射:
192.168.131.101(主机实际ip) centos01
然后重启才可以生效
5.3.7、Linux服务器上nacos的集群配置cluster.conf
如果是伪分布集群可以:
这个IP不能写127.0.0.1,必须是 Linux命令hostname -i能够识别的IP
真实三台机器:
5.3.8、编辑Nacos的启动脚本startup.sh,使它能够接受不同的启动端口
新版已经自带这个功能了(不是伪分布式和新版的可以跳过)
/mynacos/nacos/bin 目录下有startup.sh
平时单机版的启动,都是./startup.sh即可。
但是
集群启动,我们希望可以类似其它软件的shell命令,传递不同的端口号启动不同的nacos实例。 命令:./startup.sh -p 3333 表示启动端口号为3333的nacos服务器实例,和上一步的cluster.conf配置的一致。
执行方式:
5.3.9、Nginx的配置,由它作为负载均衡器
安装好nginx
修改配置文件:
老版:
upstream cluster{
server 127.0.0.1:3333;
server 127.0.0.1:4444;
server 127.0.0.1:5555;
}
server {
listen 1111;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
#root html;
#index index.html index.htm;
proxy_pass http://cluster;
}
.......省略
新版有grpc,需要多以下配置,否则启动报错:
# nacos服务器grpc相关地址和端口,需要nginx已经有stream模块
stream {
upstream nacos-server-grpc {
server 192.168.131.101:9848;
server 192.168.131.102:9848;
server 192.168.131.103:9848;
}
server {
listen 2111;
proxy_pass nacos-server-grpc;
}
}
按照指定启动
5.3.10、截止到此处,1个Nginx+3个nacos注册中心+1个mysql
测试通过nginx访问nacos
http://192.168.131.101:1111/nacos/#/login
新建一个配置测试:
linux服务器的mysql插入一条记录
如果想要三台机器的数据统一,则必须配置同一个数据库
5.3.11、微服务cloudalibaba-provider-payment9002启动注册进nacos集群
5.3.12、高可用小总结
三、SpringCloud Alibab Sentinel实现熔断与限流
1、Sentinel
1.1、官网
中文:introduction | Sentinel (sentinelguard.io)
1.2、是什么
一句话解释,之前我们学过的Hystrix
1.3、去哪下
下载jar包
1.4、能干嘛
1.5、怎么玩
spring-cloud-alibaba-group.github.io/github-page…
服务使用中的各种问题:
- 服务雪崩
- 服务降级
- 服务熔断
- 服务限流
2、安装Sentinel控制台
2.1、sentinel组件由2部分构成
- 后台
- 前台8080
2.2、安装步骤
2.2.1、下载
2.2.2、运行命令
前提:
- java8环境ok
- 8080端口不能被占用
命令:java -jar sentinel-dashboard-1.8.6.jar
2.2.3、访问sentinel管理界面
登录账号密码均为sentinel
3、初始化演示工程
3.1、启动Nacos8848成功
http://localhost:8848/nacos/#/login
3.2、Module
3.2.1、新建cloudalibaba-sentinel-service8401
3.2.2、改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>cloud2020</artifactId>
<groupId>com.lxg.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloudalibaba-sentinel-service8401</artifactId>
<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.3、改yml文件
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
#Nacos服务注册中心地址
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard地址
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
management:
endpoints:
web:
exposure:
include: '*'
3.2.4、主启动类
package com.lxg.springcloud;
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.5、controller
package com.lxg.springcloud.controller;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @auther zzyy
* @create 2020-01-09 16:34
*/
@RestController
public class FlowLimitController
{
@GetMapping("/testA")
public String testA()
{
return "------testA";
}
@GetMapping("/testB")
public String testB()
{
return "------testB";
}
}
3.2.6、启动Sentinel8080
java -jar sentinel-dashboard-1.8.6.jar
3.2.7、启动微服务8401
3.2.8、查看sentinel控制台
发现现在空空如也
Sentinel采用的懒加载说明:需要执行访问
执行一次访问即可:
效果:
结论:
sentinel8080正在监控微服务8401
4、流控规则
4.1、基础介绍
进一步解释说明:
4.2、流控模式
4.2.1、直接(默认)
直接->快速失败:系统默认
配置及说明:
表示1秒钟内查询1次就是OK,若超过次数1,就直接-快速失败,报默认错误
测试:
快速点击访问http://localhost:8401/testA
结果:Blocked by Sentinel (flow limiting)
思考:
直接调用默认报错信息,技术方面OK but,是否应该有我们自己的后续处理?
类似有个fallback的兜底方法?
转到其他页面?
4.2.2、关联模式
是什么:
- 当关联的资源达到阈值时,就限流自己
- 当与A关联的资源B达到阀值后,就限流A自己
- B惹事,A挂了
配置A:
设置效果 当关联资源/testB的qps阀值超过1时,就限流/testA的Rest访问地址,当关联资源到阈值后限制配置好的资源名
postman模拟并发密集访问testB
访问testB成功
postman里新建多线程集合组
将访问地址添加进新新线程组
Run
大批量线程高并发访问B,导致A失效了
运行后发现testA挂了
点击访问http://localhost:8401/testA
Blocked by Sentinel (flow limiting)
4.2.3、链路模式
多个请求调用了同一个微服务
针对上一级接口进行限流
可以对test1或test2进行限流
[Sentinel-限流规则(流控模式:直接、关联、链路)_sentinel 关联 链路_其然乐衣的博客-CSDN博客](blog.csdn.net/Ezerbel/art…)
被调用的getorder服务达到阈值时,限制/order/test2,而/order/test1没事
5、流控效果
5.1、直接->快速失败
直接失败,抛出异常:Blocked by Sentinel (flow limiting)
源码:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
5.2、预热
5.2.1、说明:
公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值
5.2.2、官网:
默认coldFactor为3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。
限流 冷启动:限流 冷启动 · alibaba/Sentinel Wiki · GitHub
5.2.3、源码
com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController
5.2.4、warmup配置
默认 coldFactor 为 3,即请求QPS从(threshold / 3) 开始,经多少预热时长才逐渐升至设定的 QPS 阈值。 案例,阀值为10+预热时长设置5秒。 系统初始化的阀值为10 / 3 约等于3,即阀值刚开始为3;然后过了5秒后阀值才慢慢升高恢复到10
多次点击http://localhost:8401/testB
刚开始不行,后续慢慢OK
5.2.5、应用场景
如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。
5.3、排队等待
匀速排队,阈值必须设置为QPS
5.3.1、官网
流量控制 匀速排队模式 · alibaba/Sentinel Wiki · GitHub
5.3.2、源码
com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController
5.3.3、测试
6、降级规则
6.1、官网、
熔断降级 · alibaba/Sentinel Wiki · GitHub
6.2、基本介绍
旧版:
RT(平均响应时间,秒级) 平均响应时间 超出阈值 且 在时间窗口内通过的请求>=5,两个条件同时满足后触发降级 窗口期过后关闭断路器 RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)
异常比列(秒级) QPS >= 5 且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
异常数(分钟级) 异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
新版
- 慢调用比例 (
SLOW_REQUEST_RATIO
):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。 - 异常比例 (
ERROR_RATIO
):当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是[0.0, 1.0]
,代表 0% - 100%。 - 异常数 (
ERROR_COUNT
):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
6.2.1、进一步说明
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制, 让请求快速失败,避免影响到其它的资源而导致级联错误。
当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
6.2.2、旧版Sentinel的断路器是没有半开状态的:
半开的状态系统自动去检测是否请求有异常, 没有异常就关闭断路器恢复使用, 有异常则继续打开断路器不可用。(这样再过窗口期设置时间过才能转到半开状态)具体可以参考Hystrix
复习Hystrix:
6.3、降级策略实战
6.3.1、RT(老版)
是什么:
测试:
代码:
@GetMapping("/testD")
public String testD()
{
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
log.info("testD 测试RT");
return "------testD";
}
配置:
jmeter压测:
结论:
按照上述配置,
永远一秒钟打进来10个请求(大于5个了)调用testD,我们希望200毫秒处理完本次任务, 如果超过200毫秒还没处理完,在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了
后续我停止jmeter,没有这么大的访问量了,断路器关闭(保险丝恢复),微服务恢复OK
6.3.2、慢调用比例(新版)
1.调用:一个请求发送到服务器,服务器给与响应,一个响应就是一个调用。 2.RT:响应时间,指系统对请求作出响应的时间。 3.慢调用:当调用的时间(响应的实际时间)>设置的RT的时,这个调用叫做慢调用。 4.慢调用比例:在所以调用中,慢调用占有实际的比例,= 慢调用次数 / 调用次数 5.比例阈值:自己设定的 , 慢调用次数 / 调用次数=比例阈值
统计时长:时间的判断依据 最小请求数:设置的调用最小请求数
进入熔断状态判断依据:当统计时常内,实际请求数目大于最小请求数目,慢调用比例> 比例阈值 ,进入熔断状态
熔断状态:在接下来的熔断时长内请求会自动被熔断
探测恢复状态:熔断时长结束后进入探测恢复状态
结束熔断:在探测恢复状态,如果接下来的一个请求响应时间小于设置的慢调用 RT,则结束熔断 否则继续熔断。
sentinel降级策略:慢调用比例_海滩超人的博客-CSDN博客
Sentinel熔断策略-慢调用比例_紫荆之后-的博客-CSDN博客
6.3.3、异常比例
是什么:
异常数是按照分钟统计的
测试:
代码:
@GetMapping("/testException")
public String testException()
{
log.info("testD 测试RT");
int age = 10/0;
return "------testException";
}
浏览器狂发请求,发现会进入熔断状态
由于每一次都是异常,半开尝试后仍然是异常,就继续熔断
6.3.3、异常数
是什么:
异常数 (ERROR_COUNT
):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
测试:
代码:
@GetMapping("/testExceptionCount")
public String testExceptionCount()
{
log.info("testD 测试异常数");
int age = 10/0;
return "------testExceptionCount";
}
配置:
10秒内请求数大于5,且这些请求中有5个是异常即会进入熔断状态,等3秒后进入半开状态,如果下一个请求还异常就继续熔断3秒,如果正常就取消熔断,重新判断
7、热点key限流
7.1、基本介绍
是什么:
何为热点 热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作
7.2、官网:
热点参数限流 · alibaba/Sentinel Wiki (github.com)
7.3、承上启下复习start
兜底方法 分为系统默认和客户自定义,两种
之前的case,限流出问题后,都是用sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
我们能不能自定?类似hystrix,某个方法出问题了,就找对应的兜底降级方法?
结论 从HystrixCommand 到@SentinelResource
7.4、代码
@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";
}
//sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
com.alibaba.csp.sentinel.slots.block.BlockException
7.5、配置
限流模式只支持QPS模式,固定写死了。(这才叫热点) @SentinelResource注解的方法参数索引,0代表第一个参数,1代表第二个参数,以此类推 单机阀值以及统计窗口时长表示在此窗口时间超过阀值就限流。 上面的抓图就是第一个参数有值的话,1秒的QPS为1,超过就限流,限流后调用dealHandler_testHotKey支持方法。
-
@SentinelResource(value = "testHotKey")
异常打到了前台用户界面看到,不友好
-
@SentinelResource(value = "testHotKey",blockHandler = "dealHandler_testHotKey")
方法testHotKey里面第一个参数只要QPS超过每秒1次,马上降级处理
用了我们自己定义的
7.6、测试
error:http://localhost:8401/testHotKey?p1=abc
error:http://localhost:8401/testHotKey?p1=abc&p2=33
right:http://localhost:8401/testHotKey?p2=abc
7.7、参数例外项
7.7.1、配置
添加按钮不能忘
7.7.2、测试
right:http://localhost:8401/testHotKey?p1=5
error:http://localhost:8401/testHotKey?p1=3
当p1等于5的时候,阈值变为200
当p1不等于5的时候,阈值就是平常的1
前提条件:热点参数的注意点,参数必须是基本类型或者String
7.8、其他
手贱添加异常看看....../(ㄒoㄒ)/~~
@SentinelResource
处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;
RuntimeException
int age = 10/0,这个是java运行时报出的运行时异常RunTimeException,@SentinelResource不管
总结
@SentinelResource主管配置出错,运行出错该走异常走异常
8、系统规则
8.1、是什么
系统自适应限流 · alibaba/Sentinel Wiki (github.com)
8.2、各项配置参数说明
配置全局QPS
9、@SentinelResource
9.1、按资源名称限流+后续处理
启动Nacos成功:http://localhost:8848/nacos/#/login
启动Sentinel成功:java -jar sentinel-dashboard-1.8.6.jar
9.1.1、修改cloudalibaba-sentinel-service8401
pom:(新增以下内容)
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.lxg.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
yml不变
controller:
package com.lxg.springcloud.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.lxg.springcloud.entities.CommonResult;
import com.lxg.springcloud.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 服务不可用");
}
@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult byUrl() {
return new CommonResult(200, "按url限流测试OK", new Payment(2020L, "serial002"));
}
}
9.1.2、配置规则
图形配置和代码关系
表示1秒钟内查询次数大于1,就跑到我们自定义的处流,限流
9.1.3、测试
1秒钟点击1下,OK
超过上述,疯狂点击,返回了自己定义的限流处理信息,限流发生
9.1.4、额外问题
此时关闭问服务8401看看
Sentinel控制台,流控规则消失了?????
临时/持久?
9.2、按照Url地址限流+后续处理
通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息
访问一次http://localhost:8401/rateLimit/byUrl
sentinel配置:
测试:
疯狂点击http://localhost:8401/rateLimit/byUrl
9.3、上面兜底方案面临的问题
- 系统默认的,没有体现我们自己的业务要求。
- 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
- 每个业务方法都添加一个兜底的,那代码膨胀加剧
- 全局统一的处理方法没有体现。
9.4、客户自定义限流处理逻辑
9.4.1、创建CustomerBlockHandler类用于自定义限流处理逻辑
package com.lxg.springcloud.handler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.lxg.springcloud.entities.CommonResult;
/**
* @auther xiaolin
* @creatr 2023/3/28 22:10
*/
public class CustomerBlockHandler {
public static CommonResult handleException(BlockException exception) {
return new CommonResult(4444, "按客户自定义,global handlerException----1");
}
public static CommonResult handleException2(BlockException exception) {
return new CommonResult(4444, "按客户自定义,global handlerException----2");
}
}
9.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,"按客户自定义限流处理逻辑",new Payment(2020L,"serial003"));
}
9.4.3、启动微服务后先调用一次
9.4.4、配置
9.4.5、狂刷
9.4.6、进一步说明
9.5、更多注解属性说明
所有的代码都要用try-catch-finally方式进行处理,o(╥﹏╥)o
Sentinel主要有三个核心Api:
- SphU定义资源
- Tracer定义统计
- ContextUtil定义了上下文
10、服务熔断功能
10.1、sentinel整合ribbon+openFeign+fallback
10.2、Ribbon系列
10.2.1、启动nacos和sentinel
10.2.2、新建9003/9004模块
新建cloudalibaba-provider-payment9003/9004两个一样的做法
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>cloud2020</artifactId>
<groupId>com.lxg.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloudalibaba-provider-payment9003</artifactId>
<dependencies>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.lxg.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</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、application.yml
server:
port: 9003/9004
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址
management:
endpoints:
web:
exposure:
include: '*'
3、主启动类
package com.lxg.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9004/9003
{
public static void main(String[] args) {
SpringApplication.run(PaymentMain9004/9003.class, args);
}
}
4、业务类
package com.lxg.springcloud.controller;
import com.lxg.springcloud.entities.CommonResult;
import com.lxg.springcloud.entities.Payment;
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 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, "28a8c1e3bc2742d8848569891fb42181"));
hashMap.put(2L, new Payment(2L, "bba8c1e3bc2742d8848569891ac32182"));
hashMap.put(3L, new Payment(3L, "6ua8c1e3bc2742d8848569891xt92183"));
}
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
Payment payment = hashMap.get(id);
CommonResult<Payment> result = new CommonResult(200, "from mysql,serverPort: " + serverPort, payment);
return result;
}
}
5、测试地址
http://localhost:9003/paymentSQL/1
10.2.3、新建消费者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>cloud2020</artifactId>
<groupId>com.lxg.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloudalibaba-consumer-nacos-order84</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!--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>com.lxg.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</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、application.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扫描,直至找到未被占用的端口
filter:
enabled: false
# web-context-unify: false #关闭将Controller方法做context整合
#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
nacos-user-service: http://nacos-payment-provider
3、主启动类
package com.lxg.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class OrderNacosMain84
{
public static void main(String[] args) {
SpringApplication.run(OrderNacosMain84.class, args);
}
}
4、业务类
package com.lxg.springcloud.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.lxg.springcloud.entities.CommonResult;
import com.lxg.springcloud.entities.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.sql.SQLException;
import java.util.concurrent.Executors;
/**
* @auther zzyy
* @create 2020-02-13 20:24
*/
@RestController
@Slf4j
public class CircleBreakerController
{
public static final String SERVICE_URL = "http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
// @SentinelResource(value = "fallback")
// @SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback负责业务异常
// @SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler负责在sentinel里面配置的降级限流
@SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler"
,exceptionsToIgnore = {IllegalArgumentException.class})
public CommonResult<Payment> fallback(@PathVariable Long id)
{
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);
if (id == 4) {
throw new IllegalArgumentException ("非法参数异常....");
}else if (result.getData() == null) {
throw new NullPointerException ("NullPointerException,该ID没有对应记录");
}
return result;
}
public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
Payment payment = new Payment(id,"null");
return new CommonResult<>(444,"fallback,无此流水,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);
}
}
5、ApplicationContextConfig
package com.lxg.springcloud.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();
}
}
6、修改后重启微服务
热部署对java代码级生效及时
对@SentinelResource注解内属性,有时效果不好
7、目的
fallback管运行异常
blockHandler管配置违规
8、测试地址
http://localhost:84/consumer/fallback/1
9、无任何配置情况
给客户error页面,不友好
10、只配置fallback
本例sentinel无配置
11、只配置blockHandler
本例sentinel需配置:
12、fallback和blockHandler都配置
本例sentinel需配置
13、忽略属性.......
本例sentinel无配置
程序异常打到前台了,对用户不友好
10.3、Feign系列
10.3.1、修改84模块
84消费者调用提供者9003
Feign组件一般是消费侧
1、pom文件新增
<!--SpringCloud openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
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: 8179
filter:
enabled: false
# web-context-unify: false #关闭将Controller方法做context整合
management:
endpoints:
web:
exposure:
include: '*'
# 激活Sentinel对Feign的支持
feign:
sentinel:
enabled: true
#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
#service-url:
# nacos-user-service: http://nacos-payment-provider
激活Sentinel对Feign的支持
3、主启动添加注解
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients
public class OrderNacosMain84
{
public static void main(String[] args) {
SpringApplication.run(OrderNacosMain84.class, args);
}
}
添加@EnableFeignClients启动Feign的功能
4、业务类
PaymentService
package com.lxg.springcloud.service;
import com.lxg.springcloud.entities.CommonResult;
import com.lxg.springcloud.entities.Payment;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* @auther xiaolin
* @creatr 2023/4/4 11:33
*/
@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService {
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
PaymentFallbackService
package com.lxg.springcloud.service;
import com.lxg.springcloud.entities.CommonResult;
import com.lxg.springcloud.entities.Payment;
import org.springframework.stereotype.Component;
/**
* @auther xiaolin
* @creatr 2023/4/4 12:25
*/
@Component
public class PaymentFallbackService implements PaymentService{
@Override
public CommonResult<Payment> paymentSQL(Long id) {
return new CommonResult<>(44444,"服务降级返回,---PaymentFallbackService",new Payment(id,"errorSerial"));
}
}
Controller
package com.lxg.springcloud.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.lxg.springcloud.entities.CommonResult;
import com.lxg.springcloud.entities.Payment;
import com.lxg.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.sql.SQLException;
import java.util.concurrent.Executors;
/**
* @auther zzyy
* @create 2020-02-13 20:24
*/
@RestController
@Slf4j
public class CircleBreakerController {
public static final String SERVICE_URL = "http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback")
// @SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback负责业务异常
// @SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler负责在sentinel里面配置的降级限流
// @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler"
// ,exceptionsToIgnore = {IllegalArgumentException.class})
public CommonResult<Payment> fallback(@PathVariable Long id) {
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
if (id == 4) {
throw new IllegalArgumentException("非法参数异常....");
} else if (result.getData() == null) {
throw new NullPointerException("NullPointerException,该ID没有对应记录");
}
return result;
}
public CommonResult handlerFallback(@PathVariable Long id, Throwable e) {
Payment payment = new Payment(id, "null");
return new CommonResult<>(444, "fallback,无此流水,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);
}
//==================OpenFeign
@Resource
private PaymentService paymentService;
@GetMapping(value = "/consumer/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
return paymentService.paymentSQL(id);
}
}
5、访问
http://localhost:84/consumer/paymentSQL/1
测试84调用9003,此时故意关闭9003微服务提供者,看84消费侧自动降级,不会被耗死
10.3.2、熔断框架比较
11、规则持久化
11.1、是什么
一旦我们重启应用,sentinel规则将消失,生产环境需要将配置规则进行持久化
11.2、怎么玩
将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台 的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效
11.3、步骤
11.3.1、修改cloudalibaba-sentinel-service8401
1、pom文件
<!--SpringCloud ailibaba sentinel-datasource-nacos -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
2、yml
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
#Nacos服务注册中心地址
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard地址
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
# web-context-unify: false #关闭将Controller方法做context整合
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: cloudalibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
management:
endpoints:
web:
exposure:
include: '*'
11.3.2、添加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:是否集群。
11.3.3、启动8401后刷新sentinel发现业务规则有了
11.3.4、快速访问测试接口
http://localhost:8401/rateLimit/byUrl
11.3.5、停止8401再看sentinel
11.3.6、重新启动8401再看sentinel
乍一看还是没有,稍等一会儿
多次调用:http://localhost:8401/rateLimit/byUrl
配置重新出现了,持久化验证通过
四、SpringCloud Alibaba
1、分布式事务问题\
1.1、分布式前
单机单库没这个问题
从1:1 -> 1:N -> N:N
1.2、分布式之后
单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源, 业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。
一句话:一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题
2、Seata简介
2.1、是什么
Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
官网地址:seata.io/zh-cn/
2.2、能干嘛
一个典型的分布式事务过程:
2.2.1、分布式事务处理过程的一ID+三组件模型:
- Transaction ID XID:全局唯一的事务ID
- 3组件概念:
- Transaction Coordinator (TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;
- Transaction Manager (TM):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
- Resource Manager (RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚
2.2.2、处理过程
1、TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;
2、XID 在微服务调用链路的上下文中传播;
3、RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
4、TM 向 TC 发起针对 XID 的全局提交或回滚决议;
5、TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。
2.3、去哪下
发布说明: github.com/seata/seata…
2.4、怎么玩
本地@Transactional
全局@GlobalTransactional
SEATA 的分布式交易解决方案:
3、Seata-Server安装
3.1、官网地址
3.2、下载版本
下载的是seata-server-1.6.0.zip
3.3、seata-server-1.6.0.zip解压到指定目录并修改conf目录下的application.yml配置文件
先备份application.yml
主要修改:事务日志存储模式为db+数据库连接信息
application.yml:
server:
port: 7091
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
extend:
logstash-appender:
destination: 127.0.0.1:4560
kafka-appender:
bootstrap-servers: 127.0.0.1:9092
topic: logback_to_logstash
console:
user:
username: seata
password: seata
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: file
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
#type: file
type: nacos
preferred-networks: 30.240.*
nacos:
#application: seata-server
server-addr: 127.0.0.1:8848
#group: SEATA_GROUP
namespace:
cluster: default
#username:
#password:
#context-path:
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key: ""
#secret-key: ""
store:
# support: file 、 db 、 redis
#mode: file
mode: db
#session:
# mode: file
#lock:
# mode: file
file:
dir: sessionStore
max-branch-session-size: 16384
max-global-session-size: 512
file-write-buffer-cache-size: 16384
session-reload-read-size: 100
flush-disk-mode: async
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true
user: root
password: 123456
min-conn: 5
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
query-limit: 100
max-wait: 5000
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
3.4、mysql5.7数据库新建库seata
3.5、在seata库里建表
建表sql在/seata/script/server/db/mysql.sql
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(128),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_status` (`status`),
KEY `idx_branch_id` (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
CREATE TABLE IF NOT EXISTS `distributed_lock`
(
`lock_key` CHAR(20) NOT NULL,
`lock_value` VARCHAR(20) NOT NULL,
`expire` BIGINT,
primary key (`lock_key`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
3.6、启动nacos端口号8848
3.7、启动seata-server
双击即可
3.8、以上配置可参考
4、订单/库存/账户业务数据库准备
以下演示都需要先启动Nacos后启动Seata,保证两个都OK
Seata没启动报错:no available server to connect
4.1、分布式事务业务说明
4.1.1、业务说明
这里我们会创建三个服务,一个订单服务,一个库存服务,一个账户服务。
当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存, 再通过远程调用账户服务来扣减用户账户里面的余额, 最后在订单服务中修改订单状态为已完成。
该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。
下订单--->扣库存--->减账户(余额)
4.2、创建业务数据库
seata_order:存储订单的数据库;
seata_storage:存储库存的数据库;
seata_account:存储账户信息的数据库。
建库SQL:
CREATE DATABASE seata_order;
CREATE DATABASE seata_storage;
CREATE DATABASE seata_account;
4.3、按照上述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;
4.4、按照上述3库分别建对应的回滚日志表
订单-库存-账户3个库下都需要建各自的回滚日志表
建表sql:seata/script/client/at/db at master · seata/seata · GitHub
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
4.5、最终效果
5、订单/库存/账户业务微服务准备
5.1、业务需求
下订单->减库存->扣余额->改(订单)状态
5.2、新建订单seata-order-service2001
5.2.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>cloud2020</artifactId>
<groupId>com.lxg.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>seata-order-service2001</artifactId>
<dependencies>
<!--nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--seata-->
<!-- 注意一定要引入对版本,要引入spring-cloud版本seata,而不是springboot版本的seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<!-- 排除掉springcloud默认的seata版本,以免版本不一致出现问题-->
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 上面排除掉了springcloud默认色seata版本,此处引入和seata-server版本对应的seata包-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.6.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>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.13</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.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>
<!--loadbalancer-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
</dependencies>
</project>
5.2.2、yml
server:
port: 2001
spring:
application:
name: seata-order-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
alibaba:
seata:
tx-service-group: default_tx_group
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_order
username: root
password: 123456
feign:
hystrix:
enabled: false
client:
config:
default:
# 连接超时时间,默认10s,设置单位为毫秒
connectTimeout: 2000
# 请求处理超时时间,默认60s,设置单位为毫秒
readTimeout: 5000
loggerLevel: FULL
logging:
level:
io:
seata: info
mybatis:
mapper-locations: classpath:mapper/*.xml
# seata配置
seata:
enabled: true
# Seata 应用编号,默认为 ${spring.application.name}
#application-id: ${spring.application.name}
# Seata 事务组编号,用于 TC 集群名
tx-service-group: default_tx_group
# 开启自动代理
enable-auto-data-source-proxy: true
# 服务配置项
#service:
# 虚拟组和分组的映射
# vgroup-mapping:
# gulimall-order-group: default
#config:
# type: nacos
#nacos:
# serverAddr: 127.0.0.1:8848
# group: SEATA_GROUP
# namespace: 86ab3ca5-81db-4efb-ae22-7b4a2980a96b
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
# namespace: 86ab3ca5-81db-4efb-ae22-7b4a2980a96b
5.2.3、file.conf(可能是没有配置nacos配置中心)
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.default_tx_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"
url = "jdbc:mysql://127.0.0.1:3306/seata"
user = "root"
password = "123456"
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
}
}
5.2.4、registry.conf(可在yml直接配置)
# registry {
# # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
# type = "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 = 6000
# connect.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、etcd3
# type = "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 = 6000
# connect.timeout = 2000
# }
# etcd3 {
# serverAddr = "http://localhost:2379"
# }
# file {
# name = "file.conf"
# }
# }
#
yml配置
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
# namespace: 86ab3ca5-81db-4efb-ae22-7b4a2980a96b
5.2.5、domain
1、CommonResult
package com.lxg.springcloud.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T>
{
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message)
{
this(code,message,null);
}
}
2、Order
package com.lxg.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;
}
5.2.6、Dao接口
package com.lxg.springcloud.dao;
import com.lxg.springcloud.domain.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* @auther xiaolin
* @creatr 2023/4/4 22:00
*/
@Mapper
public interface OrderDao {
//1、新建订单
void create(Order order);
//2、修改订单状态,从0改为1
void update(@Param("userId") Long userId,@Param("status") Integer status);
}
OrderMapper:
<?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.lxg.springcloud.dao.OrderDao">
<resultMap id="BaseResultMap" type="com.lxg.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>
5.2.7、Service
AccountService接口:
package com.lxg.springcloud.service;
import com.lxg.springcloud.domain.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.math.BigDecimal;
/**
* @auther xiaolin
* @creatr 2023/4/4 22:14
*/
@FeignClient(value = "seata-account-service")
public interface AccountService {
@PostMapping(value = "/account/decrease")
CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}
OrderService:
package com.lxg.springcloud.service;
import com.lxg.springcloud.domain.Order;
/**
* @auther xiaolin
* @creatr 2023/4/4 22:14
*/
public interface OrderService {
void create(Order order);
}
StorageService:
package com.lxg.springcloud.service;
import com.lxg.springcloud.domain.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @auther xiaolin
* @creatr 2023/4/4 22:14
*/
@FeignClient(value = "seata-storage-service")
public interface StorageService {
@PostMapping(value = "/storage/decrease")
CommonResult decrease(@RequestParam("productId") Long productId,@RequestParam("count") Integer count);
}
OrderServiceImpl:
package com.lxg.springcloud.service.impl;
import com.lxg.springcloud.dao.OrderDao;
import com.lxg.springcloud.domain.Order;
import com.lxg.springcloud.service.AccountService;
import com.lxg.springcloud.service.OrderService;
import com.lxg.springcloud.service.StorageService;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @auther xiaolin
* @creatr 2023/4/4 22:14
*/
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Resource
private OrderDao orderDao;
@Resource
private StorageService storageService;
@Resource
private AccountService accountService;
/**
* 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
* 简单说:
* 下订单->减库存->减余额->改状态
*/
@Override
@GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
public void create(Order order) {
log.info("Seata全局事务id=================>{}", RootContext.getXID());
log.info("--->开始新建订单");
//1、新建订单
orderDao.create(order);
log.info("--->订单微服务开始调用库存,做扣减count");
//2、扣减库存
storageService.decrease(order.getProductId(),order.getCount());
log.info("--->订单微服务开始调用库存,做扣减end");
//3、扣减账户
log.info("--->订单微服务开始调用账号,做扣减money");
accountService.decrease(order.getUserId(),order.getMoney());
log.info("--->订单微服务开始调用账号,做扣减end");
//4、修改订单状态,从0->1,1代表已经完成
log.info("--->修改订单状态开始");
orderDao.update(order.getUserId(),0);
log.info("--->修改订单状态结束");
log.info("下订单结束了,O(∩_∩)O哈哈~");
}
}
5.2.8、controller
package com.lxg.springcloud.controller;
import com.lxg.springcloud.domain.CommonResult;
import com.lxg.springcloud.domain.Order;
import com.lxg.springcloud.service.OrderService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @auther xiaolin
* @creatr 2023/4/4 22:30
*/
@RestController
public class OrderController {
@Resource
private OrderService orderService;
@GetMapping("/order/create")
public CommonResult create(Order order){
orderService.create(order);
return new CommonResult(200,"订单创建成功!");
}
}
5.2.9、config
MyBatisConfig
package com.lxg.springcloud.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan({"com.lxg.springcloud.dao"})
public class MyBatisConfig {
}
DataSourceProxyConfig:新版配置就启动报错
package com.lxg.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();
}
}
5.2.10、主启动类
package com.lxg.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;
/**
* @auther xiaolin
* @creatr 2023/4/4 22:35
*/
@EnableFeignClients
@EnableDiscoveryClient
//@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@SpringBootApplication
public class SeataOrderMainApp2001 {
public static void main(String[] args) {
SpringApplication.run(SeataOrderMainApp2001.class,args);
}
}
如果数据源配置有效就要@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
5.3、seata-order-service2001
5.3.1、pom文件
与以上模块一致
5.3.2、yml文件
server:
port: 2002
spring:
application:
name: seata-storage-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
alibaba:
seata:
tx-service-group: default_tx_group
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_storage
username: root
password: 123456
feign:
hystrix:
enabled: false
logging:
level:
io:
seata: info
mybatis:
mapper-locations: classpath:mapper/*.xml
# seata配置
seata:
enabled: true
# Seata 应用编号,默认为 ${spring.application.name}
#application-id: ${spring.application.name}
# Seata 事务组编号,用于 TC 集群名
tx-service-group: default_tx_group
# 开启自动代理
enable-auto-data-source-proxy: true
# 服务配置项
#service:
# 虚拟组和分组的映射
# vgroup-mapping:
# gulimall-order-group: default
#config:
# type: nacos
#nacos:
# serverAddr: 127.0.0.1:8848
# group: SEATA_GROUP
# namespace: 86ab3ca5-81db-4efb-ae22-7b4a2980a96b
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
cluster: "default"
# namespace: 86ab3ca5-81db-4efb-ae22-7b4a2980a96b
5.3.3、domain
package com.lxg.springcloud.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T>
{
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message)
{
this(code,message,null);
}
}
package com.lxg.springcloud.domain;
import lombok.Data;
/**
* @auther xiaolin
* @creatr 2023/4/4 23:54
*/
@Data
public class Storage {
private Long id;
/**
* 产品id
*/
private Long productId;
/**
* 总库存
*/
private Integer total;
/**
* 已用库存
*/
private Integer used;
/**
* 剩余库存
*/
private Integer residue;
}
5.3.4、dao
package com.lxg.springcloud.dao;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* @auther xiaolin
* @creatr 2023/4/4 23:55
*/
@Mapper
public interface StorageDao {
/**
* 扣减库存
*/
void decrease(@Param("productId") Long productId, @Param("count") Integer count);
}
<?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.lxg.springcloud.dao.StorageDao">
<resultMap id="BaseResultMap" type="com.lxg.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>
5.3.5、service
package com.lxg.springcloud.service;
public interface StorageService {
/**
* 扣减库存
*/
void decrease(Long productId, Integer count);
}
package com.lxg.springcloud.service.Impl;
import com.lxg.springcloud.dao.StorageDao;
import com.lxg.springcloud.service.StorageService;
import io.seata.core.context.RootContext;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
@Slf4j
public class StorageServiceImpl implements StorageService {
private static final Logger LOGGER = LoggerFactory.getLogger(StorageServiceImpl.class);
@Resource
private StorageDao storageDao;
/**
* 扣减库存
*/
@Override
public void decrease(Long productId, Integer count) {
log.info("Seata全局事务id=================>{}",RootContext.getXID());
LOGGER.info("------->storage-service中扣减库存开始");
storageDao.decrease(productId,count);
LOGGER.info("------->storage-service中扣减库存结束");
}
}
5.3.6、controller
package com.lxg.springcloud.controller;
import com.lxg.springcloud.domain.CommonResult;
import com.lxg.springcloud.service.StorageService;
import org.springframework.beans.factory.annotation.Autowired;
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,"扣减库存成功!");
}
}
5.3.7、file.conf
与上模块一致
5.3.8、registry.conf
与上模块一致
5.3.9、config
与上模块一致
5.3.10、主启动类
package com.lxg.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;
//@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class SeataStorageServiceApplication2002 {
public static void main(String[] args) {
SpringApplication.run(SeataStorageServiceApplication2002.class, args);
}
}
5.4、seata-account-service2003
5.4.1、pom
与以上一致
5.4.2、yml文件
server:
port: 2003
spring:
application:
name: seata-account-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
alibaba:
seata:
tx-service-group: default_tx_group
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_account
username: root
password: 123456
feign:
hystrix:
enabled: false
logging:
level:
io:
seata: info
mybatis:
mapper-locations: classpath:mapper/*.xml
# seata配置
seata:
enabled: true
# Seata 应用编号,默认为 ${spring.application.name}
#application-id: ${spring.application.name}
# Seata 事务组编号,用于 TC 集群名
tx-service-group: default_tx_group
# 开启自动代理
enable-auto-data-source-proxy: true
# 服务配置项
#service:
# 虚拟组和分组的映射
# vgroup-mapping:
# gulimall-order-group: default
#config:
# type: nacos
#nacos:
# serverAddr: 127.0.0.1:8848
# group: SEATA_GROUP
# namespace: 86ab3ca5-81db-4efb-ae22-7b4a2980a96b
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
# namespace: 86ab3ca5-81db-4efb-ae22-7b4a2980a96b
5.4.3、domain
package com.lxg.springcloud.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {
private Long id;
/**
* 用户id
*/
private Long userId;
/**
* 总额度
*/
private BigDecimal total;
/**
* 已用额度
*/
private BigDecimal used;
/**
* 剩余额度
*/
private BigDecimal residue;
}
package com.lxg.springcloud.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T>
{
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message)
{
this(code,message,null);
}
}
5.4.4、file.conf
与以上一致
5.4.5、registry.conf
与以上一致
5.4.6、dao
package com.lxg.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);
}
5.4.7、service
package com.lxg.springcloud.service;
import org.springframework.web.bind.annotation.RequestParam;
import java.math.BigDecimal;
public interface AccountService {
/**
* 扣减账户余额
* @param userId 用户id
* @param money 金额
*/
void decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}
package com.lxg.springcloud.service.impl;
import com.lxg.springcloud.dao.AccountDao;
import com.lxg.springcloud.service.AccountService;
import io.seata.core.context.RootContext;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class AccountServiceImpl implements AccountService {
private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);
@Resource
AccountDao accountDao;
/**
* 扣减账户余额
*/
@Override
public void decrease(Long userId, BigDecimal money) {
log.info("Seata全局事务id=================>{}", RootContext.getXID());
LOGGER.info("------->account-service中扣减账户余额开始");
//模拟超时异常,全局事务回滚
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
accountDao.decrease(userId,money);
LOGGER.info("------->account-service中扣减账户余额结束");
}
}
5.4.8、controller
package com.lxg.springcloud.controller;
import com.lxg.springcloud.domain.CommonResult;
import com.lxg.springcloud.service.AccountService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.math.BigDecimal;
@RestController
public class AccountController {
@Resource
AccountService accountService;
/**
* 扣减账户余额
*/
@RequestMapping("/account/decrease")
public CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money){
accountService.decrease(userId,money);
return new CommonResult(200,"扣减账户余额成功!");
}
}
5.4.9、config
与以上一致
5.4.10、主启动类
package com.lxg.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;
//@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class SeataAccountMainApp2003
{
public static void main(String[] args)
{
SpringApplication.run(SeataAccountMainApp2003.class, args);
}
}
6、Test
下订单->减库存->扣余额->改(订单)状态
数据库初始情况:
6.1、正常下单
http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
数据库情况:
6.2、超时异常,没加@GlobalTransactional
http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
AccountServiceImpl添加超时
故障情况:
当库存和账户金额扣减后,订单状态并没有设置为已经完成,没有从零改为1
而且由于feign的重试机制,账户余额还有可能被多次扣减
6.3、超时异常,添加@GlobalTransactional
AccountServiceImpl添加超时
OrderServiceImpl@GlobalTransactional
@GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
public void create(Order order)
{
。。。。。。
}
下单后数据库数据并没有任何改变
记录都添加不进来
7、一部分补充
7.1、Seata
2019年1月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案
Simple Extensible Autonomous Transaction Architecture,简单可扩展自治事务框架
2020起始,参加工作后用1.0以后的版本
7.2、再看TC/TM/RM三大组件
分布式事务的执行流程:
- TM 开启分布式事务(TM 向 TC 注册全局事务记录);
- 按业务场景,编排数据库、服务等事务内资源(RM 向 TC 汇报资源准备状态 );
- TM 结束分布式事务,事务一阶段结束(TM 通知 TC 提交/回滚分布式事务);
- TC 汇总事务信息,决定分布式事务是提交还是回滚;
- TC 通知所有 RM 提交/回滚 资源,事务二阶段结束。
7.3、AT模式如何做到对业务的无侵入
7.3.1、是什么
7.3.2、一阶段加载
在一阶段,Seata 会拦截“业务 SQL”, 1 解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”, 2 执行“业务 SQL”更新业务数据,在业务数据更新之后, 3 其保存成“after image”,最后生成行锁。 以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。
7.3.3、二阶段提交
二阶段如是顺利提交的话, 因为“业务 SQL”在一阶段已经提交至数据库,所以Seata框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。
7.3.4、二阶段回滚
二阶段回滚: 二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。 回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”, 如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。
7.4、自己debug
会发现seata数据库的表数据和undo.log表的数据有变化
7.5、补充
具体其他内容可以阅读官网文档:
本文作者:_xiaolin
本文链接:https://www.cnblogs.com/SilverStar/p/17415639.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)