目的:低延迟、高可用、高容量
主要方式:主从复制、多主节点复制、无主节点复制
复制策略:同步复制、异步复制
主节点与从节点
主从复制工作原理:
- 选定某个副本为主节点,写入工作通过主节点进行
- 主节点写入数据后,将数据的更改发送给从节点
- 读取数据可以从主节点活从节点进行
同步复制与异步复制
全同步:主节点复制需要等待全部从节点完成并反馈
半同步:主节点复制只需等待一个从节点完成并反馈
全异步:主节点发送复制消息后,无需等待从节点复制完成的反馈
配置新的从节点
建立新的从节点的过程:
- 生成快照
- 基于快照的全量复制
- 从节点连接主节点获取快照后所有修改的日志
- 根据日志进行增量复制
处理节点失效
主节点和从节点都存在失效的可能性,因此应当采取手段尽量保持系统的正常运行。
从节点失效:追赶式恢复
从节点失效后,重新获取在失效后主节点更新的日志,依据日志进行恢复
主节点失效:节点切换
需要选取某个从节点为主节点,通常具有三个步骤:
- 确定主节点失效:通常通过心跳包的响应时间判断
- 选举主节点:指定一个所有节点统一的主节点,该节点最好与主节点数据差异较小
- 重新配置主节点:需要令客户端将请求发送至新节点
但是主节点的切换存在多种可能的问题:
- 异步数据丢失
- 脑裂
- 心跳响应时间阈值设定
复制日志的实现
基于语句的复制
主从复制过程中直接复制主节点执行的逻辑语句,但是这存在一些问题,比如在语句中应用了时间获取、随机数生成等操作。
MySQL5.1之前的版本采用基于语句的复制方式。
基于预写日志(WAL)传输
对于负责向磁盘写入内容的存储引擎,都会在写入磁盘之前向WAL中追加将要写入的字节序列,WAL可以理解为一种物理日志,用来记录对于磁盘的修改,但是仍然有一些问题,WAL与存储引擎强相关,该方式可能无法适应存储格式的改变,不能兼容运行于主从节点不同的引擎。
Oracle等系统支持该方式。
基于行的逻辑日志复制
逻辑日志是用来记录行的增删改行为的记录,例如MySQL中的二进制日志binlog,逻辑日志与存储引擎解耦,可以通过复制逻辑日志实现主从复制。
MySQL当今应用这种方式,还要配合relay log。
基于触发器的复制
触发器支持用户注册自己的应用代码,使得数据库执行数据更改时自动执行该代码。通过该技术可以将数据更改记录同步到另一个表,外部处理逻辑访问该表从而实施必要的逻辑。
复制滞后问题
只要不采用全同步,就会产生各个副本数据不一样的问题,也就是一致性问题。
读自己的写
用户A向数据库执行某更改操作后,后续再去读取却看不到刚刚的更改操作,这种情况违反了”读写一致性“。
可能由于从节点未及时复制主节点,且读操作由从节点执行导致。
可能但不一定好使的解决方案:
- 如果访问可能被修改的数据,从主节点读取
- 更新后一段时间内保持从主节点读取,但是延迟时间不一定好确定
- 客户端请求附带时间戳,但是可能会有时钟不可靠问题
另外还有跨设备的读写一致性问题
单调读
用户A对于数据库先后执行两次读取,后一次的数据比前一次的更旧,这种情况违反了”单调读一致性“。
可能由于不同从节点复制完成时间不一致,且先访问了已经复制完成的节点,后访问未完成的节点导致。
可能的解决方案:同一用户每次从同一个从节点读取数据
前缀一致读
用户A观察用户B具有因果关系的两次更改操作,但是却观察到”果“发生于“因”之前,这种情况违反了“前缀读一致性”。
可能的解决方案:将具有因果关系的操作交由同一个从节点完成
复制滞后的解决方案
解决复制滞后属于分布式事务的范畴,分布式事务对于性能可能造成极大的影响,这个问题放在后续章节详细讨论。
多主节点复制
主从复制将写入操作放在同一个节点,多主节点复制中有多个节点可以接受写入操作,并将改写入操作同步到其他主节点,各个主节点之间保持数据尽量同步。
使用场景
跨不同的数据中心进行多主节点复制较为合理。
多数据中心
在部署于多个数据中心时,主从复制与多主节点复制的区别:
- 性能:主从复制无法实现就近复制
- 容忍数据中心失效:多主节点复制
- 容忍网络故障:多主节点采用异步复制,能够更好的解决该问题
以上是多主节点复制的优势,然而具有一个极大的劣势,不同的数据中心修改相同的数据,即写冲突。
离线客户端操作
如果用户手机、电脑中的日历等记录需要和云服务器进行同步,那么用户设备和服务器可分别视为两个主数据库。
协作编辑
类似腾讯文档,各个用户都相当于一个主节点,但是需要用锁解决并发冲突。
处理写冲突
如何解决在两个数据中心中对于相同数据执行不同修改的问题呢?
同步与异步冲突检测
同步冲突检测:在对主节点A进行修改时,同步等待其他节点修改完成,然而这样相当于回到了主从复制。
避免冲突
确保同一用户的更改请求由固定的主节点执行。不过也会产生一些问题,比如用户坐飞机出差会导致对应主节点的 切换。
收敛于一致状态
如果各个主节点存在具有冲突的记录,该保留那个,可能的方式有:
- 根据时间相关的UUID
- 根据各个副本的ID
- 将结果组合
- 设定预定的方案
自定义冲突解决逻辑
解决冲突更合适的方式是依靠其应用者,此时有两大类方式:
- 写入时执行
- 读取时执行
什么是冲突
事务的隔离性,幻读等等问题,不必在此详述。
拓扑结构
拓扑结构:写请求从一个节点路由到其他节点的路径,典型结构包括:环形,星形,全部-全部
无主节点复制
无主节点复制模式中,不存在主节点与从节点之分,所有节点都可以接受写入请求,也就是全都是主节点,该模式的两种主要实现方式:
- 分别发送多个副本
- 协调者代为写入
节点失效时写入数据库
可能存在部分节点失效,而客户端只需获得一定数量节点的写入请求,无需全部节点的请求。
崩溃节点重新上线可能会使得系统中保存过期的数据,此时不必立即同步,因为客户端读取数据时向所有节点发送请求,客户端只需从得到的相应中选取最新的就可以。
读修复与反熵
崩溃节点重新上线后如何修复那些旧数据?Dynamo风格数据库两种解决思路:
- 读修复:在用户读取数据时发现某个版本数据版本过期时,客户端将新值写入存储过期值的副本,懒汉式。
- 反熵过程:后台进程定期寻找各个副本之间的差异,并进行复制,饿汉式。
读写quroum
由于节点可能出现故障,因此每次的写入无法保证在所有节点都成功,那么应该规定一个阈值,只要写入成功的节点数大于等于该阈值就可以视为写入成功。同样的原因和道理,读取操作也应该规定这么一个阈值。
写阈值w,读阈值r,节点数目n,他们应该满足w+r>n,该方案称为仲裁写/读。
Quorum一致性的局限
如果希望提高系统的可用性,那可以将w+r>n的条件放松至w+r>=n,此时可用性提高,但是更容易出现读取数据的一致性问题。
即便是在w+r>n的条件下,也会存在一些问题:
- 采用sloppy quorum使得写入和读取的节点可能完全不重复
- 同时写入的冲突问题
- 写操作异步导致读取新值还是旧值不确定
- 如果未能够成功写入超过w个节点,但是不能回滚
- 即便一切正常也会由于异步产生的单调读问题
监控旧值
对于主从复制的方式,可以很容易的通过日志判定从节点相对于主节点落后的程度。
对于无主节点复制系统,由于每个节点都接收写入,导致判定较为困难。
宽松的quorum与数据回传
如果客户端写入/读取能够连接到的节点无法满足w/r的需求,那么有以下方式应对:
- 如果无法满足要求,是否要将错误明确返回
- 接受这些请求,并将他们暂时存储在一些可以访问的其他节点,这些节点不属于原有的n个节点,该方法称为放松的仲裁(sloppy quorum)
sloppy能很好提升系统的可用性。
多数据中心操作
如何令响应时间更小
检测并发写
对于多主节点复制,即便采用严格的quorum机制,由于异步的复制操作,也会引起一致性问题,因此需要一些解决该问题的方案。
最后写入者获胜(丢弃并发写)
最后写入者获胜(Last Write Win),通过时间戳确定,但是该方法会牺牲数据的持久性能。
happens-before关系和并发
并发:在先后顺序上存在依赖关系的两个步骤存在并发
并发的关键不在于时间上的同时,而在于逻辑上的依赖,在于两个操作是否需要意识到对方的存在。
确定前后关系
介绍了一种确定操作并发性的方法。
合并同时写入的值
如何将并发操作合并,涉及到了如何进行删除操作
版本矢量
版本矢量:各个节点版本号的集合
小结
- 复制的目的
- 复制的方案
- 主从复制
- 多主节点复制
- 无主节点复制
- 一致性问题
- 读写一致性
- 单调读一致性
- 前缀读一致性