高可用的本质: 复制
luoxn28 人气:4服务和数据的高可用性本质上是靠“复制”来解决的,比如服务通过集群部署多台机器来完成,数据通过冗余的多副本机制来完成。对于服务来说,只需要部署多个实例即可,特别是无状态服务,常见的微服务(dubbo/spring cloud)几乎都是通过集群部署对外提供服务能力,更进一步的还可使用k8s+docker技术自动管理服务的副本容量;对于数据来说,需要通过数据复制来保证数据节点的一致性,由于数据是有状态的,因此实现难度较服务复制成本要高。
复制除了提高可用性之外(多机房数据复制提高机房容错性),还可以提高性能,比如读写分离、使数据副本离用户更近等,用户可优先就近读取数据。对于数据来说,常见的复制策略有主从复制、多主复制和无主复制,除了这些之外,还有一种常见的广义上“复制”策略-快照,比如Redis快照等。本文就针对上述几种复制策略展开分析,话不多少,Let's go~
本文后续讨论的复制无特殊说明都是针对数据复制来分析的,这里的复制讨论都是基于一个节点能保存所有数据为前提,因为数据量过大需要使用分区机制,而分区机制不在本文讨论范围之内 :(
主从复制
如果数据不随着时间而变化,那么只需复制一次即可,复制的难处在于数据始终在变化,因此复制时有很多权衡,比如是否同步,复制失败的副本如何处理等。主从复制是常见的复制策略,写操作发生在主节点,然后将更新的数据同步到从节点。基于主从的复制模型如下:
主从复制是许多数据库的内置功能,比如PostgreSQL(从9.0版本开始),MySQL,Oracle Data Guard和SQL Server等,当然也有一些非关系型数据库也支持此类功能,比如MongoDB等,不仅仅是数据库,像kafka(分区的多副本)和rabbitmq的队列为了实现高可用,也实现了主从复制功能,甚至一些存储设备本身也具有复制机制。
同步复制和异步复制
复制中一个重要的细节是同步复制还是异步复制,同步复制拥有更强的数据一致性保证,如果主库失效可以保证能在从库中找到对应数据;但是缺点也很突出,那就是从库会拖慢复制性能,如果从库故障或者网络原因,主库无法处理请求直到从库或者网络正常为止。在多从库场景中,针对网络异常或者单个从库故障的异常场景,可以使用quorum机制来保证大多数节点复制OK即可(比如zookeeper的主从同步机制),或者广播复制只要有一个从库复制OK即可(比如MySQL的半同步机制)。
异步复制拥有更好的性能保证,异步不影响请求的处理,主节点请求处理之后可以继续处理下一个请求,但是异步复制由于是最终一致性的体现,所以存在一定的数据不一致时期。如果主库失效,所有尚未同步给从库的数据会丢失。大多数场景下使用异步复制策略就能满足业务场景,如果业务对数据一致性有较高的要求,可以使用同步复制机制或者将请求发给主节点处理。
新从库
有时候需要替换故障从库或者新增从库,如何确保新从库和主库数据一致呢?简单的从主节点快照数据复制到新从库是不行的,因为数据始终可能发生更新;锁定主库然后进行快照复制也不值得推荐,因此这违背了高可用原则。启动新从库,理论上可以做到不停机,过程如下:
-
某个时刻获取主库快照,大多数数据库都具备该功能;
-
将快照复制到新从库节点并应用;
-
从库连接到主库,开始拉取快照触发之后发生变更的数据,这要求快照与主库复制日志中的位置可以精确关联,比如MySQL中的⼆进制⽇志坐标(binlog coordinates);
-
当从库追上主库之后,二者达到一致状态,可以继续处理后续数据变更了。
并不是所有场景都能不停机起新从库,比如升级从库时有的数据库复制协议不能向后兼容,还有分区扩容场景下的复制等。
复制日志
主从复制底层由以下几种实现方案,比如基于语句、基于WAL(预写日志)、基于行日志和基于触发器等,不同方案有不同的优缺点,下面简要分析下:
-
基于语句:最简单的同步方式,主库将每个更新语句(udpatehttps://img.qb5200.com/download-x/delete/create)都转发给从库,从库解析之后应用到本地,这种方式看上去很合理,不过如果sql中包含非确定值函数的语句,则会造成主从库不一致,比如NOW()获取当前时间;
-
基于WAL:数据库的数据故障恢复能力,一般都是基于预写日志实现,因为直接写到数据页或索引中相当于随机写,而预写日志是追加方式的顺序写,性能较高;PostgreSQL和Oracle等使⽤这种复制⽅法,主要缺点是⽇志记录的数据⾮常底层:WAL包含哪些磁盘块中的哪些字节发⽣了更改(比如Mysql redo日志),这使复制与存储引擎紧密耦合。如果数据库将其存储格式从⼀个版本更改为另⼀个版本,通常不可能在主库和从库上运⾏不同版本的数据库软件;
-
基于行日志:也称为逻辑日志,关系型数据库通常是基于行粒度来描述数据的写入序列,对于插入的行,行日志包含所有列的值;对于更新操作,行日志包含列更新前后的值,MySQL的binlog日志就是基于这种方式实现的(statement模式下);
-
基于触发器:上面几种复制策略都是数据库本身提供的机制,而基于触发器机制涉及到应用程序代码,当数据有更新操作时触发对应的用户程序进行对应的复制逻辑。
复制延时问题
异步复制模式下存在复制延时问题,当网络异常或者服务异常时延时问题更严重,有的业务对数据延时容忍度较大,比如用户信息更新对于其他用户可见来说,延时一定时间无所谓。在读写分离场景中,如果读都是走从库并且存在延时较长时,会出现用户刚更新完信息查看信息还是老的,好像刚才更新的数据丢失了,也就是说 ⽤户写⼊后从旧副本中读取数据,这种情况需要读写一致性来保证。这是⼀个保证,如果⽤户重新加载⻚⾯,他们总会看到他们⾃⼰提交的任何更新。它不会对其他⽤户的写⼊做出承诺:其他⽤户的更新可能稍等才会看到。它保证⽤户⾃⼰的输⼊已被正确保存。
在主从复制场景中,应该如何实现读写一致性呢?首先可以明确的是,在可能存在复制延时场景中需要从主库读取数据,比如当前用户查看更新自己的信息都走主库,或者用户更新完成之后读取数据都走主库等。如果是公共信息(多个用户可以同时编辑)的更新操作,可以在客户端增加更新时间戳,在时间戳最近一定时间内的所有读取操作都走主库。
多主复制
主从复制要求更新操作都走主库,如果客户端无法连接到主库则不能进行更新,而基于多主复制的策略中,允许多个节点接收写入操作。复制仍然以同样的⽅式发⽣:处理写⼊的每个节点都必须将该数据更改转发给所有其他节点, 称之为多领导者配置(也称多主、多活复制,比如多机房的异地多活)。在这种情况下,每个领导者同时扮演其他领导者的追随者。
多主复制场景
比较常见的多主复制场景是多数据中心,要求每个数据中心都有一个主库,每个数据中心内部使用主从同步,数据中心之间是多主复制,也就是一个数据中心的主库会复制其他数据中心主库的数据,多数据中心的多主复制如下图:
多数据中心对于公司服务运维能力要求较高,一般只有较大公司才玩的转,毕竟是有一定成本的。多数据中心对于服务来说,需要进行挺大的改造的,比如主键ID就需要接入分布式ID方案,不能采用单机数据库的ID自增方案;多数据中心的多主复制方案可以容忍数据中心停机而不中断服务,大大提高公司对外服务高可用性。
写入冲突
讨论多数据中心的写入冲突之前,先看下协同编辑场景中存在的写入冲突问题,协同编辑比如google docs允许多人同时对一个文件进行编辑操作,为了解决冲突,可以采取用户在编辑前锁定文档,然后编辑之后另一个用户才能编辑,但是这种方案锁粒度太大,因此可以使用更小粒度的方案,比如针对文档中的一个单元格进行锁定操作。
多领导者复制的最⼤问题是可能发⽣写冲突,这意味着需要解决冲突,常见的冲突解决可以采用版本思想,比如按照最新时间戳,最大版本号等,但是这种方案可能导致数据覆盖丢失问题;除了版本思路之外,还可以保留冲突数据,当用户再次查看数据时,让用户选择使用哪一个冲突版本的数据,比如git的merge冲突等。
无主复制
⼀些数据存储系统采⽤不同的⽅法,放弃主库的概念,并允许任何副本直接接受来⾃客户端的写⼊。由于无主复制没有主库概念,但是也要保证多副本机制,因此需要在客户端向多个从节点进行写操作,比如bookkeeper的写入策略,就是客户端并发些,只要收到大多数数据节点的ack就认为此次数据写入成功,这样就能保证数据的“一致性”。
快照技术
快照技术就是将当前数据状态存储到文件,便于存档,当故障发生时可以使用最近一次的快照恢复数据,由于快照执行一次的成本相对较大,但是为了保证快照数据具有实时性,因此会折中在多少次更新操作或者多长时间后触发一次快照操作,比如Redis会默认当900s内有一次更新操作,或者300s内有10次更新操作,或者60s内有10000次更新操作,就会触发RDB持久化(Redis数据快照)。常说的快照技术也就是全量数据保存,还有一种是增量数据保存,比如Redis中的AOF持久化策略。
小结
俗话说,要想提高可用性就进行一次复制操作;如果再想提高可用性,那就再复制一次。复制的本质是冗余数据,提高可用性,注意,复制也不是冗余越多越好,毕竟越多网络开销更大,从而影响整体服务性能,需要根据特定场景特定考虑,一般针对数据来说,冗余3份即可。
复制可分为同步复制和异步复制两种模式,一般来说前者有较高的数据一致性保证,后者有更好的性能保证。复制策略常见的有主从同步、多主同步和无主复制等,主从同步模式容易理解,无主复制需要应用程序多做一些额外的操作保证数据一致性。快照技术也是一种复制模式,常见于本地数据的归档保存,比如于故障恢复场景。复制是为了解决高可用问题,如果数据量更大,往往会使用分区+复制的策略来保证高性能和高可用性。
推荐阅读
-
如何优雅地执行dubbo"单测"
-
Java nio 空轮询bug到底是什么
-
程序员必看| mockito原理浅析
-
Java常见几种动态代理的对比
-
开发者不可不知的 Docker 命令
欢迎小伙伴关注【TopCoder】阅读更多精彩好文。
加载全部内容