一、主从复制
将其中一个副本作为主节点,负责客户端的读写服务,存在多个从节点同步主节点的数据,并提供读服务。
三种同步模式
- 同步复制
主节点收到一个写操作的时候,需要所有从节点都同步成功才返回成功。
优点:可靠性高
缺点:性能很低,如果有一个节点无法写入,则会拖慢整个系统。 - 异步复制
主节点收到一个写操作,自己写成功之后就返回给客户端成功响应,异步同步给所有从节点
优点:性能很高
缺点:可靠性低,如果主节点给客户端返回成功之后主节点挂了,则会丢失刚提交的数据 - 半同步复制
主节点收到一个写操作,等待集群中大部分节点同步成功之后就返回给客户端返回成功
优点缺点:介于同步复制和半同步复制之间。
配置新的从节点
当有新的从节点加入集群的时候的同步方式:
- 主节点生成一个快照
- 将快照同步给从节点
- 从节点根据快照执行数据变更
- 从节点请求主节点生成快照时刻之后的数据(不同系统有不同的称呼),追赶主节点
处理节点失效
从节点失效
追赶式恢复
主节点失效
- 确认主节点失效
可以通过心跳检测 - 选举新的主节点
共识算法、Zoopkeeper - 重新配置使新的主节点生效
客户端需要重新配置新的主节点,并且需要保证老的主节点上线之后自动降级为从节点,否则会出现脑裂。
复制日志的实现
基于语句的复制
将主节点收到的每个写请求的操作语句(比如update语句)同步给从节点,从节点直接执行语句。
**存在的问题:**一些随机函数,时间的Now()函数同步给从节点执行会出现主从不一致的问题。
**解决方法:**可以将不确定的值转成确定的值再同步给从节点。
基于预习日志(WAL)传输
每个写操作都有对应的日志,可以通过复制日志的方式让从节点处理日志实现同步。
存在的问题:一个WAL的数据非常底层,记录的事哪些磁盘块的哪些字节发生了变更,和存储引擎紧密耦合
基于行的逻辑日志复制
基于WAL存在的问题,采取复制用的日志(逻辑日志)和存储的日志格式不同的方法来解耦。例子:Mysql binlog
优点:容易向后兼容,支持不同版本的软件甚至不同的存储引擎,也可以同步给外部系统用(例如ES)
所以Mysql,用RedoLog(WAL)进行奔溃恢复,因为其中记录的日志格式偏底层(磁盘块的字节变动,考虑这么做的原因猜测是这种记录方式速度快,日志文件内容小),所以别的存储引擎并不支持解析这种格式。而Binlog就是基于行的逻辑日志,这个又分为多种格式,可以很好的给外部系统,或者别的存储引擎用(因为解析起来方便)。
触发器的复制
灵活性高
复制滞后的问题
读自己写
**问题描述:**用户编辑完自己的数据之后,马上读取数据,有可能从从节点中读取的数据,如果这个时候同步未完成,则有可能读取不到刚才提交的数据。
写后读一致性的解决方式:
- 如果用户访问会被修改的内容,则从主节点读取。
**存在的问题:**有可能导致主节点压力过大 - 在一定时间内从主节点读取。例如更新操作一分钟之内从主节点读取,之后从从节点读取。一分钟的时间基本能保证主从已经同步完成。
**存在的问题:**系统复杂度升高,而且不能保证一分钟肯定同步完成(非常小的概率一分钟还同步不完) - 根据时间戳读取。客户端传给服务器一个时间戳,服务器必须返回这个时间戳之后的数据,如果该服务器没有这个数据则转发给别的节点,或者阻塞该请求直到该服务器同步完成。
**存在的问题:**会有不可靠时钟的问题
单调读
**问题描述:**如果用户一直刷新页面,有可能出现读到不同的从节点的情况,如果不同节点的同步进度不一致,有可能导致页面的数据一直变化。
**解决方式:**可以通过用户Id的hash路由到指定的节点上去。
前缀一致性读
**问题描述:**有两个因果关系的数据写入主节点,但是从节点同步的顺序不一致。
**解决方式:**有因果关系的数据交给一个分区来处理。用happen before来判断是否有因果关系。
二、多主节点复制
**适用场景:**一个数据中心内部使用多主节点复制的意义不大,而且复杂度过高。所以一般用于多数据中心、离线客户端操作、协作编辑。
每个数据中心都有主节点进行写入,各个数据中心的主节点进行同步的时候会存在冲突。
解决冲突
- 给每个写入分配一个时间戳,冲突的时候比较时间戳,时间戳大的覆盖时间戳小的数据。这样会有数据丢失的情况,而且时钟有可能不可靠。适用于不严格要求时间先后顺序的系统。
- 给每个副本一个id,处理冲突的时候以id大的副本数据为准。这样会有数据丢失的情况。适用于离线客户端,例如电脑端编辑的数据优先于手机端编辑的数据。
- 合并冲突数据,例如:B/C
- 保留冲突记录,让应用层或者用户来解决冲突。类似于git解决冲突。
三、无主节点复制
不存在主节点。
- 写入数据:写入数据的时候需要将写请求发送给集群中的多个副本,当大多数副本写入成功之后才表示写入成功,并且写入请求中附带版本号。(例如至少写入1/2以上的节点)
- 读取数据:从多个副本中读取数据,读取副本的数量需要保证一定能读取到存了最新数据的副本,以版本号最大的数据为准返回给客户端。(例如读取1/2以上的节点)
读修复与反熵
- 读修复:读数据的时候,把版本号最大的数据更新到别的落后的副本中去。(懒更新),适用于读频繁的数据,否则有些数据一直没读到的话,多个副本则一直不一致
- 反熵:弄个后台进程不断查找多副本之间的差异并处理不一致的顺序。
遗留问题
Mysql为什么不用基于语句的复制方式,如果不确定性的语句全部替换成确定的值不就可以保证主从一致了吗?