Skip to main content

springCloudAlibaba

Nacos

什么是 nacos

nacos (Nacos:Dynamic Naming and Configuration Service) 一个更易于构建云原生应用的动态服务发 现,配置管理和服务管理中心

Nacos 就是注册中心+配置中心的组合 等价于 netflix 版本的 Eureka+Config+Bus+zik

解决了什么痛点

  • 之前我们在 netflix 版本遇到的配置需要手动的区分发信息,
  • 杜绝了我们需要专门自己建一个注册中心的包,阿里给我们提供了开箱即用的发行版本,我们只需要简单的配置,直接脚本启动
  • 可以直接查看到集群信息,和调用链路

如何获得和社区文档

下载地址 :https://github.com/alibaba/Nacos

中文文档地址 :https://nacos.io/zh-cn/index.html

官方文档地址 :https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_nacos_discovery

主流注册中心对比

在这里插入图片描述

安装 nacos

环境:

  • java 8
  • Maven

解压安装包,直接运行 bin 目录下的 startup.cmd 2.xx 版本,需要修改单机版本,然后用命令启动 startup.cmd -m standalone 命令运行成功后直接访问 http://localhost:8848/nacos 就是这么简单 在这里插入图片描述

作为服务注册中心

我们新建两个模块 基于 nacos 的服务提供者集群:cloudalibaba-provider-payment9001 基于 nacos 的服务提供者集群:cloudalibaba-provider-payment9002

cloudalibaba-provider-payment9001

nacos 需要的主要依赖包

    <dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</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-actuator</artifactId>
</dependency>

核心配置文件

server:
port: 9001

spring:
application:
name: nacos-payment-provider
cloud:
#注册到nacos
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址

management:
endpoints:
web:
exposure:
include: "*"

配置注意项

  • 一定要导入服务暴露
  • 配置的时候 要加入 nacos 的地址

编写一个方法测试

package com.atguigu.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;
}
}

主启动类加上注解

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class PaymentMain9001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain9001.class,args);
}
}

启动服务,就可以在 nacos 中看到我们的服务了 在这里插入图片描述 集群另一个模块 参考 9001 的编写方式

作为服务配置中心

我们来建一个 demo 来体验一下,nacos 的动态配置 配置模块 :cloudalibaba-config-nacos-client3377

基础配置

导入需要的 jar 包

<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>

编写配置文件 这里引入一个概念

bootstrap 的启动优先级比 application 高 为了确保我们的 nacos 上的配置被加载成功

server:
port: 3377

spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848 #服务注册中心地址
config:
server-addr: localhost:8848 #配置中心地址
file-extension: yaml #指定yaml格式的配置

编写一个查看测试信息的 controller

package com.atguigu.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
public class ConfigClientController
{
@Value("${config.info}")
private String configInfo;

@GetMapping("/config/info")
public String getConfigInfo() {
return configInfo;
}
}

RefreshScope 我们可以使用 cloud 的原生注解,来实现动态查看配置刷新,

创建一个 nacos 上的配置 在这里插入图片描述

nacos 中的配置规则

在这里插入图片描述 之后启动就可以在 nacos 上更改配置了

nacos 配置持久化

我们可以试验一下,配置都时候 nacos 是自带了一个嵌入式数据库 derby 我们这里可以修改设置,到自己的 mysql 上 先去 nacos 的文件夹中找到,nacos 的 sql 的脚本 在这里插入图片描述 导入到 mysql 中 创建一个数据库 nacos_config 在这里插入图片描述 之后找到 application 文件 修改配置

spring.datasource.platform=mysql

db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config_tmall?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&serverTimezone=UTC
db.user=root
db.password=root

之后启动 nacos ,就会看到全新的 naocs 我们之后再创建的各种配置,就会保存在我们自己创建的数据库中

小结

使用过 Eureka 之后,使用 nacos,感受到 nacos 是十分优秀的一款注册中心,我们可以快速启动,省去了很多的配置,这个技术简化了我们想要使用注册中心和动态配置查看链路需要的繁琐配置,

Sentinel

Sentinel 是什么

独立于项目外的 服务流量控制台,可以用于熔断 防止服务崩溃无响应,服务重启,流量限制等 在这里插入图片描述

能做什么 针对于什么场景

sentinel 给我们带来的服务

  • Sentinel 独立于项目外,开箱即用
  • 控制台可以可视化的实时监控和服务规则配置
  • 可以无缝配合 cloud 的注册中心 最佳匹配 nacos,也可以使用其他的如 Eureka apollo zookeeper 等

解决了什么场景问题

  • 服务雪崩
  • 服务降级
  • 服务熔断
  • 服务限流

在这里插入图片描述

如何获取

下载地址 :sentinel 下载地址 文档地址 :sentinel 文档地址

Sentinel 组件由两部分组成

  • 使用 sentinel 的服务
  • sentinel 的服务控制台

安装环境我们只需要有 java 8 以上就可以了 下载完成之后,直接运行 java -jar 启动查看即可 java -jar sentinel-dashboard-1.7.0.jar

我们只需要访问 默认的启动端口就可以看到 sentinel 的控制台了 账号和密码均为 :sentinel

演示工程 8401

我们主要以介绍 sentinel 实战,对于演示工程,选核心部分展示 核心依赖

        <dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

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

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

演示项目配置

server:
port: 8401

spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8719 #默认8719,假如被占用了会自动从8719开始依次+1扫描。直至找到未被占用的端口

management:
endpoints:
web:
exposure:
include: "*"

编写一个 controller 来演示

@RestController
public class FlowLimitController
{
@GetMapping("/testA")
public String testA() {
return "------testA";
}

@GetMapping("/testB")
public String testB() {

return "------testB";
}
}

之后启动 nacos 和 sentinel 这个时候你访问 sentinel 会发现什么也没有 为什么? 应为 sentinel 是懒加载的,我们访问其中一个方法,就会在 sentinel 中看到我们项目 在这里插入图片描述

流控规则

基础流控

我们可以看到面板 在这里插入图片描述

  • 资源名:唯一名称,默认请求路径

  • 针对来源: Sentinel 可以针对调用者进行限流,填写微服务名,默认 default (不区分来源)阈值类型/单机阈值:

  • QPs(每秒钟的请求数量):当调用该 api 的 QPS 达到阈值的时候,进行限流。线程数:当调用该 api 的线程数达到阈值的时候,进行限流

  • 是否集群:不需要集群

  • 流控模式:

    • 直接: api达到限流条件时,直接限流
    • 关联:当关联的资源达到阈值时,就限流自己
    • 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【api级别的针对来源】
  • 流控效果:

    • 快速失败:直接失败,抛异常
    • Warm Up:根据codeFactor (冷加载因子,默认3)的值,从阈值codeFactor,经过预热时长,才达到设置的QPS阈值
    • 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效

直接失败

快速失败,配置如下:

在这里插入图片描述

之后我们快速访问 testa 就会发现 Blocked by Sentinel (flow limiting) 触发了 sentinel 的默认提示 在这里插入图片描述

关联

当关联的资源达到阈值时,就限流自己 在这里插入图片描述 只要我们访问 b 超过了 qps a 就会把自己限流

高级配置

预热 Warm Up

**Warm Up ( RuleConstant.CONTROL_BEHAVIOR_UARM_uP)**方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动”,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。详细文档可以参考流量控制-Warm Up 文档,具体的例子可以参见 WarmUpFlowDemo。

公式:阈值除以 coldFactor(默认值为 3),经过预热时长后才会达到阈值 默认 coldFactor 为 3,即请求 QPS 从 threshold/3 开始,经预热时长逐渐升至设定的 QPS 阈值。

适合应用场景 大量数据情况 : 如秒杀 在这里插入图片描述 在这里插入图片描述 这里设置好之后,我们去访问,会发现,当到了阈值之后,慢慢的还是可以访问,直到 10

排队等待

匀速排队 匀速排队( RuleConstant.CONTROL_BEHAVIOR_RATE_LTIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。详细文档可以参考流星控制–匀速器模式,具体的例子可以参见 PaceFlowDemo。 在这里插入图片描述

在这里插入图片描述 匀速排队,阈值必须设置为 QPS 在一定时间内,逐渐处理进入到的请求,而不是第一时间拒绝请求

总结

Sentinel 这个控制台,十分到强大,给我们带来了很多很多可以使用的服务规则,我们这里主要介绍了流控,这只是 Sentinel 的冰山一角,

在这里插入图片描述

Sentinel 可以看到,还有很多模块可以根据不同的场景和需求是了解, 天道酬勤,我们一起进步吧

Seata 处理分布式事务

分布式问题的出现

出现分不是之前 :单机单库没这个问题 出现分布式之后 在这里插入图片描述 我们分开的模块,原来模块都有独立的数据源,那么我如何保证一致性呢? 一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题 这个时候就需要一套解决方案,那么 seata 营运而生

Seata 简介

Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务 官网地址 :http://seata.io/zh-cn/ 下载地址 :https://github.com/seata/seata/releases

Seata 能给我们带来什么

先介绍一下 seata 的三个核心组件和一个全局 id

  • 全局唯一的事务 ID 当开启事务就会生成 xid,凭借这个 id 来标识是哪个事务

三个组件

  • Transaction Coordinator(TC) :事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;
  • Transaction Manager(TM) : 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
  • Resource Manager(RM) : 控制分支事务,负责分支注册,状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚;

举个例子 : 一个典型的分布式事务过程

  • TM 向 TC 申请开启一个全局事务,
  • 全局事务创建成功并生成一个全局唯一的 XID;XID 在微服务调用链路的上下文中传播;
  • RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
  • TM 向 TC 发起针对 XID 的全局提交或回滚决议;
  • TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。 在这里插入图片描述

seata 安装和配置

我们首先需要有的环境

  • java 8
  • mysql 演示版本为 0.9 版本, 1.0 之后有变化,有升级需求查看官方文档升级

下载完成之后,进入到 config 文件夹中

在这里插入图片描述 我们可以创建副本吗,避免修改玩坏了, 这里我们配置 file 先 找到 service 模块 自定义名字

vgroup_mapping.fsp_tx_group = "default"

store 模块

mode = "db" url = "jdbc:mysql://127.0.0.1:3306/seata" user = "root" password = "你自己的密码"

配置玩之后,我们去配置 registry.conf,配置自己 nacos 的相关信息

registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "nacos" nacos { serverAddr = "localhost:8848" namespace = "" cluster = "default" }

之后去 数据库中 导入 seata 的数据库脚本 在这里插入图片描述 启动即可

演示示例

准备环境

创建三个库

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

建库 sql

CREATE DATABASE seata_order;

CREATE DATABASE seata_storage;

CREATE DATABASE seata_account;

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;

之后在三个数据库 创建 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;

最终效果 在这里插入图片描述

之后构建三个模块

新建订单 Order-Module

引入依赖


<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>

配置文件

server:
port: 2001

spring:
application:
name: seata-order-service
cloud:
alibaba:
seata:
#自定义事务组名称需要与seata-server中的对应
tx-service-group: fsp_tx_group
nacos:
discovery:
server-addr: localhost:8848
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_order
username: root
password: 1111111

feign:
hystrix:
enabled: false

logging:
level:
io:
seata: info

mybatis:
mapperLocations: classpath:mapper/*.xml

之后将 .file.confregistry.conf两个文件放入到项目 resource 文件夹中 创建实体类 这里业务类我们使用的是 openfeign 来作为调用的组件

  • OrderService
public interface OrderService{
void create(Order order);
}

实现类 这里我们使用 @GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)开启全局注解

name 是我们的事务名字 之后回掉方法是异常 class

@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("----->开始新建订单");
//新建订单
orderDao.create(order);

//扣减库存
log.info("----->订单微服务开始调用库存,做扣减Count");
storageService.decrease(order.getProductId(),order.getCount());
log.info("----->订单微服务开始调用库存,做扣减end");

//扣减账户
log.info("----->订单微服务开始调用账户,做扣减Money");
accountService.decrease(order.getUserId(),order.getMoney());
log.info("----->订单微服务开始调用账户,做扣减end");


//修改订单状态,从零到1代表已经完成
log.info("----->修改订单状态开始");
orderDao.update(order.getUserId(),0);
log.info("----->修改订单状态结束");

log.info("----->下订单结束了");

}
}


  • StorageService

@FeignClient(value = "seata-storage-service")
public interface StorageService{
@PostMapping(value = "/storage/decrease")
CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
}


  • AccountService
@FeignClient(value = "seata-account-service")
public interface AccountService{
@PostMapping(value = "/account/decrease")
CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}

  • MyBatisConfig

@Configuration
@MapperScan({"com.atguigu.springcloud.alibaba.dao"})
public class MyBatisConfig {
}

  • DataSourceProxyConfig(配置 seata 的代理,)
@Configuration
public class DataSourceProxyConfig {

@Value("${mybatis.mapperLocations}")
private String mapperLocations;

@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource(){
return new DruidDataSource();
}

@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}

@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return sqlSessionFactoryBean.getObject();
}

}

新建库存 Storage-Module

pom 基本一致,就不重复复制了,直接上配置

server:
port: 2002

spring:
application:
name: seata-storage-service
cloud:
alibaba:
seata:
tx-service-group: fsp_tx_group
nacos:
discovery:
server-addr: localhost:8848
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_storage
username: root
password: 111111

logging:
level:
io:
seata: info

mybatis:
mapperLocations: classpath:mapper/*.xml

之后将 .file.confregistry.conf两个文件放入到项目 resource 文件夹中 这里就略过实体类了,直接实现类,主要感受 seata 带来的事务

@Service
public class StorageServiceImpl implements StorageService {

private static final Logger LOGGER = LoggerFactory.getLogger(StorageServiceImpl.class);

@Resource
private StorageDao storageDao;

// 扣减库存
@Override
public void decrease(Long productId, Integer count) {
LOGGER.info("------->storage-service中扣减库存开始");
storageDao.decrease(productId,count);
LOGGER.info("------->storage-service中扣减库存结束");
}
}


如下两个配置与模块一一致

  • MyBatisConfig
  • DataSourceProxyConfig

新建账户 Account-Module

配置文件

server:
port: 2003

spring:
application:
name: seata-account-service
cloud:
alibaba:
seata:
tx-service-group: fsp_tx_group
nacos:
discovery:
server-addr: localhost:8848
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_account
username: root
password: 1111111

feign:
hystrix:
enabled: false

logging:
level:
io:
seata: info

mybatis:
mapperLocations: classpath:mapper/*.xml

之后将 .file.confregistry.conf两个文件放入到项目 resource 文件夹中

实现类

/**
* 账户业务实现类
*/
@Service
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) {

LOGGER.info("------->account-service中扣减账户余额开始");
try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
accountDao.decrease(userId,money);
LOGGER.info("------->account-service中扣减账户余额结束");
}
}


这里我们设置超时,之后去创建订单,看看是否成功

http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100 我们会发现,数据库没变化,出现超时异常,立刻就回滚了

当我们把超时代码注释,再次创建订单,数据库的值就变化了 在这里插入图片描述 只是配置了一下,添加了个全局事务注解,就可以实现分布式事务了,这其中 seata 做了什么呢?

seata 原理

分布式事务的执行流程

  • TM 开启分布式事务(TM 向 TC 注册全局事务记录)
  • 换业务场景,编排数据库,服务等事务内资源(RM 向 TC 汇报资源准备状态)
  • TM 结束分布式事务,事务一阶段结束(TM 通知 TC 提交/回滚分布式事务)
  • TC 汇总事务信息,决定分布式事务是提交还是回滚
  • TC 通知所有 RM 提交/回滚资源,事务二阶段结束。

在这里插入图片描述

seata 的几种模式

AT 模式如何做到对业务的无侵入 在这里插入图片描述 在这里插入图片描述

一阶段

在一阶段,Seata 会拦截“业务 SQL”,

  1. 解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image'
  2. 执行“业务 SQL”更新业务数据,在业务数据更新之后,
  3. 其保存成“after image”,最后生成行锁。

以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。 在这里插入图片描述

二阶段

提交

在这里插入图片描述

回滚

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

完整过程流程图

在这里插入图片描述

总结

seata 分布式事务解决方案,我们微服务的事务发生了质变,原本只有单机和单数据源,开启事务 而多个数据源多个模块的事务开启,seata 回去寻找在一个事务里的数据源,拦截 sql 去做一系列的处理 解决了单机单数据源一致性的业务痛点。 一个字 猛!