Java 基础面试题【分布式】二
分布式锁解决方案
思考的点:
需要这个锁独立于每一个服务之外,而不是在服务里面。数据库:利用主键冲突控制一次只有一个线程能获取锁,非阻塞、不可重入、单点、失效时间
Zookeeper 分布式锁
zk 通过临时节点,解决了死锁的问题,一旦客户端获取到锁之后突然挂掉(Session 连接断开),那么这个临 时节点就会自动删除掉,其他客户端自动获取锁。临时顺序节点解决羊群效应
Redis 分布式锁
setNX,单线程处理网络请求,不需要考虑并发安全性
所有服务节点设置相同的 key,返回为 0、则锁获取失败
setnx 问题:
早期版本没有超时参数,需要单独设置,存在死锁问题(中途宕机) ,可通过过期时间保证。
后期版本提供加锁与设置时间原子操作,但是存在任务超时,锁自动释放,导致并发问题,加锁与释放锁不是同一线程问题 ,可重入性及锁续期没有实现,通过 redisson 解决(类似 AQS 的实现,看门狗监听机制)
可参考文章:面试官:你真的了解Redis分布式锁吗?
redlock:意思的机制都只操作单节点、即使 Redis 通过 sentinel 保证高可用,如果这个 master 节点由于 某些原因发生了主从切换,那么就会出现锁丢失的情况(redis 同步设置可能数据丢失)。redlock 从多 个节点申请锁,当一半以上节点获取成功、锁才算获取成功,redission 有相应的实现
分布式事务解决方案
XA 规范:分布式事务规范,定义了分布式事务模型
四个角色:事务管理器(协调者 TM)、资源管理器(参与者 RM),应用程序 AP,通信资源管理器 CRM
全局事务:一个横跨多个数据库的事务,要么全部提交、要么全部回滚
JTA 事务时 java 对 XA 规范的实现,对应 JDBC 的单库事务
两阶段协议:
**说明: **第一阶段( prepare ) :每个参与者执行本地事务但不提交,进入 ready 状态,并通知协调者已经准备就绪。
第二阶段( commit ) 当协调者确认每个参与者都 ready 后,通知参与者进行 commit 操作;如果有 参与者 fail ,则发送 rollback 命令,各参与者做回滚。问题:
单点故障:一旦事务管理器出现故障,整个系统不可用(参与者都会阻塞住)
数据不一致:在阶段二,如果事务管理器只发送了部分 commit 消息,此时网络发生异常,那么 只有部分参与者接收到 commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一 致。
响应时间较长:参与者和协调者资源都被锁住,提交或者回滚之后才能释放
不确定性:当协事务管理器发送 commit 之后,并且此时只有一个参与者收到了 commit,那么当 该参与者与事务管理器同时宕机之后,重新选举的事务管理器无法确定该条消息是否提交成功。
三阶段协议
主要是针对两阶段的优化,解决了 2PC 单点故障的问题,但是性能问题和不一致问题仍然 没有根本解决
引入了超时机制解决参与者阻塞的问题,超时后本地提交,2pc 只有协调者有超时机制
第一阶段:CanCommit 阶段,协调者询问事务参与者,是否有能力完成此次事务。
如果都返回 yes,则进入第二阶段
有一个返回 no 或等待响应超时,则中断事务,并向所有参与者发送 abort 请求
第二阶段:PreCommit 阶段,此时协调者会向所有的参与者发送 PreCommit 请求,参与者收到后 开始执行事务操作。参与者执行完事务操作后(此时属于未提交事务的状态),就会向协调者反馈 “Ack”表示我已经准备好提交了,并等待协调者的下一步指令。
第三阶段:DoCommit 阶段, 在阶段二中如果所有的参与者节点都返回了 Ack,那么协调者就会从 “预提交状态”转变为“提交状态”。然后向所有的参与者节点发送"doCommit"请求,参与者节点在 收到提交请求后就会各自执行事务提交操作,并向协调者节点反馈“Ack”消息,协调者收到所有参 与者的 Ack 消息后完成事务。 相反,如果有一个参与者节点未完成 PreCommit 的反馈或者反馈超 时,那么协调者都会向所有的参与者节点发送 abort 请求,从而中断事务。
TCC(补偿事务):
Try、Confirm、Cancel 针对每个操作,都要注册一个与其对应的确认和补偿(撤销)。
操作 Try 操作做业务检查及资源预留,
Confirm 做业务确认操作,
Cancel 实现一个与 Try 相反的操作既回滚操 作。
TM 首先发起所有的分支事务的 try 操作,任何一个分支事务的 try 操作执行失败,TM 将会发起所有 分支事务的 Cancel 操作,若 try 操作全部成功,TM 将会发起所有分支事务的 Confirm 操作,其中 Confirm/Cancel 操作若执行失败,TM 会进行重试。TCC 模型对业务的侵入性较强,改造的难度较大,每个操作都需要有 try 、 confirm 、 cancel 三个接 口实现 confirm 和 cancel 接口还必须实现幂等性。
消息队列的事务消息:
发送 prepare 消息到消息中间件
发送成功后,执行本地事务
如果事务执行成功,则 commit,消息中间件将消息下发至消费端(commit 前,消息不会被 消费)
如果事务执行失败,则回滚,消息中间件将这条 prepare 消息删除
消费端接收到消息进行消费,如果消费失败,则不断重试
如何实现接口的幂等性
唯一 id。每次操作,都根据操作和内容生成唯一的 id,在执行之前先判断 id 是否存在,如果不存在 则执行后续操作,并且保存到数据库或者 redis 等。
服务端提供发送 token 的接口,业务调用接口前先获取 token,然后调用业务接口请求时,把 token 携带过去,务器判断 token 是否存在 redis 中,存在表示第一次请求,可以继续执行业务,执行业务 完成后,最后需要把 redis 中的 token 删除
建去重表。将业务中有唯一标识的字段保存到去重表,如果表中存在,则表示已经处理过了
版本控制。增加版本号,当版本号符合时,才能更新数据
状态控制。例如订单有状态已支付 未支付 支付中 支付失败,当处于未支付的时候才允许修改为支 付中等
如有问题,欢迎加微信交流:w714771310,备注- 技术交流 。或关注微信公众号【码上遇见你】。
版权声明: 本文为 InfoQ 作者【派大星】的原创文章。
原文链接:【http://xie.infoq.cn/article/159824c4fbab0d963abbbcf70】。
本文遵守【CC BY-NC-ND】协议,转载请保留原文出处及本版权声明。
评论