数据复制
假设数据规模较小,集群的每一台机器都可以保存数据集的完整副本。
一、主节点与从节点
原理如下:
1.指定一个副本为主节点,当客户写数据库时,必须将请求发给主副本,主副本首先将新数据写入本地存储。
2.其他副本称为从节点,主副本将数据更改作为复制的日志或更改流发送给所有从节点,从节点严格保持与主副本相同的写入顺序。
3.客户端的读操作可以任意选择,但是严格在主节点写入。
同步复制与异步复制
同步复制的优点是,写入确认后,用户总是可以在从节点访问最新数据,缺点是如果从节点无法完成确认,会堵塞住主节点其后所有的写操作。
半同步:一个从节点是异步,而其他节点是异步,当同步的从节点不可用则将另一个异步的从节点提升为同步模式。这样可以保证至少有两个系欸但(主节点和一个同步从节点)拥有最新的数据副本。
全异步:不管从节点上数据多么滞后,主节点总是可以相应写请求,系统吞吐性能好。
配置新的从节点
对于不断写入的数据库,简单的复制是不行的,而将数据库锁定来使磁盘文件保持一致也会违反设计目标,一般操作如下:
1.某个时间点对主节点进行一致性快照。
2.将快照拷给新的从节点。
3.从节点连接到主节点并请求快照点后的所发生的数据更改日志。
4.获得日志后,从节点应用快照点后所有数据变更,也称为追赶。接下来,重复1~4.
处理节点失效
从节点失效—追赶式恢复:上面所示
主节点失效—节点切换:1.确认主节点失效;2.选举新的主节点;3.重新配置系统使新的主节点生效。问题在于:
如果是异步复制,在失效前,新的主节点没有收到原来的主节点所有数据,那可能会导致冲突的写请求,目前的解决方案是,原主节点未完成复制的写请求就此丢弃。
如果数据库之外还有其他系统依赖于数据库并一起协同使用,丢弃就会十分危险。
“脑裂”。
超时检测装置,问题在于时间的判断,太长则回恢复时间较慢,太短会导致不必要的切换。
复制日志的实现
二、复制滞后问题
最终一致性:如果一个应用在读取一个异步的从节点,而该节点落后于主节点,那应用可能读取到过期信息,但是这种不一致只是一个暂时的状态,如果停止写入数据库,经过一段时间后,从节点最终会赶上主节点并保持一致。
读自己的写
对于落后的从节点,如果用户写入主节点后读取从节点,可能收到错误信息,此时需要“read-after-writer”一致性,也就是读写一致性。可行方案有:
-如果用户访问可能被修改的内容,则在主节点读取;否则在从节点读取,例如对于社交网络,总是从主节点读取用户自己的首页配置文件,而在从节点读取其他用户的配置文件。
-如果应用大部分内容都是可修改,则需要其他方案来判断,例如追踪最近更新的时间,如果更新后一分钟之内,则总是主节点读取;同时监控从节点复制滞后程度,避免从那些滞后时间超过1分钟的从节点读取。
-如果副本分布在多数据中心,必须请求路由到主节点附近的数据中心。
还要考虑跨数据的读写一致性,即在某些设备上输入了信息,在另一个设备上查看。此时,需要考虑的问题:
-记住时间戳比较麻烦,因为设备不互通
-如果数据分布在多数据中心,无法保证两个设备连接到同一个节点
单调读
假定有多个从节点,各个从节点和主节点之间延迟不一,则A可能在从节点一读取到新消息后,在从节点二发现消息没了,好像时间被回拨,此时需要单调读。
实现单调读的一种方式是确保每个用户总是从固定的一个从节点执行读取。
前缀一致读
该保证是说,对于一系列按照某个顺序发生的写请求,那么读取这些内容时也会按照写入的顺序,这是分区(分片)数据库中一个特殊问题,解决方案是确保任何具有因果顺序关系的写入都交给一个分区完成,但是该方案的真实实现效率会打折扣。
复制滞后的解决方案
对于最终一致性,如果有同步时间的要求,那就要考虑一个更强的一致性保证,比如写后读。
三、多主节点复制
主从复制的缺点在于只有一个主节点,主节点状态会影响整个数据库,但是多个主节点可以很好的避免这一问题。
适用场景
多数据中心
离线客户端操作
架构来看,类似于数据中心之间的多主复制,只不过是极端情况,一个设备就是一个数据中心,而且网络连接不可靠。
协作编辑
对于多用户同时编辑文档,如果要确保不发生编辑冲突,则应用程序必须将文档锁定,才能进行编辑,也就是加锁。
处理写冲突
多主复制的最大问题是可能发生写冲突。.
同步与异步冲突检测
如果是主从复制数据库,第二个写请求会被阻塞到第一个写完成,但是在多主节点的复制模型,这两个都是成功的,并且只能在稍后的时间上才能异步检测到冲突,理论上也可以做到同步,也就是等待写请求对所有副本的同步,然后通知用户写入成功,但是这就失去了多主节点的优势:允许每个主节点接受写请求。
避免冲突
收敛于一致状态
-给每一个写入分配唯一ID,例如时间戳,挑选最高ID的写入作为胜利者。这种方法很流行,但是容易造成数据丢失。
拓扑结构
四、无主节点复制
或者称为去中心化复制,对于无主节点系统,客户端直接将写请求发送到多副本,在一些实现中,由一个协调者节点代表客户端写入,但是与主节点数据库不同,协调者不负责维护写入的顺序。
节点失效时写入数据库
假定三个副本中有两个确认写操作,用户收到两个确认的回复后,即可以认为写入成功,忽略其中一个副本无法写入。当客户端读取数据时,并行的将请求发送给多个副本,采用版本号技术确定哪个是最新的值。复制模型应该确保所有数据是最新的,当一个失效节点上线后,如果做?
读修复:当客户端并行读取时,通过判断副本过期值,然后将新值写入该副本。
反熵过程:一些数据存储有后台进程查找副本之间的数据差异,将缺少的数据复制到另一个副本,但是与主节点复制不用,反熵不保证以特定顺序写入。
读写Quorum
如果有n个副本,写入需要w个节点确认,读取需要n个节点确认,且r+w>n,这样读取的节点必定包含最新值。
Quorum一致性的局限性
Qurum也可能存在返回旧值的边界条件:如果两个写同时发生,由于时钟偏差,某些写入可能会抛弃;写和读同时发生,写可能仅在一部分副本完成,而读取是新还是旧不确定;如果具有新值的节点发生失效,但恢复数据来自旧值,则总的新值副本数低于w。
检测并发写
核心问题在于,由于网络延迟不稳定或者局部失效,请求在不同节点可能会呈现不同顺序。如果节点每当收到新请求就覆盖原有的主键,那节点永远无法达成一致。
最后写入者获胜(丢弃并发写入)
一种方法是,永远保存最新值,只要有明确的方法确定哪一个写入是最新的,则副本永远可收敛到相同值。例如,每个写请求添加时间戳,然后选择最新的时间戳(LWW),lww可以实现最终收敛的目标,但是会牺牲数据的持续性。确保lww安全无副作用的唯一方法是,只写入一次然后写入值不可变。
Happen-before关系和并发
并发的一个定义:两个操作之间,都不在另一个之前发生,那么操作并发(或者两个都不知道对方)。或者,如果两个操作并不需要意识到对面,我们即可声称他们是并发操作。
确定前后关系
服务器判断操作是否并发的依据主要依靠对比版本号,而并不需要解释新旧值本身,算法如下:
-服务器为每个主键维护一个版本号,每当主键新值写入时递增版本号,并将新版本号与写入值一起保存。
-客户端读取主键时,服务器返回所有当前值以及最新版本号,且要求写之前,客户端必须发送读请求。
-客户端写主键,写请求必须包含之前独到的版本号、读到的值和新值合并后的集合。写请求的响应可以像读一样,返回所有当前值。
-服务器收到带有特定版本号的写入时,覆盖该版本号,但必须保存更高版本号的所有值。
合并同时写入的值
客户端通过合并并发写入的值来继承旧值。
版本矢量
类之于矢量时钟。
数据分区
分区,即每一条数据只属于某个特定的分区,每个分区都可以视为一个完整的小型数据库。
对单个分区查询是,每个节点对自己所在的分区可以独立执行查询操作。一个节点可能存储了多个分区。
分区与复制通常结合使用,每个分区在多个节点都存有副本。
五、键-值数据的分区
分区目标是将数据和查询负载均匀分布在所有节点,但是如果分区不均匀,会出现倾斜。避免热点最简单的方法式将记录随机分配给所有节点,但是缺点在于,读取数据时,因为不知道数据保存位置,需要并行查询所有节点。可以进行改进~
基于关键字区间分区
一种分区方式是为每个分区分配一段连续的关键字或者关键字范围,例如百科全书,关键字的区间段不一定要均匀分布。
问题在于关键字可能会导致热点,例如关键字是时间戳,分区对应一个时间范围。这个问题,可以使用时间戳以外的其他内容作为关键字的第一项。
基于关键字哈希值分区
关键字哈希进行分区的问题在于,丧失了良好的区间查询特性。Cassandra在两个之间做了折中。
负载倾斜与热点
对于高度倾斜的负载,只能通过应用层来减轻倾斜程度。例如某个关键字被确认为热点,一个简单的技术就是在关键字的开头或者结尾添加随机数,这样就可以将关键字的写操作分布到不同的关键字,从而分配到不同的分区。