数据复制是指将同样的数据存储在不同的节点上,之间经由网络进行数据同步。数据复制的主要目的是:
- 使用距离用户近的节点服务用户,以此降低网络传输延迟;
- 提供更好的灾备能力,在某些节点失效的时候依旧能够提供服务;
- 提高对外的服务能力,增加服务吞吐量;
数据复制所要面对的主要问题是如何应对数据的写入操作,并同步到所有数据节点当中。目前主要的方式是主从架构:
- 在所有数据节点中选出一个主节点,负责处理客户端的所有写入请求;
- 其他数据节点是从节点,当主节点的数据落库之后,会将所有的数据变更以复制日志的形式,经由网络传输发送到所有的数据从节点,从节点重现复制日志以完成正确的数据复制操作;
- 数据库主从库都可以处理客户端的查询操作;
主从复制的机制被很多主流关系型数据库所采用,比如MySQL, PostgreSQL等等。还有很多非关系数据库也采用了这种机制,比如MongoDB, RethinkDB等等。甚至被高可靠消息队列Kafka,RabbitMQ采用。
数据复制:同步与异步
一个非常常见的场景:用户修改了自己的账户信息,主节点返回了修改成功的响应。那么,此时的从节点的数据是否已经完成了修改呢?
上图中的从节点1的复制是同步的:在完成数据复制之后才对客户端返回修改成功的响应;而从节点2的复制则是异步的:先返回客户端修改成功的响应,之后才完成数据的真正复制操作。
需要注意的一点是,主节点的数据写入成功并不能保证从数据节点的数据能够完成数据复制,很多因素可能会导致从数据节点的数据写入失败:从节点故障,网络传输问题等等。
同步复制机制能够保证在客户端获得操作成功响应的前提下,保证主从节点的数据一致性,不存在主成功从失败而导致脏数据出现的情况;缺点在于客户端需要等待从节点的响应,延长了整个请求响应的过程,在从节点发生故障的时候会阻塞整个数据系统无法继续响应写入请求。
鉴于系统容错性的考虑,一般不会对于所有节点都采用同步复制的策略。在一些分布式系统中,采用对于一个从节点采用同步复制策略其他采用异步策略,以此保证一个主节点的灾备节点,这种方式又叫做半同步。而在大多数系统中,异步复制策略是更常用的,在从节点发生故障的时候,依旧能够对外提供服务,提升系统可用性。
设立新主节点
有时系统需要设立新主节点的操作来接替崩溃的主节点工作,设立新主节点的主要问题是要保证新主节点已经保有最新的数据。整个过程分以下几步:
- 抽取主节点最近时间点的快照;
- 将上面的快照拷贝到新的数据节点中;
- 新数据节点向主节点请求快照之后的数据变更操作;
- 新数据节点完成变更操作同步之后,就可以接替之前主节点的工作了;
应对节点崩溃
在一个拥有大量节点的分布式系统中,任何节点崩溃都应被当做常规事件来应对。在主从架构中,一般分为主节点和从节点两种情况。
从节点:主节点一般会储存快照和操作日志两种备份日志,在从节点重新启动之后,根据断开时间长短采用快照+操作日志或者单纯操作日志的数据恢复方式。
主节点:主节点崩溃之后所做的工作就有一点多了:设立新的主节点来处理客户端请求,从节点需要接受新主节点所发送的数据同步操作。一般分为以下几步:
- 确定主节点确实崩溃宕机。一般采用的是在一定时间(比如30秒)内无法对外产生任何响应。
- 产生一个新的主节点。这里涉及到不同的选主算法。
- 重新配置系统设置,包括系统接受客户端请求到主节点的路由配置,从节点到主节点之间的路由配置。在旧的主节点恢复之后要正确的进行一个从节点的工作。
变更操作日志
这里主要说一下WAL(write-ahead log)。
- 在日志型存储引擎中(LSM-Tree),日志就是数据存储的主要形式,后台按照一定策略进行日志压缩和垃圾回收;
- 在B树型数据存储中,数据变更会先写入WAL中,保证即使在写入B树索引过程中发生崩溃也不会丢失数据;
需要注意的是WAL是物理级别的日志,与数据存储引擎的类型和版本强依赖。