阿里云资深技术专家闫卫斌:打造具备极致容灾能力的对象存储
数字经济时代,数据呈现出爆炸式的增长。有数据表明,近 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,已经在线下以工单的方式帮非常多的客户完成了迁移,产品化的迁移功能也马上会推出。
Region 故障的应对
前面介绍的是 AZ 级故障的应对,接下来我介绍一下第三部分。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 如何应对这些问题呢?我们会做一些常态化的运维演练,再结合监控报警和自动修复功能来解决这些容灾腐化问题。
这些功能主要都是通过我们的智能运维平台提供的,最后一部分会对我们的智能运维平台做一个介绍。
对象存储 OSS 智能运维平台-数据湖仓
运维的基础是数据,要有丰富的数据,才有做智能运维的基础。
在 OSS 这里,我们通过多年积累,沉淀了非常多的数据。上图左下就是运维平台收集的数据的示意。比如各种各样的监控数据,如网络探针,可以从外部探测,知道某个区域不可用了;比如服务器的角度,有机器的多种维度数据;应用角度,有每个进程,每个模块的日志等各种数据;系统的角度,有内核的内存、CPU、磁盘、进程等等多种维度的数据。
阿里云本身有 50 多个的横向的支撑系统,有非常多的数据沉淀,我们的运维平台也做了打通。在数据处理上的话也是非常有挑战的。这里以一个最简单的 OSS 访问日志的例子。我们的访问日志每秒钟有亿级的日志条目,在处理上的压力是非常大的,处理的性能、成本都是问题。我们做了非常多的工作,比如采集端,会在本地做一些加工处理,例如过滤、聚合之类的来缩减数据量。
在缩减完之后,我们会使用阿里云的各种功能非常强大的产品,比如说 SLS、Blink 来做数据的实时处理。处理的这些结果会存储到 ODPS 或者 OSS 自身这样的存储产品里面供后续使用。
对一些离线的数据、复杂的数据处理,我们会用到基于 OSS 的数据湖方案来处理。
对象存储 OSS 智能运维平台:运维动作自动化
如果把这个智能运维平台比作一个人,数据就像是人的眼睛。接下来,我要讲的运维动作有点像人的手脚,给我们提供运动能力。
在这一块,我们的理念有点像 Linux 平台的理念。首先研发同学会把各种各样的原子操作、原子运维动作都做脚本化,有点像 Linux 里的一个个命令,每个命令只完成一件事情,这些运维动作的原则也是每个脚本只完成一件事情。然后我们的运维平台赤骥提供的工作流功能,有点像 Linux 里的管道,类似一个个简单的命令,用管道组合起来提供一些复杂的功能。运维平台通过工作流,可以把各种基础的运维动作组合起来,实现一些复杂的运维动作,比如说集群上线,集群下线。
当然,有些运维动作相对来说是比较复杂的。以数据迁移为例,在架构上,通过把一个对象拆成 META 元数据和 DATA 数据两部分,右下角就是一个对象的简单示意,可以看一个 META 的 KV 对结合用户元数据的 KV 对加若干个数据 KV 对,就组成了一个对象。通过这样的架构设计,就能实现提供几个基础的运维动作:
第一,可以把一个 Bucket 数据打散到多个存储集群,每个集群的比例还可以按照需要做不同的调节,不一定完全对等。
第二,可以通过迁移部分数据来做一些灵活的容量调度。
有了眼睛和手脚之后,在大脑的指挥下就可以做一些复杂的高级动作了,对运维来说,也就是说可以完成一些复杂的运维任务。
对象存储 OSS 智能运维平台:复杂运维任务
以我们怎么应对容灾腐化的例子来做一个收尾吧。
前面提到过一个例子,就是当业务压力增长,让某个 ZRS 集群的 IO 压力超过了安全水位线,这个时候运维平台首先会收到报警信息。
收到报警信息之后,我们会提前有一些预设的规则,比如报警持续了超过多长时间,报警里面产生的 IO 压力,用户的 IO 行为是持续的,而不是一个瞬时的行为。明确了这些信息之后就会做出决策,然后就要做数据的调度来把一部分 IO 压力迁移到别的集群,来让原本这个集群的 IO 水位恢复到安全水位。这个时候它就要去调度前面说的那些数据迁移的原子能力来做迁移。这个时候问题就来了:因为数据和 IO 压力其实不是直接关联的,关系比较复杂,到底要调度哪些数据、调度多少数据才能迁移走想要的那么多 IO 压力呢?这个又用到了数据,就是前面讲的我们沉淀了各种各样的数据,其中就有用户的每个 Bucket 的 IO 行为的用户画像。
右边这张图就是其中某个维度的示意。一个用户 Bucket,我们会持续跟踪他读取的行为。举一个例子,比如说你上传了一天内的数据,你到底有多少比例是读它的,1-3 天的有多少比例,3 天以上的有多少比例。有了这个比例划分,再结合一个用户每天 IO 行为的总量的变化情况,就可以在后台计算出来,是调度新写入的数据效率更高,还是迁移写入了某个时间以上的的数据效率更高、以及要迁多少。
计算出这个结果之后就开始真正做执行了,通过工作流去把指定的数据搬迁到其他集群之后,原本这个集群的 IO 压力就恢复到安全水位,等于说靠智能运维平台,研发人员可以把各种约束都沉淀为这样的复杂运维任务来录入智能运维平台上,保证在长时间运行下所有容灾设计都像预期的那样去工作。
以上,就是本次分享的主要内容,谢谢大家。
评论