关于S3A的一些踩坑和思考

最近工作中架构升级,将原来的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/file1a/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

相信从这里就能看出一丝不妙了吧,当新老版本的产品在某些约定上存在分歧时,往往会出问题。简单举个例子,当你用aws s3命令创建了目录,但是却用s3a 去连接时,往往会找不到该目录,因为在aws s3工具中创建的目录没有创建s3a的DM,因此在s3a协议中找不到目录。同理,当一个集群从低版本的hadoop升级到高版本时,也需要格外留意空目录的存在问题,因为有可能升级的同时将s3n 升级到了s3a, 识别的DM发生变化,就检查不到原来的空目录。

创建文件

  • 在一般目录文件树的文件系统中,只需要按照目录所在路径,创建单一文件即可
  • 在s3中,创建文件的操作可能会伴随着一系列DM的删除操作, s3a 需要在一个请求中包含删除所有父级DM的请求

删除目录和文件

  • HDFS目录文件树的删除目录操作和删除文件操作基本相同,其删除的语义也更符合一般逻辑
  • 在s3中,由于DM的存在,当删除文件/目录的时候如果父级目录变成了空目录,则需要将DM补充添加

存在的问题

s3 对目录的处理虽是无奈之举,但也确实成为了许多问题的源头

  1. s3n 和s3a 上采用的DM不同,意味着s3a 所在的hadoop版本无法向下兼容
  2. 用s3a 创建或删除文件的时候一般会需要删除或创建一批DM,这会导致实际的请求量较大。而且,s3中每个对象的读写视为一次操作,因此可能会带来较大的开销(结合s3的读写限制,这是s3 性能不佳的主要原因,具体内容下节讨论)
  3. 在使用list操作的时候,由于每个请求中列出的对象的数量是父目录的数量,因此目录层级越深,请求越长。
  4. 在正式版本的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,这里依然会有一些优化建议。

  1. 使用 s3a committer。hadoop 内置了多种s3a commiter来优化s3 文件的提交,其核心思路就是利用s3 的multipart Upload机制来加速文件上传,同时不同的committer也有不同的优化思路,具体可以参考这篇文档
  2. 一些参数的调优, 例如合理配置线程数和连接数,加大块读取的大小等等
    更多优化的场景和细节,可以查看官方文档

总结

总的来说,s3本身在某些场景还是很有优势的,作为AWS 生态的重要一环,人们更看重它的安全可靠,物美价廉,以及与AWS 其他云产品之间联动带来的叠加优势。但我们也要承认在不适当的场景中使用s3依然会不小的副作用。在实际生产中,也可能存在各种妥协因素,无法做不到釜底抽薪, 但我们至少可以了解产品特性,尽最大可能优化性能。
因此我就简单写了一些s3a 常见的可能会踩坑的点以及如何采取措施,其实大多来自于官方文档的整理,希望对你有所帮助~

参考文档

  1. Hadoop-AWS module: Integration with Amazon Web Services
  2. Experimental: Controlling the S3A Directory Marker Behavior
  3. Maximizing Performance when working with the S3A Connector
  4. Committing work to S3 with the “S3A Committers”
分享到: