分布式数据系统
为什么要构建分布式数据系统?
- 拓展性的要求
- 容错与高可用性
- 延迟
系统怎么拓展?
- 共享内存结构
- 缺点是成本增长过快,由于性能的瓶颈,两倍的硬件不一定能两倍的性能。异地容错能力差。
- 无共享结构
- 水平拓展。
数据怎么分布在多个节点?
- 复制
- 提供冗余,如果节点发生不可用,可以继续通过其他节点
- 分区
- 大的拆分成小的,不同分区分配给不同的节点
第五章 数据复制
复制的目的?
- 数据更接近用户
- 提高可用性
- 提高吞吐量
三种主流的复制数据的方法:
- 主从复制
- 多主节点复制
- 无主节点复制
主从复制
主从复制工作原理?
- 指定某个副本为主副本,向主副本写入数据
- 朱福珍将数据更改作为日志或者更改流发送给其他所有的从副本,从副本将更改应用到本地
- 客户端可以从主副本或者从副本查询,但是主要主副本才可以接受写请求
同步复制和异步复制
案例:
同步复制就是从节点1,主节点等到从节点1返回OK之后再返回用户,异步复制就是从节点2。
同步复制的优点:
一旦从节点确认就可以认为数据已经处于最新的版本。
同步复制的缺点:
如果无法完成确认,主节点会阻塞后面所有的写操作。
半同步:
保证有一个从节点是处于同步复制,如果这个从节点不可用,则另一个从节点变成同步的。
异步复制:
虽然有时候不太靠谱,但是还是有很多应用场景。
链式复制:
同步复制的一种变体。
如何快速增加新的从节点
替换或者增加新的从节点怎么保证又快又好?
- 某个时间点对主节点的数据副本产生一个快照。这样就不用锁定整个数据库
- 快照复制到新的从节点
- 从节点请求快照之后的数据更改日志。
- 从节点应用快照点之后的所有数据变更
节点失效了怎么办?
从节点失效:
追赶式恢复,比较容易,只需要请求失效之后的复制日志即可
主节点失效,节点切换:
- 确认主节点失效,一般是通过超时机制。
- 选举新的节点。后面会详细介绍共识问题
- 重新配置系统使得新的主节点生效。原来的主节点重新上线之后也需要认可新的主节点。
存在的问题:
- 如果采用异步复制,新的主节点并未收到原主节点所有数据,选举之后,原主节点上线,可能还会尝试同步其他从节点。
- 新的主节点和原主节点数据不一致,导致问题。
- 脑裂现象,出现多个主节点。
- 如何选择合适的超时来检测主节点失效。太短,则发生不必要的切换,反而会使总体情况变糟糕。太长,则总体恢复的时间就越长。
复制日志怎么实现的?
基于语句的复制
最简单的情况,主节点记录的每个写请求都发送给从节点:
但是存在一些问题:
- 调用非确定性函数的语句,如RAND() 产生一个随机数,不同的副本不一样。
- 类似于自增列这种,需要所有都按照相同顺序执行。并发情况下可能会出现问题。
- 有副作用的语句。(比如说是调用当前时间,调用udf函数)
如何解决?
有可能从去一些手段来保证这些问题,但是存在太多边界条件需要考虑,因此首选的是其他复制实现条件。
基于预写日志传输
所有对数据库写入的字节序列都被计入日志,因此可以使用完全相同的日志在另一个节点上构建副本,除了将日志写入磁盘外,主节点还可以通过网络将其发生给从节点。从节点收到日志进行处理,建立和主节点内容完全相同的数据副本。
主要缺点:日志描述的数据结果非常底层:一个WAL包含了哪些磁盘块的哪些字节发生改变,诸如此类的细节。这使得复制方案和存储引擎紧密耦合。如果数据库的存储格式从一个版本改为另一个版本,那么系统通常无法支持主从节点上运行不同版本的软件。
对运营产生的影响:如果复制协议允许从节点的软件版本比主节点更新,则可以实现数据库软件的不停机升级。首先升级从节点,然后执行主节点切换,使升级后的从节点成为新的主节点。但是WAL传输,要求版本必须严格一致,那么势必以停机为代价。
基于行的逻辑日志复制
关系数据库的逻辑日志通常是指一系列记录来描述数据表行级别的写请求:
- 对于行插入:日志包含所有相关列的新值
- 对于行删除:日志里有足够的信息来唯一标识已删除的行。主要是靠主键,但如果表上没有定义主键,就需要记录所有列的旧值。
- 对于行更新,日志包含足够的信息来唯一标识更新的行,以及所有列的新值。
基于行的逻辑日志的优势:
- 由于逻辑日志和存储引擎逻辑解耦,因此更容易地保持向后兼容,从而使主从节点能够运行不同版本的软件甚至是不同的存储引擎。
- 对于外部应用程序来说,逻辑日志程序也更容易解析。如果要将数据库的内容发送到外部系统(如离线分析的数据仓库),或者构建自定义索引和缓存等,基于逻辑日志的复制更有优势。
基于触发器的复制
为了实现更高的灵活性,例如,只想复制数据的一部分,或者想从一种数据库复制到另一种数据库,或者需要定制、管理冲突解决逻辑,则需要将复制控制交给应用程序层。可以手动注册一个触发器,数据库有数据变动时执行触发器中的相关复制逻辑,但是这种方式开销更大,也比原生的逻辑更容易出错。
复制滞后的问题
什么是复制滞后问题?
在多个节点采取异步复制的情况下,如果一个应用正好从一个异步的从节点读取数据,而该副本落后于主节点,读到的数据就是不一致的。
后面介绍三种复制滞后可能带来的影响。
读自己的写
如何实现写后读一致?
方案:
- 如果用户访问可能会被修改的内容,主节点读,否则,从节点读。例如用户自己的主页只能自己改,读取自己的主页读主节点,其他用户从节点。
- 如果大部分内容都可能会修改,可以通过类似跟踪最近更新时间的方式来判断总哪里读取。
- 客户端请求带着时间戳,系统根据时间戳来判定。
问题:
- 记住用户上次更新时间戳可能比较困难。
- 多个设备。
单调读的问题
看图片还是非常好理解的,这种主要还是异步复制,多个从节点不一致导致的问题。
如何实现单调读?
比如确保用户总是从固定的同一副本执行读取。比如负载均衡中的粘性算法。
前缀一致读
对于一系列按照某个顺序发生的写请求,读取这些内容也应该按照当时写入的顺序。
如何解决复制滞后问题?
首先根据应用场景采取不同的方案,更强的一种保证方式是事务,后续会更加深入探讨。
多主节点复制
什么情况下采用多主节点复制?
- 一个数据中心内部采用多主节点意义不大。
- 多数据中心的情况,就近写入性能更好,可以容忍一个主节点故障。写请求的同步操作同一个数据中心也更快。
- 离线客户端操作,比如说离线编辑日历应用程序,联网后才同步,这种基本等同于每个设备都是一个主节点。
- 协作编辑
处理写冲突
多主复制的最大问题是可能发生的写冲突。
同步与异步冲突检测
可以做同步和异步冲突检测。异步冲突检测,那时再要求用户层解决冲突为时已晚。同步冲突检测,等待写请求完成对所有副本的同步,然后再通知用户写入成功,会失去多主节点的主要优势。
避免冲突
通过一些手段最终收敛于一致状态
- 给每个写入分配唯一ID,挑选最高的ID作为胜利者。很容易造成数据丢失。
- 每一个副本分配唯一ID,通过一定规则选取获胜者。
- 以某种方式将冲突合并起来。
- 预定的格式存储冲突,应用层解决冲突。
自定义冲突解决逻辑
最适合的方式还是应用层解决。大多数多主节点复制都有工具让用户编写应用代码来解决冲突。
- 写入时执行:调用应用层的冲突处理程序。
- 读取时执行:应用层提示用户或自动解决,最终结果返回数据库。
多主节点复制的拓扑结构
无主节点复制
节点失效时写入数据库
考虑三个副本的情况,有一个副本不可用。对于无主节点来说,用户只需要收到两个确认即可认为写入成功。如果失效的节点重新上线,这个时候用户去读可能读到过期的数据,这个时候用户可以向两个节点读取数据,通过版本号等机制来确定哪个值更新。
读修复与反熵
- 读修复:直接看图即可,适合频繁读取的场景,就是读取的过程中修复落后的节点。只有读取时才能修复,所以很少访问的数据库有可能在某些副本中已经丢失而无法检测到。降低了写的持久性。
- 反熵:后台进程不断查找副本之间的数据差异,并修复。反熵过程不保证以特定的顺序复制写入,并且会引入明显的同步滞后。
读写quorum(法定人数)
多少个副本写入完成才可以认为写入成功?
n个副本,写入需要w个节点确认,读取至少查询r个节点,则主要
w + r > n还有一系列的规则
quorum一致性的局限性
w r n只是理论上满足某些关系即可,但是现实情况往往更加复杂,而且也夹杂着前几个章节包含的问题,这个地方最好还是看书复习。
监控旧值
从运维角度看,监视数据库是否返回最新结果非常重要,如果已经出现了明显的滞后,他就是一个非常重要的信号提醒我们必须采取必要措施来排查问题。
宽松的quorum与数据回传
在一个大规模集群中,节点数远远大于n,客户端可能再网络终端期间还能连接到某些数据库节点,但是这些节点不是能够满足数据仲裁的那些节点。
- 如果无法达到w或r所要求的的quorum,将错误的明确的返回给客户端?
- 我们是否应该接受该写请求,只是将它们暂时写入一些可访问的节点中?这些节点不在n个节点集合中。等到n个集合节点可用时,有写入的节点发送回这n个节点。
并发写带来的一些问题讨论
对于节点2认为X的最终值是B,而其他节点认为是A。对于这种情况,大多数系统实现都不是很能令人满意。
- 最后写入者获胜:每个副本总是保存最新值,允许覆盖并丢弃旧值。可能会丢失持久性,而且可能会删除哪些非并发写。