目录
一、主从复制
二、同步复制和异步复制
三、节点失效处理方案
四、复制日志的实现
五、复制滞后问题
读自己的写
单调读
前缀一致读
数据复制就是相同的数据在多台机器上传输,多台机器可以在一个机房也不可以跨区域。通过数据复制有以下好处:降低访问延迟(数据复制到离用户更近的地方)、当机器出现故障时,可以切换到副本机器,从而提高可用性、多台机器可以同时提供服务,从而提高吞吐量。
现在计算机技术来说数据复制就几种方法:主从复制、多主复制和无主节点复制。
一、主从复制
我们常用的很多中间件比如Mysql 以及 Redis 还有消息队列如kafka 都有主从复制,虽然他们的日志不一样,比如Mysql是通过binlog日志,Redis 是通过RDB 完成主从复制。KafKa 和他们完全又不一样,是通过生产者之间把消息写到主副本以及从副本,这个单独谈的。
主从复制工作原理可以总结如下:
-
通过配置去确定主从节点,主副本首先将数据写入本地存储
-
从副本和主副本进行网络连接后,主副会把新数据写入到本地存储后,然后将数据更改作为复制的日志或更改流发送给所有从副本。每个从副本获得更改日志之后将其应用到本地,且严格保持与主副本相同的写入顺序。
-
客户端读取数据时可以从主从节点读取数据,为啥会从主节点读取数据呢,一些特殊情况比如说Mysql的事务,从节点可以由于网络或者负载可能并不能有最新的数据,所以需要从主节点读。从节点只能读不能写。
主从复制又分为同步复制和异步复制,对于关系数据库系统如mysql ,可以配置同步复制还是异步复制,其他的系统需要根据官网文档查看
从上图可以看出节点1是同步复制,主节点需要等待节点1返回信息才能像用户1234反馈是否成功,节点2是异步,主节点不需要等待节点1返回信息。
二、同步复制和异步复制
同步复制的优点:一旦向用户确认,那么可以保证所有的数据都同步完成与主节点的数据是一致的,缺点也是很明显的,从节点出现故障比如网络故障、节点崩溃等。那么主节点将会堵塞,会影响真个系统的性能。一般都会设置成半同步,不管出现何等情况,比如说同步从节点故障,可以把其他的一些异步的从节点设置成同步的从节点,这样至少有一个节点是同步从节点,主从切换的时候,可以保证数据不会丢失。
全异步模式:优点就是主节处理数据的速度很快,系统吞吐快。缺点就是主节点发生故障不可恢复,从节点没有完成数据的同步,进行主从切换,数据丢失了,无法保证数据的持久化。这听起来不靠谱,但是对于多主复制来说,有些主节点在不同区域,还是有广泛应用的。
如果我们增加了新的从节点,可能是为了增加容错能力也可能是替换失效的副本,那么新节点与主节点如何保证数据的一致呢?
我这里首先想到了一种解决方案,直接把主节点的数据或者日志文件直接拷贝到新的从节点,不可以的,主节点还是不断的从客户端更新数据,新节点的数据与主节点的数据就会不一致。不一致好办呀,直接主节点离线或者给主节点设置成readonly 等同步完成后在上线或者设置成读写,这样会影响整个系统的性能,无法完成业务。
正确的做法应该是这样的:
-
在某个时间点对主节点的数据副本产生一个一致性快照,这样避免长时间锁定整个数据库。目前大多数数据库都支持此功能,快照也是系统备份所必需的。而在某些情况下,可能需要第三方工具,如MySQL 的innobackupex
-
将此快照拷贝到新的从节点。
-
从节点连接到主节点并请求快照点之后所发生的数据更改日志。因为在第一步创建快照时,快照与系统复制日志的某个确定位置相关联,这个位置信息在不同的系统有不同的称呼,如PostgreSQL 将其称为“log sequence number” (日志序列号),而MySQL 将其称为“bi nlog coordinates”, redis 是offset 。
-
获得日志之后,从节点来应用这些快照点之后所有数据变更,这个过程称之为追赶。接下来,它可以继续处理主节点上新的数据变化。井重复步骤l ~步骤4
三、节点失效处理方案
如果节点失效该怎么处理:
从节点失效:追赶式恢复
从节点的本地磁盘上都保存了副本收到的数据变更日志。如果从节点发生崩愤,然后顺利重启,或者主从节点之间的网络发生暂时中断(闪断),则恢复比较容易,根据副本的复制日志,从节点可以知道在发生故障之前所处理的最后一笔事务,然后连接到主节点,并请求自那笔事务之后中断期间内所有的数据变更。在收到这些数据变更日志之后,将其应用到本地来追赶主节点。之后就和正常情况一样持续接收来自主节点数据流的变化。
主节点失效:节点切换
主节点失效了,选择一个从节点作为主节点,完成主从切换,这个步骤可以是手动的也可以是自动切换。具体的步骤如下:
-
确定主节点失效。现在都是采用超市机制:节点间频繁地互相发送心跳存活消息,如果发现某一个节点在一段比较长时间内(例如30s)没有响应,并且在半数以上节点都发现某个节点没有响应,认为该节点失效
-
选出新的主节点方式有很多种比如Raft、paxos,etcd 用的就是Raft。节点间相互投票,可以根据数据的差异大小、机器的配置等打分,分数最高的就是新的主节点
-
确定新的主节点后就进行切换,客户端以后就往新的主节点写数据,如果原来的主节点上线连接到集群,发现有主节点,自动降级为从节点
上面的步骤充满了很多的变数
-
使用的异步复制并且在失效之前,新的主节点并没有收到原主节点的所有数据;在选举之后,原主节点很快又重新上线并加入到集群,新的主节点可能会收到冲突的写请求。这是因为原主节点未意识的角色变化,还会尝试同步其他从节点,但其中的一个现在已经接管成为现任主节点。常见的解决方案是,原主节点上未完成复制的写请求就此丢弃,但这可能会违背数据更新持久化的承诺。丢失数据也不是一个明智的选举如果数据表的主键 作为redis 的key,还可能造成数据的覆盖
-
由于网络问题,可能会有两个节点同时认为自己是主节点这种情况称为脑裂,非常危险:两个节点都可能接受写请求,并且没有很好解决冲突的办法,最后数据可能会丢失或者破坏。这种情况就必须强制关闭一个节点。
-
设置合适的超时时间检测主节点失效呢,设置过长,意味着切换时间过长,设置时间太短,导致不必要的切换,例如突发的负载峰值会导致节点的响应时间变长甚至超肘,或者由于网络故障导致延迟增加。如果系统此时已经处于高负载压力或网络已经出现严重拥塞,不必要的切换操作只会使总体情况变得更糟。
四、复制日志的实现
-
基于语句的复制:主节点记录所执行的每个写请求(操作语句)井将该操作语句作为日志发送给从节点。对于关系数据库,这意味着每个INSERT、UPDATE 或DELETE 语句都会转发给从节点,并且每个从节点都会分析井执行这些SQU 吾句,如同它们是来自客户端那样。对于mysql 来说 binlog 是 statement 模式。这种模式日志好处是小并且看起来很明了。
也有一些不使用的场景如下:
-
比如说sql 语句有now()以及Rand(),主从数据就不一致。
-
如果语句使用了自增列,或者依赖于数据库的现有数据(例如,UPDATE ... WHERE 条件)所有的副本必须按照完全相同的顺序执行,否则会出现不同的结果。
-
如果有副作用语句(触发器、存储过程、用户自定义函数)可能在每个副本产生不同的副作用
-
-
基于预写日志(WAL)传输
这种日志格式主要有以下两个方式:
-
对于日志结构存储引擎(SSTables 和L SM - trees ),日志是主要的存储方式。日志段在后台压缩井支持垃圾回收。
-
对于采用覆盖写磁盘的Btree结构,每次修改会预先写入日志,如系统发生崩愤,通过索引更新的方式迅速恢复到此前致状态。
所有对数据库写入的字节序列都被记入日志,然后把这些日志通过网络发送到其他从节点构建数据 这种方式好处是它记录的是磁盘块的那些字节发生了改变,非常底层,节约磁盘空间,坏处也很明了,主从节点必须是相同的存储引擎,这么说也不对,只是说相同的存储引擎具有相同的存储格式。
这种格式在mysql 内部用的比较多也就是先写日志在刷盘,主从之间一般不会这么用,但是理论上也是可以的,只是缺点大于优点
-
-
基于行的逻辑日志复制
对于mysql 来说这种模式就是binglog 是row 模式。日志里面记录的是数据:
-
对于行插入,日志包含所有相关列的新值
-
对于行的删除,日志里有足够的信息来唯一标识已删除的行,通常是靠主键,但如果表上没有定义主键,就需要记录所有列的旧值。
-
对于行更新,日志包含足够的信息来唯一标识更新的行,以及所有列的新值(或至少包含所有已更新列的新值)。
-
如果一条事务涉及多行的修改,则会产生多个这样的日志记录,并在后面跟着一条记录,指出该事务已经提交
这种方式有很多优点:
-
日志和存储引擎解藕,也就意味着主从节点可以是不同的存储引擎以及不同的版本
-
由于是数据,我们可以通过CDC 技术把数据异步同步到Redis 或者 ES 或则数据仓库 对数据进行分析以及实现搜索功能
-
-
基于触发器的复制 可以用户自定义触发器,把部分数据写到其他的表中,也是非常方便的
五、复制滞后问题
主从复制我们不可能把所有的节点都设置成同步,所以就有一个特别,异步同步的节点会有一个特性最终一致性,最终是多久呢,我也不知道,可能与你的网络以及负载有关。那么就会有一个现象,用户可能看不到自己提交的数据,也可能一会看到一会看不到。对于这样的方式有没有什么好的解决方案
读自己的写
也就是说用户读自己的数据是在主节点读,其他人读数据在从节点读也就是所谓写后读一致性也称为读写一致性,保证当前用户总是能立刻读到自己写的数据,其他用户可能会稍后读到相关数据。
这种方式有以下几种方案:
-
如果用户访问可能会被修改的内容,从主节点读取;否则,在从节点读取。这背后就要求有一些方法在实际执行查询之前,就已经知道内容是否可能会被修改。也就是说是自己可以修改的内容,内容所有者是自己
-
对于第一点是有问题的,如果大部分内容都可能被所有者修改,都读主节点,主节点负载会很高,就失去了意义。我们可以根据数据修改的时间,最近一分钟修改的话从主节点读,其他的从节点读。我们可以在redis 记录数据修改时间也可以在客户端记录数据修改的时间,传给服务端。都是通过 proxy 选择节点去读数据
-
如果副本分布在多数据中心(例如考虑与用户的地理接近,以及高可用性),情况会更复杂些。必须先把请求路由到主节点所在的数据中心(该数据中心可能离用户很远)
单调读
还有一种情况是多个副本他们同步速度不一样,用户1先从副本1读取到了数据,刷新页面从副本2没有读取到数据。那是不是很懵逼,用户莫名其妙,就可以用单调读。
单调读一致性可以确保不会发生这种异常。这是一个比强一致性弱,但比最终一致性强的保证。当读取数据时,单调读保证,如果某个用户依次进行多次读取,贝l j 他绝不会看到回攘现象,即在读取较新值之后又发生读旧值的情况。
实现方式很简单,在读从节点的时候,通过用户id的hash总是路由到同一副本
前缀一致读
对于那些需要保证事件的先后顺序比如聊天,A聊天了B回。不管怎么读总是这个顺序,有一种反常现象是看到B回然后是A。这种情况通常在分布式数据库,数据写在不同分区上。就不存在全局写入顺序。解决这个问题就必须用到前缀一致读。
前缀一致读,该保证是说,对于一系列按照某个顺序发生的写请求,那么读取这些内容时也会按照当时写入的顺序。一个解决方案是确保任何具有因果顺序关系的写人都交给一个分区来完成,但该方案真实实现效率会大打折扣。现在有一些新的算法来显式地追踪事件因果关系。
接下来文章将在数据复制二讲解多活