原文链接,介绍大数据中常见的文件格式。如发现错误,请留言或者发送邮件到zwzm85@gmail.com。
Apache Spark支持多种不同的数据格式,比如无处不在的CSV格式和web友好的JSON格式。主要用于大数据分析用途的常见格式是Apache Parquet和Apache Avro。
在本文中,我们会讨论在Apache Spark中使用的这四种格式的特性-- CSV,JSON,Parquet和Avro。
CSV
CSV文件(逗号分割不同列的值)常被使用普通文本格式的系统用作交换它们的表格数据。CSV是基于行的文件格式,这意味着文件中的每行数据都对应于表格的一行。总的来说,CSV包含一个头部行,它为数据提供列的名称。不然的话,这些文件会被认为是半结构化。起初,CSV文件不能表示层级或关系型数据。数据之间的关联基本是通过多个CSV文件来实现。一个或多个文件中都会在列中存储外键,但是CSV格式本身并不能表达这些文件之间的关系。而且,CSV格式并没有完全标准化,可以使用除逗号之外的其他分隔符,比如制表符和空格。
CSV的另一个特性是:只有在使用原始未压缩文件和类似bzip2或lzo可拆分压缩格式时才可被拆分(注意:lzo需要索引以后才能被拆分)。
优点:
- CSV易于人们理解和手动编辑;
- CSV提供了简单易懂的信息格式;
- 几乎所有现存的系统都可以处理CSV;
- CSV易于实现和解析;
- CSV格式紧凑。在XML中,每行数据的每列都需要开始和结束标签。在CSV中你只需要写一次列头信息。
缺点:
- CSV只能处理扁平化的数据。复杂的数据结构需要额外处理;
- 不支持设置列的数据类型。文本类型的列和数字类型的没有任何区别;
- 没有标准方式表示二进制数据;
- CSV的导入问题(不区分NULL和“null”引用)(译者语:CSV空列在导入时会变成字符串“null”)
- 对特殊字符的支持很差
- 缺少标准
尽管有这些局限性,CSV文件仍然是分享数据的的热门选择。它们得到了广泛的商业应用、消费者和科学应用的支持。同样的,大多数批量和流式数据处理模块(比如,Spark和Hadoop)一开始就支持CSV文件的序列化和反序列化,并且在读取它们的时候提供添加schema的方法(译者语:这里schema不做翻译,很多数据库数据中都是保留英文,中文含义是模式。保留原文对于程序员更加来说更加容易理解)。
JSON
Json数据(JavaScript object notation,即JavaScript对象符号)在一个部分结构化的格式中被表示成一系列键值对。因为可以以层级格式存储数据,所以JSON常被和XML做对比。孩子数据由双亲数据负责呈现。两种格式都是自描述的,并且方便用户阅读理解,但是通常情况下JSON文档会小很多。因此,它们被越来越多的用于网络通讯,特别是伴随着基于REST网络服务的出现。
因为已经有很多数据传输在使用JSON格式,所以大多数网络语言一开始就支持JSON数据的序列化和反序列化,也有些语言是通过外部工具库来实现。因为有这样的支持,JSON被用于展示数据结构的逻辑格式,交换热数据,和存储冷数据。
很多批量和流式数据处理模块天然支持JSON的序列化和反序列化。尽管JSON文档包含的数据最终被存储在性能更优的格式中,比如Parquet或Avro。但是JSON可以作为原始数据的格式,这在重新处理数据(如果有需要)时非常重要。
JSON文件有很多优点:
- JSON支持层级结构,简化了在一个文档中存储关联数据和展示复杂关系的难度;
- 大多数语言提供了简化JSON序列化的工具库,或内建支持JSON序列化/反序列化;
- JSON支持对象列表,帮助避免列表转换成关系型数据模型时的不确定性;
- JSON是一种被NoSQL数据库广泛使用的文件格式,比如MongoDB,Couchbase和Azure Cosmos DB;
- 目前大部分工具都内置支持JSON。
Parquet
Parquet于2013年面世,由Cloudera和Twitter开发,用来作为列存储的格式,对多列数据集做了优化。由于数据是按列储存,所以它可以被高度压缩(压缩算法在低信息熵的数据中表现更好,而列数据通常正是如此)而且具有良好的可拆分性。该格式的开发者声称这种存储格式是解决大数据问题的理想方案。
不像CSV和JSON,Parquet文件是二进制文件,包含描述内容的元数据。所以,不需要读取/解析文件内容,Spark仅仅依赖元数据就可以确定列的名称、压缩/编码信息、数据类型甚至一些基本的统计数据。Parquet文件列的元数据存储在文件的尾部,这样允许快速的、一次性写入。
Parquet针对“一次写入,多次读取(WORM)”特性做了优化。它写入慢,但是读取特别快,特别当你只访问全部列的一部分时。有大量读取工作时,Parquet是个好选择。对于需要对整行数据处理的场景,你需要使用类似CSV或者AVRO格式。
Parquet数据存储的优点:
- Parquet是列式格式。只有被需要的列会被获取/读取,降低了磁盘I/O。这个概念被称为投影下推(译者语:原文projection pushdown);
- Schema和数据在一起,所以数据是自描述的;
- 尽管它最初是为HDFS创建的,但是数据也可以存放在其他文件系统中,比如GlusterFs或者NFS;
- Parquet只是一些文件,这意味着很容易移动、备份和复制它;
- 在Spark中原生支持,开箱即用,能够很简单的获取和存储文件到你的存储中;
- Parquet提供优异的压缩功能,当使用类似snappy压缩格式时,压缩比高达75%;
- 实践表明,和其他文件格式相比,该格式读取速度最快;
- Parquet非常适合要对海量数据的特定列做汇总的数据仓库;
- 可以使用Avro API和Avro Schema读取和写入Parquet(这允许使用Avro格式存储原始数据,但是使用Parquet处理数据);
- 它也支持谓词下推(译者语:原文是predicate pushdown),因此可以进一步降低磁盘I/O开销。
谓词下推/过滤下推
谓词下推的基本思想是某些查询部分(谓词)可以被“推向”数据存放的地方。比如,当我们给定了一些过滤条件,数据存储就可以尝试在从磁盘读取数据的时候就过滤数据记录。谓词下推的优势是更低的磁盘I/O,因此整体性能会更好。不然的话,所有数据都要被装载进内存,然后再过滤,这样会导致非常大的内存需求。
这个优化通过更早的过滤掉多余数据可以极大降低查询/处理的时间。依据不同的处理框架,谓词下推可以通过以下方式优化你的查询,比如在网络传输之前进行数据过滤,在装载进内存之前过滤,或者直接避免整个文件或文件块。
大多数的RDBMS都采用这个概念,一些类似Parquet和ORC的大数据存储格式也已经采用。
投影下推
在从数据存储中读取数据时,只读取每个查询中需要的列,而不是所有的列。基本上,列式格式都遵循这个概念,比如Parquet和ORC,这样会获得更好的I/O性能。
Avro
Apache Avro是由Hadoop工作组于2009年发布。它是基于行的格式,并且具备高度的可分拆性。它也被描述为一个类似Java序列化的数据序列化系统。它的schema存储在JSON格式中,但是数据以二进制方式存储,以求文件尺寸最小同时效率最高。通过管理新增字段、缺失字段和被修改的字段,Arvo为schema进化提供了强大的支持。这允许老系统读取新的数据,也允许新系统读取老数据--如果你的数据可能会更改,这会是非常重要的特性。
借助Avro管理schema进化的能力,我们可以在不同时间独立地更新各个组件,并且不兼容的风险很低。这让应用避免使用if-else语句处理不同的schema版本,也让开发者避免查看老代码来理解老的schema。因为所有版本的schema都存储在易于人们阅读理解的JSON头部,所以你很容易理解所拥有的全部字段。
很多不同语言都支持Avro。因为schema存储在JSON中,但是数据存储为二进制,所以Avro在持久化数据存储和数据传输中都是相当紧凑的方案。Avro是写入很多场景的首选方案,因为它可以很容易在尾部添加新行。
优点:
- Avro是语言中立的数据序列化方案;
- Avro在文件头部存储schema,所以数据是自描述的;
- Avro格式的文件既可拆分又可压缩,因此是Hadoop生态系统中文件存储的好选择;
- 读Avro文件使用得schema不用和写文件所用的schema一样。这使得我们可以独立的添加新字段;
- 和Sequence Files(译者语:一种Hadoop的文件格式)一样,Avro文件也包含用来分隔块的Sync标识符。这使得它有高度可分拆性;
- 这些块可以使用类似snappy这样的压缩格式压缩。
总结
CSV | JSON | Parquet | Avro | |
---|---|---|---|---|
是否是列式 | 否 | 否 | 是 | 否 |
是否可压缩 | 是 | 是 | 是 | 是 |
是否可分拆 | 是 | 是 | 是 | 是 |
易于人们阅读 | 是 | 是 | 否 | 否 |
是否支持复杂数据结构 | 否 | 是 | 是 | 是 |
是否支持schema进化 | 否 | 否 | 是 | 是 |
当使用CSV一样的压缩方式时,JSON的可拆分性也有和其一样的限制条件,而且还有一个额外的不同。当设置“wholeFile”为true(见:SPARK-18352),JSON将不可被拆分。
性能测试
为了对于不同格式,我用Netflix数据集创建了一系列的测试。它被称为窄数据--因为它只有三列但是相当多的行。
所有的测试都运行在我的笔记本上(我将不会展示笔记本的配置,因为我认为这不会影响对比的结果),使用pyspark 2.4.3运行spark-submit命令。每个格式的实验都会运行三次,然后取它们的平均值。所有的脚本都公布在此。
每种格式的空间占用情况
任何文件的基本功能都是存储数据。在大数据领域,需要按照不同用途存储不同类型的数据,至少也要存储原始数据。这些数据占用多少空间也就意味着它会花费多少钱。
这里需要注意的是:完全使用默认配置并且不压缩。我承认采用gzip压缩方式的CSV文件可能占用空间不到1.5GB。所以二进制格式的优势将不会特别显著。结论--不要使用JSON存储原始数据。
每种格式的写入延迟
其次,我尝试向不同文件格式存入数据,然后评估延迟。
这可能表明大多数时间都花费在读取2GB的源数据。剩下的其他工作话费差不多5--10秒(我没校验Spark UI)。不论如何,结果都容易理解--写入普通文本比需要编码和收集元数据的复杂格式(比如Avro和Parquet)更加容易。
每种格式的随机读取延迟
在这个测试中,我尝试通过提供数据记录的识别码(在spark中使用sample转换器)来了解获取记录的延迟。
存储在CSV、JSON和Avro格式中的单个记录只有在暴力扫描整个数据分区以后才能获得。在Parquet中,列式特性让分区扫描相对快一些。感谢列投影和列谓词下推,扫描量最终降低了。
每种格式上的基本统计操作耗时
这个测试展示常用的列统计操作(min,max,count)会有多快。
Parquet和Avro在计算数据集中行数时,使用了它们的黑科技:直接读取元数据。对于CSV和JSON来说,情况更加糟糕--它们必须解析每个字节。
每种格式的数据处理耗时
这个测试对时间列执行过滤函数。
每种格式按列分组操作的耗时
这个测试使用了更加复杂的转换--按列分组。
每种格式获取去重值耗时
这个测试将会尝试测量获取列中去重值的延迟。
结果看起来和随机数读取完全一样,但是去重操作更加复杂。简单来说,我们可以假设在任务的结尾增加了一个reduce阶段,这意味着在worker中交换数据的时间也被测量了。
从性能测试中学到得
- CSV写入速度最快,JSON最易于人们阅读,Parquet在读取列的子集时最快,而Avro在一次读取所有列时最快。
- JSON是web通讯的标准。由于它们拥有类似定义良好schema等的可用性,API和网站始终使用JSON通讯;
- Parquet和Avro确实为应对大数据的需求做了更多的优化--可拆分性,支持压缩,复杂数据结构的支持。但是可读性和写入数独相当糟糕;
- 在Parquet和Avro中,可以并发访问的最小数据单元是HDFS的文件块--因为有它,我们可以轻而易举的平均分配处理流程到Hadoop集群中的可用资源中;
- 当你要在Hadoop中选择数据存储格式时,你需要考虑很多因素,比如和第三方应用的对接、scheme进化、支持特殊的数据类型...但是如果对你来说性能是第一位的,那么上面的测试表明Parquet是你最好的选择(译者语:小心这种不够完善的基准测试,还是要在你自己的环境中做大量细致的性能测试);
- 明显的,压缩算法在减少数据容量和增强数据写入和读取性能上都扮演着极其重要的角色。但是这部分并没有被测试到;
- Apache Avro被证明是一种快速且通用的数据结构编码器。由于其非常高效的序列化和反序列化,这个格式可以在需要同时访问记录全部属性时保证优良的性能;
- 根据上述测试,类似Apache Parquet这样的列存储在快速数据写入、快速随机数据读取和可扩展性的数据分析上都取得了非常好的适应性。很多情况下,这提供了保持系统简单的额外优势,因为存储数据和其他使用场景(随机访问和分析)只需要一种技术;
- 不管你做什么--永远不要使用JSON格式。在几乎全部测试中,它都是性能最差。
附言:当你只需要查询少量列而不是全部列的时候,应该使用列式格式。因为面向列的存储模式非常适合。相反你需要访问行中的每个字段,应该使用行格式。所以一般来说Avro被用来存储原始数据,因为在处理过程中通常需要所有字段。