表格存储如何实现跨区域的容灾

系列文章

表格存储如何实现高可靠和高可用
表格存储如何实现跨区域的容灾

前言

上一篇文章介绍了表格存储如何实现高可靠和高可用,本文会介绍表格存储如何做跨区域的容灾。容灾跟高可用在概念上有一些交叉,但是场景和相关技术体系有很多不同,所以单独写一篇介绍容灾的文章。容灾是在集群本身的高可用基础上,再提供一层保障,防止罕见但却严重的故障发生,因此,读者可以先阅读上一篇高可用的文章,对表格存储如何保障高可靠和高可用有一个了解。

本文首先会介绍容灾的一些背景和相关场景,以及实现数据库容灾的两个重要能力,即数据同步和切换。然后介绍表格存储如何实现相应的功能,以及我们如何把相应的功能服务化,让用户能够方便而灵活的搭建容灾场景,给业务提供更高级别的可用性保障,或者是通过异地多活优化不同地域的终端用户的延迟。

表格存储是阿里自研的一款分布式NoSQL数据库,已经在阿里内部服务多年,并积累了很多容灾方面的场景和经验,本文介绍的内容也包含了我们对分布式NoSQL如何做好容灾的一些看法和经验教训等。

容灾实现方式和场景

容灾面向的问题范围与高可用有较大的不同,但是容灾确实也能够提高系统的可用性。容灾一般应对的是罕见但是却严重的故障发生,比如IDC断电/断网、地震、火灾,还有人为导致的大范围软硬件设施不可用等等,这些场景其实是打破了很多高可用设计上的假设。

容灾有两个重要的衡量指标,RTO和RPO。RTO是衡量故障的恢复时间,RPO是衡量故障发生前多久时间的数据会丢失。今天很多业务对可用性要求非常高,对RTO的要求常在一两分钟内,RPO则要尽可能小,一般在秒级,金融等场景常要求RPO为0。有时候RTO和RPO未必能同时满足,要在可用性和数据一致性之间做一些取舍。

实现容灾的思路与实现容错类似,仍然是通过冗余,即多搞一套或几套备用的数据和计算资源,这些备用的数据和计算资源分布在不同的地域,以防止天灾人祸。传统的容灾方式大部分是冷备份,不用多说,这种方式下RPO和RTO都难以满足今天的业务要求,而且不可靠。因此今天的容灾设计都是采用热备份和热切换的模式,这里面又有两种方式:

  1. 采用一套分布式系统,把数据的多个副本分散到不同的地域,实现容灾。分布式系统中常会应用paxos等一致性算法维护多个副本的一致性,因此这种方式可以做到RPO为0。当某个机房或者地域发生故障时,如果集群中多数派仍可服务,可自动恢复,相当于一次failover。
  2. 采用两套(或多套)独立的系统,在不同的系统之间建立同步,当发生故障时切换到另一套系统。这种模式下两套系统是独立的,只是通过数据同步保障两边拥有基本一致的数据。因为数据同步往往是异步的,所以真实故障情况下,RPO不为0,会有少量数据丢失。这种架构下也有两种访问模式,一种是平时只会访问一个系统,另一个系统是standby的,另一种模式是两套系统各自承担一部分流量,并做双向数据同步,在故障时把一套系统承担的流量切换到另一套系统。简单来说,第一种访问模式是主备,第二种是双活。

利用分布式系统的多副本做容灾

这种方式是指上面讲的方式1, 采用一套分布式系统,通过把多个副本分散到不同的地域来实现容灾。我们以表格存储为例,介绍一下这种方式如何实现,以及其适用的场景。

下图是表格存储的一个架构图,表格存储的服务层下面有个分布式存储系统盘古,我们依赖盘古的多副本强一致来保障数据可靠性。盘古已经具备了将多个副本分布在不同可用区的能力,具体来说我们使用了3个副本,这3个副本会分布在3个不同的可用区,每个可用区中都会部署盘古的master、chunkserver以及表格存储的前后端角色。跨越3个可用区的整套系统在逻辑上仍是属于一个集群,任意一个可用区故障不会影响服务,会进行自动的failover。

表格存储如何实现跨区域的容灾
这种模式被称为同城三可用区模式,未来会在公共云推出。这种模式提供了抵御某个可用区故障的容灾能力,但是不能抵御整个地域发生的故障。因为三份副本仍分布在同一个城市,而没有跨城市或者更远的地域分布。原因一方面是三份副本是保障强一致性的,如果跨度太大,写数据时的网络延迟将会很大,这会对整个系统的性能造成较大影响,另一方面是这种模式下整个系统是一个整体,如果做到完全跨地域,相当于实现一套全局容灾的全球数据库,这是另一种产品形态,暂不讨论。

利用多套系统间的数据同步和切换做容灾

这种方式是上面讲的实现容灾的方式2,采用两套或多套不同的系统,通过数据同步和切换来做容灾。这种方式分为主备模式以及双活/多活模式。主备模式有同城双集群、两地三中心等,双活/多活模式有异地双活,异地多活(单元化)等。

1. 主备模式

下面以表格存储的双机房主备场景为例,介绍一下整个过程:

表格存储如何实现跨区域的容灾
表格存储如何实现跨区域的容灾

通过上面的流程可以看到,这种容灾方式中,最重要的两个内容就是数据同步和切换。在最初搭建双集群时,数据同步包括一次全量同步和实时的增量同步。在切换后,备集群开始提供服务,然后等主集群恢复后重新建立同步关系,并最终可以再切换回主集群。

需要额外说明的是,这里的数据同步是异步的,一条数据在主集群写入成功后即返回给客户端,此时可能还未同步给备集群。异步同步会带来一部分数据一致性问题,业务需要明确这种数据不一致带来的影响,并有相应的措施。
具体来说,在故障时刻我们触发切换,这时假设主集群还有一部分数据未同步给备集群,那么:

  1. 原来的主集群上拥有切换前写入的所有数据,还包括切换前未同步给备集群的数据。
  2. 除了切换前未同步的一部分数据外,新的主集群(即备集群)上拥有所有写入的数据。

当原来的主集群恢复,这时候有几种选择:

  1. 以备集群数据为准,在原来的主集群上做备库重搭。什么意思呢,就是说以备集群数据为准,再同步一次全量数据,然后再进行实时增量同步。这种情况下主集群之前未同步的数据就消除了,也再不可见。
  2. 一般而言,主集群故障不会导致表中存量数据的丢失,所以我们可以只将切换后新写入备集群的数据增量同步回主集群。这种情况下可以快速重建主备关系,因为只同步增量,数据量小的多。但当重新切换回主集群后,之前未同步到备集群的数据又可见了,或者被备集群的更新所覆盖(同一行情况下)。
  3. 可以将主集群未同步给备集群的数据获取出来,重新补到备集群中或者做一些业务上的处理,然后进行增量同步。相当于在2的基础上做的更好,把不一致的问题解决掉。

下面简单说一下切换的问题,切换的目的是使应用层不再访问出现故障的系统,而转向访问备用的系统。因此,在从应用层到服务端的中间路径上,必须有一个点需要感知到发生了切换,然后走向另一个分支。可以从以下几个方面来考虑,仅供参考:

  1. 因为用户是通过实例域名访问表格存储服务,服务端可以更改实例域名的cname记录,指向备集群。这种模式只适用于同城双集群模式,主备实例名是同一个,但是后端集群是主备的。
  2. 应用层在自身代码中加入一个proxy层, 这个proxy层支持连接主备两套系统,其内部设置一个切换开关,当打开切换开关后,自动访问另一套系统。这种情况下主备实例完全可以是不同的实例,应用层准备好主备实例的各自配置即可。
  3. 类似于2,采用某种配置推送的中间件来实现访问配置的动态更改。

2. 双活/多活模式

双活/多活的场景如下图所示:

表格存储如何实现跨区域的容灾
表格存储如何实现跨区域的容灾
以双活为例,解释一下其与主备模式的不同:

  1. 双活模式下两套服务都接收访问,具体来说就是图中的表格存储华北2和华东1的两个实例都接受读写访问,而主备模式下备实例是不接受任何访问的。
  2. 双活模式下数据同步是双向的,而主备模式下数据同步是单向的。

多活模式与双活类似,但是会有中心和单元的概念,所以这种异地多活架构又被称为单元化。单元写入的数据会全部同步给中心,中心会向某个单元同步其他单元的数据,最终保证所有单元和中心都拥有完整的数据。

双活/多活模式下,业务层必须限制一条记录只被一个节点修改,因为多节点写入同一条数据会带来数据的不一致,从业务层面来看可能就是某个用户或者某个设备的信息只在某个节点上修改。假如业务面向的是终端用户,那么通过单元化架构可以减少延迟,优化终端用户的体验,特别是在某些全球性的业务中。

另一方面就是避免数据的循环同步,否则数据流向会形成一个环,一条记录不停的在各节点上进行同步。避免循环复制的方法是对每条数据附带上该数据已经写过的节点信息,当发现要同步的节点已经写过该条数据时,停止同步。

双活/多活模式下的切换非常灵活。还是以业务面向的是终端用户为例,某个终端用户访问哪个单元的服务是由业务层进行设置的,所以业务层可以通过更改规则,将访问某个节点的用户切换到另外的节点去。需要注意的是,业务需要考虑异步的数据同步可能造成的数据不一致问题。正常情况下,数据同步的RPO在秒级或者亚秒级,如果业务在切换时对数据写入做一些限制或者对数据同步做一些检测,是可以避免非故障场景下造成数据不一致的。当然业务也要考虑如果出现某些数据不一致时,是否可以容忍或者通过其他手段修复。

表格存储如何实现增量数据同步

通过上面对容灾场景的介绍,我们可以发现,增量数据同步是构造容灾场景的核心功能,而这一节会重点介绍表格存储如何实现增量数据同步。除了增量数据同步,为了搭建一个容灾场景还需要表Meta同步功能、全量数据同步功能、切换相关功能等等,是一个很复制的系统,暂不对这些功能进行介绍。

数据库的实时增量同步常被称为Replication,Replication的前提是说如果两套数据库有序的应用相同的更改,可以获得相同的最终结果。比如使用MySQL的binlog进行主备同步,就是将同样的修改有序的应用到备库中,表格存储的Replication也是采用的类似的方式。

我们首先看一下表格存储的写入过程,表格存储使用了log structured merge tree的结构,如下图所示(图片来自互联网):

表格存储如何实现跨区域的容灾
图中的Write-ahead log也称为commitlog,一条数据更新会先写入commitlog进行持久化,然后再写入内存中的MemTable,MemTable会定期的flush成一个新的数据文件,后台定期对不同的数据文件进行compaction,合并为一个更大的数据文件,并清理垃圾数据等。

表格存储一张表的每个分片都拥有独立的commitlog,每次修改的内容都会顺序的append写入commitlog,保证了写入的数据被持久化。当节点故障时,内存中的MemTable还未flush成数据文件,此时发生failover,分片被另外的节点加载,只需要重新replay一部分commitlog即可恢复MemTable,保证写入的数据不丢。

commitlog是一种redo log,其顺序的记录了对一个分片的所有修改,并且是确定性的修改,一些写入时未决的值(比如自增列的值、未自定义的时间戳)在commitlog里都已经变成确定性的值。表格存储的Replication就是通过对commitlog进行回放来实现的。与单机数据库相比的复杂之处在于,一方面表格存储一个表拥有众多的分片,每个分片都有独立的commitlog,分片还会分裂与合并,是一个有向无环图结构。另一方面表格存储作为大规模的分布式NoSQL,很多表的写入量都在几个GB/s,这要求数据同步模块也必须是分布式的。

目前Replication功能是作为表格存储服务端Table Worker的一个模块。对每一个开启了Replication的表的分片,Table Worker中的这个模块会负责按照commitlog中的顺序,发送这个分片新写入的数据到目的端,记录checkpoint用于failover,在分片分裂与合并时生成新分片的replication配置等等一系列与Replication相关的事情。我们也通过这个模块进行Replication流控、监控RPO指标等等。

表格存储Stream功能:增量数据同步能力服务化

上面讲到,增量数据同步是构造容灾场景的核心。事实上,增量数据的价值已经在各种场景中体现出来,包括增量数据的实时ETL、离线导入MaxCompute/Spark进行分析,实时导入ElasticSearch提供全文搜索能力等等。下图描绘了表格存储与各种计算生态的打通,其中增量数据通道都扮演重要的角色。为了把增量数据同步能力开放出来,我们开发了Stream功能,提供了一套Stream API用于读取表中的增量数据。

表格存储如何实现跨区域的容灾
另一方面,我们也希望把Replication功能开放出来,因为目前Replication模块做在Table Worker内部,在Table Worker内部管理同步的checkpoint,处理各种数据同步逻辑等等,与系统其他功能耦合太多,不够灵活,难以适应越来越多的容灾场景和定制化需求。在此同时,Stream功能也提供了出来,对外发布,基于Stream来实现Replication的想法也逐渐明确下来,开始进行开发。

基于Stream来实现Replication,一方面可以把Replication模块从系统内部独立出来,微服务化,与其他模块进行解耦,另一方面可以把这套Replication模块服务化,作为一个数据平台提供给用户灵活的定制空间,甚至用户可以基于我们提供的lib自己实现Replication,或者使用函数计算用Serverless的思想做Replication。

总结

本文基于表格存储实现各种容灾场景的一些经验和实践,总结了各种容灾场景的实现方式,介绍了表格存储如何实现容灾场景中的重要功能——增量数据同步,并简要介绍了表格存储的Stream功能,以及我们将在Stream功能基础上实现容灾能力、增量数据同步能力的服务化。欢迎各位读者加我们表格存储的公开交流群进行交流,钉钉公开群群号:11789671。

表格存储如何实现跨区域的容灾

上一篇:Unity3d 使用DX11的曲面细分


下一篇:iedriverserver使用报错