java 分布式面试题
分布式分为分布式缓存(Redis)、分布式锁(Redis或Zookeeper)、分布式微服务(Dubbo或SpringCloud)、分布式服务协调(Zookeeper)、分布式消息队列(Kafka、RabbitMq)、分布式事务、分布式搜索(elastaticSearch)等。
不可能所有分布式内容都熟悉,一定要在某个领域有所专长。
分布式理论
Q:分布式有哪些理论?
CAP、BASE。
分布式CAP理论,任何一个分布式系统都无法同时满足Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性) 这三个基本需求。最多只能满足其中两项。
而Partition tolerance(分区容错性) 是必须的,因此一般是CP,或者AP。
BASE理论。Base Available(基本可用)、Soft state(软状态)、Eventually consistent(最终一致性)。
基本可用:指分布式系统在出现不可预知故障的时候,允许损失部分可用性。比如响应时间上的损失、功能上的损失。
弱状态:也称软状态。是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
最终一致性:系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。
Q:你怎么理解分布式一致性?
数据一致性通常指关联数据之间的逻辑关系是否正确和完整。
在分布式系统中,数据一致性往往指的是由于数据的复制,不同数据节点中的数据内容是否完整并且相同。
一致性还分为强一致性,弱一致性,还有最终一致性。
强一致性就是马上就保持一致。
最终一致性是指经过一段时间后,可以保持一致。
分布式事务
Q:你怎么理解分布式事务?分布式事务的协议有哪些?
分布式事务是指会涉及到操作多个数据库的事务。目的是为了保证分布式系统中的数据一致性。
分布式事务类型:二阶段提交2PC,三阶段提交3PC。
2PC:第一阶段:准备阶段(投票阶段)和第二阶段:提交阶段(执行阶段)。
3PC:三个阶段:CanCommit(询问能否提交)、PreCommit(执行事务预提交)、DoCommit(执行提交事务)
Q:分布式事务的解决方案有哪些?
分布式事务解决方案:TCC、本地消息表、消息事务、最大努力通知。
详情见:https://www.cnblogs.com/expiator/p/9841703.html
面试题详细答案见:https://juejin.im/post/5d70b0535188251637727de8
TCC
Q:讲一下TCC
T(try):锁资源。锁定某个资源,设置一个预备类的状态,冻结部分数据。
比如,订单的支付状态,先把状态修改为"支付中(PAYING)"。
比如,本来库存数量是 100,现在卖出了2个,不要直接扣减这个库存。
在一个单独的冻结库存的字段,比如prepare_remove_stock字段,设置一个 2。也就是说,有 2 个库存是给冻结了。
积分服务的也是同理,别直接给用户增加会员积分。你可以先在积分表里的一个预增加积分字段加入积分。
比如:用户积分原本是 1190,现在要增加 10 个积分,别直接 1190 + 10 = 1200 个积分啊!
你可以保持积分为 1190 不变,在一个预增加字段里,比如说 prepare_add_credit 字段,设置一个 10,表示有 10 个积分准备增加。
C(comfirm):
在各个服务里引入了一个 TCC 分布式事务的框架,事务管理器可以感知到各个服务的 Try 操作是否都成功了。
假如都成功了,TCC 分布式事务框架会控制进入 TCC 下一个阶段,第一个 C 阶段,也就是 Confirm 阶段。
此时,需要把Try阶段锁住的资源进行处理。
比如,把订单的状态设置为“已支付(Payed)”。
比如,扣除掉相应的库存。
比如,增加用户积分。
C(cancel):
在 Try 阶段,假如某个服务执行出错,比如积分服务执行出错了,那么服务内的 TCC 事务框架是可以感知到的,然后它会决定对整个 TCC 分布式事务进行回滚。
TCC 分布式事务框架只要感知到了任何一个服务的 Try 逻辑失败了,就会跟各个服务内的 TCC 分布式事务框架进行通信,然后调用各个服务的 Cancel 逻辑。
也就是说,会执行各个服务的第二个 C 阶段,Cancel 阶段。
比如,订单的支付状态,先把状态修改为"closed"状态。
比如,冻结库存的字段,prepare_remove_stock字段,将冻结的库存2清零。
比如,预增加积分的字段,prepare_add_credit 字段,将准备增加的积分10清零。
详情见:https://www.cnblogs.com/jajian/p/10014145.html
Q:事务管理器宕掉了,怎么办?
做冗余,设置多个事务管理器,一个宕掉了,其他的还可以用。
Q:什么是tcc空回滚、业务悬挂?
空回滚:当某分支事务的try阶段阻塞时,可能导致全局事务超时而触发二阶段的cancel操作。在未执行try操作时先执行了cancel操作,这时cancel不能做回滚,就是空回滚。
业务悬挂:对于已经空回滚的业务,如果以后继续执行try,就永远不可能confirm或cancel,事务一直处于中间状态,这就是业务悬挂。
执行try操作时,应当判断cancel是否已经执行过了,如果已经执行,应当阻止空回滚后的try操作,避免悬挂。
Q:怎么解决tcc空悬挂、空回滚?
记录当前事务id和执行状态
CREATE TABLE `account_freeze_tbl` (
`xid` varchar(128) NOT NULL COMMENT '事务id',
`user_id` varchar(255) DEFAULT NULL COMMENT '用户id',
`freeze_money` int(11) unsigned DEFAULT '0' COMMENT '冻结金额',
`state` int(1) DEFAULT NULL COMMENT '事务状态,0:try,1:confirm,2:cancel',
PRIMARY KEY (`xid`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
-
如何判断是否空回滚
cancel业务中,根据事务id(xid)查询account_freeze,如果为null则说明try还没做,需要空回滚。 -
如何避免业务悬挂
try业务中,根据事务id(xid)查询account_freeze ,如果已经存在则证明Cancel已经执行,拒绝执行try业务。
参考资料: https://cloud.tencent.com/developer/article/2048776
本地消息表
执行流程:
- 消息生产方,需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过 MQ 发送到消息的消费方。如果消息发送失败,会进行重试发送。
- 消息消费方,需要处理这个消息,并完成自己的业务逻辑。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。
- 此时如果本地事务处理成功,表明已经处理成功了。如果处理失败,那么就会重试执行。
- 生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。
消息事务
消息事务的原理是将两个事务通过消息中间件进行异步解耦,和上述的本地消息表有点类似,但是是通过消息中间件的机制去做的,其本质就是“将本地消息表封装到了消息中间件中”。
执行流程:
- 发送 prepare 消息到消息中间件。
- 发送成功后,执行本地事务。
- 如果事务执行成功,则 commit,消息中间件将消息下发至消费端。如果事务执行失败,则回滚,消息中间件将这条 prepare 消息删除。
- 消费端接收到消息进行消费,如果消费失败,则不断重试。
这种方案也是实现了 「最终一致性」 ,对比本地消息表实现方案,不需要再建消息表, 「不再依赖本地数据库事务」。
了,所以这种方案更适用于高并发的场景。目前市面上实现该方案的 「只有阿里的 RocketMQ」 。
最大努力通知
最大努力通知的方案实现比较简单,适用于一些最终一致性要求较低的业务。
- 执行流程:
系统 A 本地事务执行完之后,发送个消息到 MQ。
这里会有个专门消费 MQ 的服务,该服务会消费 MQ 并调用系统 B 的接口。
要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B,反复 N 次,最后还是不行就放弃。
幂等性
Q:怎么保证分布式系统的幂等性?
-
业务表内唯一索引
比如订单id,创建一个unique key唯一索引,如果接口重试,两次创建同一个订单,一定会违反唯一索引,报错。
这种方案常见于插入,并且这个数据在数据库中只能有一条数据。 -
状态机制。版本号机制。利用乐观锁。
update tt_order set status = 2 where status = 1 and id = ABC
- 高并发的场景。可以用唯一key放到redsi。
用请求相关的参数拼接一个key出来,直接set设置到redis里去,如果下一次再请求再过来了,此时会发现这个key已经存在了,那么这个时候就不能执行了,因为已经出现重复调用了.