文章目录
- 1 什么是 UUID 算法?
- 2 什么是雪花算法?🔥
- 3 说说什么是幂等性?🔥
- 4 怎么保证接口幂等性?🔥
- 5 paxos算法
- 6 Raft 算法
- 7 CAP理论和 BASE 理论
- 7.1 CAP 理论🔥
- 7.2 为什么无法同时保证一致性和可用性?🔥
- 7.3 BASE理论了解吗?🔥
- 8 什么是分布式事务?
- 9 分布式事务常见的实现方案?🔥
- 9.1 2PC
- 9.2 3PC
- 9.3 TCC
- 9.4 Saga事务
- 9.5 本地消息表⭐
- 9.6 MQ事务
- 10 Seata
- 10.1 了解 Seata 的实现原理吗?
- 10.2 Seata 支持哪些模式的分布式事务?
- 10.3 全局事务 ID 和分支事务 ID 是怎么传递的?
- 10.4 Seata 的事务回滚是怎么实现的?
- 11 分布式锁
- 11.1 什么是分布式锁
- 11.2 分布式锁的实现方案及优缺点🔥
本文作者:夏日。
1 什么是 UUID 算法?
在分库之后,数据遍布在不同服务器上的数据库,数据库的自增主键已经没办法满足生成的主键唯一了。就要用到分布式 ID 保证全局唯一性。
UUID 是 Universally Unique Identifier(通用唯一标识符) 的缩写。UUID 包含 32 个 16 进制数字(8-4-4-4-12)。
UUID.randomUUID();//输出示例:cb4a9ede-fa5e-4585-b9bb-d60bce986eaa
虽然,UUID 可以做到全局唯一性,但是,我们一般很少会使用它。比如使用 UUID 作为 MySQL 数据库主键的时候就非常不合适:
-
数据库主键要尽量越短越好,而 UUID 的消耗的存储空间比较大(32 个字符串,128 位)。
-
UUID 是无顺序的,InnoDB 引擎下,数据库主键的无序性会严重影响数据库性能。
-
优点:生成速度比较快、简单易用
-
缺点:存储消耗空间大(32 个字符串,128 位)、 不安全(基于 MAC 地址生成 UUID 的算法会造成 MAC 地址泄露)、无序(非自增)、没有具体业务含义、需要解决重复 ID 问题(当机器时间不对的情况下,可能导致会产生重复 ID)
2 什么是雪花算法?🔥
Snowflake 是 Twitter 开源的分布式 ID 生成算法。Snowflake 由 64 bit 的二进制数字组成,这 64bit 的二进制被分成了几部分,每一部分存储的数据都有特定的含义:
- sign(1bit):符号位(标识正负),始终为 0,代表生成的 ID 为正数。
- timestamp (41 bits):一共 41 位,用来表示时间戳,单位是毫秒,可以支撑 2 ^41 毫秒(约 69 年)
- datacenter id + worker id (10 bits):一般来说,前 5 位表示机房 ID,后 5 位表示机器 ID(实际项目中可以根据实际情况调整)。这样就可以区分不同集群/机房的节点。
- sequence (12 bits):一共 12 位,用来表示序列号。 序列号为自增值,代表单台机器每毫秒能够产生的最大 ID 数(2^12 = 4096),也就是说单台机器每毫秒最多可以生成 4096 个 唯一 ID。
在实际项目中,我们一般也会对 Snowflake 算法进行改造,最常见的就是在 Snowflake 算法生成的 ID 中加入业务类型信息。
- 优点:生成速度比较快、生成的 ID 有序递增、比较灵活(可以对 Snowflake 算法进行简单的改造比如加入业务 ID)
- 缺点:需要解决重复 ID 问题(ID 生成依赖时间,在获取时间的时候,可能会出现时间回拨的问题,也就是服务器上的时间突然倒退到之前的时间,进而导致会产生重复 ID)、依赖机器 ID 对分布式环境不友好(当需要自动启停或增减机器时,固定的机器 ID 可能不够灵活)。
3 说说什么是幂等性?🔥
幂等性(Idempotence)是指对于同一个操作,无论执行多少次,结果都是相同的。换句话说,如果对同一操作进行多次调用,产生的效果应该与一次调用的效果相同。
如果没有保证幂等性,例如用户同时点击了多次付款按钮,后端处理了多次相同的扣款请求,结果导致用户的账户被扣了多次钱。
4 怎么保证接口幂等性?🔥
- 插入前先查询:在保存数据的接口中,在插入数据前,先根据
id
等字段查询下数据。如果不存在,再执行插入操作。 - 悲观锁:同一时刻只允许一个请求获得锁,其他请求则等待。可以使用 ReetrantLock 类、synchronized 关键字或者数据库自带的排它锁。
- 乐观锁:乐观锁一般会使用版本号机制或 CAS 算法实现。拿版本号机制来说,每次更新数据时,检查当前的版本号否和数据库中的一致。如果一致,则更新成功,并且版本号加一。如果不一致,则更新失败,表示数据已经被其他请求修改过。
- 唯一索引:通过在表中加上唯一索引,保证数据的唯一性。如果有重复的数据插入,会抛出异常,程序可以捕获异常并处理。
- 去重表:去重表本质上也是一种唯一索引方案。去重表是一张专门用于记录请求信息的表,其中某个字段需要建立唯一索引。
- 状态机:将业务流程抽象为一个状态机,定义各个状态之间的转换规则。
- 分布式锁:分布式系统下,不同的服务通常运行在独立的 JVM 进程上,需要使用分布式锁。一般都是使用Redission 实现分布式锁。
- Token 机制:首先需要获取一个唯一的 token,服务端一般将该 token 保存到缓存中。然后携带这个 token 发起业务请求,服务端判断该 token 是否存在。如果存在,执行业务逻辑,并删除 token,防止重复提交;如果该 token 不存在则拒绝请求。
5 paxos算法
Paxos
算法是 基于消息传递 且具有 高效容错特性 的一致性算法,目前公认的解决 分布式一致性问题 最有效的算法之一。
Paxos算法的角色有三种,一个节点可以同时充当不同角色。
- Proposer(提议者) : 提议者负责接受客户端的请求并发起提案。
- Accecptor(接受者) : 对提案进行投票,同时需要记住自己的投票历史。
- Learner(学习者) : 如果有超过半数接受者就某个提议达成了共识,那么学习者就需要接受这个提议,并就该提议作出运算,然后将运算结果返回给客户端。
Paxos算法的流程:
-
准备阶段:
- 提议者向接受者发送准备请求。
- 接受者收到准备请求后,检查是否有更高的提议序号,如果没有则投票支持当前提议,并将已经接受的提议发送给提议者。
-
提交阶段:
- 如果提议者收到了半数以上接受者的投票支持,它就会发送提交请求,请求接受者接受该提议。
- 接受者收到提交请求后,检查提议序号是否符合规则,如果符合则接受该提议。协商结束后会通知所有学习者学习提议。
前面描述的可以称之为Basic Paxos 算法,在单提议者的前提下是没有问题的,但是假如有多个提议者互不相让,那么就可能导致整个提议的过程进入了死循环。
Lamport 提出了 Multi Paxos 的算法思想,简单说就是在多个提议者的情况下,选出一个Leader(领导者),由领导者作为唯一的提议者,这样就可以解决提议者冲突的问题。
6 Raft 算法
Raft
也是一个 一致性算法,和 Paxos
目标相同。但它还有另一个名字 易于理解的一致性算法。Paxos
和 Raft
都是为了实现 一致性 产生的。这个过程如同选举一样,参选者 需要说服 大多数选民 (Server) 投票给他,一旦选定后就跟随其操作。Paxos
和 Raft
的区别在于选举的 具体过程 不同。
Raft 算法将整个分布式系统划分为 Leader、Follower 和 Candidate 三种角色。
- Leader(领导者):一个任期内只能有一个 Leader,负责处理客户端的请求。
- Follower(跟随者):接受 Leader 的心跳和日志同步数据,投票给 Candidate。
- Candidate(候选人):Leader 选举过程中的临时角色,由 Follower 转化而来,发起投票参与竞选。
工作流程:就像一个民主社会,领导者由跟随者投票选出。刚开始没有 领导者,所有集群中的 参与者 都是 跟随者。
那么首先开启一轮大选。在大选期间 所有跟随者 都能参与竞选,这时所有跟随者的角色就变成了 候选人,民主投票选出领袖后就开始了这届领袖的任期,然后选举结束,所有除 领导者 的 候选人 又变回 跟随者 服从领导者领导。
7 CAP理论和 BASE 理论
7.1 CAP 理论🔥
CAP 理论指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)和 Partition tolerance(分区容错性)这3个需求,最多只能同时满足其中的2个。
-
一致性(Consistency):所有节点在同一时间的数据完全一致(强一致性),不能存在中间状态。
-
可用性(Availability) :系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
-
分区容错性(Partition tolerance) :分布式系统在遇到任何网络分区故障时,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。
示例:ZooKeeper 保证的是 CP,Eureka 保证的则是 AP,Nacos 不仅支持 CP 也支持 AP。
7.2 为什么无法同时保证一致性和可用性?🔥
首先对于分布式系统而言,分区容错性是一个最基本的要求。因为如果要牺牲分区容错性,就得把服务和资源放到一个机器,或者一个“同生共死”的集群,那就违背了分布式的初衷。因此基本上我们在设计分布式系统的时候只能从一致性(C)和可用性(A)之间进行取舍。
如果保证了一致性(C):对于节点 N1和 N2,当往 N1里写数据时,N2上的操作必须被暂停,只有当 N1同步数据到 N2时才能对 N2进行读写请求,在 N2被暂停操作期间客户端提交的请求会收到失败或超时。显然,这与可用性是相悖的。
如果保证了可用性(A):那就不能暂停 N2的读写操作,但同时 N1在写数据的话,这就违背了一致性的要求。
7.3 BASE理论了解吗?🔥
BASE 是 CAP 理论中 AP 方案的延伸,核心思想是即使无法做到强一致性(StrongConsistency,CAP 的一致性就是强一致性),但可以采用适合的方式达到最终一致性(Eventual Consitency)。
-
Basically Available(基本可用):分布式系统在出现不可预知的故障的时候,允许损失部分可用性,但不等于整个系统不可用。
-
Soft state(软状态):允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点之间进行数据同步的过程存在延时。
-
Eventually consistent(最终一致性):强调系统中所有的数据,在经过一段时间的同步后,最终能够达到一个一致的状态,而不需要实时保证系统数据的强一致性。
8 什么是分布式事务?
在分布式系统中,这些系统或服务可能分布在不同的物理位置,通过网络进行通信和协作。分布式事务需要满足传统事务的 ACID 特性,这意味着所有的事务操作必须作为一个整体被提交或回滚,以确保数据的完整性和一致性。
9 分布式事务常见的实现方案?🔥
9.1 2PC
两阶段提交(Two-Phase Commit,2PC):参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情况决定各参与者是否要提交操作还是回滚操作。
- 准备阶段:
- 协调者向所有的参与者发送事务预处理请求,并等待各参与者的响应。
- 各个参与者节点执行事务预操作,此时并不会提交事务。
- 如果参与者成功执行了事务操作,那么就反馈给协调者 Yes 响应,表示事务可以执行,否则反馈 No 响应。
- 提交阶段:
- 如果所有的参与者都反馈给协调者Yes, 协调者会向所有参与者节点发出Commit请求。
- 参与者收到Commit请求之后,就会提交本地事务,并在完成提交之后释放整个事务执行期间占用的事务资源。
- 任何一个参与者向协调者反馈了 No 响应, 协调者向所有参与者节点发出 RoollBack 请求。
- 参与者接收到RoollBack请求后,会回滚本地事务。
- 优点:尽量保证了数据的强一致,实现成本较低。
- 缺点:
- 单点问题:协调者如果宕机,可能会导致全局事务的无法提交或回滚
- 同步阻塞:第一阶段和第二阶段,所有的参与者资源和协调者资源都是阻塞的,直到提交完成,才会释放资源。
- 数据不一致:仍然存在数据不一致性的可能,比如在第二阶段中,因为网络问题该通知仅被一部分参与者所收到并提交了事务,其余的参与者则因为没有收到通知一直处于阻塞状态,这时候就产生了数据的不一致性。
9.2 3PC
三阶段提交(Three-Phase Commit,3PC)相对两阶段提交来说,引入了一个预提交阶段,旨在解决两阶段提交的单点故障和同步阻塞问题。
- CanCommit:准备阶段。协调者向参与者发送准备请求,参与者如果认为可以顺利执行事务,则返回Yes响应,否则返回No响应。
- PreCommit:预提交阶段。3PC 的预提交阶段和2PC 的准备阶段类似。不过如果准备阶段有一个参与者返回 NO 响应就会中断事务。并且协调者和参与者都引入了超时机制,超过等待时间后,事务就会中断。
- DoCommit:提交阶段。3PC 的提交阶段和2PC 的提交阶段类似。协调者根据参与者在预提交阶段的响应判断是否提交事务还是中断事务。
2PC 和3PC 区别:3PC对于协调者和参与者都设置了超时时间,而2PC只有协调者才拥有超时机制。避免了参与者在长时间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题。3PC 的参与者自身拥有超时机制,会在超时后,自动进行本地事务提交从而释放资源。
无论是2PC还是3PC都不能保证分布式系统中的数据100%一致。
9.3 TCC
TCC(Try-Confirm-Cancel)是一种乐观锁的方式,通过在业务逻辑层面将一个完整的分布式事务拆分为三个步骤:Try、Confirm 和 Cancel 来实现。
-
Try(尝试): 业务逻辑会尝试执行操作,预留资源,并记录操作的执行状态。在尝试阶段,不会对数据库或其他资源进行真正的修改,而是记录操作的执行状态和需要的资源。
-
Confirm(确认): 如果所有的业务逻辑执行成功,就会进入确认阶段。在这个阶段,之前预留的资源会被真正的锁定,操作将会被提交到数据库或其他资源中,并且确认操作成功。如果有任何一个业务逻辑执行失败或超时,将会跳过确认阶段,直接进入取消阶段。
-
Cancel(取消): 如果在确认阶段发生了失败或超时,就会进入取消阶段。在这个阶段,之前预留的资源会被释放,操作将会被撤销,并且取消操作。这个阶段的目的是将之前的尝试阶段所做的操作进行回滚,保证操作的一致性。
TCC 是业务层面的分布式事务,保证最终一致性,不会一直持有资源的锁。
- 优点 :把数据库层的二阶段提交交给应用层来实现,规避了数据库的 2PC 性能低下问题
- 缺点:TCC 的 Try、Confirm 和 Cancel 操作功能需业务提供,开发成本高。TCC 对业务的侵入较大和业务紧耦合,需要根据特定的场景和业务逻辑来设计相应的操作
9.4 Saga事务
Saga模式的核心思想是将一个大的事务拆分为多个小的、可撤销的事务,每个小事务都有自己的确认和补偿操作。
和 TCC 类似,Saga 正向操作与补偿操作都需要业务开发者自己实现。和 TCC 很大的一点不同是 Saga 没有“Try” 动作,它的本地事务直接被提交。因此,性能非常高!但因为 Saga 没有进行“Try” 动作预留资源,所以不能保证隔离性。这也是 Saga 比较大的一个缺点。
理论上来说,补偿操作一定能够执行成功。不过,当网络出现问题或者服务器宕机的话,补偿操作也会执行失败。这种情况下,往往需要我们进行人工干预。并且,为了能够提高容错性(比如 Saga 系统本身也可能会崩溃),保证所有的短事务都得以提交或补偿,我们还需要将这些操作通过日志记录下来(Saga log,类似于数据库的日志机制)。这样,Saga 系统恢复之后,我们就知道短事务执行到哪里了或者补偿操作执行到哪里了。
9.5 本地消息表⭐
本地消息表的主要思想是将消息的发送和接收过程分成两个独立的步骤,并使用本地数据库表来记录消息的发送状态和接收状态。
具体来说,当需要发送消息时,首先将消息内容保存到本地消息表中,并将消息状态标记为“待发送”。然后,使用定时任务轮询查询状态为“待发送”的消息表,通过消息队列将消息发送到目标服务。当目标服务接收到消息并成功处理后,将会在本地消息表中更新消息的状态为“已发送”。如果消息发送失败或者处理失败,则可以根据消息表中的状态进行相应的处理,例如重试发送消息。
优点:本地消息表利用本地数据库来记录消息状态,即使在消息传递过程中出现异常或者系统故障,消息也不会丢失,可以通过本地消息表进行恢复和处理。
缺点:由于本地消息表使用本地数据库来记录消息状态,可能会给数据库带来一定的压力。特别是在高并发和大规模消息传递的情况下,可能会对数据库性能产生影响。
9.6 MQ事务
MQ(消息队列)事务能够保证消息在发送和消费过程中的原子性。MQ事务依赖于消息中间件的事务消息,例如RocketMQ 就支持事务消息(半消息)。
-
生产者将事务消息发送到MQ,发送成功后,执行本地事务。
-
如果本地事务执行成功,生产者向MQ发送确认消息(Commit),通知MQ可以将事务消息投递给消费者。
-
如果本地事务执行失败,生产者会向MQ服务器发送取消消息(Rollback),通知MQ服务器丢弃该事务消息。
-
MQ收到确认消息后,会将事务消息投递给消费者进行消费。
-
MQ收到消费者的确认消息后,将消息标记为已完成,从队列中移除该消息。如果消费者没有发送确认消息,MQ 会进行重试,直到消息被确认或者达到最大重试次数为止。
对比本地消息表实现方案,不需要再建消息表,对性能的损耗和业务的入侵更小。
10 Seata
10.1 了解 Seata 的实现原理吗?
Seata 是一款开源的分布式事务解决方案,旨在解决分布式事务的一致性和可靠性问题。Seata 最初由阿里巴巴公司发起,现在是一个独立的开源项目,被广泛应用于云原生和微服务架构中。
- 事务协调器(Transaction Coordinator):事务协调器负责协调和管理分布式事务的整个过程。它接收事务的开始和结束请求,并根据事务的状态进行协调和处理。事务协调器还负责记录和管理事务的全局事务 ID(Global Transaction ID)和分支事务 ID(Branch Transaction ID)。
- 事务管理器(Transaction Manager):事务管理器负责全局事务的管理和控制。它协调各个分支事务的提交或回滚,并保证分布式事务的一致性和隔离性。事务管理器还负责与事务协调器进行通信,并将事务的状态变更进行持久化。
- 资源管理器(Resource Manager):资源管理器负责管理和控制各个参与者(Participant)的事务操作。它与事务管理器进行通信,并根据事务管理器的指令执行相应的事务操作,包括提交和回滚。
10.2 Seata 支持哪些模式的分布式事务?
-
原子性事务模式(AT,ATomikity): 原子性事务模式类似于传统数据库的 ACID 原子性事务特性,它通过在全局事务中注册和管理分支事务,保证分布式事务的一致性和可靠性。在 AT 模式中,全局事务的提交要求所有的分支事务都提交成功,否则进行回滚操作。
-
TCC 模式(Try-Confirm-Cancel): TCC 是一种补偿性事务模式,适用于需要进行业务补偿的场景。TCC 模式通过分别执行 Try、Confirm 和 Cancel 三个操作来保证分布式事务的一致性和可靠性。在 TCC 模式中,Try 阶段负责预留资源和执行业务逻辑,Confirm 阶段负责确认事务提交,Cancel 阶段负责事务回滚。
-
Saga 模式: Saga 是一种分布式事务模式,通过分布式状态机的方式来管理分布式事务的状态和流转。Saga 模式支持长事务和业务流程的补偿,适用于复杂的分布式事务场景。在 Saga 模式中,通过多个连续的子事务来执行业务逻辑,并通过补偿事务来保证事务的一致性。
-
XA 模式:XA 模式是一种基于两阶段提交协议的分布式事务模式。在 XA 模式中,Seata 通过与数据库的 XA 事务协议进行交互,实现对分布式事务的管理和协调。XA 模式需要数据库本身支持 XA 事务,并且需要在应用程序中配置相应的 XA 数据源。
10.3 全局事务 ID 和分支事务 ID 是怎么传递的?
全局事务 ID 和分支事务 ID 在分布式事务中通过上下文传递的方式进行传递。常见的传递方式包括参数传递、线程上下文传递和消息中间件传递。具体的传递方式可以根据业务场景和技术选型进行选择和调整。
10.4 Seata 的事务回滚是怎么实现的?
Seata 的事务回滚是通过回滚日志实现的。每个参与者在执行本地事务期间生成回滚日志,记录了对数据的修改操作。
当需要回滚事务时,事务协调器向参与者发送回滚请求,参与者根据回滚日志中的信息执行撤销操作,将数据恢复到事务开始前的状态。
回滚日志的管理和存储是 Seata 的核心机制,可以选择将日志存储在不同的介质中。通过回滚日志的持久化和恢复,Seata 确保了事务的一致性和恢复性。
11 分布式锁
11.1 什么是分布式锁
分布式锁是一种用于在分布式系统中控制并发访问的机制,它可以确保在分布式环境下的多个线程对共享资源的访问互斥性。
场景:
-
共享资源访问控制: 分布式系统中可能存在多个线程同时访问共享资源的情况,需要使用分布式锁来确保对共享资源的访问互斥性,避免出现并发访问的问题。
-
防止资源竞争: 分布式系统中的线程可能会竞争有限的资源,例如数据库连接、文件系统资源等,需要使用分布式锁来协调资源的竞争,避免资源的过度消耗或者浪费。
-
避免重复操作: 在某些场景下,需要确保某个操作只能被执行一次,例如定时任务的唯一性执行、分布式系统中的幂等性操作等,可以使用分布式锁来避免重复操作。
-
分布式事务: 在分布式事务中,可能需要使用分布式锁来确保事务的一致性和隔离性,避免分布式系统中的数据竞争和并发问题。
11.2 分布式锁的实现方案及优缺点🔥
-
基于数据库的分布式锁:利用唯一索引,在数据库中创建一个表,用来存储锁的状态信息。加锁的时候,在表中增加一条记录。释放锁的时候删除记录就行。
- 优点: 实现简单,适用于多种数据库系统。
- 缺点: 性能较差,数据库的性能和可用性可能会成为瓶颈,不适合高并发和大规模的分布式系统。
-
基于 Redis 的分布式锁:通过设置一个唯一的键作为锁,利用 Redis 的SETNX(SET if Not eXists)来获取锁,也就是如果不存在则更新,并通过DEL命令来释放锁。不过一般项目中都是使用Redission 来实现分布式锁。
- 优点: 性能较高,具有良好的可扩展性和高并发能力,适用于大规模分布式系统。
- 缺点: 可用性依赖于缓存系统,可能存在单点故障,需要考虑缓存系统的高可用架构。
-
基于ZooKeeper的分布式锁:基于 临时顺序节点 和 Watcher(事件监听器) 实现的。创建临时顺序节点后,若是最小的子节点则加锁成功。若加锁失败会在前一个节点注册一个事件监听器,当前一个节点释放锁之后进行通知避免无效自旋。
- 优点: 具有良好的一致性和可靠性,适用于分布式协调和领导者选举等场景。
- 缺点: 性能较差,不适合高频率的锁竞争,而且依赖ZooKeeper集群,配置和管理较为复杂。