分布式的数据
为了更高的负载,更高的可用性,更低的延迟,分布式的架构被利用起来,scale out,扩展能力是一个系统必须考虑的因素
- 数据的分布,常常会有2中途径,复制和分片
- 复制
复制主要是提供冗余,高可用。但某些情况下,也可以用于提升性能 - 分片
partitioning或者shading,将一份完整数据拆分为多个分片
Chapter 5 复制
单独考虑复制的前提是每一个节点可以包含所有的数据,而不需要分片。复制的难点在于各个副本的数据一致性。有3种算法去处理节点数据变化,single-leader, multi-leader, and leaderless replication,各自有利有弊,需要权衡
其他的考虑要素,诸如同步/异步,处理失败副本
1.1、leader和follows
Leader-based(master-slave)replication
- leader:数据写入的第一个节点
- folllows: 写入leader后,将写入请求也转发到follower,每个follower使用log或者change stream 去更新自己的copy
- 查询:一般leader和follower都进行read操作
1.2、同步和异步
同步和异步是一个重要的属性,指 复制这个过程是同步还是异步的
2者的区别很明显,同步保证副本和主上的同时更新,但延迟可能因为网络等很大,但异步速度快,确不能保证副本更新成功。因此有时候,也会有所谓的semi-synchronous,半同步,即在异步的节点上,选出一个要求同步,当这个同步节点挂掉时,在选一个异步节点改为同步
1.3、创建一个新follower流程
常规的操作是
1.创建一个主的快照
2.拷贝快到到新follower
3.新follower加载快照后,再向主订阅快照后的变化,通常使用一个位点,如mysql的binlog position
4.追上后即变成了一个正常的副本节点
1.4.节点宕机
副本节点宕机
这个很简单,和之前创建时一样,通过日志去追就可以了主节点宕机
failover,故障切换,当主节点宕机后的一系列操作过程的统称,整个过程通常由一下几部分构成
1.确定leader宕机:主节点的问题可能有网络,宕机等各种因素,如何确定主节点真正不可用并不简单,一般的架构都用了 延迟最为判断的标准。通过leader和follower之前的心跳探测频率等,如果响应超时,即认为leader宕机
2.选举新的leader
这是一个比较大的话题,但原则上一定是要选出 存储了最新最全信息的节点
3.重新配置新leader
如将客户端的写入定向到新的leader中等操作,防止旧的主节点恢复后出现双主节点。旧主节点哪怕恢复也应当做新的follower看待故障转移时的一些需要考虑的点:
1.异步情况下,副本节点当选为主节点,它可能并没有更新到最新的数据
2.可能需要丢弃一些数据,而这很可能产生数据不一致的问题
3.脑裂问题,多个节点认定自己为leader
4.判断下线的超时时间,太长故障转移过慢,太短又很可能是误操作
1.5 复制日志的实现
- statement-base replication
将每一条修改,增加,删除语句,进行同步,每一个副本节点将请求再自己这重新执行一次,即回放
问题:
- 非确定语句,如now(),rand()等在不同机器上执行可能会有不同结果
- 所有的设置主副需要一致,如主单数递增,副双数递增,则有些查询需要进行特殊处理
很多的边界条件需要考虑,因此,一般现在不采用这个方式
- Write-ahead log(WAL) shiping
相比于之前的statement,WAL参考我们之前的内容,它在实际的数据引擎中写入的操作,具体执行是和选用的引擎相关联的。
wal避免了之前数据的不一致随机性,但也带来了新的问题,因为WAL是依赖于数据引擎的,这会导致强依赖,以后的升级或者改换实现,都必须停机做处理 - Logical (row-based) log replication
在WAL的基础上,我们为了使日志与引擎解耦,所以第三种方式是逻辑日志,那么为什么我们又称之为 row-based log,那是因为逻辑日志的粒度一般是基于row的
row-based到底记录了哪些数据
1 add 时,记录新的所有列的值
2 delete时,记录主键,或者记录主键和所有原有列的值
3 update,新旧所有列的值。最起码是有改变的那几列 - Trigger-based replication
触发器,更为灵活,但没用过,触发后还需要其他程序配合
1.6 复制日志带来的问题
单节点接入,扩展靠通过增加follower实现,一般用读写分离。这时候同步可能会导致写入过慢,异步的话,各个follower的数据一致性会受到影响。
这里只能说,我们保证最终数据一致性,即当停止写入后,随着时间,各个follower的数据会满足一致性
此时若网络出现问题,或写入接近处理能力,可能会造成数十秒,甚至分钟级的影响,在这实际汇中是需要注意的
问题举例1:read-after-write consistency
异步时,follower还没有写入,收到了读取请求
read-after-write consistency, also known as read-your-writes consistency
这个一致性,只是保证你的修改和你读取,你的读取一定是你修改后的数据,但不保证其他用户
实现方式
1.当只有自己能修改自己的数据时
每个用户读取自己的数据时,在leader上读,读取其他数据时,在follower上读
2.当别人也能修改自己的数据时
这个相对复杂,当异步时无法保证,只能记录key的更新时间,请求判断如一分钟以内更新到转发到主节点,一分钟以后到任意follow
还有目前的所谓跨设备一致性,同一个用户,在手机,pad,pc上的访问数据保持一致
问题举例2:monotonic read 单调读一致性
因为读不同的follow,导致读取结果不一致
解决方案,哪怕是读follower,同一个用户也要定位到同一个follower上
问题举例3:prefix read,保证结果的因果性,其实就是顺序
当旁观者,从不同的节点,可能先改变的后读到,违反了因果性。
- 这个例子和之前的不同点在哪?
我觉得是强调的是因果,之前的例子都是一个partiiton的一份数据先后性,这个例子主要是2个partition数据由于各自复制导致的先后
有一些类似于kafka先后2份数据,到不同的partition上就无法保证先后一致性的感觉
解决这个问题,需要保证的是一系列的写入,读取的时候也必须能按顺序读取。
实际中常常通过制定partition解决,因为依赖算法去解决这个成本一般较高
1.7 muti-leader replication
master–master or active/active replication,每一个都是其他节点的follower,但自己也是可以写入的
首先要明确使用场景:
-
多中心
多中心是目前最常见的提高可靠性,提升服务速度的方式,每个中心还采用leader-follower,各个中心之间,同城双活和异地双活都是很大的工程
- 离线服务
此时的本地离线也可以写入,可以当做一个datacenter,只不过这个本地仓库可能和线上相差数个小时,甚至更久
1.7 Collaborative editing
协作编辑,作为商业办公的重要组成部分,重点是在解决冲突,当在某些方面,和之前离线的例子很像,每个人的编辑需要在本地先确认,在体现在其他人的数据中
多中心的问题---冲突
multi-leader,是的写入可以在多个leader上进行,随之而来的,write conflict是最主要的问题
- 冲突探测的异步和同步
冲突的异步,后一个人将需要处理冲突,冲突的同步探测,会损失太多性能,还不如单leader架构 - 避免冲突
鉴于冲突是很难解决的,避免冲突永远是最佳的选择,多个中心可以写入,但应该将写入尽量固定,同一个人写入回到离它最近的固定的datacenter,以避免在多个datacenter中写入,同步时产生冲突 - 收敛一致性
多个写入时的顺序,需要保证按顺序执行,不然无法保证最终的数据
1、 last write wins (LWW),即用timestamp等,给每个写入一个uuid,最大的成功写入,但会有数据丢失的风险
2.每个节点一个uuid,更大的节点拥有更高的优先权,也会有数据丢失的风险
3.合并结果,例B/C
4.保留所有数据,后续处理
典型的冲突处理方式
写入时如果有冲突,调用提前写好的handler
读取时进行提示
多节点复制
传送给所有节点是最简单的,但问题是,各个节点网络可能会延迟不同,节点A和节点C同一份数据同步到节点B可能会违反因果性,此时需要一些特殊处理,如版本号管理
而环形和星形需要给uuid,以避免循环。
1.7 leaderless replication
这个很陌生,amason有 in-house Dynamo system,工作中暂时没有碰到这种架构的,只做简单了解
有节点宕机,就当没发生过,读和写都需要达到法定的投票数,当宕机节点恢复后,仍然每一个读取发到各个节点,通过数据的版本号决定拿哪一个
辅助机制
- Read repair
当读取到旧版本数据时,会将更新的数据,往那个节点写入一份