共计 5051 个字符,预计需要花费 13 分钟才能阅读完成。
在信息爆炸的大数据时代,如何以更低成本来解决海量数据的存储问题,已成为企业大数据业务中的重要一环。UCloud 自研的新一代对象存储服务 US3,在过来一段时间,针对大数据业务场景推出了计算存储拆散和大数据备份解决方案。
这背地的次要起因包含:
1、因为网络技术的高速倒退,使得网络传输性能不再是大数据场景下高吞吐业务需要的瓶颈;
2、Hadoop 技术栈中的 HDFS 存储解决方案运维简单且老本昂扬;
3、云平台基于海量存储资源池构建的对象存储服务 US3 具备按需应用、操作简略、牢靠稳固、价格便宜的劣势,是替换 HDFS 的最佳存储计划抉择。因而,为了让用户可能更加不便的在 Hadoop 场景下,应用 US3 实现计算存储拆散和大数据备份解决方案,US3 自研了 US3Hadoop 适配器、US3Vmds、US3Distcp 三个组件。
本文次要介绍 US3Hadoop 适配器在研发设计过程中的一些思路和问题解决。
总体设计思路
Hadoop 生态里对存储的操作基本上都是通过一个通用的文件系统基类 FileSystem 来进行的。US3Hadoop 适配器 (简称: 适配器) 是通过 US3FileSystem 实现该基类来操作 US3。相似于 HDFS 实现的 DistributedFileSystem 和基于 AWS S3 协定实现的 S3AFileSystem。适配器间接把 IO 和索引都申请发给 US3,架构如下图所示:
这里的索引操作次要是不波及读写数据的 API,如: HEADFile, ListObjects, Rename, DeleteFile, Copy(用于批改 metadata);IO 操作的 API,如 GetFile,PutFile(小于 4M 文件)曾经分片上传相干的 4 个 API: InitiateMultipartUpload,UploadPart,FinishMultipartUpload,AbortMultipartUpload。US3 有了这些 API 后,怎么跟 FileSystem 的成员办法能对应起来,能够看下 FileSystem 须要重写哪些办法。结合实际需要和参考 DistributedFileSystem、S3AFileSystem 的实现,咱们确定了须要重写的次要办法:initialize、create、rename、getFileStatus、open、listStatus、mkdirs、setOwner、setPermission、setReplication、setWorkingDirectory、getWorkingDirectory、getSchem、getUri、getDefaultBlockSize、delete。同时对一些难以模仿的办法,重写为异样不反对,如 Append 成员办法。
其实从下面 FileSystem 的成员办法阐明来看,其语义和单机文件系统引擎的接口语义相似,基本上也是以目录树结构来组织治理文件系统。US3 提供的 ListObjects API 刚好也提供了目录树拉取的一种形式,当 Hadoop 调用 listStatus 办法时,就能够通过 ListObjects 循环拉取到当前目录 (前缀) 下所有子成员从而返回对应的后果。
设置文件所属用户 / 组,操作权限等相干操作则利用了 US3 的元数据性能,把这些信息都映射到文件的 KV 元数据对上。写入文件流则会优先缓存在内存中最多 4MB 数据,再依据后续的操作来决定采纳 PutFile 还是分片上传的 API 来实现。
读取文件流则通过 GetFile 返回流实例来读取期待的数据。尽管这些办法实现看上去很直白,然而潜在着很多值得优化的中央。
getFileStatus 的时空博弈
通过剖析 FileSystem 的调用状况,能够晓得索引操作在大数据场景中占比达 70% 以上,而 getFileStatus 在索引操作重占比最高,所以有必要对其进行优化。那优化点在哪里呢?
首先因为 US3 中的“目录”(对象存储是 KV 存储,所谓目录只是模仿而已)是以‘/’结尾的 Key,而 FileSystem 的对文件的操作是通过 Path 构造进行,该构造的门路都不会以‘/’结尾,所以通过 Path 拿到的 Key 去 US3 中进行 HeadFile 时,有可能因为该 Key 在 US3 中是目录,HeadFile 就会返回 404, 必须通过第二次用“Key/”去 US3 中 Head 能力确认。如果这个 Key 目录还是不存在,就会导致 getFileStatus 时延大大增加了。
因而 US3 适配在创立目录时做了以下两件事:1.向 US3 写入 mime-type 为“file/path”, 文件名为“Key”的空文件;2.向 US3 写入 mime-type 为“application/x-director”, 文件名为“Key/”的空文件;
而一般文件 mime-type 为“application/octet-stream”。这样在 getFileStatus 中通过一次 HeadFile API 就判断以后 Key 到底是文件还是目录,同时当该目录下为空时,也能在 US3 控制台展现出该目录。而且因为 Hadoop 场景写入的次要是大文件,减少一次空文件索引的写入耗时在 ms 级别,时延根本可疏忽。
此外,getFileStatus 在 Hadoop 的应用中具备显著的“时空局部性”特色,在具体的 FileSystem 实例中最近被 getFileStatus 操作的 Key,在短时间会被屡次操作。利用这个特点,US3FileSystem 在实现过程中,getFileStatus 失去对应的后果在 FileStatus 返回之前,会把有 3s 生命周期的 FileStatus 插入到 Cache 中。那后续 3 秒内对该 Key 的操作就会复用 Cache 中该 Key 的 FileStatus 信息,当然 delete 操作会在 US3 中删除完 Key 后,间接把 Cache 中的无效 FileStatus 标记为有 3s 生命周期的 404 Cache,或者直接插入一个有 3s 生命周期的 404 Cache。如果是 rename,会复用源的 Cache 来结构目标 Key 的 Cache,并删除源,这样就能缩小大量跟 US3 的交互。Cache 命中 (us 级别) 会缩小 getFileStatus 上百倍的时延。
当然这会引入肯定的一致性问题,但仅限于在多个 Job 并发时至多有一个存在“写”的状况,如 delete 和 rename 的状况下,如果仅仅只有读,那么无影响。不过大数据场景根本属于后者。
ListObjects 一致性问题
US3 的 ListObjects 接口跟其余对象存储计划相似,目前都只能做到最终一致性(不过 US3 后续将推出强一致性保障的 ListObjects 接口),因而其余对象存储实现的适配器也都会存在写入一个文件,而后立刻调用 listStatus 时会偶然呈现这个文件不存在的状况。其余对象存储计划有时会通过引入一个中间件服务(个别是数据库),当写入一个文件会向这个中间件写入这个文件索引,当 listStatus 时会跟中间件的索引信息进行合并,这样的确缓解了这种状况,进一步提高了一致性。
但还不够,比方写入对象存储胜利,但写入中间件时程序奔溃了,这样就导致不统一的问题,又回到了最终一致性的问题。
US3Hadoop 适配器的实现绝对更加简略无效,不须要借助额定的服务,能提供索引操作级别的 Read-Your-Writes 一致性,而该一致性级别在 Hadoop 大部分场景根本等同于强一致性。US3Hadoop 适配器不像 S3AFileSystem 的实现,在 create 或者 rename、delete 后立马返回,而是在外部调用 ListObjects 接口做了一次“对账”,直到“对账”后果合乎预期则返回。
当然这里也是有优化空间的,比方 delete 一个目录时,对应会把这个目录下所有文件先拉进去,而后顺次调用 DeleteFile API 去删除,如果每次 DeleteFile API 删除都“对账”一次,那么整个时延会翻倍。US3Hadoop 适配器的做法是只对最初一次索引操作进行“对账”,这是因为索引的 oplog 是按时序同步到列表服务中,如果最初一条索引“对账”胜利,那么后面的 oplog 肯定在列表服务中写入胜利。
Rename 的深度定制
后面提到的 rename 也是 US3 的一个重要优化点,其余对象存储计划的实现个别通过 Copy 的接口会先把文件复制一遍,而后再删除源文件。能够看出如果 rename 的文件很大,那么 rename 的整个过程势必导致时延很高。
US3 依据该场景的需要,专门开发了 Rename 的 API 接口,因而 US3Hadoop 适配器实现 rename 的语义绝对比拟轻量,而且时延放弃在 ms 级别。
保障 read 高效稳固
读是大数据场景的高频操作,所以 US3Hadoop 适配器的读取流实现,不是对 http 响应的 body 简略封装,而是思考了多方面的优化。例如,对读取流的优化,通过退出预读 Buffer,缩小网络 IO 零碎调用频率,升高 read 操作的期待时延,特地是大批量程序读的 IO 晋升成果显著。
另外,FileSystem 的读取流具备 seek 接口,也就是须要反对随机读,这里又分两种场景:
1、seek 到已读流地位的前置地位,那么作为 Underlay Stream 的 Http 响应的 body 流就要作废敞开掉,须要从新发动一个从 seek 的地位开始分片下载的 GetFile API,取得其 Http 响应的 body 流来作为新的 Underlay Stream。然而理论测试过程中发现,很多 seek 操作过后不肯定会进行 read 操作,有可能间接敞开,或者 seek 回到已读取流地位的后置地位,所以在 seek 产生时,US3Hadoop 适配器的实现是只做 seek 地位标记,在 read 的时候依据理论状况对 Underlay Stream 做提早敞开关上解决。此外如果 seek 的地位还在 Buffer 中,也不会从新关上 Underlay Stream,而是通过批改 Buffer 的生产偏移。
2、随机读的另一种场景就是,seek 到已读流地位的后置地位。这里同样跟后面一样采纳提早流关上,然而在确定要做实在的 seek 操作时,不肯定会通过敞开老的 Underlay Stream,从新在指标地位关上新的 Underlay Stream 来实现。因为以后已读的地位跟 seek 的后置地位可能间隔很近,假如只有 100KB 间隔,说不定这段距离齐全在预读 Buffer 的范畴中,这时也能够通过批改 Buffer 的生产偏移来实现。
事实上 US3Hadoop 适配器的确也是这么做的,不过目前的规定是 seek 的后置地位到以后已读流地位的间隔小于等于预读 Buffer 残余空间加上 16K 的和,则间接通过批改预读 Buffer 的生产偏移和生产 Underlay Stream 中的数据来定位到 seek 的后置地位上。之所以还加了 16K 是思考到 TCP 接管缓存中的数据。当然后续确定从一个 ready 的 Underlay Stream 中生产 N 字节数据的工夫老本大抵等于从新发动一个 GetFile API 并在筹备传输该 Http 响应 body 之前的工夫老本,也会思考把这 N 字节的因素计入偏移计算过程中。
最初流的优化还要思考到 Underlay Stream 异样的状况,比方 HBase 场景长时间持有关上的流,却因为其余操作导致长时间没有操作该流,那么 US3 可能会被动敞开开释 Underlay Stream 对应的 TCP 连贯,后续对在 Underlay Stream 上的操作就会报 TCP RST 的异样。为了提供可用性,US3Hadoop 适配器的实现是在曾经读取地位点上进行 Underlay Stream 的从新关上。
写在最初
US3Hadoop 适配器的实现在借鉴开源计划下,进一步优化了相干外围问题点,晋升了 Hadoop 拜访 US3 的可靠性与稳定性,并在多个客户案例中施展着买通 Hadoop 与 US3 的重要桥梁作用,帮忙用户晋升大数据场景下的存储读写效率。
但 US3Haoop 适配器还存在很多可晋升的空间,相比于 HDFS,索引、IO 的时延还有差距,原子性保障上也绝对比拟弱,这些也是咱们接下来要思考解决的问题。目前推出的 US3Vmds 解决了索引时延的大部分问题,使得通过 US3Hadoop 适配器操作 US3 的性能失去大幅晋升,并在局部场景靠近原生 HDFS 的性能。具体数据能够参考官网文档(https://docs.ucloud.cn/ufile/…
将来,US3 产品会不断改进优化大数据场景下的存储解决方案,在升高大数据存储老本的同时,进一步晋升用户在大数据场景下的 US3 应用体验。