关于人工智能:京东智联云对象存储高可用架构设计思考

56次阅读

共计 5935 个字符,预计需要花费 15 分钟才能阅读完成。

在刚刚过来的 618 大促中,京东视频摈弃了公有存储,将京东智联云对象存储作为京东视频的惟一存储。在整个 618 过程中,京东智联云对象存储提供了稳固的服务,助力 618 完满闭幕。

618 大促作为京东团体最重要的流动,对所有服务的可用性有极高的要求,京东视频作为京东的一级零碎,对存储的故障更是零容忍,那么如何保障系统的高可用呢?上面咱们就一起来探讨下京东智联云对象存储在高可用架构设计上的一些思考。

作为一个有状态的服务,影响服务可用性的因素有很多,一般来说会有以下几大类:

  • 硬件 / 网络故障,该故障会导致局部数据无奈读取或者写入,如果是核心节点故障甚至会导致整个服务无奈应用;
  • 误操作,人工的误操作可能会导致服务不可用甚至数据失落 / 损坏,如果是对核心类的节点误操作可能导致整个服务无奈应用;
  • 程序 Bug,存储系统也在不断更新迭代的过程中,每次更新迭代都可能会引入 Bug,导致系统不可服务甚至数据失落 / 损坏。

对象存储是一个简单的零碎,在设计和实现的过程中,咱们遵循了以下准则,来保障对象存储的高可用:

  • 所有数据都是三正本存储,数据逾越三个 AZ,保障任何级别的硬件故障都不会导致服务不可用;
  • 数据只读化,一个数据存储之后就不会再被批改,这也意味着只有数据在磁盘上,就不会影响到读,保障读的高可用;
  • 应用多个集群独特组成一个服务,在多个集群上做写的高可用,确保写入不会中断;
  • 蓝绿部署,灰度公布,确保任何操作都只会在一个集群上进行,防止了 Bug/ 误操作等对写入的影响;
  • 没有核心节点。

上面咱们以对象存储的架构为例,具体探讨下在对象存储中,咱们是如何实际以上准则的。

整体来说,对象存储包含 业务层(绿色局部)、数据存储(黄色局部)、元数据存储(蓝色局部)三个局部组成,上面对这三个局部别离做更具体的介绍:

对象存储业务层次要做了一些认证鉴权限流等业务操作,从数据流的角度来看,他次要做了数据流的拆分和转发的工作,下图形容了一个根本的上传流程:

从上图中,咱们能够看到,对象存储业务层上传的流程如下:

  1. 接管流式数据,拆分成一个个的 Slice;
  2. 把每个 Slice 写入到后端数据存储,并且记录下数据存储返回的 SliceId(clusterId, rgId, blobId);
  3. 把 Key 和所有 SliceId 存储在元数据存储中;
  4. 返回给客户胜利。

从下面的形容能够看出,对象存储业务自身是一个无状态的服务,能够简略的通过多个节点来实现高可用,在事实中咱们也是这么做的。

从下面的数据上传流程中能够看到,数据存储是一个 Blob 的零碎,它的根本接口是用户写入一份数据,数据存储返回一个 Id,这意味着能够实现以下两点:

  1. 写入到数据存储中的数据只会被读取和删除,永远不会被批改,也意味着任意工夫只有从任何一个正本读到某个 SliceId 的数据,该数据肯定是最新的数据;
  2. 任何一个 Slice 能够写入到任意一个集群的任意一个复制组,保障写入永远高可用;

首先,咱们来看一下对象存储数据存储系统多集群部署多逻辑结构图:

从下面的逻辑部署图能够看到,数据存储系统由两类存储系统组成,上面别离介绍一下:

上图展现了一个 Region 规范的部署图,一般来说,一个 Region 由三个 AZ 组成,业务层会逾越三个 AZ 部署,存储集群、WriteCacheDataStore 都会部署三个集群,做蓝绿部署,其中 DataStore 逾越三个 AZ,而 WriteCache 每个 AZ 部署一套。

对象存储是一个 Blob 零碎,数据写入到后端任意一个存储集群都能够,Sched 负责调度一次写入写入到具体哪个集群。

流量调度会综合集群的容量 / 压力等信息,把申请调度到适合的集群,确保各个集群能最大化地被利用。

在对象存储数据存储中,每个区域会部署三套存储集群 / 缓存集群,这些集群做蓝绿部署,任何更新或者运维操作都只会在一个集群上进行,确保了任何的 Bug 或者人工误操作不会影响到对象存储的写入。

在部署上,对象存储数据存储除了部署逾越多个可用区的 DataStore,还在每个 AZ 中部署了 AZ 内的 WriteCache,确保了在跨 AZ 网络中断某个 AZ 造成了孤岛也不影响数据的写入。

在数据存储中,底层的数据存储有 WriteCache,规范存储 -DataStore,EC 存储等多个零碎,它们的根本架构都是一样的,都是基于 ReplicateGroup/Raft/Log 的零碎,写入胜利都只依赖于复制组中大部分节点响应胜利。

上面咱们来看看底层存储集群的架构:

和一个常见的分布式存储系统相似,对象存储底层存储集群也是由 Client,DataNode,Master 三个局部组成,上面别离对三个局部做一个介绍:

底层数据存储高可用的外围思路如下:

  1. 所有的数据 / 服务都是多正本的,并且逾越多个 AZ,保障单磁盘 / 机器 / 交换机 / 机房故障都不会影响用户;
  2. 骨干流程上不存在核心节点,常见的零碎设计中,Master 可能会是一个核心节点,少数几个 Master 故障可能导致整个集群不可用,在 DataStore 的设计中,咱们把 Master 分成了多个零碎,和骨干流程相干的数据路由信息被拆分到 Allotter 中,并且多节点部署,Master 自身不影响外围流程;
  3. 数据读取不依赖于复制组的存活,只有数据存在且能路由到就能拜访;
  4. 数据不强制要求三正本,然而保障绝大部分数据都是三正本。

上面咱们具体从读写的角度看看是如何实现高可用:

写入流程如下:

  1. Client 筛选一个可用的 Allotter,从中调配一个可用 RG;
  2. Client 拜访 Cache(有本地缓存),获取到该 RG 对应的 Leader 的地址;
  3. Client 向 Leader 发送写入申请;
  4. Leader 通过 Raft 把数据复制到所有正本,提交后响应客户端;
  5. 如果两头有任何失败,Client 会重试 1 - 5 步骤。

读取的流程和写入根本相似,就不再反复阐明。

  1. 数据写入路由依赖于 Allotter 和 Cache,Allotter 和 Cache 都是多节点有任意一个可用的节点就能提供服务,另外 Client 自身也能承当绝大部分 Allotter/Cache 的性能,保障写入路由高可用;
  2. 数据最终能够存储在任意一个 RG 中,一个集群中会有数十万到百万级别的 RG,有任何一个可用的 RG 就能够胜利地写入;
  3. 一个集群会包含数百台存储服务器,RG 会随机地扩散在这些服务器上,保障了只有有局部存储服务器可用,就肯定会有可用的 RG。

  1. 数据的路由会缓存在多个节点的 Cache 中以及 Client 外部,保障大量节点故障的时候依然能找到数据的地位;
  2. 数据自身设计为不会被批改,因而数据的读取只依赖于数据所在节点过程的存储而不依赖于数据所在复制组的存活,只有能找到数据的地位(上一步形容了其高可用),就能读取到数据;
  3. 写入自身不是三正本强统一,然而 Allotter 在做写入流量调度时会优先选择主从复制 delay 少的复制组,保障了绝大部分数据实际上都是有三正本的;
  4. 过程疾速启动,启动后能在数秒开始提供读服务;
  5. 每个复制组数据较少,磁盘故障实践上在 20 分钟以内能实现修复。

综上所述,数据不可拜访的概率根本和数据失落的概率一样低。

对象存储元数据管理系统外围是一个全局有序的 KV 零碎,和数据存储相比,它会有以下几点不同:

  1. 数据会被笼罩,用户能够对一个 Object 做笼罩上传,该操作会批改元数据存储中该 Key 对应的 Value;
  2. 数据量小,通常元数据存储大略只有数据千分之一以下。

对于元数据的的高可用,咱们也采取了跨 AZ 多正本存储、多集群等机制,然而这些机制和数据存储又不完全一致,接下来咱们来具体看看元数据的高可用计划:

上图所示是对象存储元数据存储系统的外围架构,元数据存储系统底层应用了 Tikv 作为最终的存储系统,和数据存储一样,元数据存储在每个区域同样会部署三套逾越三个 AZ 的 Tikv,多个 Tikv 集群之间做蓝绿部署,升高人为操作 / 降级引入 Bug 等因素对整个元数据存储系统等影响。

元数据管理系统采纳了相似 LSM Tree 的架构,在多个集群上通过各种不同角色的组件构建了对立的元数据管理服务,各个组件的关系见下表形容:

组件

形容

Writeable KV

以后正在读写的集群,和 LSM 相似,删除在 Writeable KV 写入删标记

ReadOnly KV

只读集群,写入流量从 Writeable 切换到 Backup 之后,原来的 Writeable 就变成只读

Stable KV

每次写入流量切换会产生一个 ReadOnly 的 KV,ReadOnly 的 KV 过多会影响到读取和 Scan 的性能,在 ReadOnly 的 KV 达到肯定数量后就会合并进 Stable KV

Backup KV

Writeable 集群的写备份,在 Writeable KV 对应的物理集群变成不可写入的时候,写入会切换到 Backup,保障写入高可用

Standby KV

Writeable 集群的读备份,Standby 集群会通过相似 Binlog 的形式重做所有更新操作,保障 Standby KV 和 Writeable KV 的数据基本一致(有短暂延时),在 Writeable 集群不可读的时候会之间读 Standby 集群,Standby 集群的数据会随着集群切换只读而抛弃

和数据高可用的思路相似,元数据高可用也是由两个局部组成:

  1. 存储集群做高可用;
  2. 多集群容灾,防止单集群故障对可用性的影响。

上面咱们别离从读写两个方面来看看具体是怎么做的:

写入高可用由 Tikv 集群外部的高可用和多集群两个方面独特组成:

  1. Tikv 自身逾越 3 个 AZ 做三正本存储,单个故障域(磁盘 / 机器 / 交换机 / 机房) 故障不会影响集群可用性;
  2. Writeable 所在 Tikv 集群故障,写入能够疾速切换到 Backup,且 Backup 和 Writeable 所在物理 Tikv 集群不会是一个集群,确保写入不会中断。
  1. 对于曾经只读的集群(Readonly, Stable),数据曾经不会发生变化,读任意一个正本即可,只有数据存在就能够读到正确的数据,数据的可用性也能够简略的通过减少副原本晋升;
  2. 对于读写集群中的数据,数据会准实时的通过 Binlog 来同步到 Standby:

    a) 数据自身是三正本,保障高可用;

    b) 集群不可用的时候,能够间接读取 Standby 集群数据,做集群级别的容灾。

一般来说,三个 AZ 高可用的存储系统(例如对象存储数据,元数据系统),因为复制组能够逾越三个 AZ,能够简略的容忍单个 AZ 的彻底故障。然而如果呈现了 AZ 之间的网络故障,导致某个 AZ 和其它 AZ 失联,该 AZ 外部还能失常服务,这个时候就会造成一个孤岛。

对于对象存储来说,对象存储下层的很多业务,比如说数据库 / 主机等都是在一个 AZ 外部,也就是说如果某个 AZ 和其它 AZ 失联,该 AZ 外部还是会源源不断的产生数据,咱们须要保障孤岛外部生成的数据能胜利的写入对象存储,上面咱们次要介绍对象存储在这种状况下的解决。

数据孤岛实质上须要解决两个问题:

  1. 孤岛造成的时候,须要把数据写入到孤岛外部;
  2. 孤岛完结后,须要把孤岛内外的数据做合并。

对象存储包含数据存储和元数据存储两个有状态的服务,上面别离介绍一下咱们如何利用多集群来解决数据孤岛的问题:

在数据存储高可用的时候咱们提到过,在每个 AZ 中,咱们会部署一套牢靠的写缓存服务,写缓存的数据最终会 Writeback 到跨 AZ 的存储集群。

因为数据存储能够抉择任何一个集群写入,某个 AZ 造成数据孤岛后,该孤岛外部产生的数据都会写入到本 AZ 的写缓存,保障了数据写入的高可用。这部分数据最终会在 AZ 之间从新连贯后写入到跨 AZ 的集群。

因为对象存储数据存储系统是一个不可批改的零碎,这意味着孤岛内外不可能会对同一个 ID 做操作,孤岛完结后数据合并会变的很简略。

对象存储自身容许笼罩上传,这意味着元数据是一个容许批改的零碎,这也让元数据存储的数据孤岛解决方案变的绝对比较复杂。

和数据存储孤岛不同,元数据造成孤岛后,如果孤岛内外对同一个 Object 做批改,在一段时间内必然会导致孤岛内外看到的数据不统一,这些不统一最终会达成统一,也就是说元数据存储是一个最终统一的零碎。

在对象存储中,咱们通过 多集群 + 多版本 来解决数据孤岛问题。

  1. 对象存储反对批改,然而对象存储理论应用中批改绝对较少;
  2. 一个 Key 在短时间内 (1s) 被并发批改的概率很低,咱们认为在极其异常情况下毫秒级别的并发批改最终乱序是能够承受的;
  3. 极其状况下可能读到老的数据,甚至交替读到新老数据,然而最终会读到确定的一份数据。

上图是一个多版本的元数据管理系统的架构,和最后版本相比,多了一个 IDService 的服务,IDService 自身是一个高可用的 Id 生成器,它会 依据以后机器工夫生成枯燥递增的 ID,一个大抵的 Key 的构造为 timestampMS_自增 ID_IDService 集群 ID。IDService 会多节点部署,保障高可用。

基于 IDService,咱们实现了多版本的元数据管理系统,任何一次对元数据的批改都会通过 IDService 获得惟一的 ID 来作为 Version,并且在 Tikv 中存储下所有 Version 的元数据。举个例子,两次的 PutObjectMeta(Key, meta) + 一次 Delete 最终会在 Tikv 中存储下以下三条记录:

  • Key_version1 -> meta
  • Key_version2 -> meta
  • Key_version3 -> DELETED

因为 IDService 生成的是有序的,该 Meta 的所有版本中版本最大的记录就是 Meta 的最新记录,比方下面的例子,最新记录是删除记录,该 Object 曾经被删除。

下图是一个孤岛故障切换的例子,在该例子中,AZ1 造成了数据孤岛,AZ2 和 AZ3 能失常互联。

在上图能够看到,咱们会在每个 AZ 部署独立的 AZ 外部独立的 Tikv 和 IDService,在 AZ1 造成数据孤岛后,整个 Meta 的服务会分成两个独立的局部:

  1. 全局的 Meta 服务(绿色局部),会应用跨 AZ 的 Tikv 和 IDService,此时服务于 AZ2 和 AZ3;
  2. AZ1 外部 Meta(蓝色局部),会写入到 AZ1 外部的 Tikv。

在 Meta 决裂成两个集群后,所有的写入都能够胜利。

在 AZ1 复原和 AZ2,3 互联后,所有的写入会切换到多 AZ 多集群,AZ1 外部 Tikv 集群的数据须要合并到跨 AZ 集群。

因为 Meta 实现了多版本,多个 IDService 之间也保障能生成惟一的 ID,也就是说 Key_Version 是惟一的,因而 AZ1 外部 Tikv 的数据能够间接合并到跨 AZ 集群即可。

基于以下起因,合并是无效的:

  1. IDService 依据机器工夫生成自增 ID。
  2. 多个 IDService 机器的工夫偏移保障可控。
  3. 极其异常情况下能够承受段时间外患序。

基于以上三点,能够保障间接合并的后果合乎咱们预期。

点击 ” 浏览原文 ”,理解更多京东大规模分布式对象存储服务

正文完
 0