21

高可用的本质: 复制

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzIwNTI2ODY5OA%3D%3D&%3Bmid=2649938739&%3Bidx=1&%3Bsn=a71d169bcdcb3ca8adfd99e78831a3ef
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

f6R7V3m.png!web 戳蓝字「TopCoder 」关注我们哦!

jMfUJ3Y.jpg!web

服务和数据的高可用性本质上是靠“复制”来解决的,比如服务通过集群部署多台机器来完成,数据通过冗余的多副本机制来完成。对于服务来说,只需要部署多个实例即可,特别是无状态服务,常见的微服务(dubbo/spring cloud)几乎都是通过集群部署对外提供服务能力,更进一步的还可使用k8s+docker技术自动管理服务的副本容量;对于数据来说,需要通过数据复制来保证数据节点的一致性,由于数据是有状态的,因此实现难度较服务复制成本要高。

复制除了提高可用性之外(多机房数据复制提高机房容错性),还可以提高性能,比如读写分离、使数据副本离用户更近等,用户可优先就近读取数据。对于数据来说,常见的复制策略有主从复制、多主复制和无主复制,除了这些之外,还有一种常见的广义上“复制”策略-快照,比如Redis快照等。本文就针对上述几种复制策略展开分析,话不多少,Let's go~

本文后续讨论的复制无特殊说明都是针对数据复制来分析的,这里的复制讨论都是基于一个节点能保存所有数据为前提,因为数据量过大需要使用分区机制,而分区机制不在本文讨论范围之内 :(

主从复制

如果数据不随着时间而变化,那么只需复制一次即可,复制的难处在于数据始终在变化,因此复制时有很多权衡,比如是否同步,复制失败的副本如何处理等。主从复制是常见的复制策略,写操作发生在主节点,然后将更新的数据同步到从节点。基于主从的复制模型如下:

YFraEby.jpg!web

主从复制是许多数据库的内置功能,比如PostgreSQL(从9.0版本开始),MySQL,Oracle Data Guard和SQL Server等,当然也有一些非关系型数据库也支持此类功能,比如MongoDB等,不仅仅是数据库,像kafka(分区的多副本)和rabbitmq的队列为了实现高可用,也实现了主从复制功能,甚至一些存储设备本身也具有复制机制。

同步复制和异步复制

复制中一个重要的细节是 同步复制 还是 异步复制 ,同步复制拥有更强的数据一致性保证,如果主库失效可以保证能在从库中找到对应数据;但是缺点也很突出,那就是从库会拖慢复制性能,如果从库故障或者网络原因,主库无法处理请求直到从库或者网络正常为止。在多从库场景中,针对网络异常或者单个从库故障的异常场景,可以使用quorum机制来保证大多数节点复制OK即可(比如zookeeper的主从同步机制),或者广播复制只要有一个从库复制OK即可(比如MySQL的半同步机制)。

异步复制拥有更好的性能保证,异步不影响请求的处理,主节点请求处理之后可以继续处理下一个请求,但是异步复制由于是最终一致性的体现,所以存在一定的数据不一致时期。如果主库失效,所有尚未同步给从库的数据会丢失。大多数场景下使用异步复制策略就能满足业务场景,如果业务对数据一致性有较高的要求,可以使用同步复制机制或者将请求发给主节点处理。

新从库

有时候需要替换故障从库或者新增从库,如何确保新从库和主库数据一致呢?简单的从主节点快照数据复制到新从库是不行的,因为数据始终可能发生更新;锁定主库然后进行快照复制也不值得推荐,因此这违背了高可用原则。启动新从库,理论上可以做到不停机,过程如下:

  1. 某个时刻获取主库快照,大多数数据库都具备该功能;

  2. 将快照复制到新从库节点并应用;

  3. 从库连接到主库,开始拉取快照触发之后发生变更的数据,这要求快照与主库复制日志中的位置可以精确关联,比如MySQL中的⼆进制⽇志坐标(binlog coordinates);

  4. 当从库追上主库之后,二者达到一致状态,可以继续处理后续数据变更了。

并不是所有场景都能不停机起新从库,比如升级从库时有的数据库复制协议不能向后兼容,还有分区扩容场景下的复制等。

复制日志

主从复制底层由以下几种实现方案,比如基于语句、基于WAL(预写日志)、基于行日志和基于触发器等,不同方案有不同的优缺点,下面简要分析下:

  • 基于语句:最简单的同步方式,主库将每个更新语句(udpate/delete/create)都转发给从库,从库解析之后应用到本地,这种方式看上去很合理,不过如果sql中包含非确定值函数的语句,则会造成主从库不一致,比如NOW()获取当前时间;

  • 基于WAL:数据库的数据故障恢复能力,一般都是基于预写日志实现,因为直接写到数据页或索引中相当于随机写,而预写日志是追加方式的顺序写,性能较高;PostgreSQL和Oracle等使⽤这种复制⽅法,主要缺点是⽇志记录的数据⾮常底层:WAL包含哪些磁盘块中的哪些字节发⽣了更改(比如Mysql redo日志),这使复制与存储引擎紧密耦合。如果数据库将其存储格式从⼀个版本更改为另⼀个版本,通常不可能在主库和从库上运⾏不同版本的数据库软件;

  • 基于行日志:也称为逻辑日志,关系型数据库通常是基于行粒度来描述数据的写入序列,对于插入的行,行日志包含所有列的值;对于更新操作,行日志包含列更新前后的值,MySQL的binlog日志就是基于这种方式实现的(statement模式下);

  • 基于触发器:上面几种复制策略都是数据库本身提供的机制,而基于触发器机制涉及到应用程序代码,当数据有更新操作时触发对应的用户程序进行对应的复制逻辑。

复制延时问题

异步复制模式下存在复制延时问题,当网络异常或者服务异常时延时问题更严重,有的业务对数据延时容忍度较大,比如用户信息更新对于其他用户可见来说,延时一定时间无所谓。在读写分离场景中,如果读都是走从库并且存在延时较长时,会出现用户刚更新完信息查看信息还是老的,好像刚才更新的数据丢失了,也就是说 ⽤户写⼊后从旧副本中读取数据,这种情况需要读写一致性来保证 。这是⼀个保证,如果⽤户重新加载⻚⾯,他们总会看到他们⾃⼰提交的任何更新。它不会对其他⽤户的写⼊做出承诺:其他⽤户的更新可能稍等才会看到。它保证⽤户⾃⼰的输⼊已被正确保存。

在主从复制场景中,应该如何实现读写一致性呢?首先可以明确的是,在可能存在复制延时场景中需要从主库读取数据,比如当前用户查看更新自己的信息都走主库,或者用户更新完成之后读取数据都走主库等。如果是公共信息(多个用户可以同时编辑)的更新操作,可以在客户端增加更新时间戳,在时间戳最近一定时间内的所有读取操作都走主库。

多主复制

主从复制要求更新操作都走主库,如果客户端无法连接到主库则不能进行更新,而基于多主复制的策略中,允许多个节点接收写入操作。复制仍然以同样的⽅式发⽣:处理写⼊的每个节点都必须将该数据更改转发给所有其他节点, 称之为多领导者配置(也称多主、多活复制,比如多机房的异地多活)。在这种情况下,每个领导者同时扮演其他领导者的追随者。

多主复制场景

比较常见的多主复制场景是多数据中心,要求每个数据中心都有一个主库,每个数据中心内部使用主从同步,数据中心之间是多主复制,也就是一个数据中心的主库会复制其他数据中心主库的数据,多数据中心的多主复制如下图:

yau2Mjr.jpg!web

多数据中心对于公司服务运维能力要求较高,一般只有较大公司才玩的转,毕竟是有一定成本的。 多数据中心对于服务来说,需要进行挺大的改造的,比如主键ID就需要接入分布式ID方案,不能采用单机数据库的ID自增方案; 多数据中心的多主复制方案可以容忍数据中心停机而不中断服务,大大提高公司对外服务高可用性。

写入冲突

讨论多数据中心的写入冲突之前,先看下协同编辑场景中存在的写入冲突问题,协同编辑比如google docs允许多人同时对一个文件进行编辑操作,为了解决冲突,可以采取用户在编辑前锁定文档,然后编辑之后另一个用户才能编辑,但是这种方案锁粒度太大,因此可以使用更小粒度的方案,比如针对文档中的一个单元格进行锁定操作。

多领导者复制的最⼤问题是可能发⽣写冲突,这意味着需要解决冲突,常见的冲突解决可以采用版本思想,比如按照最新时间戳,最大版本号等,但是这种方案可能导致数据覆盖丢失问题;除了版本思路之外,还可以保留冲突数据,当用户再次查看数据时,让用户选择使用哪一个冲突版本的数据,比如git的merge冲突等。

无主复制

⼀些数据存储系统采⽤不同的⽅法,放弃主库的概念,并允许任何副本直接接受来⾃客户端的写⼊。由于无主复制没有主库概念,但是也要保证多副本机制,因此需要在客户端向多个从节点进行写操作,比如bookkeeper的写入策略,就是客户端并发些,只要收到大多数数据节点的ack就认为此次数据写入成功,这样就能保证数据的“一致性”。

快照技术

快照技术就是将当前数据状态存储到文件,便于存档,当故障发生时可以使用最近一次的快照恢复数据,由于快照执行一次的成本相对较大,但是为了保证快照数据具有实时性,因此会折中在多少次更新操作或者多长时间后触发一次快照操作,比如Redis会默认当900s内有一次更新操作,或者300s内有10次更新操作,或者60s内有10000次更新操作,就会触发RDB持久化(Redis数据快照)。常说的快照技术也就是全量数据保存,还有一种是增量数据保存,比如Redis中的AOF持久化策略。

小结

俗话说,要想提高可用性就进行一次复制操作;如果再想提高可用性,那就再复制一次。复制的本质是冗余数据,提高可用性,注意,复制也不是冗余越多越好,毕竟越多网络开销更大,从而影响整体服务性能,需要根据特定场景特定考虑,一般针对数据来说,冗余3份即可。

复制可分为同步复制和异步复制两种模式,一般来说前者有较高的数据一致性保证,后者有更好的性能保证。复制策略常见的有主从同步、多主同步和无主复制等,主从同步模式容易理解,无主复制需要应用程序多做一些额外的操作保证数据一致性。快照技术也是一种复制模式,常见于本地数据的归档保存,比如于故障恢复场景。复制是为了解决高可用问题,如果数据量更大,往往会使用分区+复制的策略来保证高性能和高可用性。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK