点击上方小坤探游架构笔记可以订阅哦
前面我们尝试从独享数据以及共享数据两个角度去分析不同复制模型实现一致性所面临的问题以及困难点,主要分为时效性以及写冲突问题,其中时效性主要是由于复制延迟导致,但是并不破坏数据安全性, 因为经过时间T之后, 数据最终趋于一致;但是写冲突则不同,因为它产生的数据一致性会间接破坏我们数据的安全性,今天我们就复制一致性的写冲突做一个分析与总结。
单主复制模型的写冲突
什么是写冲突呢? 如果是独享的私有数据, 会不会存在冲突呢? 假如现在有两个用户UserA以及UserB都分别拥有一份数据UserName, 如果我们设计的系统对UserName没有施加任何约束, 那么独享数据不存在冲突; 如果我们对UserName施加唯一性约束, 那么这个时候就存在写冲突.
如果是共享数据呢? 答案是肯定的, 假如现在UserA以及UserB同时需要预订时间段为10-12点的会议室, 那么共享数据就是10-12点段的会议室, 只能允许被其中一个用户占用, 因此对于被拥有写入权的共享数据就存在写冲突.
那么按照上述的思路, 我们来看下不同复制模型算法是如何处理写冲突问题. 对于Leader-Base Replication模型, 如果是一份不施加唯一性约束的独享数据, 这个时候是不存在并发问题, 用户发起对数据的写操作的每个动作在时间序列上是满足全序属性,也就是具备线性一致性,那么这个时候我们只需要保证Follower从Leader上复制的数据也是按照客户端向leader写入顺序同样复制到Follower即可,如下:
如果是具备唯一性约束的独享数据, 那么在单点存储层面, 我们通过定义唯一性约束的语义告知存储引擎, 存储引擎就会建立一个唯一索引用于数据写入前的检索, 然后通过加锁互斥或者多版本控制保证唯一性, 那么同样地, 我们通过leader节点复制到follower节点也按照应用到主节点的顺序写入即可, 因此这个时候我们的存储引擎为保证数据的可靠性, 会在写入之前先写入日志如WAL, 再落盘. 由此可见, 唯一性约束在存储引擎上会涉及三个层面, 一是唯一性索引, 二是写入路径, 即落盘之前的其他额外数据写操作步骤, 三是并发写冲突的控制, 比如加锁或者多版本控制.
从上述我们看到采用是异步复制的方式, 那么就会存在延迟, 这个时候如果有一个UserC看到没有重复的数据, 也发起写操作write userName = ‘xiaokun’, 这个时候写操作会转移到Leader节点这个时候写入也是不成功的, 但是如果Leader节点发生不可用可能数据还没落盘但已写入Log, 重新选举的时候为保证数据的完整性与安全性, 不论是Follower1还是Follower2都需要从Leader的WAL拉取并应用数据, 这也是我们要处理唯一性约束还需要考虑额外写入的可靠性.
那么如果是共享数据呢? 这里我们讨论的是单值数据. 同时我们还会将共享数据按可覆盖以及不可覆盖进行区分, 我们先讨论单值情况, 比如预订10-11点时间段的会议室, 如果被其中一个用户占用, 那么其他用户是无法进行操作的, 这种不可被覆盖的共享资源是只能使用一次, 如会议室是可重复利用的资源, 但是我们为这个可重复利用的资源施加了一个时间的约束就成为了在这个时间约束下只能被使用一次; 那么这种如何保证呢?
另一种共享资源是可被覆盖, 比如多人协作编辑同一份文档的场景. 那么我们要如何保证对共享资源修改的安全性呢? 我想我们很容易想到就是对共享资源进行加锁互斥保证安全性, 但是这个时候却无法实现多人协作编辑场景, 只要有一个人持有资源未释放, 那么其他用户都无法进行编辑. 那么在这种情况下要实现多人协作编辑我们就需要采用多版本控制, 每个用户修改会提交对应的版本号, 那么当我们要提交保存的时候就会发生冲突, 如下:
那么如何解决冲突呢? 其一我们可以采用手动冲突解决, 就是类似GIT的版本控制, 在原有的分支新建一个分支A以及分支B, 如同上述UserA以及UserB在时空上是并发编辑, 由于每个User都拥有一份自己的版本号, 即能够看到自己修改后的内容, 只有当UserA以及UserB提交保存的时候才会发现冲突, 这个时候我们需要将Va版本以及Vb版本应用到Vx的版本, 由于Vx是共享资源, 并发写入我们需要进行对Vx加锁以控制安全性, 假如是Va先持有资源与Vx进行冲突解决应用, 那么这个时候Va与Vx组成新的版本Vx1, 如下:
接下来就是Vb与Vx1解决冲突得到新的内容如下:
可见版本号是我们解决冲突问题的一个思路, 上述我们讲述是手动处理冲突方式, 如果是自动解决冲突方式, 我们前面也有提及到LWW机制, 同时控制版本号具备全序递增且唯一性, 利用LWW机制自动解决, 但是在上述多人协同编辑的场景下, 采用LWW机制会导致数据丢失, 那么在业界关于自动冲突解决方案, 在前面我们讲述分布式领导复制算法模型有提过, 以下是一些思路可供参考:
如果是多值的共享数据, 其实就是在上述的基础上增加一个原子性保证, 即能够在故障发生的时候这一系列的操作都能保证全部回滚.
总结
今天主要聊单主复制冲突问题, 我们同样是从独享数据以及共享数据两个角度去分析存在冲突的问题,如果独享数据不具备唯一性约束, 那么在单主复制模型下是不存在冲突问题, 而如果是具备唯一性的独享数据、共享可覆盖数据以及共享仅修改一次的不可覆盖数据都存在写冲突, 但是由于是单点写入, 我们是有办法解决并发写冲突的问题.
另外是我们看到版本设计以及顺序性在处理写冲突问题上起到很重要的作用, 在下一篇我们谈及多主以及无主复制中, 版本以及顺序性将是我们讨论的重点. 最后感谢阅读, 如有收获欢迎转发!!!
你好,我是疾风先生, 主要从事互联网搜广推行业, 技术栈为java/go/python, 记录并分享个人对技术的理解与思考, 欢迎关注我的公众号, 致力于做一个有深度,有广度,有故事的工程师,欢迎成长的路上有你陪伴,