分布式系统题目答案(3)
8、MySQL 如何实现 XA 规范?
说 XA 规范之前,需要先说明一下 MySQL 及 InnoDB 引擎中涉及到的日志:
重做日志(redo log),数据真正更改前,会先把相关操作写入 redo 日志,遇到意外情况,待系统恢复后,可以根据日志继续完成记录中的更改操作。
回滚日志(undo log),记录事务开始前数据的状态,更改在执行一半时发生意外,就可以根据撤消日志恢复到更改之前的状态。
二进制日志(binlog),MySQL sever 层维护的一种二进制日志,记录了所有的 DDL 和 DML 语句,主要用来复制和恢复。
XA 规范是由 X/Open 组织提出的分布式事务规范,主要定义了事务协调者(Transaction Manager)和资源管理器(Resource Manager)之间的接口。
XA 事务由两阶段提交实现:
Prepare 阶段,Transaction Manager 向 Resource Manager 发送prepare 指令,Resource Manager 收到指令执行操作,返回是否可以提交。
Commit 阶段,Transaction Manager 收到 Resource Manager 返回结果,有不可提交或超时的情况,向 Resource Manager 发出回滚指令;若都可以提交,则发出提交指令。
MySQL 单机 XA 的实现,binlog 作为协调者,在事务提交时,则需要将提交信息写入二进制日志。
分布式的 XA 的实现,通过代理层使用 START/ END / PREPARE / COMMIT 这些指令完成分布式事务。
9、TCC 事务模型的实现
目的是解决复杂业务中,跨表跨库等大颗粒度资源锁定的问题,基于业务层面的事务定义,锁粒度完全由业务自己控制。
TCC 把事务运行过程分成 Try、Confirm/Cancel 两个阶段,每个阶段的逻辑由业务代码控制,避免长事务,获取更高性能。
Try 阶段:调用 Try 接口,尝试执行业务,完成所有业务检查,预留业务资源
Confirm/Cancel阶段:提交或取消,接口幂等、允许失败重试。
TCC 主要把数据库 XA 中的二阶段提交,提升到微服务层面实现,解决了跨服务的业务操作原子性问题。
TCC 的不足主要体现在对微服务的侵入性强,需要对业务系统进行改造,业务逻辑的每个分支都需要实现 try、Confirm、Cancel 三个操作,并且 Confirm、Cancel 必须保证幂等。
实现 TCC 模型的组件:
Seata TCC 模式、Tcc-transaction、ByteTCC、Spring-cloud-rest-tcc 等。
10、分布式锁如何实现
常用三种实现方式:
1、基于数据库的实现,依赖数据库的唯一性来实现资源锁定-- 建表
CREATE TABLE `t_lock` (
`lock_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的方法或者资源',
PRIMARY KEY (`lock_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 尝试加锁
insert into t_lock(lock_name) values ('lock_name');
执行 insert 语句尝试加锁,插入成功代表加锁成功,执行完成,删除记录释放锁。
但基于数据库实现的分布式锁,存在很多问题,如:需要数据库高可用防止单点故障、需要自行解决超时锁释放的问题、处理锁可重入需要修改表结构加上线程唯一标识进行判断、数据库连接资源比较宝贵。所以生产环境一般不使用这种方式。
2、基于 Redis 实现
setnx + expire,设置缓存值 + 过期时间,由于两个指令非原子操作,仍然可能存在 setnx 成功 expire 失败的情况导致锁不会释放的情况。
Redis 2.8 新增了 SETEX 指令支持 setnx + expire 的原子操作,但如果过期时间设置过短,会导致锁提前释放。所以业务操作不要时间太长;set 的 value 给一个随机数,释放时线程持有值与redis缓存值进行比对,相同才给予释放;记录上下游链路,异常情况进行追踪、人工介入处理。
在 redis 集群环境下,数据同步是异步的,所以会存在加锁数据未同步到其他节点,导致其他线程依然可以加锁成功的情况。为解决这个问题,redis 作者推荐使用 Redlock 算法,Redisson 内置了对 Redlock 的实现。
3、基于 zookeeper 实现
利用 zk 支持临时顺序节点的特性实现分布式锁。
当客户端对某个方法加锁时,在 ZooKeeper 中该方法对应的指定节点目录下,生成一个唯一的临时有序节点。
判断是否获取锁,只需要判断持有的节点是否是有序节点中序号最小的一个。
释放锁时,将这个临时节点删除。
客户端 watch 持有节点的子节点变动,若有变动则激活获取锁的竞争。
实际开发中一般使用 Apache Curator 来快速实现分布式锁。