数据密集型应用系统设计 读书笔记 第二部分(上):复制,分区,事务

(1)数据复制:几乎所有的分布式数据库都使用了一种数据变化的方法:主从复制、多主节点复制、无主节点复制。主从复制和多主节点复制中,读请求可能发送到滞后的从节点,导致读到过期数据:写后读一致性和单调读一致性可以解决相关问题,无主节点复制实现了Quorum一致性,可以读到最新数据。多主节点复制和无主节点复制对写入的顺序没有保证,可能存在因果混乱,需要保证前缀读一致性,没有分区的主从复制可以保证主节点的写入顺序,又使用全序广播保证从节点的一致性,即保证了前缀读一致性。多主节点复制和无主节点复制可能存在并发写入,需要处理并发写,主从复制可以通过加锁来保证并发安全。

[1]主从复制:

半同步:某一个从节点是同步的,其他从节点是异步模式。

主节点失效:节点切换:<1>确认主节点失效(心跳);<2>选举新的主节点,让所有节点同意新的主节点是一个共识问题(无主节点的共识问题:Quorum一致性);<3>重新配置系统使新主节点生效,使原主节点降级为从节点,并认可新主节点(通过epoch实现,主节点在提出任何提议之前,都需要附带自己的epoch,只有当没有更高的epoch存在时,从节点才会对当前携带epoch号的提议投票)。

复制日志的实现:<1>直接发送写入主节点的SQL语句给从节点;<2>redo日志(物理日志);<3>逻辑日志;<4>基于触发器的复制:例如Databus(数据变更抓取系统),一般用于异构数据系统,基于触发器的复制比一般复制开销更高,也比数据库内部复制更容易出错,但是具有更高的灵活性和可配置性。

复制滞后问题:主从复制通过全序广播实现最终一致性保证,通过一些方法可以实现更强的一致性保证:<1>写后读一致性:自己可以读到自己刚刚写入的数据;<2>单调读一致性:用户连续两次读,数据应该是单调向前发展的,可以通过使用户总是从同一副本读来实现;<3>前缀读一致性:也称为因果一致性,若B操作建立在A的基础之上,则称AB属于因果依赖关系,在未分片的主从复制中不会存在因果混乱,因为主从复制的同步方式为全序广播。

[2]多主节点复制:允许每个主节点独立接受写请求。

适用场景:多数据中心、离线客户端操作(一个设备就是一个数据中心)、协作编辑(当编辑时会立刻更新到本地副本,再异步复制到其他副本)。

处理并发写冲突:注意并发与因果关系区分:在因果关系中,后面的操作可以覆盖前面的操作,因为后面操作是在前面操作的基础上进行的,具有因果关系(因果的特征是先读再写),而并发的两个操作互相都不知道对方,且都不在另一个操作之前发生,因此若通过某种方式只保留一个操作,会导致数据丢失。处理并发写冲突的最好方式是避免冲突(如果是单主节点,可以通过加锁实现),在多主节点中,可以令特定的用户路由到特定的数据中心。

[3]无主节点复制:

Quorum一致性:写入至少收到w个节点确认,读取至少要读r个节点,若w+r大于n,则读取的节点中一定包含最新值。

当一个Dynamo风格的数据库节点没有保存最新数据时,会通过读修复和反熵来修复自己的数据。

Dynamo风格的数据库在并发时也缺少顺序保证:在主从复制中,若并发请求同时写入,数据库会通过加锁来实现并发控制,而在多主节点复制和无主节点复制中,并发请求是可以同时写入数据库的,因此需要对并发写入的冲突结果进行处理。

(2)数据分区:每个分区都可以视为一个完整的小型数据库,尽管数据库可能会存在一些跨分区的操作。采用分区的主要目的是提高可扩展性:一个大数据集可以分布在更多的磁盘上,查询负载也随之分布到更多的处理器上。复制使同样的内容保存在不同的节点上,以提高容错性。

分区的问题:如何分区(防止倾斜/热点),实现分区数据库的二级索引,动态再均衡,请求路由。

[1]如何分区:基于关键字区间分区(可能产生热点),基于关键字哈希值分区(无法区间查询),组合索引(多个列组成复合主键,只有复合主键的第一部分可以用于哈希分区,其他列进行排序)。

[2]实现分区数据库的二级索引:

基于文档的二级索引(本地索引):每个分区完全独立,各自维护自己的二级索引。需要将查询发送到所有分区,合并所有返回的结果。写延迟低,读延迟高。

基于词条的二级索引(全局索引):对所有数据构建全局索引,且对全局索引也进行分区,可以与数据关键字使用不同的分区策略。读取时根据索引分区将查询发送到特定分区即可,但会引入写放大。

[3]分区再均衡:将负载迁移到其他节点。

<1>固定数量的分区(不需拆分和合并分区,实现简单):创造远超实际节点数的分区数,令该分区数目保持不变。数据量增加时,每个分区的大小增加;节点增加时,系统从其他节点匀出相应数目的分区提供给该节点,实现分区再均衡。

<2>动态分区(避免热点分区出现):分区具有最大值和最小值阈值,当分区大小大于最大值时,会分裂为两个分区,当分区大小小于最小值时,会和与其相邻的分区合并。数据量增加时,分区大小增加,直到分裂,当一个大分区分裂后,可以将其中一半转移到其他节点以平衡负载,实现分区再均衡。

<3>按节点比例分区(新节点会拿走相当大数目的负载):分区数与集群节点数成正比,每个节点具有固定数目的分区。数据量增加时,分区大小增加;当节点数目增加时,它随机选择固定数目的现有分区进行分裂,然后拿走这些分区一半的数据量,实现分区再均衡。

[4]请求路由:分区后,当客户端发起连接,如何知道应该连接哪个节点的哪个分区:服务发现问题。需要所有参与节点达成共识,也可以通过独立的协调服务(例如ZooKeeper)保存集群范围内的元数据。其他节点向ZooKeeper注册自己,并提供了分区到节点的映射关系,其他参与者(客户端或其他节点)可以向ZooKeeper订阅此消息。

(3)事务:事务的概念存在于单节点和分布式场景,这里的分布式指的是“分区”。事务是为了解决“在同一事务中包含多个写操作”时可能出现的问题,解决的是“写”的问题,即“主节点”的问题。而一致性解决的是分布式数据库的“复制”中,主节点与从节点数据不一致的问题,即主节点写入后,从节点读不到刚写入数据的问题,即“读”的问题,也是“从节点”的问题。

弱隔离级别:[1]读-提交,[2]可重复读。使用快照隔离可以实现读-提交和可重复读:在读-提交级别下,对每一个查询都创建一个快照;在可重复读级别下,在一个事务开始时创建一个快照。

防止更新丢失:更新丢失发生在两个写事务并发时,可行的解决方案有:[1]原子写操作,写操作本身就是原子的;[2]显式加锁;[3]自动检测更新丢失;[4]原子CAS;[5]冲突解决,对于多主节点或无主节点的数据库,可能存在多个最新的数据副本(主从复制无论是否分区,同一条目只可能存在一个最新的数据副本),可以保留多个冲突版本,由应用层逻辑来解决冲突。

并发丢失与因果混乱区别:[1]只有不分区的主从复制数据库不会出现因果混乱的问题,因为不分区的主从复制数据库只能从单一主节点写入,对于主节点来说可以看到确定的写入顺序,由于复制采用全序广播,因此从节点因果顺序也是正确的。对于可以从多个节点写入的数据库(分片,多主节点,无主节点),不同的分区或节点独立运行,不存在全局的写入顺序,这就导致当用户从数据库读数据时可能看到果但却没有看到因(因果混乱)。[2]只有多主节点和无主节点会出现并发丢失问题,分区的主从复制不存在并发数据丢失。

写倾斜:写操作(INSERT,UPDATE,DELETE)改变了读操作(SELECT)的结果,即使写和读操作的是不同的表或字段。解决方法:事务串行执行。

串行化:

[1]实际串行执行:采用单线程循环来执行事务。单线程有时候比支持并发的系统效率更高,尤其是可以避免锁开销。但需要采用存储过程封装事务,把整个事务代码打包提交发送到数据库,而不是实现交互的事务。可以通过分区来提高数据库的总体吞吐量,但是一定要保证跨分区的事务也是严格串行的。

[2]两阶段加锁(2PL):事务获取锁后,一直持有锁到事务结束(两阶段=获取锁阶段+释放锁阶段,在执行写操作时获取锁,在事务结束后释放锁)。

[3]可串行化的快照隔离:乐观的并发控制。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值