阿里云闫卫斌:打造具备极致容灾能力的对象存储

数字经济时代,数据正呈现爆炸式的增长。有数据表明,近6年来中国数据增量年均增速超过30%。如此快速增长的数据,离不开相应的存储设备提供支撑。传统集中式存储受性能天花板的限制,难以满足海量数据存储的需求。分布式存储以Scale out(横向扩展)为特征,正成为海量数据存储的首选,这已经成为业内的普遍共识。

时间进入2023年,分布式存储又向何处去呢?哪些场景、业务创新会成为新的突破口呢?如何帮助传统产业更好应对海量数据增长和数据创新的挑战?2023年3月10日,由百易传媒(DOIT)主办、上海市计算机学会与上海交通大学支持的第六届分布式存储高峰论坛(Distributed Storage Forum 2023)于线上举行,十多位业界专家、厂商代表与近万名观众就时下热点关注的话题进行分享、互动和交流。

阿里云智能资深技术专家闫卫斌应邀出席本次论坛,并发表主题演讲。

以下内容根据速记整理。

闫卫斌:大家好,我是来自阿里云存储的闫卫斌。非常高兴今天有机会在DOIT论坛做一个阿里云对象存储OSS技术架构的分享。

阿里云智能资深技术专家 闫卫斌

我今天分享的题目是:“如何打造具备极致容灾能力的对象存储”。

容灾是分布式存储领域的一个关键问题。

最基础的是对服务器的容灾,这是所有对象存储产品都要解决的问题。

进一步,就是AZ(Availability Zone)级故障容灾,AZ也就是可用区。通常一个可用区会映射到一个或者多个数据中心。一个AZ和其他AZ在制冷、供电等方面都是故障域隔离的。几大云厂商也都提供支持AZ级容灾的对象存储产品。

可能有的同学会问,做AZ级容灾是不是有必要?大家如果去网上去搜索一下就会发现,各大云厂商的AZ级故障还是时有发生的,包括一些不可抗灾害或者制冷、供电中断等故障。从底层逻辑上来讲,AZ级故障是不可能彻底避免的。对于一些高可用的应用,采用具备AZ级容灾能力的存储产品还是非常有必要的。

Region故障也是同样的道理。每个AZ之间会间隔几十公里,但是在一些极端场景,比如高等级地震的时候,那同时影响到一个Region的多可用区也是有可能的。比如最近土耳其7、8级地震,它是有可能导致Region级故障的。各家厂商也有提供一些应对Region故障的产品或者说解决方案,对象存储OSS也有类似的产品,后面会介绍。

本次分享。首先会介绍我们在本地冗余,也就是LRS(本地冗余)这个产品做了哪些容灾设计,其次,我会介绍我们应对AZ故障的ZRS(同城冗余)产品的容灾设计,第三部分是应对Region故障的跨区域复制功能。在第二、第三部分我分别以案例深入介绍我们在技术上做的一些改进或者设计。最后分享我们是如何以智能运维平台来应对生产过程中系统长时间运行以后架构腐化的问题。

一、LRS(本地冗余产品)的容灾设计

这个图左边是我们OSS系统模块的划分图,从上到下大致可以分为四层。

用户请求进来之后,首先会到达我们的负载均衡,也就是AliLB,AliLB是阿里自研的高性能的负载均衡产品,它是基于LVS原理的。

往下请求会来到业务层,在这一层主要是做协议的解析、各种各样的业务功能的实现。这两层我们都是一个无状态的设计。在这种无状态的服务里面如何做高可用的呢?我们会把它的部署按机架来打散并保证两个机架故障情况下,它的服务能力还是足够的。

再往下,请求就会来到索引层,索引层我们叫做KV,是一个Master Server结构。每个Server又根据字典序划分为多个分区,它的Master跟每个分区的Server都是采用Raft协议实现的一致性组。

底下就是存储服务盘古,它跟KV类似,也是一个Master Server的结构。区别是它的Server没有采用一致性组的架构,主要是性能考虑。另外在高可靠上是通过副本或者EC机制来做的。

在LRS产品里,阿里云整体的容灾设计目标是希望做到两个机架故障不影响服务的可用性和可靠性。

刚才已经提到了无状态服务,对有状态服务的话,他们的一致性组,我们都是采用五节点部署的。这五个节点会打散部署在至少五个机架上。这样显而易见:如果说故障两个机架,还是有多数节点存活,还可以提供服务。

数据方面,我们有两种数据的存放形式,有三副本,也有EC。三副本的话,只要做了机架打散,显然是可以实现两机架的容灾的。对于EC,我们也会保证它的校验块的数量大于等于二。同样也是做机架打散,能实现两个机架故障不影响可用性可靠性。

除了这些数据的分散方式,我们还做了非常多的其他的设计来保障高可用。比如数据分片是做全机群打散的,这样做有什么好处呢?如果一台服务器发生故障,如果做全打散的话,是可以利用这个集群里剩余的所有机器来并行的做数据修复,这样缩短了数据的重建时间,相应的也就提高了可靠性。

另外,我们也会刚性的保留足够的复制带宽。所有的这些,包括集群水位,副本的配置,包括打散方式,还有复制带宽,我们都会用一个模型来去计算并且测试验证它的可靠性,保障设计达到12个9的可靠性。

二、ZRS(同城冗余)产品容灾设计

通俗来说,OSS ZRS(同城冗余)产品,也叫3AZ型态。它和LRS最主要的区别就是在容灾设计目标上,LRS要容忍两个机架故障,ZRS则是要保证一个AZ外加一个机架故障时不影响可靠性可用性。

稍微提一下,假设你一个AZ故障发生以后,如果修复时间相对比较长,那再坏一台机器的概率还是比较高的。如果在容灾设计上只容忍1AZ的话,最后因为AZ修复期间单台服务器的故障影响了可用性,那就有点得不偿失了,所以我们在设计上特意采用了1AZ加额外一机架的故障容忍的设计目标。

再来讲讲怎么实现容灾设计的。

在模块划分上ZRS和LRS基本是一样的。主要的区别是模块的打散方式,在LRS里都是跨机架打散,在ZRS里,我们会把它升级到跨AZ打散,所有的模块都是跨AZ部署的。当然在AZ内还是会做机架级的打散的。

关于有状态服务。有状态服务的一致性组在LRS 都采用5节点部署,ZRS产品里面就会变为9节点。9个节点会均匀打散到3个AZ,每个AZ有3个节点。这个设计是可以容忍4个节点故障的。也就是说可以容忍“1AZ+剩下两个AZ里的某个节点故障”。

在数据的高可靠方面,前面提过我们有3副本的EC。3副本显而易见,只要做了AZ打散,是可以容忍刚才提到的容灾设计目标的。EC是要做比较大的EC配置的重新设计。理论上,3个AZ要容忍1个AZ故障,那数据冗余至少要1.5倍。实际上早期我们采用6+6的EC配置,大家可以理解一个数据集,把它拆分为6个数据块+6个校验块,平均分布到3个AZ,每个AZ有4个块。这样一个AZ故障的时候,一个数据集里还剩余8个块,其实只要有6个就可以恢复数据了。所以,这个时候还可以再容忍2个机架故障,相比于容灾设计目标是有一定的超配。

当然,做了这样的设计之后,看起来是可以容忍“1AZ+额外1机架故障不影响可用性可靠性”,但实际没这么简单。

实际的运行过程中还要考虑到非常多的其他因素。比如如何解决跨AZ的超高吞吐的带宽需求?AZ间的延迟显然是要比AZ内的延迟高很多,如何通过系统的设计尽量让这个延迟的增加不影响到用户?又比如说在1个AZ故障的时候,本来就有1/3的机器不可服务的,如果还要再做数据重建,那又带来非常大的IO放大。如何保证在AZ故障时候的高质量的服务?是有非常大的挑战的。

 接下来以刚才提到的最后这个挑战为例来做一个相对深入的介绍。

下图左边是一个示意图,我们前面提到了,我们早期是用6+6的EC编码。我们采用的是RS的编码。在单个AZ故障的时候剩余8个块提供服务,其中4个数据块+4个校验块。

简单计算一下,在这种AZ故障的情况下,单台机器或者单块磁盘承受的IO压力,相比在故障前日常情况下大概是什么水平。

发生故障的时候,显而易见是有2/3的数据是可以直接读取的,也就是说1份IO没有放大。另外有1/3是要通过rebuild来做重建的。重建的话,RS编码至少要读6块数据,不考虑额外的校验也要读6块数据才能重建。也就是说有1/3的数据是要读6块才能重建。再考虑故障期间只有2/3的机器提供服务。

右上角有一个简单的算式,可以看到,故障期间每台机器或者每块盘承受的IO压力是日常的4倍。换个角度来解读一下,如果这个机器的IO能力是100%的话,那要保证在故障以后还是高可用的,日常最多只能用到它IO能力的25%。考虑还要留一定的安全水位,假设打个八折,那可能就只有20%了,这个数据显而易见是非常夸张的,给我们带来一个很大的挑战:要么让ZRS产品相比LRS只能提供低的多的IO能力,要不然就是要承受高的多的成本。

正因为有这样的挑战,所以EC的编码方式,尤其是在同城冗余型态下EC的编码方式一直是我们持续投入的研究或者改进的方向,我们也做了非常多的工作,提出了自己独创的AZC的编码,这个编码算法本身也有在顶会中发表论文。

这里对它做一个简单的介绍。

简单来说,AZC编码就是把原本1维的EC编码升级成了2维。水平方向,是一个AZ间的编码,我们首先会把一些数据块划分为多个小组。每个小组内,如果以上图左侧的示意图为例,每个小组是“2个数据片+1个校验片”做了一个2+1的RS编码。用小的EC配比有一个好处,在AZ故障的时候重建代价小,相比前面要读6份重建,现在只要读2份就可以了。

当然这种小配比的EC也是有缺点的,代价就是它的容灾能力差,可靠性低。因为只要有2个机架故障,丢掉2个分片,它的数据就无法恢复了,这显然是不可接受的。所以在垂直方向,也就是AZ内,我们又把多个分组内的数据块结合到一起,额外做了一个垂直方向的编码。通常,比如说用12个片,额外再加2-3个校验块做一个RS编码,两者结合起来就能在12个9可靠性的前提下,仍然能保证在AZ故障的时候重建的IO放大是比较小的。

当然AZC也不只是AZ故障重建代价低的好处。通过一些仔细的编码配比的选择,它的数据冗余相比前面提到的6+6 EC也是要好非常多的。

前面也提到,我们会在垂直方向做一个AZ内的RS编码。这也就是说日常情况下,如果AZ内有机器故障,是可以在AZ内本地重建的,可以完全避免跨机房的重建流量。也就是说,AZC编码其实是在AZ的故障重建代价和数据冗余的成本和日常的跨机房重建流量这三个方面取得了一个比较好的平衡。相比6+6 RS编码提升是巨大的,根据这个公式简单算一下,流量放大倍数从4降到了2,换句话说,也就是说IO利用能力提升了整整1倍。

近几年,从ZRS产品业务情况上的观察,越来越多的客户开始重视AZ级容灾能力,使用ZRS的比例越来越高。在香港故障以后,很多客户把他们的数据存储转到了ZRS型态上。很多本身已经在用LRS的客户,也想无缝的升级到ZRS。

针对这些需求,结合前面做的各种技术改进,阿里云推出了两个大的升级。第一,将以前不支持归档型的ZRS 升级到支持归档类型。另外在迁移能力上做了非常多的工作,支持LRS无缝迁移到ZRS,已经在线下以工单的方式帮非常多的客户完成了迁移,产品化的迁移功能也马上会推出。

前面介绍的是AZ级故障的应对,接下来我介绍一下第三部分。

三、Region故障的应对

Region故障的应对,主要是跨区域复制功能。

上图左边是一个简单的示意图。

前面提到,我们有服务层OSS Server,也有索引层KV Server。所有的请求在到达KV Server之后,增删改操作都会有一条redolog,而且这个redolog是可以定序的。所以为了实现跨区域复制,首先我们增加了Scan Service,它的功能就是扫描所有的redolog来生成复制任务,并且是可定序的复制任务。

这里要强调一点——我们的复制任务是异步的,它跟用户的前台请求完全解耦,也就是说它出任何问题,是不影响用户的前台业务的。在图里用不同的颜色进行了区分。

有了任务之后,第二个重要的模块就是图里面的Sync Service,它的功能就是把每个复制任务去源端读取数据,通过跨区域复制的专线或者公网把它写到目的端去。除了完成这个复制任务本身以外,也要做很多其他的功能,比如说各种维度的QoS,还有在目的端写入的时候,因为用户自己也有可能直接写入,所以要做一致性的数据冲突的仲裁等等。

这部分我也挑了一个功能来做一个相对深入的介绍,我选的是RTC功能,顾名思义就是数据复制时间控制。

一句话来表达,开启了RTC以后,OSS会对跨区域复制的RPO时间做一个SLA承诺。在以前如果不开启的话是不提供承诺的。大多数情况下是可以秒级完成复制的,另外,对长尾情况也保证P999的对象可以在10分钟内完成复制。另外,我们还提供了非常多的复制进度的监控,比如复制的带宽量、数据量,复制的延迟情况,剩余多少没有完成的复制任务等等。

为了实现这些,我们后台做了非常多的技术改进。

首先,因为是一个跨区域的复制,所以带宽资源非常宝贵,在带宽资源管理上做了多维度的隔离。首先就是租户间的隔离,任意的用户不能影响到其他的用户。另外,其他的业务的跨区域的流量不能影响到这个RTC服务的流量。以及其他类型的多种维度的QoS隔离。

除了隔离以外,在优先级上也做了非常多角度的划分。最直白的就是开启了RTC,那在复制带宽上就有更高的优先级。另外,在同一个客户的RTC的复制边内,已经延迟了的这些任务,相比刚生成的任务就有更高的优先级等等。

除了物理资源的管理,在架构上也做了很多改造来提升复制的实时性。

举一个例子,OSS的对象,一个对象是可以支持数十T大小的。如果在这个对象上传完成之后才开始复制,那显然不可能做到秒级复制,所以在开启了RTC之后,我们会用更多的IO来保证复制的实时性。简单来说,没开启RTC的时候,我们是在一个PART 上传完成之后触发一个复制任务,如果开启了RTC,那就会在每个PART的1MB或者其他大小的分片上传完之后就会生成一个复制任务。这样才能有比较好的实时性保证。

另外,也做了多种维度的精细化的监控,包括对RPO破线的报警等等。

正是因为所有的这些改进,我们才有信心对客户承诺RPO的SLA 的保障。

四、防止良好的容灾设计在执行中逐步腐化

前面介绍完了我们对服务器故障、AZ级故障和Region故障容灾上的设计,但是实际的运行过程中也不是这么简单的,就像一个良好的架构设计在执行较长时间之后会有各种各样的腐化问题,容灾设计也是同理。

举一个简单的例子,前面讲了要有IO压力控制,不管是20%还是40%,总是要有一个IO水位的控制的。假设线上用户的业务情况增长短时间突破了容灾水位,如果不及时做扩容的话,那其实就已经丧失了容灾能力了,这就是一个典型的腐化问题。

又比如说前面讲了各种EC、3副本的数据分布,理想的情况它是应该分布均匀的,但如果某个版本的软件在这些数据分布算法上出了bug,那它有可能导致分布不均匀。

所有这些问题都会导致容灾设计失效。

OSS如何应对这些问题呢?我们会做一些常态化的运维演练,再结合监控报警和自动修复功能来解决这些容灾腐化问题。

这些功能主要都是通过我们的智能运维平台提供的,最后一部分会对我们的智能运维平台做一个介绍。

1.对象存储OSS智能运维平台-数据湖仓

运维的基础是数据,要有丰富的数据,才有做智能运维的基础。

在OSS这里,我们通过多年积累,沉淀了非常多的数据。上图左下就是运维平台收集的数据的示意。比如各种各样的监控数据,如网络探针,可以从外部探测,知道某个区域不可用了;比如服务器的角度,有机器的多种维度数据;应用角度,有每个进程,每个模块的日志等各种数据;系统的角度,有内核的内存、CPU、磁盘、进程等等多种维度的数据。

阿里云本身有非常多的横向的支撑系统,应该是50多个,他们本身有非常多的数据沉淀,我们的运维平台也做了打通。在数据处理上的话也是非常有挑战的。这里以一个最简单的OSS访问日志的例子。我们的访问日志每秒钟有亿级的日志条目,在处理上的压力是非常大的,处理的性能、成本都是问题。我们做了非常多的工作,比如采集端,会在本地做一些加工处理,例如过滤、聚合之类的来缩减数据量。

在缩减完之后,我们会使用阿里云的各种功能非常强大的产品,比如说SLS、Blink来做数据的实时处理。处理的这些结果会存储到ODPS或者OSS自身这样的存储产品里面供后续使用。

对一些离线的数据、复杂的数据处理,我们会用到基于OSS的数据湖方案来处理。

2.对象存储OSS智能运维平台:运维动作自动化

如果把这个智能运维平台比作一个人,数据就像是人的眼睛。接下来,我要讲的运维动作有点像人的手脚,给我们提供运动能力。

在这一块,我们的理念有点像Linux 平台的理念。首先研发同学会把各种各样的原子操作、原子运维动作都做脚本化,有点像Linux里的一个个命令,每个命令只完成一件事情,这些运维动作的原则也是每个脚本只完成一件事情。然后我们的运维平台赤骥提供的工作流功能,有点像Linux里的管道,类似一个个简单的命令,用管道组合起来提供一些复杂的功能。运维平台通过工作流,可以把各种基础的运维动作组合起来,实现一些复杂的运维动作,比如说集群上线,集群下线。

当然,有些运维动作相对来说是比较复杂的。以数据迁移为例,在架构上,通过把一个对象拆成META元数据和 DATA数据两部分,右下角就是一个对象的简单示意,可以看一个 META的KV对结合用户元数据的KV对加若干个数据KV对,就组成了一个对象。通过这样的架构设计,就能实现提供几个基础的运维动作:

第一,可以把一个Bucket数据打散到多个存储集群,每个集群的比例还可以按照需要做不同的调节,不一定完全对等。

第二,可以通过迁移部分数据来做一些灵活的容量调度。

有了眼睛和手脚之后,在大脑的指挥下就可以做一些复杂的高级动作了,对运维来说,也就是说可以完成一些复杂的运维任务。

2.对象存储OSS智能运维平台:复杂运维任务

以我们怎么应对容灾腐化的例子来做一个收尾吧。

前面提到过一个例子,就是当业务压力增长,让某个ZRS集群的IO压力超过了安全水位线,这个时候运维平台首先会收到报警信息。

收到报警信息之后,我们会提前有一些预设的规则,比如报警持续了超过多长时间,报警里面产生的IO压力,用户的IO行为是持续的,而不是一个瞬时的行为。明确了这些信息之后就会做出决策,然后就要做数据的调度来把一部分IO压力迁移到别的集群,来让原本这个集群的IO水位恢复到安全水位。这个时候它就要去调度前面说的那些数据迁移的原子能力来做迁移。这个时候问题就来了:因为数据和IO压力其实不是直接关联的,关系比较复杂,到底要调度哪些数据、调度多少数据才能迁移走想要的那么多IO压力呢?这个又用到了数据,就是前面讲的我们沉淀了各种各样的数据,其中就有用户的每个Bucket的IO行为的用户画像。

右边这张图就是其中某个维度的示意。一个用户Bucket,我们会持续跟踪他读取的行为。举一个例子,比如说你上传了一天内的数据,你到底有多少比例是读它的,1-3天的有多少比例,3天以上的有多少比例。有了这个比例划分,再结合一个用户每天IO行为的总量的变化情况,就可以在后台计算出来,是调度新写入的数据效率更高,还是迁移写入了某个时间以上的的数据效率更高、以及要迁多少。

计算出这个结果之后就开始真正做执行了,通过工作流去把指定的数据搬迁到其他集群之后,原本这个集群的IO压力就恢复到安全水位,等于说靠智能运维平台,研发人员可以把各种约束都沉淀为这样的复杂运维任务来录入智能运维平台上,保证在长时间运行下所有容灾设计都像预期的那样去工作。

以上,就是本次分享的主要内容,感谢各位的观看。