复制数据的原因有三点
- 减少用户和服务间的延迟,低延迟(数据与用户在地理上接近)
- 复制多份数据可以提高可用性(某个节点挂掉不影响使用)
- 通过扩展多台节点后,提高吞吐量(大部分应用都是读多写少,提高吞吐量)
复制
模式
主要分为三种方式
- 同步复制
- 异步复制
- 半同步复制
同步复制能保证主从数据的一致性,注意是强一致性,对应到cap模型中CP,按照分布式事务理解,算是刚性事务。但缺点比较明显,节点越多写入效率越低,因为要等到所有节点都报告写入成功,才能给客户端返回写入成功的响应。同时,出现故障会导致服务不可用。异步复制效率高,但会造成数据不一致。半同步复制是一种折中,现实场景用的比较多,多节点集群中,可以指定少数节点为同步模式,其他节点为异步模式,保证数据的最终一致。
日志复制
- 基于语句复制
- 基于预写日志
- 基于行逻辑复制
- 基于触发器复制
采用基于语句复制,那就要对一些sql函数比如Now()获取当前时间Rand()获取随机数等进行业务处理,否则的话进行同步会导致数据不一致。同样使用自增以及存储过程等等,也会相同的问题出现。基于行逻辑复制,好处是正确的复制数据,但缺点是重放一些查询代价非常大。除此之外,如果主节点进行了一些全表的update操作,那么从节点拿到的行复制开销会很大,每一行的数据都被记录进行同步,在这点上不如基于语句复制,语句复制只需要一个update的更新操作。基于触发器复制其实就是依靠自带的同步工具或者第三方提供的同步工具。
分布式集群
单主机群
适合的场景是单一数据中心。不管是哪种复制模式,在进行主从切换的时候,都有一定几率造成数据丢失。
多主集群
-
适合多数据中心
-
多采用异步复制
文章中给了三个拓扑模型,但最常用应该还是全部至全部的拓扑模型,其他两个模型环形和星形很明显如果单点故障会影响同步。第三种模型有一个比较明显问题容易造成重复更新,需要在内部的请求中增加已更新节点,保证不会重复处理。
-
最大的问题-写冲突
造成写冲突的场景是,不同的主节点收到针对同一数据的不同修改,这个时候进行同步的时候会造成数据冲突。文中给出来自动处理和人工介入的两种方式。
自动处理
- 业务层面进行保证,比如用户更新自己的数据,进入指定的数据中心主节点处理
- 判断写操作时间戳,以较新的为主,其余的丢弃
人工介入
- 写入执行时,进行提示
- 写入进行暂存,读取时候进行合并提示
无主集群
- 数据要有版本号,以版本号最新的为主
- 读写quorum ,w+r>n 保证读写有效性 w:写,r:读,n:节点总数
- 数据合并,和多主集群的写冲突处理一致
问题
节点同步
快照+数据更改日志
节点失效
- 从节点失效。处理较为简单,节点重启后进行数据同步
- 主节点实现。进行主从切换,数据差异最小的为新主,如果没有采用域名方式,客户端需要进行重新配置新主的地址。
复制滞后问题
写入的数据读取不到
用户修改数据成功后,去从节点读取不到刚修改的数据。解决的方式其实很多,但作者给出的两个方案:
-
主节点读取数据
-
客户端记录时间戳,服务根据时间戳找到最新的数据
这两种方案都有弊端,第一种会造成主节点读取和写入非常频繁。第二种增加了服务逻辑的复杂性。合理的解决方式,个人感觉应该引入缓存,新内容在缓存设置时效性,暂时从中读取。
多次读取数据不一致
这个其实有点像数据库的不可重复读,每次看到的数据都不一样。解决的方式需要读取到时候从固定节点读,比如基于id的哈希进行节点分配
因果顺序问题
作者举了一个购物车的例子,给出的解决方案是利用版本号解决。每次写入的数据都增加版本号,读取时候返回多个版本的数据,业务进行合并,再进行重新写入。数据删除时候不能直接删除,而是增加标记,方便数据合并。