将数据库分布到多台机器上的原因:
当负载增加需要更强的处理能力时,最简单的方法就是购买更强大的机器,也叫垂直扩展或向上扩展。
无共享架构也叫水平扩展或向外扩展。在这种架构中,运行数据库软件的每台机器/虚拟机都称为节点(node)。每个节点只使用各自的处理器,内存和磁盘。节点之间的任何协调,都是在软件层面使用传统网络实现的。
复制
在几个不同的节点上保存数据的相同副本,可能放在不同的位置。 复制提供了冗余:如果一些节点不可用,剩余的节点仍然可以提供数据服务。 复制也有助于改善性能。
分区
将一个大型数据库拆分成较小的子集(称为分区(partitions)),从而不同的分区可以指派给不同的节点(node)(亦称分片(shard))。
我们将讨论三种流行的变更复制算法:主从复制(single leader),多主节点复制(multi leader)和无主节点复制(leaderless)。几乎所有分布式数据库都使用这三种方法之一。
如何保证所有副本之间数据一致:
客户端写入数据时先将数据写入主副本,然后将数据更改作为复制的日志或更改流发送给所有从副本,客户端从数据库中读数据时,可以从主副本或者从副本副本上执行查询(只有主副本才可以接收写请求)
同步复制优点是确保从节点与主节点数据更新同步,万一主节点附身股占可以从从节点继续访问最新数据。
同步复制缺点是如果从节点发生故障或网络故障写入失败,则会阻塞主节点其他操作。
实践中通常将一个从节点同步,而其他是异步的,一旦同步的从节点不可用,则将另一个异步的从节点转化为同步的,这种配置称为半同步
全异步模式如果主节点发生失败则所有未复制的请求都会丢失,优点是不管从节点数据多么滞后,主节点总是可以继续相应写请求,系统的吞吐性能更好
可以将主节点进行快照保存,然后新的从关节点根据快照内容进行复制,结束后再次对主节点进行快照,并将上一个快照复制阶段时主节点中新增加的内容进行复制,一直重复这个些步骤直到追赶上新的数据变化。
根据副本的复制日志就可以知道在发生故障之前所处理的最后一笔事务,然后连接到主节点,请求那笔事务之后中断期间内所有的数据变更,并进行追赶式恢复。
自动切换步骤:
问题:
这些问题会在第8、9章讨论
主节点记录所执行的每个操作语句作为日志发送给从节点
不适用的场景:
可以采取一些手段避免这些问题,但目前首选的还是其他复制实现方案
通常每个写操作都是以追加写的方式写入到日志中,因此可以用完全相同的日志在另一个节点上构建副本。
缺点是日志描述的数据结果非常底层,似的复制方案和存储引擎紧密耦合,如果数据库的存储格式版本变化,那么系统通常无法支持主从节点上运行不同版本的软件。
复制和存储引擎采用不同的日志格式
关系数据库的逻辑日志通常是以行的粒度描述对数据库表的写入的记录序列:
逻辑日志与存储日志解耦,可以实现更好的兼容,对于外部应用程序来说逻辑日志格式也更容易解析。
基于触发器的复制通常比其他复制方法具有更高的开销,并且比数据库的内置复制更容易出错,也有很多限制。然而由于其灵活性,仍然是很有用的。
当应用程序从异步从库读取时,如果从库落后,它可能会看到过时的信息。这会导致数据库中出现明显的不一致
虽然在主节点停止写入一段时间后会达到一致,这叫“最终一致性”。
用户写数据后立刻去读自己刚写的数据,由于读数据可能是在从节点上读取的,可能导致读不到
对于这种情况需要“写后读一致性”,也叫“读写一致性”。
实现写后读一致性有以下方法:
如果用户写入数据后使用不同的设备读取,则会变得更复杂
用户两次从不同的从节点读取数据,可能出现第一次访问到了数据,而第次访问的从节点同步较慢导致第二次没有读到数据。此时需要“单调读一致性”。
实现单调读的一种方式是确保每个用户总是从固定的同一副本执行读取。例如基于用户ID的哈希方法选择副本,而不是随机选择副本。
一前一后的两条消息到达从节点的顺序可能会颠倒。此时需要“前缀一致读”。
一种解决方案是,确保任何因果相关的写入都写入相同的分区。
在本章的“关系与并发”会继续该问题的探讨。
交给数据库来做
系统只有一个主节点时如果主节点失效会带来很大的问题,因此可以配置多个主节点,每个主节点都接受写操作,每个处理写的主节点都必须将该数据更改转发到所有其他节点,此时,每个主节点还同时扮演其他主节点的从节点。
与单主节点之间的差异:
缺点:
不同的数据中心可能会同时修改相同的数据,因而必须解决潜在的写冲突
在数据库中多主复制时使用自增主键、触发器、完整性约束等功能时可能会出现副作用
把每个客户端都当做是一个数据中心,每个设备都有一个本地数据库,网络断开时各个设备依旧可以读写本地数据,在下次联网后即可同步到服务器上。
这是一个比较极端的情况,此时每个客户端和服务器都处于平等的地位,而且他们之间的网络连接非常不可靠。
多个用户同时编辑文档,一个用户编辑文档时所作的更改会立即应用到本地副本,然后异步复制到服务器以及编辑同一文档的其他用户。
要保证不会发生编辑冲突,则需要给文档上锁,为了加快协作编辑的效率,可以减小锁的粒度。
不同用户往不同主节点提交修改数据,节点之间同步时会发现存在冲突。
可以等写请求完成对所有副本的同步,再通知用户写入成功,但这样会失去多主节点的优势。
如果您想要同步冲突检测,那么您可以使用单主程序复制。
如果应用程序可以确保特定记录的所有写入都通过同一个主节点,那么冲突就不会发生。
有时可能是因为一个数据中心出现故障,您需要将流量重新路由到另一个数据中心,或者可能是因为用户已经迁移到另一个位置,现在更接近不同的数据中心。在这种情况下,冲突避免会失效,你必须处理不同主库同时写入的可能性。
本来可以根据时间顺序,将新的数据直接覆盖旧的数据来解决冲突,但是多主节点中不同的结点判断顺序的结果可能不同,因此这个方法无效
实现收敛的冲突解决有以下可能的方式:
解决冲突最合适的方式可能还是依靠应用层
不同的应用逻辑会有不同的冲突,例如数值的冲突,例如预定会议室的时间冲突。
最常见的是全部到全部的拓扑。
为了防止数据在多个节点中无限循环,需要为每个写请求标记已通过的节点标识符。
环形和星型的问题是如果一个结点发生了故障,会影响其他节点之间的转发。
全链接拓扑会导致复制日志之间的覆盖。
由于无法保证时钟同步,所以无法使用时间戳来保证日志的顺序,可以使用后面提到的版本向量的技术
无主节点复制:放弃主节点,允许任何副本直接接受来自客户端的写请求。或者会有一个一个协调者节点代表客户端写入。
用户发送的请求不再只发送给主节点,而是同时发送给多个节点,当大多数节点有效并且返回成功信息,则表示写入成功。
当失效的副本重新上线时,节点中的数据会比其他节点中的数据更旧,用户从节点读取信息时,会并行地发送到多个副本,客户端可以采取版本号技术确定哪个值更新。
读修复:
当客户端读取多个副本检测到过期的值时,将新的值写会到该副本。
适合那些频繁被读取的场景。
反熵过程:
后台有进程查找旧的数据,并从其他副本复制数据进行更新。
此反熵过程不会以任何特定的顺序复制写入,并且在复制数据之前可能会有显著的延迟。
如果有n个副本,写入需要w个节点确认,读取必须至少查询r个节点,则只要w+r>n,读取的节点中一定会包含最新值。
满足上述这些r,w值的读写操作称之为法定票数读(仲裁读)或法定票数写(仲裁写)。
这里的w和r可以灵活配置,比如对于读多写少的负载,设置w=n,r=1比较合适,这样读取速度更快,但是一个失效的节点就会使得数据库所有的写因为无法完成quorum而失败。
如果可用节点数小于所需的w或r,则写入或读取就会返回错误。
quorum不一定非的是多数,只要保证读节点和写节点有一个是重合的即可,因此可以使用w+r<=n的方式,可以容忍更多的副本失效,但是也可能出现正好没有包含新值的节点。
尽管法定人数似乎保证读取返回最新的写入值,但在实践中并不那么简单。 Dynamo风格的数据库通常针对可以忍受最终一致性的用例进行优化。允许通过参数w和r来调整读取陈旧值的概率,但把它们当成绝对的保证是不明智的。
对与主从复制的系统,主节点和从节点都遵从相同的顺序写入,每个节点都维护了复制日志执行的当前偏移量,通过对比主节点和从节点当前偏移量的差值,即可衡量该从节点落后于主节点的程度。
如果数据库只支持读时修复(不支持反熵)那么旧值的落后就可能没有上限。
quorum并不总如期待的那样提供高容错能力,一个网络中断可以很容易切断一个客户端到多数数据库节点的链接,尽管这些集群节点是活着的,这种情况下可能无法满足最低的w和r所要求的的节点数。
此时宽松的quorum会接受写请求,只是将他们暂时写入一些可访问的节点中,这些节点可能不在n个节点集合中。写入和读取仍然需要w和r个成功的响应,一旦网络问题得到解决,临时节点需要把接收到的写入全部发送到原始主节点上,这就是所谓的数据回传(或暗示移交)。
可以看出宽松的quorum对于提高写入可用特性也别有用,只要有任何w个节点可用,数据库就可以接收新的写入。
无主复制还适用于多数据中心操作,因为它旨在容忍冲突的并发写入,网络中断和延迟尖峰。
无论数据中心如何,每个来自客户端的写入都会发送到所有副本,但客户端通常只等待来自其本地数据中心内的法定节点的确认,从而不会受到跨数据中心链路延迟和中断的影响。对其他数据中心的高延迟写入通常被配置为异步发生。
如果允许多个客户端对相同的主键同时发起写操作,但即使使用严格的quorum机制也会发送写冲突,
如果节点每当收到新的写请求时就简单的覆盖原有的主键,那么由于到达的时间不同,那么这些节点将永远无法达成一致。
由于客户端是并行写入的,因此无法确定请求的自然顺序,但可以强制对其排序,例如为每个请求附加一个时间戳,选择最新即最大的时间戳,丢弃较早的时间戳的写入。这个算法称为最后写入者获胜(LWW)
但是这个方法会将旧值覆盖和丢弃,如果覆盖丢弃不可接受,则LWW并不是解决冲突很好的选择。
与LWW一起使用数据库的唯一安全方法是确保一个键只写入一次,然后视为不可变,从而避免对同一个主键进行并发更新。
只要有两个操作A和B,就有三种可能性:A在B之前发生,或者B在A之前发生,或者A和B并发。我们需要的是一个算法来告诉我们两个操作是否是并发的。如果一个操作发生在另一个操作之前,则后面的操作应该覆盖较早的操作,但是如果这些操作是并发的,则存在需要解决的冲突。
如果同时写入的值只是在增加数据,则可以使用union操作即可知道最终值,但如果是在删除数据,则需要使用一个保留一个版本号进行删除标记,这种删除标记被称为墓碑。
当多个副本并发接受写入时,除了对每个键使用版本号之外,还需要在每个副本中使用版本号。每个副本在处理写入时增加自己的版本号,并且跟踪从其他副本中看到的版本号。这个信息指出了要覆盖哪些值,以及保留哪些值作为兄弟。
当读取值时,版本向量会从数据库副本发送到客户端,并且随后写入值时需要将其发送回数据库。版本向量允许数据库区分覆盖写入和并发写入。
原文:https://www.cnblogs.com/aojun/p/15242054.html