138-150-springcloud-seata

138-150-springcloud-seata:

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

Seata

1、seata是什么

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

官网地址:https://seata.io/zh-cn/

能干嘛,一个典型的分布式事务过程,分布式事务处理过程的一ID+三组件模型:

1、Transaction ID XID 全局唯一的事务ID
2、三组件概念
2.1、TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。
2.2、TM (Transaction Manager) - 事务管理器:定义全局事务的范围:开始全局事务、提交或回滚全局事务。
2.3、RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

处理过程:

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ifAaiDXW-1670857406237)(png/image-20220518221629986.png)]

2、Seata-server安装

https://github.com/seata/seata/releases

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JvWCRr8e-1670857406238)(png/image-20220518221707871.png)]

我们只需要使用一个 @GlobalTransactional 注解在业务方法上:

Seata-Server安装,官网地址 - http://seata.io/zh-cn/

下载版本 - 0.9.0,seata-server-0.9.0.zip解压到指定目录并修改conf目录下的file.conf配置文件

先备份原始file.conf文件

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

file.conf

service {
    ##fsp_tx_group是自定义的
    vgroup_mapping.my.test.tx_group="fsp_tx_group" 
}
## transaction log store
store {
	## store mode: file, db
	## 改成db
	mode = "db"
	
	# database store
	db {
	.....
		url = "jdbc:mysql://127.0.0.1:3306/seata"
		user = "root"
		password = "密码"
	}
}

mysql数据库新建库seata,在seata库里建表

建表db_store.sql在\seata-server-0.9.0\seata\conf目录里面

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

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  # 改用为nacos
  type = "nacos"

  nacos {
  	## 加端口号
    serverAddr = "localhost:8848"
    namespace = ""
    cluster = "default"
  }
  ...
}

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

先启动Nacos端口号8848 nacos\bin\startup.cmd

再启动seata-server - seata-server-0.9.0\seata\bin\seata-server.bat

3、Seata尚硅谷案例练习

1、业务数据库准备

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=1 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=1 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=1 DEFAULT CHARSET=utf8;

INSERT INTO seata_account.t_account(`id`, `user_id`, `total`, `used`, `residue`)
VALUES ('1', '1', '1000', '0', '1000');

SELECT * FROM t_account;

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

seata-order-service2001 seata-storage-service2002 seata-account-service2003

测试下单 - http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100

超时异常,没加@GlobalTransactional和加了做比较

4、Seata原理

4.1、分布式事务的执行流程:
  • TM开启分布式事务(TM向TC注册全局事务记录) ;
  • 按业务场景,编排数据库、服务等事务内资源(RM向TC汇报资源准备状态) ;
  • TM结束分布式事务,事务一阶段结束(TM通知TC提交/回滚分布式事务) ;
  • TC汇总事务信息,决定分布式事务是提交还是回滚;
  • TC通知所有RM提交/回滚资源,事务二阶段结束。
4.2、AT模式如何做到对业务的无侵入

什么是seata:https://seata.io/zh-cn/docs/overview/what-is-seata.html

前提
	基于支持本地 ACID 事务的关系型数据库。
	Java 应用,通过 JDBC 访问数据库。
整体机制
两阶段提交协议的演变:
	一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
	二阶段:
			提交异步化,非常快速地完成。
			回滚通过一阶段的回滚日志进行反向补偿。

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

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

以上操作全部在一个数据库事务内完成, 这样保证了一阶段操作的原子性。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V2ctVKbJ-1670857406240)(png/image-20220518222708042.png)]

  • 二阶段提交

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ANb5juOl-1670857406240)(png/image-20220518222725225.png)]

  • 二阶段回滚

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WfxuII0I-1670857406241)(png/image-20220518222807568.png)]

补充:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HP1CfjD7-1670857406241)(png/image-20220518223029110.png)]

雪花算法

借鉴摘抄自:https://www.bilibili.com/video/BV18E411x7eT?p=148

为什么需要分布式全局唯一ID以及分布式ID的业务需求?集群高并发情况下如何保证分布式唯一全局Id生成?

在复杂分布式系统中,往往需婴对大量的数据和消息进行唯一标识,如在美团点评的金融、支付、餐饮、酒店,猫眼电影等产品的系统中数据日渐增长,对数据分库分表后需要有一个唯一ID来标识一条数据或消息。特别一点的如订单、骑手、优惠券也都雷要有唯一ID做标识。此时一个能够生成全局唯一ID的系统是非常必要的。

1、ID生成规则部分硬性要求

全局唯一:不能出现重复的ID号,既然是唯一-标识,这是最基本的要求

趋势递增:在MySQL的InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用Btree的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能。

单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求

信息安全:如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可。如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,需要ID无规则不规则,让竞争对手否好猜。

含时间戳:这样就能够在开发中快速了解这个分布式id的生成时间。

2、ID号生成系统的可用性要求

高可用:发一个获取分布式ID的请求,服务器就要保证99.999%的情况下给我创建一个唯一分布式ID。

低延迟:发一个获取分布式ID的请求,服务器就要快,极速。

高QPS:假如并发一口气10万个创建分布式ID请求同时杀过来,服务器要顶的住且一下子成功创建10万个分布式ID。

一般通用方案

UUID

UUID(Universally Unique ldentifer)的标准型式包含32个16进制数字,以连了号分为五段,形式为8-4-4-4-12的36个字符, 示例:550e8400-e29b-41d4-a716-446655440000

性能非常高:本地生成,没有网络消耗,如果只考虑唯一性,ok。但是,入数据库性能差

为什么无序的UUID会导致入库性能变差呢?(数据库的B+树排序修改等操作,数据库和数据结构这里不做讲解。)

mysql: https://dev.mysql.com/doc/refman/8.0/en/innodb-index-types.html

无序,无法预测他的生成顺序,不能生成递增有序的数字。首先分布式ID一般都会作为主键, 但是安装MySQL官方推荐主键要尽量越短越好,UUID每一个都很长,所以不是很推荐。

主键,ID作为主键时在特定的环境会存在一些问题。比如做DB主键的场景下,UUID就非常不适用MySQL官方有明确的建议主键要尽量越短越好36个字符长度的UUID不符合要求。

索引,既然分布式ID是主键,然后主键是包含索引的,然后MySQL的索引是通过B+树来实现的,每一次新的UUID数据的插入,为了查询的优化,都会对索引底层的B+树进行修改,因为UUID数据是无序的,所以每一次UUID数据的插入都会对主键地械的B+树进行很大的修改,这一点很不好。 插入完全无序,不但会导致一-些中间节点产生分裂,也会白白创造出很多不饱和的节点,这样大大降低了数据库插入的性能。

数据库自增主键

单机

在单机里面,数据库的自增ID机制的主要原理是:数据库自增ID和MySQL数据库的replace into实现的。

REPLACE INTO的含义是插入一条记录,如果表中唯一索引的值遇到冲突,则替换老数据。

这里的replace into跟inset功能类似,不同点在于:replace into首先尝试插入数据列表中,如果发现表中已经有此行数据(根据主键或唯一索引判断)则先删除,再插入。否则直接插入新数据。

Twitter的分布式自增ID算法snowflake

概述

Twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移到Cassandra(由Facebook开发一套开源分布式NoSQL数据库系统)。因为Cassandra没有顺序ID生成机制,所以开发了这样一套全局唯一生成服务。

Twitter的分布式雪花算法SnowFlake ,经测试snowflake 每秒能够产生26万个自增可排序的ID

  1. Twitter的SnowFlake生成ID能够按照时间有序生成。
  2. SnowFlake算法生成ID的结果是一个64bit大小的整数, 为一个Long型(转换成字符串后长度最多19)。
  3. 分布式系统内不会产生ID碰撞(由datacenter和workerld作区分)并且效率较高。

分布式系统中,有一些需要使用全局唯一ID的场景, 生成ID的基本要求:

  1. 在分布式的环境下必须全局且唯一 。
  2. 一般都需要单调递增,因为一般唯一ID都会存到数据库,而Innodb的特性就是将内容存储在主键索引树上的叶子节点而且是从左往右,递增的,所以考虑到数据库性能,一般生成的ID也最好是单调递增。 为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点, 首先他相对比较长, 另外UUID一般是无序的。
  3. 可能还会需要无规则,因为如果使用唯一ID作为订单号这种,为了不然别人知道一天的订单量是多少,就需要这个规则。
    结构

雪花算法的几个核心组成部分:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZvXkFLOF-1670857406242)(png/image-20220518223830656.png)]

号段解析:

1bit

不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所以最高位固定为0。

41bit - 时间戳,用来记录时间戳,毫秒级:

  • 41位可以表示2^41 − 1个数字。
  • 如果只用来表示正整数(计算机中正数包含0),可以表示的数值范围是:0至2^41 − 1减1是因为可表示的数值范围是从0开始算的,而不是1。
  • 也就是说41位可以表示2^41−1个毫秒的值,转化成单位年则是( 2 41 − 1 ) / ( 1000 ∗ 60 ∗ 60 ∗ 24 ∗ 365 ) = 6969年。

10bit - 工作机器ID,用来记录工作机器ID:

  • 可以部署在2 10 = 1024 2^{10}=1024个节点,包括5位DataCenterId和5位Workerld。
  • 5位(bit) 可以表示的最大正整数是2^5 − 1 = 31,即可以用0、1、2、3、…31这32个数字,来表示不同的DataCenterld或Workerld。

12bit - 序列号,用来记录同毫秒内产生的不同id。

12位(bit) 可以表示的最大正整数是2^12 − 1 = 40954095, 即可以用0、1、2、 3、…4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号。
SnowFlake可以保证:

所有生成的ID按时间趋势递增。
整个分布式系统内不会产生重复id(因为有DataCenterId和Workerld来做区分)
源码

以下代码仅供学习:

git:

Hutool的Snowflake文档

添加依赖

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-captcha</artifactId>
    <version>4.6.8</version>
</dependency>

示例程序:

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.IdUtil; 
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Slf4j
@Component
public class IdGeneratorSnowflake{
	private long workerId = 0;
	private long datacenterId = 1;
	private Snowflake snowflake = IdUtil.createSnowflake(workerId, datacenterId);
    public synchronized long snowflakeId(){
        return snowflake.nextId();
    }

    public synchronized long snowflakeId(long workerId, long datacenterId){
        Snowflake snowflake = IdUtil.createSnowflake(workerId, datacenterId);
        return snowflake.nextId();
    }

	public static void main(String[] args){
    IdGeneratorSnowflake idGenerator = new IdGeneratorSnowflake();
	System.out.println(idGenerator.snowflakeId());
    
    ExecutorService threadPool = Executors.newFixedThreadPool(5);
	for (int i = 1; i <= 20; i++){
		threadPool.submit(() -> {
			System.out.print1n(idGenerator.snowflakeId());
		});
	}
	threadPool.shutdown();

}

优缺点

优点:
毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。
不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。
可以根据自身业务特性分配bit位,非常灵活。
缺点:
依赖机器时钟,如果机器时钟回拨,会导致重复ID生成。
在单机上是递增的,但是由于设计到分布式环境,每台机器上的时钟不可能完全同步,有时候会出现不是全局递增的情况。
(此缺点可以认为无所谓,一般分布式ID只要求趋势递增,并不会严格要求递增,90%的需求都只要求趋势递增)

其他补充

百度开源的分布式唯一ID生成器UidGenerator

美团点评分布式ID生成系统Leaf

学习路径:https://space.bilibili.com/302417610/,如有侵权,请联系q进行删除:3623472230

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值