分布式全局ID与分布式事务
1. 概述
老话说的好:人不可貌相,海水不可斗量。以貌取人是非常不好的,我们要平等的对待每一个人。
言归正传,今天我们来聊一下分布式全局 ID 与分布式事务。
2. 分布式全局ID
2.1 分布式数据库引发的问题
在数据库中,每个表都有一个主键(ID),用于作为一条数据的唯一标识。
在单体数据库中,大多数时候,我们会采用主键自增的方式生成 ID。
但在分布式的数据库中,使用了分库分表后,数据会被分配到多个表中,这时再用主键自增的方式就不合适了,每张表的 ID 都是从 0 开始自增,多个表中会出现重复的 ID,从而引发业务问题。
2.2 分布式全局ID的常用策略
2.2.1 UUID
UUID 我们再熟悉不过了,即使是单体数据库,我们也常常会使用 UUID 作为 ID 的值。
UUID的好处是使用简单,一句Java代码就能简单的生成,而且保证绝对全局唯一。
UUID也有不好的地方,一个是长度较长,至少需要32位,而且只是单纯的ID,没有实际意义,也不能作为排序的依据。
之前介绍过的 MyCat 和 ShardingJDBC 都有自动生成ID的机制,但 MyCat 不支持 UUID。
我们也可以使用工具类帮我们生成UUID,或者使用框架生成,例如:JPA。
2.2.2 雪花算法
雪花算法生成的ID,是一个 64bit 的 Long 型数字,是一个递增的数字,可用于排序。
雪花算法基本可以保持全局唯一,毫秒内可并发4096个数字。
但时间的回调可能会引起 ID 的重复。
3. 分布式事务
3.1 分布式数据库导致的事务问题
事务这个词,我们并不陌生,在单体数据库中,所有的业务表都在一个数据库中,在 SpringBoot 中我们常常会使用 @Transactional 注解去保证事务。
但到了分布式数据库,常常会根据业务对数据库进行 垂直切分 和 水平切分,一个业务方法常常会操作两个或多个数据源,此时 @Transactional 注解 就无法保证事务了。
3.2 分布式事务的常用策略
3.2.1 基于XA协议的两阶段提交
两阶段提交,分为两个阶段,准备阶段 和 提交阶段。
简单理解就是 所有的事务先准备成功后,然后一起提交,如果有一个事务准备失败,所有事务都会回滚。
如果在提交阶段出现了问题,则会造成事务的不一致,需要人工介入。
在两阶段提交过程中,在所有事务没有完全提交完成前,数据是锁死状态,其他线程访问会被阻塞。
由于两阶段提交需要等待所有的事务提交完成,因此效率低下,性能与本地事务相差10倍,用户体验不是很好。
在生产环境不建议使用。
3.2.2 事务补偿机制(TCC)
TTC就是 try、confirm、cancel,简单说就是每个操作都有一个对应的取消(cancel)方法,如果执行失败,则调用取消(cancel)方法撤销之前的操作。
如果取消(cancel)方法执行时发生了错误,则需要定时任务去轮询补偿,或者人工介入。
事务补偿机制的好处是逻辑比较简单,先执行 A 事务,再执行 B 事务,B 事务执行失败了,则调用 A 事务的取消(cancel)方法撤销 A事务 之前的操作。
但需要程序员自己编写的代码较多,无形中增加了很多工作量,且容易出错。
3.2.3 使用消息队列(MQ)实现最终一致性
利用消息队列的机制,异步的去执行每一个事务,达到最终一致。
通常的做法是 在本地维护一个消息表,或者给业务数据增加一个消息状态字段。
将后续的操作以消息的形式发送到消息队列,消息队列收到消息后给予Ack回执,收到回执后修改消息的状态。
消费者从消息队列订阅消息,执行接下来的逻辑,要注意消费方法的幂等性。
当然还需要有一个定时程序,轮询消息表,发现有问题的消息,执行重发或修改状态人工介入。
推荐以这种方式实现分布式事务。
4. 综述
今天聊了一下 分布式全局ID与分布式事务,希望可以对大家的工作有所帮助。
欢迎帮忙点赞、评论、转发、加关注 :)
关注追风人聊Java,每天更新Java干货。
5. 个人公众号
追风人聊Java,欢迎大家关注