面试总结-分布式
1.uuid和主键自增的优缺点
1.1使用自增主键
优点:
1、数据存储空间小
2、查询效率高
缺点:
1、如果数据量过大,会超出自增长的值范围
2、分布式存储的表操作,尤其是在合并的时候操作复杂
3、安全性低,因为是有规律的,如果恶意扒取用户信息会很容易,如果是单据编号使用,竞争对手会容易查询出货量
4、因自动增长,当手动插入的时候会出现麻烦,主键冲突的情况会有发生
1.2使用UUID主键
优点:
1、出现重复的机会少
2、适合大量数据的插入和更新操作,尤其是在高并发和分布式环境下
3、安全性较高
缺点:
1、存储空间大(16 byte),因此它将会占用更多的磁盘空间, MySQL官方有明确的建议主键要尽量越短越好,36个字符长度的UUID不符合要求
2、性能降低,对MySQL索引不利: 如果作为数据库主键,在InnoDB引擎下,UUID的无序性可能会引起数据位置频繁变动,严重影响性能。
3、无序的字符串,不具备趋势自增特性
4、没有具体的业务含义
1.3适用场景
1、项目是单机版的,并且数据量比较大(百万级)时,用自增长的,此时最好能考虑下安全性,做些安全措施
2、项目是单机版的,并且数据量没那么大,对速度和存储要求不高时,用UUID
3、项目是分布式的,那么首选UUID,分布式一般对速度和存储要求不高
4、项目是分布式的,并且数据量达到千万级别可更高时,对速度和存储有要求时,可以用自增长。
1.4分布式环境主键
1.4.1UUID:
可以做分布式ID吗?答案是可以的,但是并不推荐!
原因同UUID的缺点。
1.4.2主键自增:
当我们需要一个ID的时候,向表中插入一条记录返回主键ID,但这种方式有一个比较致命的缺点,访问量激增时MySQL本身就是系统的瓶颈,用它来实现分布式服务风险比较大,不推荐!DB单点存在宕机风险,无法扛住高并发场景
1.4.3基于数据库集群模式:
多主集群模式,各自生成主键
设置起始值和自增步长
MySQL_1 配置:
set @@auto_increment_offset = 1; – 起始值
set @@auto_increment_increment = 2; – 步长
MySQL_2 配置:
set @@auto_increment_offset = 2; – 起始值
set @@auto_increment_increment = 2; – 步长
增加第三台MySQL实例需要人工修改一、二两台MySQL实例的起始值和步长,把第三台机器的ID起始生成位置设定在比现有最大自增ID的位置远一些,但必须在一、二两台MySQL实例ID还没有增长到第三台MySQL实例的起始ID值的时候,否则自增ID就要出现重复了,必要时可能还需要停机修改。
优点:
解决DB单点问题
缺点:
不利于后续扩容,而且实际上单个数据库自身压力还是大,依旧无法满足高并发场景。
1.4.4基于数据库的号段模式
号段模式是当下分布式ID生成器的主流实现方式之一,号段模式可以理解为从数据库批量的获取自增ID,每次从数据库取出一个号段范围,例如 (1,1000] 代表1000个ID,具体的业务服务将本号段,生成1~1000的自增ID并加载到内存。表结构如下:
biz_type :代表不同业务类型
max_id :当前最大的可用id
step :代表号段的长度
version :是一个乐观锁,每次都更新version,保证并发时数据的正确性
生成方式不强依赖于数据库,不会频繁的访问数据库,对数据库的压力小很多。
1.4.5基于Redis模式
原理就是利用redis的 incr命令实现ID的原子性自增。用redis实现需要注意一点,要考虑到redis持久化的问题
RDB会定时打一个快照进行持久化,假如连续自增但redis没及时持久化,而这会Redis挂掉了,重启Redis后会出现ID重复的情况。
AOF会对每条写命令进行持久化,即使Redis挂掉了也不会出现ID重复的情况,但由于incr命令的特殊性,会导致Redis重启恢复的数据时间过长。
1.4.6基于雪花算法(Snowflake)模式
Snowflake ID组成结构:正数位(占1比特)+ 时间戳(占41比特)+ 机器ID(占5比特)+ 数据中心(占5比特)+ 自增值(占12比特),总共64比特组成的一个Long类型。
第一个bit位(1bit):Java中long的最高位是符号位代表正负,正数是0,负数是1,一般生成ID都为正数,所以默认为0。
时间戳部分(41bit):毫秒级的时间,不建议存当前时间戳,而是用(当前时间戳 - 固定开始时间戳)的差值,可以使产生的ID从更小的值开始;41位的时间戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年
工作机器id(10bit):也被叫做workId,这个可以灵活配置,机房或者机器号组合都可以。
序列号部分(12bit),自增值支持同一毫秒内同一个节点可以生成4096个ID
1.4.7百度(uid-generator)
1.4.8美团(Leaf)
1.4.9滴滴(Tinyid)
2.分布式事务如何实现
RocketMQ
- 第一步先给 Broker 发送事务消息即半消息,半消息不是说一半消息,而是这个消息对消费者来说不可见,然后发送成功后发送方再执行本地事务。
- 再根据本地事务的结果向 Broker 发送 Commit 或者 RollBack 命令。
- 并且 RocketMQ 的发送方会提供一个反查事务状态接口,如果一段时间内半消息没有收到任何操作请求,那么 Broker 会通过反查接口得知发送方事务是否执行成功,然后执行 Commit 或者 RollBack 命令。
- 如果是 Commit 那么订阅方就能收到这条消息,然后再做对应的操作,做完了之后再消费这条消息即可。
- 如果是 RollBack 那么订阅方收不到这条消息,等于事务就没执行过。
2.1 2PC(Two-phase commit protocol)二阶段提交
- 二阶段提交是一种强一致性设计,2PC 引入一个事务协调者的角色来协调管理各参与者(也可称之为各本地资源)的提交和回滚,二阶段分别指的是准备(投票)和提交两个阶段。
准备阶段协调者会给各参与者发送准备命令(除了提交事务之外)。
同步等待所有资源的响应之后就进入第二阶段即提交阶段(注意提交阶段不一定是提交事务,也可能是回滚事务)。
假如在第一阶段所有参与者都返回准备成功,那么协调者则向所有参与者发送提交事务命令,然后等待所有事务都提交成功之后,返回事务执行成功。
2PC 是一种尽量保证强一致性的分布式事务,因此它是同步阻塞的,而同步阻塞就导致长久的资源锁定问题,总体而言效率低,并且存在单点故障问题,在极端条件下存在数据不一致的风险。
1.性能问题
XA协议遵循强一致性。在事务执行过程中,各个节点占用着数据库资源,只有当所有节点准备完毕,事务协调者才会通知提交,参与者提交后释放资源。这样的过程有着非常明显的性能问题。
2.协调者单点故障问题
事务协调者是整个XA模型的核心,一旦事务协调者节点挂掉,参与者收不到提交或是回滚通知,参与者会一直处于中间状态无法完成事务。
3.丢失消息导致的不一致问题。
在XA协议的第二个阶段,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,那么就导致了节点之间数据的不一致。
2.2 3PC(Three-phase commit protocol)三阶段提交
- 3PC(三阶段提交) 的出现是为了解决 2PC 的一些问题,引入了参与者超时机制,并且增加了预提交阶段使得故障恢复之后协调者的决策复杂度降低,但整体的交互过程更长了,性能有所下降,并且还是会存在数据不一致问题。
- 3PC 包含了三个阶段,分别是准备阶段、预提交阶段和提交阶段,对应的英文就是:CanCommit、PreCommit 和 DoCommit。
2.3 TCC(Try - Confirm - Cancel)预留-确认-撤销
- 2PC 和 3PC 都是数据库层面的,而 TCC 是业务层面的分布式事务,就像我前面说的分布式事务不仅仅包括数据库的操作,还包括发送短信等,这时候 TCC 就派上用场了!
- TCC 指的是Try - Confirm - Cancel。
- Try 指的是预留,即资源的预留和锁定,注意是预留。
- Confirm 指的是确认操作,这一步其实就是真正的执行了。
- Cancel 指的是撤销操作,可以理解为把预留阶段的动作撤销了。
因为是在业务上实现的,所以TCC可以跨数据库、跨不同的业务系统来实现事务。
2.4 本地消息表
- 本地消息表顾名思义就是会有一张存放本地消息的表,一般都是放在数据库中,然后在执行业务的时候 将业务的执行和将消息放入消息表中的操作放在同一个事务中,这样就能保证消息放入本地表中业务肯定是执行成功的。
- 然后再去调用下一个操作,如果下一个操作调用成功了好说,消息表的消息状态可以直接改成已成功。
- 如果调用失败也没事,会有 后台任务定时去读取本地消息表,筛选出还未成功的消息再调用对应的服务,服务更新成功了再变更消息的状态。