00 背景

CubeFS自3.0.0版本开始提供低成本的纠删码存储(BlobStore),BlobStore是一个高可靠、高可用、低成本、支持EB规模的Blob存储。通过前文(CubeFS存储技术揭密(1) — 纠删码引擎系统设计)大家对纠删码存储子系统已经有了初步认识,本文将带领大家探讨纠删码单机存储引擎的设计实现。

01 整体架构

纠删码存储子系统(BlobStore)是由5个模块组成,各自职责分工如下:

  • Access:请求接入层,提供blob的读、写、删、数据编解码等操作;

  • ClusterManager:元数据管理模层, 负责存储资源(如磁盘、节点、卷单元)的元数据管理;

  • Proxy:ClusterManager与异步消息代理模块,提供数据写入空间的分配、删除与修补消息转发等;

  • Scheduler:异步任务调度中心,负责磁盘修复、磁盘下线、数据均衡、数据巡检、数据修补以及数据删除等任务的生成和调度。

  • BlobNode:单机存储引擎,接收接入层模块的数据,持久化到磁盘上,负责用户数据的底层格式组织与落盘操作,  执行卷修补、迁移和回收任务;

Blobstore提供的是在线EC存储服务,采用了存算分离的设计,其中Access模块负责EC编/解码计算,单机存储引擎BlobNode负责数据读写;Access和BlobNode可部署在不同规格服务器上,独立扩缩容。本文将主要介绍单机存储引擎BlobNode的高可靠和高性能相关设计。

02 基本概念

收到上传数据流后,Access根据size大小选择是否切片及对应的EC模式。假设切片大小为 4MB,则每次从数据流中读取出 4MB 大小数据,将 4MB 数据进行 EC 计算后分散到存储卷 volume 上的不同 chunk 上,这里每个 4MB 大小的块为一个 blob。如下图,先描述 volume,chunk,blob,shard 之间的关系。

Volume:卷,逻辑存储单元,有固定容量上限,VolumeID为其唯一标识,由ClusterManager统一创建、分配与销毁,不同的卷支持不同EC模式。

Chunk:Volume的基本组成单元,是存储数据的容器,对应磁盘上的一段实际物理存储空间,chunkID为其唯一标识,由BlobNode负责创建、销毁。多个chunk组织成一个volume,由ClusterManager维护其绑定关系。

Blob:Blob是上传数据流被切分后的数据片,BlobID为其唯一标识,由ClusterManager统一分配管理,保证全局唯一。

Shard:是EC条带数据的组成单元,Blob写入卷时,对应切分成多个块分别写入到卷中的各个chunk中,每一个小数据块称为shard。

如上图所示,一个 blob 被切分为 3个数据块(shard1~shard3),通过EC编码计算出3个校验块(shard4~shard6),将这6个块分别写到一个volume中, 如上图中卷vid(1),该卷是位于不同磁盘上6个chunk构成,每个chunk作为该卷的一个组成单元,也称为vuid。

03 数据管理

存储引擎的数据管理主要分为两类:元数据管理与数据管理,下图表示数据分布的物理视图,元数据与数据存储在不同的目录下(支持将元数据与数据存储在不同的磁盘,比如利用SSD盘存储元数据以提高其读写性能),支持KV存储分离。

1、系统部分

.sys/ : 存放一份只读的格式化信息,磁盘格式化之后就不再改变;

.trash/ : 垃圾站,元数据不存在,数据存在的场景。清理垃圾会将 data file 放入垃圾站,做个兜底的保护;

2、元数据部分

meta/ : rocksdb 格式的数据库;

3、数据部分

data/ : 无数个chunk datafile 格式的大文件。

元数据存储

元数据存储于rocksdb中,记录数不多:假设单盘容量16TB ,shard平均大小为128K,单盘约1.25亿条原数据;单个shard 元数据在rocksdb中kv大小按64 字节算,一共7G 左右。

  • 元数据可全量缓存到内存,读性能好

  • LSM-Tree将多条记录合并落盘的特性,能有效减少IO次数,另外因元数据量小,compact次数少,写放大相对可控;

数据存储​​​​​​​

数据部分存储在Chunk中,如下图所示,Chunk 是由一个header 和多个shard 构成:

  • header:包含了version、create_time、crc等元信息;

  • shard: 包含了 header、footer 和block, 用户数据会存在大小为64kB 的一个个block 内。Block 由数据和 crc 组成,footer 里面保存了魔数信息,用于界定 shard 的边界,同样的 footer 也有自己的 crc。

数据和元数据都有 crc 校验,并且数据的 crc 不仅存在于细粒度的 block 中,对 header 和 footer 都有校验,这样的多重校验极大提高了数据可靠性。

  • 空间顺序分配,分配完之后多个 shard 可并行写入,效果还是顺序 IO;

  • append 写入,无覆盖场景;

  • 每个shard的header会保存一份元数据做双重保证,即使rocksdb数据丢失也能恢复元数据;

  • 读到损坏的数据块会发起异步修复,结合定时的巡检任务,可及时修复EC条带中缺损块,保证数据可靠性。

这里元数据和数据部分的存储设计采用不同的方式,主要是请求大小不同对设计影响很大。元数据的一次落盘可以携带大量的元数据,但是数据的一个 body 会有多次落盘。

04 核心流程设计

写关键设计

1、我们知道HDD的随机IO速度很慢,但是顺序IO速度还可以。BlobNode在分配的时候,就采用顺序分配空间,做到顺序的IO;

2、组织数据落盘的时候,只采用Append写,这样不存在数据覆盖的场景,避免了数据覆盖的风险;

3、为了提高写入效率,BlobNode支持多个shard的并发写入,可提高系统的吞吐量,并且降低单次写时延,顺便提一下,Access在上传Blob的shards时,采用了Quorum写模式,可有效避免节点或网络故障时导致的尾延时;

4、此外,IO设计利用rocksdb缓存以及批量落盘特性提高读写效率,数据部分利用文件系统IO的page cache,最后调用sync批量落盘;

5、写入偏移按照 4K 对齐,在删除场景利用文件系统稀疏语义,可以让 punch hole 快速释放空间

异常场景:

1、写入数据失败,这种可能有垃圾,通过 chunk compact 可回收,也可以通过巡检ChunkMeta的 bidlist 来回收空间;

2、写入元数据失败,由于是先写数据后写元数据,这里失败会产生垃圾,通过巡检 ChunkMeta 或者 chunk compact 来回收空间;

3、如果写入元数据成功,但是返回上层失败,则需要上层处理,自身无法感知;

4、如果写入的时候进程重启,重启之后,写入偏移直接定位到文件最后(按照 4k 对齐),这样就不用持久化记录偏移位置,只需要在内存里计算;

5、如果写数据遇到坏盘:  正常返回错误码 EIO,disk 内存结构设置坏盘状态,并且起一个后台 goroutine notify 通知 ClusterManager 更新 disk 状态(持续通知直到成功)。

读关键设计

读流程总体分为两点,读IO是很容易随机的,提升读性能的话还是要依托缓存机制:

1、 读元数据,元数据是存储在rocksdb的,可以配置rocksdb 的缓存LRUcache

2、读数据阶段,数据依赖于 page cache 缓存

删除关键设计

上层调用的删除流程分两阶段删除:

1、/shard/markdelete 上层调用标记删除 (元数据标删, 声明不再使用该 bid 数据, 但是不能直接回收),状态设计为 delete;

2、/shard/delete 上层声明一个 bid 可回收空间; normal -> delete -> destroy

删除流程通过两阶段删除来区分修补中间状态和删除中间状态,比如RS(6,6)的模式,不需要写成功全部的数据块,比如写8块成功,那剩下4块会走修补流程,如何区分剩下的4块是需要修补的状态,还是说这8块是未来得及删除的数据块呢,就需要两阶段删除先标记删除,这样可以确保数据不被误删除,保证数据的安全。

删除数据之后的空间可以实时释放,通过调用系统 punch hole 的接口,做到非常高效的释放,并且空间达到利用率 99.99%+,可以做到几乎没有空间碎片。

异常考虑:

1、如果中间过程产生垃圾,清理可以通过遍历 ChunkMeta 上面所有的 bid 数据, 或者 compact 清理;

2、调用删除无确认回复响应的时候, 需要上层重试。

05 后台任务

BlobNode 服务启动后会有一些后台任务、异步流程,包括清理垃圾 chunk、继续未完成的 compact 任务、与 ClusterManager保持心跳等

数据巡检

数据巡检主要是为了及时发现并修复损坏的数据块,当 m+n 个块中有部分损坏,又没有读请求的时候,如果不是磁盘损坏,是很难发现坏块的情况。长此以往,当同一个条带的坏块越来越多,数据的耐久度相对就会降低。为了避免坏块导致数据耐久度降低, BlobNode 启动了异步巡检流程,可以进行 chunk 粒度的数据巡检。

1、异步巡检流程会首先拿到进程管理的所有磁盘句柄,列举每个磁盘拿到 chunk 句柄,如果元数据有  vuid ,但是没有找到对于 chunk 会报错,继续巡检下一个  chunk 。

2、从元数据获取 chunk 的  shard 列表信息,根据 bid 信息生成 reader 读数据。

3、如果数据正常,继续下一个。如果不正常,报错并上报指标。

空间回收

compact 任务的来源有两个地方:

1、chunk 文件触发 compact 条件;

2、之前未完成的 compact 流程;

针对第一种情况直接走 compact 流程;第二种情况需要先清理上一次 compact 过程中生成的垃圾数据,包括元数据和数据,元数据的清理方式是从 ChunkMeta 中删除所有以垃圾 chunkid 为前缀的元数据,数据的清理时直接删除 chunkdata 文件即可。

垃圾清理

垃圾chunk的来源有两个地方:通过与ClusterManager的通信得知本机某个chunk是垃圾chunk,判断依据是chunk对应的vuid的epoch小于ClusterManager同步过来的同一vuid的epoch,这里epoch值相当于版本号,如果修盘之后卷的部分chunk被修复到其他磁盘,对应vuid的epoch值会递增;另一个是某一chunk完成compact后源chunk的数据未清理完成。

垃圾 chunk 的清理流程:

1、从本磁盘的 ChunkMeta 中删除所有以此 chunkid 为前缀的元数据

2、针对从上述第一种类型的垃圾 chunk,需要从 SuperBlock 中清理 vuid 到 chunkid 的映射关系;第二种类型的垃圾 chunk,在 compact 流程中此映射关系已经清理

3、删除此 chunk 对应的数据文件

坏盘修复

磁盘IO的时候,如果遇到EIO的明确错误码,会设置自身状态,并上报ClusterManager,等到磁盘状态修复开启确认,就可以释放磁盘相关句柄。volume感知到有坏盘,则会降低卷的健康度,新数据写入会优先挑选健康度高的卷。对于已经分配出去的卷在续租时会失败,分配的时候不会优先分配出去。

坏盘之后Scheduler会调度后台任务触发数据重建,进入reparing修复状态,修复进度状态底层可感知,修复完之后,则彻底摘除句柄,状态改为repaired,换上新盘之后,走在线注册的接口,注册之后自动发现。BlobNode 提供了一个接口用于热加载磁盘,在机房换盘之后,运维人员格式化,check 完整之后,触发 BlobNode 接口即可自动加载磁盘,无需进程重启。

QoS

针对 BlobNode 各种层次的 IO 流量细分,可视化出来。为后续流控,运维,监控提供手段。

BlobNode 流量类型根据触发者的不同,可以区分为以下几种触发方式:

  • 用户触发的读写删请求

  • 后台组件Scheduler调度

  • BlobNode 内部的流量,比如巡检等等

并且几乎每种触发方式都会涉及数据和元数据的读写,所以大分类有元数据和数据两类。在可视化的时候需要分别展示。封装成可视化的 iostat 库,任何模块可用,无性能损耗,也可以随意扩展,含义自己解析,计数和可视化分离,下图是通过iostat库写的可视化工具的展示效果。

不同的流量通过竞争的方式确定下发到硬件的IO顺序,因此无法确保某种流量IO服务质量,比如内部数据迁移流量可能占用过多的带宽影响业务流量读写,导致存储对外提供的服务质量下降,由于资源竞争结果的不确定性无法保障存储对外能提供稳定的集群环境。

在保障服务带宽与IOPS的情况下,合理分配存储资源,有效缓解或控制应用服务对资源的抢占,实现流量监控、资源合理分配、重要服务质量保证以及内部流量规避的效果,我们考虑实现多层级的流量控制,首先对流量类型的优先级进行分类。

我们大致分为5层:

  • level0是用户IO

  • level1是shard修补的流量

  • level2是磁盘修复、删除、回收流量

  • level3是后台迁移和下线的流量

  • level4是巡检和垃圾数据删除的流量

对于存储来说业务流量的稳定性是我们需要优先保障的,实际的流控设定中,我们可以对业务流量的带宽跟IOPS不受限制,而内部流量如迁移、修复则需要限定其带宽或者IOPS。在资源充足的情况下,内部流量是可以限定一个稳定值,但是当资源紧张,比如业务流量突增或者持续性的高流量水位,这个时候需要进一步限制内部流量,极端情况下可以暂停。当然,如果内部流量都停了还是不能满足正常业务流量的读写需求,这个时候就需要考虑扩容了。

在实现流控功能时,我们采用令牌桶算法,令牌桶算法最初来源于计算机网络。在网络传输数据时,为了防止网络拥塞,需限制流出网络的流量,使流量以比较均匀的速度向外发送。

如上图所示,每个qos 处理器包含了iops controller和bandwidth controller,多个 chunk 是磁盘上承载数据的文件,同一磁盘上的所有 chunk 共用 qos 处理器。然后在读写 shard 的位置用带了处理器的 reader 和 writer 来操作数据。带宽和 iops 的控制器依赖于一个后台协程,定期更新投放的令牌个数,令牌个数的调整来源于当前磁盘负载的高低。

06 小结

至此,这篇文章已经介绍完存储资源池模块的关键设计点,主要概况为以下两点:

高可靠:

1、通过细粒度crc保障数据完整性,并结合数据巡检发现静默错误或数据损坏

2、数据采用append 写,不存在覆盖写风险

3、两阶段删除,区分不同数据块中间状态,避免误删风险

高性能:

1、存储引擎采用kv分离方案

2、顺序分配空间避免随机IO

3、数据追加写策略以及分级流控等保障读写性能最优

4、通过punch hole策略高效回收资源空间提高利用率

5、元数据与数据分离,元数据缓存、批量落盘,提高读写性能

在未来的计划中,还考虑结合后台模块,对限流做进一步的优化,保证整个底层存储基座更加稳定和智能。

CubeFS简介

CubeFS于2019年开源并在SIGMOD发表工业界论文,目前是云原生计算基金会(CNCF)托管的孵化阶段开源项目。作为新一代云原生分布式存储平台,兼容S3、POSIX、HDFS等协议,支持多副本和纠删码引擎,提供多租户,多AZ部署、跨区域复制等特性;适用于大数据、AI、容器平台、数据库及中间件存算分离,数据共享、数据保护等广泛场景。

GitHub:https://github.com/cubefs

微信群:搜索并添加微信号blkleaf或mervinkid联系,注明您的来意

公众号:微信搜索“CubeFS官微”

CubeFS存储技术揭秘(2)— 纠删码单机存储引擎相关推荐

  1. CubeFS存储技术揭密(1) — 纠删码引擎系统设计

    00  背景 CubeFS 3.0.0以前版本只提供多副本存储,随着数据规模持续增长,业务面临着更大的成本挑战,用户对更低成本的纠删码(ErasureCode, 下文简称EC)的需求愈加强烈:Cube ...

  2. K8s使用Ceph纠删码池做持久化卷

    K8s使用Ceph纠删码池做持久化卷 Ceph侧准备 Ceph纠删码相关 创建纠删码规则 创建纠删码池 创建复制集池 创建用户并授权 K8s消费ec池 验证 (可选)缓存方式 Kubernetes版本 ...

  3. 全网最新最全的 HDFS 文件纠删码技术分析

    前言 本文隶属于专栏<1000个问题搞定大数据技术体系>,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和参考文献请见1000个问题搞定大数据技 ...

  4. RS(纠删码)技术浅析及Python实现

    前言 在Ceph和RAID存储领域,RS纠删码扮演着重要的角色,纠删码是经典的时间换空间的案例,通过更多的CPU计算,降低低频存储数据的存储空间占用. 纠删码原理 纠删码基于范德蒙德矩阵实现,核心公式 ...

  5. RS 纠删码为什么可以提高分布式存储可靠性?| 原力计划

    作者 | .NY&XX 来源 | CSDN博客专家,责编 | 夕颜 封图 | CSDN 下载自视觉中国 出品 | CSDN(ID:CSDNnews) 前言 Erasure Code(EC),即 ...

  6. 什么是纠删码(与纠错码的区别)|纠删码与副本对比|LDPC码

    什么是纠删码(与纠错码的区别) 存储领域来看,数据冗余机制其实这几十年来没有太多进展,RAID.副本一直是当仁不让的最终选择.而近几年,尤其是规模较大的应用场景下,纠删码越来越多的出现在选择的视野范围 ...

  7. Hadoop 3.0 Erasure Coding 纠删码功能预分析

    前言 HDFS也可以支持Erasure Coding功能了,将会在Hadoop 3.0中发布,可以凭图为证: 在HDFS-7285中,实现了这个新功能.鉴于此功能还远没有到发布的阶段,可能后面此块相关 ...

  8. Beehive:用于修复分布式存储系统中多个故障的纠删码

    Beehive:用于修复分布式存储系统中多个故障的纠删码 摘要:分布式存储系统越来越多地部署纠删码(例如 Reed-Solomon 码)以实现容错.尽管 Reed-Solomon 代码需要的存储空间比 ...

  9. 纠删码存储系统中的投机性部分写技术

    本文已被USENIX'17年度技术大会录用,此处为中文简译版. 阅读英文论文完整版请点击:Speculative Partial Writes in Erasure-Coded Systems 多副本 ...

最新文章

  1. Android音量控制曲线
  2. python读取大文件-python如何读取大文件以及分析时的性能优化小技巧
  3. 【经典C#.NET入门教程】管理软件开发必备知识免费视频教程下载
  4. 我所碰到的智能手机自动重启的情况
  5. shortcut icon 修改浏览器标签网站图标
  6. 2021-04-10 【数据库导数】数字类型的列如果位数过长,变为科学计数法问题
  7. C++/OpenCV:将数据保存到xml、yaml / 从xml、yaml读取数据
  8. 计算机组成中CM,基于TDN-CM++计算机组成原理课程设计.doc
  9. WEB安全基础 - - -漏洞扫描器
  10. ltspice滑动变阻器在哪_NB物理创新课堂|变阻器
  11. 人脸识别测试点整理思维导图方式
  12. 转载: Fisher精确检验概述
  13. MP4视频播放问题(有声音无图像)分析与解决——FFmpeg视频处理教程
  14. matlab曼德勃罗集,YaK与您一起欣赏BBC纪录片:''''神秘的混沌理论''''
  15. rinetd 端口转发
  16. Laravel项目+Google验证器
  17. java八音盒_基于汇编语言的音乐盒设计与实现
  18. 微信云托管常见问题FAQ(一)
  19. 手机短号(C语言————AC)
  20. linux内核Kmalloc - GFP_ATOMIC - GFP_KERNEL - GFP_USER

热门文章

  1. Python下对setup.py模块的安装方法
  2. 什么是RUN CARD?
  3. 用EasyGBD做国标GB28181协议级联
  4. JDK1.5后新特性
  5. 链表中倒数第k个结点——《剑指offer》
  6. 互融云借条APP系统开发 六大系统优势全面保障
  7. 一图看懂FC存储网络架构
  8. 来自灵魂深处的拷问:人为什么要活着?
  9. 2021新年算法小专题—2.股票买卖利润(Java)
  10. PHP数组排序函数 ksort() 、krsort