最近工作中架构升级,将原来的 EMR 集群迁移到基于开源的自建集群上,原来使用的一些组件自然也需要改造,其中就包括 s3。在我们的自建集群中,选用的开源 hadoop 中 s3a client(或者 s3a connector,下面简写成 s3a, 意义基本相同)来连接原有的 s3 存储。接下来我就会分享我在用 s3a client 时总结的一些经验
s3 协议的类型#
首先介绍一下 s3,s3 全称应该叫AWS S3
,又称AWS Amazon Simple Storage Service
,是亚马逊公司开发的对象存储服务,广义上讲是一款包含了 Web 服务在内的完整存储产品,狭义上也能特指 AWS 的对象存储。它通过自定的 s3 协议访问存储上的文件,其路径类似:s3://xxx/yyy/zzz
鉴于 AWS 在云市场的占有地位,s3 存储在各个行业也有广泛的应用。然而 s3 协议是 AWS 的私有协议,只有 AWS 产品可用,为了能巩固和扩大 s3 在市场的地位,让用户能任何地方连上 s3 存储,放心地把数据存储到 s3 上,AWS 向 Hadoop 提供了开源版本的 s3 client 来连接 s3,也即是s3n client, 在 s3n 协议上的文件路径类似:s3n://xxx/yyy/zzz
后来 Hadoop 在升级中淘汰了 s3n client, 采用了全新的s3a client,在 s3a 协议下的文件路径类似:s3a://xxx/yyy/zzz
(Hadoop 最初推出的 s3 client,也采用 s3 协议,即文件路径类似:s3://
,但是年代久远,早已废弃,这里就不提了)
总结一下三种文件协议的区别
- s3 协议是 AWS 最初也是私有的协议,广泛用在 AWS 的产品中,例如 EMR,EC2 等等。如果你们公司使用的是 AWS 全家桶,基本都是通过 s3 协议访问 s3
- s3n 和 s3a 是 hadoop 上基于 AWS s3 SDK 开发的开源 s3 client,能够摆脱 AWS 产品的限制,在自建的服务中自由访问 s3 存储,适用于仅使用了 AWS 的 s3 存储服务,需要和多方产品交互的场景
- 在性能上,三者差别不大,考虑到 s3 由 AWS 自己维护,在版本迭代的速度上可能要快于开源的 s3n 和 s3a
- 在兼容性上,作为 s3 协议的派生,原则上 s3n 和 s3a 都是兼容 s3 协议的;而 s3n 和 s3a 之间一般情况也是兼容(即原来采用的 s3n 协议,后来升级到了 s3a),但是 s3n 和 s3a 之间在目录创建上存在一些区别(下一 part 会提及),在一些场景中需要注意
s3a 在目录处理上的不同#
实际上很多人并不认同 s3 为文件系统,因此 s3 是对象存储,和传统的文件系统有较大的区别。在官方的文档也说:
Amazon S3 is not a filesystem, it is an object store.
两者的一个重要区别就是对目录概念的解释。
- 传统的 Unix 风格的文件系统,例如 HDFS,其文件系统的组成是目录文件树,即生成的目录都是 “一直存在” 的,无论目录内是否存在文件。
- s3 “文件系统”,由于其底层是通过对象存储(或者称之为块存储),其目录是 “虚拟” 的,举个例子:如果两个对象有相同的路径前缀:
a/b/file1
和a/b/file2
,那么 s3 会认为这里存在目录a/b/
两者在这方面的差异就会导致在实际使用中可能出现各种坑
在具体场景的区别#
让我们再细分一下场景:
创建目录
- HDFS 等目录文件树会创建一个空目录,可以往里面添加文件和目录,也可以在任意时候(无论目录中有没有文件或其他目录)通过 ls 来发现这个目录
- 实际上,要是 s3 也能做到这一点,那它也可以被视为实现了目录文件树,
可惜它做不到。由于目录是 s3 是通过前缀来识别目录,因此当目录中没有任何文件时,需要通过目录标记(Directory Marker,后面简称 DM)来标记目录。当目录中创建了文件后,就会删除这个 DM;反之,当目录删除成为空目录时,又会添加这个 DM- 在 s3a 中,会在空目录的场景中生成以
path_name+/
为名称的文件来作为 DM,例如执行mkdir(s3a://bucket/a/b)
会创建一个标记对象a/b/
- 在更老的 s3n 中,则是以
path_name_$folder$
方式作为 DM
- 在 s3a 中,会在空目录的场景中生成以
相信从这里就能看出一丝不妙了吧,当新老版本的产品在某些约定上存在分歧时,往往会出问题。简单举个例子,当你用aws s3
命令创建了目录,但是却用 s3a 去连接时,往往会找不到该目录,因为在aws s3
工具中创建的目录没有创建 s3a 的 DM,因此在 s3a 协议中找不到目录。同理,当一个集群从低版本的 hadoop 升级到高版本时,也需要格外留意空目录的存在问题,因为有可能升级的同时将 s3n 升级到了 s3a, 识别的 DM 发生变化,就检查不到原来的空目录。
创建文件
- 在一般目录文件树的文件系统中,只需要按照目录所在路径,创建单一文件即可
- 在 s3 中,创建文件的操作可能会伴随着一系列 DM 的删除操作, s3a 需要在一个请求中包含删除所有父级 DM 的请求
删除目录和文件
- HDFS 目录文件树的删除目录操作和删除文件操作基本相同,其删除的语义也更符合一般逻辑
- 在 s3 中,由于 DM 的存在,当删除文件 / 目录的时候如果父级目录变成了空目录,则需要将 DM 补充添加
存在的问题#
s3 对目录的处理虽是无奈之举,但也确实成为了许多问题的源头
- s3n 和 s3a 上采用的 DM 不同,意味着 s3a 所在的 hadoop 版本无法向下兼容
- 用 s3a 创建或删除文件的时候一般会需要删除或创建一批 DM,这会导致实际的请求量较大。而且,s3 中每个对象的读写视为一次操作,因此可能会带来较大的开销(结合 s3 的读写限制,这是 s3 性能不佳的主要原因,具体内容下节讨论)
- 在使用 list 操作的时候,由于每个请求中列出的对象的数量是父目录的数量,因此目录层级越深,请求越长。
- 在正式版本的 s3 桶中,即使没有对象没删除,逻辑删除的标记也会写进索引里,这对使大型目录的查询变慢
s3a 的性能问题#
在 s3a 的官方文档上,展示了 s3a 和 HDFS 的一些不同,我将它贴了过来:
总的来说,以下几点原因导致了 s3a 的性能问题
- 由于桶分片导致 IOPS 被限制。
- 不同类型的 AWS EC2 虚拟机可能进行网络 IO 进行不同的限制。
- 对象和数据越多,目录重命名和复制操作花费的时间就越长。rename () 的性能更加缓慢。
- 在读取 s3 时使用 seek () 操作会强制新的 HTTP 请求。这可能会增大使读取 Parquet/ORC 文件的开销。
另外,还需要注意的是,由于 AWS s3 在读写上做出了频率限制,按照一般约定,当对 s3 的一个分区每秒超过读 5500 次或写 3500 次时,s3 就会拒绝请求,显示错误 503
可见,若是使用 s3 进行大量读写还是存在着不小的挑战,在大数据领域中使用 s3 还是更多看重的是它的计费成本和性价比。如果你的场景追求性能,那我的建议是:快跑!
解决方法
当然,实际场景下很多选择往往不是我们能左右的,假如我们必须要在一些 OLAP 或者其他大规模数据处理场景中使用 s3,这里依然会有一些优化建议。
- 使用 s3a committer。hadoop 内置了多种 s3a commiter 来优化 s3 文件的提交,其核心思路就是利用 s3 的 multipart Upload 机制来加速文件上传,同时不同的 committer 也有不同的优化思路,具体可以参考这篇文档
- 一些参数的调优,例如合理配置线程数和连接数,加大块读取的大小等等
更多优化的场景和细节,可以查看官方文档
总结#
总的来说,s3 本身在某些场景还是很有优势的,作为 AWS 生态的重要一环,人们更看重它的安全可靠,物美价廉,以及与 AWS 其他云产品之间联动带来的叠加优势。但我们也要承认在不适当的场景中使用 s3 依然会不小的副作用。在实际生产中,也可能存在各种妥协因素,无法做不到釜底抽薪,但我们至少可以了解产品特性,尽最大可能优化性能。
因此我就简单写了一些 s3a 常见的可能会踩坑的点以及如何采取措施,其实大多来自于官方文档的整理,希望对你有所帮助~