本节书摘来自华章出版社《Spark大数据分析:核心概念、技术及实践》一书中的第1章,第1节,作者穆罕默德·古勒(Mohammed Guller)更多章节内容可以访问云栖社区“华章计算机”公众号查看。
大数据技术一览
我们正处在大数据时代。数据不仅是任何组织的命脉,而且在指数级增长。今天所产生的数据比过去几年所产生的数据大好几个数量级。挑战在于如何从数据中获取商业价值。这就是大数据相关技术想要解决的问题。因此,大数据已成为过去几年最热门的技术趋势之一。一些非常活跃的开源项目都与大数据有关,而且这类项目的数量在迅速增长。聚焦在大数据方向的创业公司在近年来呈爆发式增长。很多知名公司在大数据技术方面投入了大笔资金。
尽管“大数据”这个词很火,但是它的定义是比较模糊的。人们从不同方面来定义“大数据”。一种定义与数据容量相关,另一种则与数据的丰富度有关。有些人把大数据定义为传统标准下“过于大”的数据,而另一些人则把大数据定义为捕捉了所描绘实体更多细节的数据。前者的例子之一就是超过数拍字节(PB)或太字节(TB)大小的数据集,如果这样的数据存储在传统的关系数据库(RDBMS)表中,将会有数十亿行。后者的一个例子是有极宽行的数据集,这样的数据存储在RDBMS中,将会有数千列。另一种流行的大数据定义是由3个V(volume、velocity和variety,即容量、速度和多样性)所表征的数据。我刚才讨论了容量。速度指的是数据以极快的速率产生,多样性则指的是数据可以是非结构化、半结构化或多结构的。
标准的关系数据库无法轻易处理大数据。这些数据库的核心技术在数十年前所设计,当时极少有组织拥有拍字节级甚至太字节级的数据。现在对一些组织来说,每天产生数太字节的数据也很正常。数据的容量和产生速度都呈爆发式增长。因此,迫切需要新的技术:能快速处理和分析大规模数据。
其他推动大数据技术的因素包括:可扩展性、高可用性和低成本下的容错性。长期以来,处理和分析大数据集的技术被广泛研究并以专有商业产品的形式被使用。例如,MPP(大规模并行处理)数据库已经诞生有段时间了。MPP数据库使用一种“无共享”架构,数据在集群的各个节点进行存储和处理。每一个节点有自己的CPU、内存和硬盘,节点之间通过网络互联来通信。数据分割在集群的各个节点,而节点之间不存在竞争,所以每个节点可以并行处理数据。这种数据库的例子包括Teradata、Netezza、Greenplum、ParAccel和Vertica。Teradata发明于20世纪70年代末,在20世纪90年代前,它就能够处理太字节级别的数据了。但是,专有的MPP数据库非常昂贵,不是所有人能负担得起的。
本章介绍一些开源的大数据相关技术。本章涉及的技术看起来好像随意挑选的,实际上它们由共同的主题而连接:它们和Spark一起使用,或者Spark可以取代其中一些技术。当你开始使用Spark时,你可能会涉及这些技术。而且,熟悉这些技术会帮你更好地理解Spark(这将在第3章介绍)。
1.1 Hadoop
Hadoop是最早流行的开源大数据技术之一。这是一个可扩展、可容错的系统,用来处理跨越集群(包含多台商用服务器)的大数据集。它利用跨集群的可用资源,为大规模数据处理提供了一个简单的编程框架。Hadoop受启发于Google发明的一个系统(用来给它的搜索产品创建反向索引)。Jeffrey Dean和Sanjay Ghemawat在2004年发表的论文中描述了这个他们为Google而创造的系统。第一篇的标题为“MapReduce:大集群上简化的数据处理”,参见research.google.com/archive/mapreduce.html;第二篇的标题为“Google文件系统”,参见research.google.com/archive/gfs.html。受启发于这些论文,Doug Cutting和Mike Cafarella开发了一个开源的实现,就是后来的Hadoop。
很多组织都用Hadoop替换掉昂贵的商业产品来处理大数据集。一个原因就是成本。Hadoop是开源的,并可以运行在商用硬件的集群上。可以通过增加廉价的服务器来轻松地扩展。Hadoop提供了高可用性和容错性,所以你不需要购买昂贵的硬件。另外,它对于特定类型的数据处理任务非常合适,比如对于大规模数据的批处理和ETL(Extract、transform、load,提取、转换、加载)。
Hadoop基于几个重要的概念。第一,使用商用服务器集群来同时存储和处理大量数据比使用高端的强劲服务器更便宜。换句话说,Hadoop使用横向扩展(scale-out)架构,而不是纵向扩展(scale-up)架构。
第二,以软件形式来实现容错比通过硬件实现更便宜。容错服务器很贵,而Hadoop不依赖于容错服务器,它假设服务器会出错,并透明地处理服务器错误。应用开发者不需要操心处理硬件错误,那些繁杂的细节可以交给Hadoop来处理。
第三,通过网络把代码从一台计算机转到另一台比通过相同的网络移动大数据集更有效、更快速。举个例子,假设你有一个100台计算机组成的集群,每台计算机上有1TB的数据。要处理这些数据,一个选择是:把数据转移到一台能够处理100TB数据的超级计算机。然而,转移100TB的数据将花费极长时间,即使是在高速网络上。另外,通过这种方式处理数据将需要非常昂贵的硬件。另一个选择是:把处理数据的代码转移到具有100个节点的集群中的每台计算机。这比第一种选择更快、更高效。而且,你不需要高端、昂贵的服务器。
第四,把核心数据处理逻辑和分布式计算逻辑分开的方式,使得编写一个分布式应用更加简单。开发一个利用计算机集群中资源的应用比开发一个运行在单台计算机上的应用更加困难。能写出运行在单台机器上的应用的开发者数量比能写分布式应用的开发者多好几个数量级。Hadoop提供了一个框架,隐藏了编写分布式应用的复杂性,使得各个组织有更多可用的应用开发者。
尽管人们以一个单一产品来讨论Hadoop,但是实际上它并不是一个单一产品。它由三个关键组件组成:集群管理器、分布式计算引擎和分布式文件系统(见图1-1)。
2.0版本以前,Hadoop的架构一直是单一整体的,所有组件紧密耦合并绑定在一起。从2.0版本开始,Hadoop应用了一个模块化的架构,可以混合Hadoop组件和非Hadoop技术。
图1-1中所示的三个概念组件具体实现为:HDFS、MapReduce和YARN(见图1-2)。
HDFS和MapReduce在本章讨论,YARN将在第11章介绍。
1.1.1 HDFS
正如其名,HDFS(Hadoop Distributed File System)是一个分布式文件系统,它在商用服务器集群中存储文件,用来存储和快速访问大文件与大数据集。这是一个可扩展、可容错的系统。
HDFS是一个块结构的文件系统。正像Linux文件系统那样,HDFS把文件分成固定大小的块,通常叫作分块或分片。默认的块大小为128MB,但是可以配置。从这个块的大小可清楚地看到,HDFS不是用来存储小文件的。如果可能,HDFS会把一个文件的各个块分布在不同机器上。因此,应用可以并行文件级别的读和写操作,使得读写跨越不同计算机、分布在大量硬盘中的大HDFS文件比读写存储在单一硬盘上的大文件更迅速。
把一个文件分布到多台机器上会增加集群中某台机器宕机时文件不可用的风险。HDFS通过复制每个文件块到多台机器来降低这个风险。默认的复制因子是3。这样一来,即使一两台机器宕机,文件也照样可读。HDFS基于通常机器可能宕机这个假设而设计,所以可以处理集群中一台或多台机器的宕机问题。
一个HDFS集群包含两种类型的节点:NameNode和DataNode(见图1-3)。Name-Node管理文件系统的命名空间,存储一个文件的所有元数据。比如,它追踪文件名、权限和文件块位置。为了更快地访问元数据,NameNode把所有元数据都存储在内存中。一个DataNode以文件块的形式存储实际的文件内容。
图1-3 HDFS架构
NameNode周期性接收来自HDFS集群中DataNode的两种类型的消息,分别叫作心跳消息和块报告消息。DataNode发送一个心跳消息来告知NameNode工作正常。块报告消息包含一个DataNode上所有数据块的列表。
当一个客户端应用想要读取一个文件时,它首先应该访问NameNode。NameNode以组成文件的所有文件块的位置来响应。块的位置标识了持有对应文件块数据的DataNode。客户端紧接着直接向DataNode发送读请求,以获取每个文件块。NameNode不参与从Data-Node到客户端的实际数据传输过程。
同样地,当客户端应用想要写数据到HDFS文件时,它首先访问NameNode并要求它在HDFS命名空间中创建一个新的条目。NameNode会检查同名文件是否已存在以及客户端是否有权限来创建新文件。接下来,客户端应用请求NameNode为文件的第一个块选择DataNode。它会在所有持有块的复制节点之间创建一个管道,并把数据块发送到管道中的第一个DataNode。第一个DataNode在本地存储数据块,然后把它转发给第二个Data-Node。第二个DataNode也本地存储相应数据块,并把它转发给第三个DataNode。在所有委派的DataNode上都存储第一个文件块之后,客户端请求NameNode为第二个块来分配DataNode。这个过程持续进行,直到所有文件块都已在DataNode上存储。最后,客户端告知NameNode文件写操作已完成。
1.1.2 MapReduce
MapReduce是Hadoop提供的分布式计算引擎。HDFS提供的是存储大数据集的分布式文件系统,MapReduce则提供集群中并行处理大数据集的计算框架。它抽象了集群计算,提供了编写分布式数据处理应用的高级结构,使得没有编写分布式或并行应用的程序员也可以编写运行在商用计算机集群上的应用。
MapReduce框架自动在集群中各计算机上调度应用的执行。它会处理负载均衡、节点宕机和复杂的节点内通信。它处理分布式计算的繁杂细节,使得程序员可以关注于数据处理的逻辑本身。
MapReduce应用的基本组成块是两个函数:map和reduce,名称借鉴于函数式编程。MapReduce中所有的数据处理作业都用这两个函数来表达。map函数以键值对作为输入,输出中间产物键值对。MapReduce框架对输入数据集中每一个键值对调用map函数。接下来,对map函数的输出进行排序,并根据值进行分组,作为输入传给reduce函数。reduce函数聚合这些值,输出最终的聚合值。
第3章将介绍的Spark被视为MapReduce的继承者,相比MapReduce,它有诸多优势。这将在第3章详细讨论。
1.1.3 Hive
Hive是一个数据仓库软件,它提供了类SQL语言来处理和分析存储在HDFS或其他兼容Hadoop的存储系统(如Cassandra和Amazon S3)中的数据。尽管Hadoop使得编写可利用集群中计算机资源的数据处理应用更加简单,但是能写出这样应用的程序员相对于了解SQL的人来说依然少得多。
SQL是广泛使用的数据处理语言,是一种描述性语言。它看似简单,实则功能强大。SQL比Java和其他用来编写MapReduce应用的编程语言更易学易用。Hive把SQL的简洁性引入到Hadoop中,让更多人可用。
Hive提供一种类SQL的查询语言,叫作Hive查询语言(HiveQL),来处理和分析存储在任何兼容Hadoop的存储系统中的数据。它提供了一种机制把对应结构映射到存储在HDFS中的数据上,并用HiveQL来查询。在底层,它会把HiveQL查询转换成MapReduce作业。它也支持UDF(用户定义函数)和UDAF(用户定义聚合函数),二者用来进行无法用HiveQL有效表达的复杂数据处理。
第7章讨论的Spark SQL被视为Hive的继承者。然而,Spark SQL提供的不仅是SQL接口,它还做了更多工作。这将在第7章详细讲述。
1.2 数据序列化
数据有自己的生命周期,独立于创建或使用它的程序。大多数情况下,数据比创建它的应用存活得更久。一般来说,数据保存在硬盘上。有时,也会通过网络把数据从一个应用发送给另一个应用。
在硬盘上存储或通过网络发送的数据格式与数据在内存中的格式是不一样的。把内存中的数据转换为可在硬盘上存储或通过网络发送的过程叫作序列化,而把硬盘或网络中的数据读取到内存的过程叫作反序列化。
数据可以用多种不同的格式进行序列化,比如CSV、XML、JSON和各种二进制格式。每种格式各有优缺点。比如,像CSV、XML和JSON这样的文本格式对人类友好,但在存储空间或解析时间方面并不十分高效。另一方面,二进制格式更加紧凑,在解析上比文本格式更快,但可读性较差。
在数据集较小时,文本和二进制格式之间的序列化/反序列化时间和存储空间差异不是什么大问题。因此,人们通常首选文本格式来处理小数据集,因为它更容易管理。然而,对于大数据集,文本和二进制格式之间的序列化/反序列化时间和存储空间差异将是极大的。因此,首选二进制格式来存储大数据集。
本节讲述一些常用的用来序列化大数据的二进制格式。
1.2.1 Avro
Avro提供了一个简洁的且独立于语言的二进制格式,用来数据序列化。它可用来存储数据到文件或通过网络发送数据。它支持多种数据结构,包括嵌套数据。
Avro使用一种自描述的二进制格式。使用Avro序列化数据时,模式与数据同时存储。这样一来,稍后Avro文件可以被任何应用读取。另外,因为模式与数据同时存储,所以写数据时没有关于值的间接开销,使得序列化快速、紧实。使用Avro通过网络交换数据时,发送端和接收端在初始化连接握手时交换模式。Avro模式使用JSON描述。
Avro自动处理字段的添加和删除、前向和后向兼容性,这些都不需应用来负责。
1.2.2 Thrift
Thrift是一个独立于语言的数据序列化框架,主要提供工具来完成不同编程语言所写的应用之间通过网络进行的数据交换序列化。它支持多种语言,包括:C++、Java、Python、PHP、Ruby、Erlang、Perl、Haskell、C#、Cocoa、JavaScript、Node.js、Smalltalk、OCaml、Delphi和其他语言。
Thrift提供一个代码生成工具和一组用于序列化数据并通过网络传输的库。它抽象了序列化数据和通过网络传输数据的机制。因此,它使得应用开发者可以集中精力于核心的应用逻辑,而不用担心如何序列化数据和可靠、有效地传输数据。
通过Thrift,应用开发者在一个语言中立的接口定义文件中定义数据类型和服务接口。在接口定义文件中定义的服务由服务器端应用提供,并由客户端应用使用。Thrift编译器编译这个文件,并生成开发者用来快速构建客户端和服务器端应用的代码。
基于Thrift的服务器和客户端可以在相同计算机或网络上的不同计算机上运行。同样地,服务器端和客户端应用可以使用同一种编程语言来开发,也可以用不同编程语言来开发。
1.2.3 Protocol Buffers
Protocol Buffers是Google开发的开源数据序列化框架。类似于Thrift和Avro,它也是语言中立的。Google内部用Protocol Buffers作为主要的文件格式,也将其用来进行应用间的数据交换。
Protocol Buffers与Thrift类似,前者提供一个编译器和一组库来帮助开发者序列化数据。开发者在一个文件中定义数据集的结构或模式,然后用Protocol Buffers编译器进行编译,由此生成可用来轻松读写数据的代码。
相对Thrift而言,Protocol Buffers支持较少的编程语言。目前,它支持C++、Java和Python。另外,不像Thrift那样同时提供数据序列化和构建远程服务的工具,Protocol Buffers主要是一种数据序列化格式,可以用来定义远程服务,但并未限定到任何RPC(远程过程调用)协议。
1.2.4 SequenceFile
SequenceFile是一种用于存储键值对的二进制文件格式。它通常作为Hadoop的输入和输出文件格式。MapReduce也用SequenceFile来存储map函数返回的临时输出。
SequenceFile有三种不同的格式:未压缩格式、记录压缩格式和块压缩格式。在记录压缩格式的SequenceFile中,只有记录中的值才压缩;而在块压缩格式的SequenceFile中,键和值都压缩。
1.3 列存储
数据可以面向行或面向列的格式来存储。在面向行格式中,一行的所有列或字段存储在一起。这里的一行,可以是CSV文件中的一行,或者是数据库表中的一条记录。当数据以面向行格式保存时,第一行后面是第二行,接着是第三行,以此类推。面向行存储对于主要执行数据的CRUD(创建、读取、更新、删除)操作的应用来说是完美的。这些应用一次操作数据中的一行。
然而,面向行存储对于分析类应用来说不够高效。这样的应用要对数据集的列进行操作。更重要的是,这些应用只读取和分析跨越多行的列的一个小子集。因此,读取所有列是对内存、CPU周期和硬盘I/O的浪费,这是一个昂贵的操作。
面向行存储的另一个缺点是数据无法高效地压缩。一条记录可能由多种不同数据类型的列构成,一行的熵就会很高。压缩算法不适用于压缩多样化数据。因此,使用面向行格式存储在硬盘上的一个表格比用列存储格式所生成的文件更大。更大的文件不仅要耗费更多的硬盘空间,还会影响应用的性能,因为硬盘I/O与文件大小成正比,而硬盘I/O是一个昂贵的操作。
面向列存储系统以列的形式在硬盘上存储数据。列中的所有单元保存在一起,或者连续地保存。比如,当以列格式在硬盘上保存一个表格时,所有行的第一列首先保存,然后是所有行的第二列,接着是第三列,以此类推。列存储在分析类应用方面比面向行存储更加高效,使分析更加迅速,而所需硬盘空间更小。
下一节讨论Hadoop生态系统中3种常用的列存储文件格式。
1.3.1 RCFile
RCFile(列式记录文件)是一种构建于HDFS之上用来存储Hive表格的列存储格式。它实现了一种混合的列存储格式。RCFile首先把表格分割成行组(row group),然后以列格式保存每一个行组。所有行组分布在整个集群上。
RCFile使得我们可以同时利用列存储和Hadoop MapReduce的优势。因为行组分布在整个集群上,所以它们可以并行处理。一个节点上,行的列存储有助于高效的压缩和更快的分析。
1.3.2 ORC
ORC(Optimized Row Columnar)是另一种高效存储结构化数据的列存储文件格式。相对RCFile,它有很多优势。比如,它保存行索引,使得查询中可以快速搜索一个指定行。因为它基于数据类型采用块模式的压缩,所以它能提供更好的压缩效果。另外,可以用zlib或Snappy在基于数据类型的列级别的压缩之上进行通用压缩。
和RCFile类似,ORC文件格式把表格分割成可配置大小的条带(见图1-4)。默认的条带大小为250MB。一个条带类似于RCFile中的一个行组,但是每个条带不仅包含行数据,还包括索引数据和条带脚部。条带脚部含有流位置的目录。索引数据包括每一列的最小值和最大值以及行索引。ORC文件格式在一个条带中为每10000行保存一个索引。在每个条带内部,ORC文件格式使用特定数据类型的编码技术来压缩列,如:针对整型列的行程编码和针对字符串列的字典编码。还可以使用zlib或Snappy之类的通用压缩编解码器来进一步压缩列。
所有条带之后是文件脚部,其中包含文件中条带的列表、条带中的行数和各个列的数据类型,还包括每一列的统计数据,比如:数目、最小值、最大值和总数。文件脚部之后是附录(postscript)部分,其中包含压缩参数和压缩的脚部大小。
ORC文件格式不仅高效存储数据,还有助于高效查询。应用在一次查询中可以只请求所需的列。同样地,应用可以使用谓词下推来跳跃读取整个行集。
1.3.3 Parquet
Parquet是为Hadoop生态系统而设计的另一个列存储格式。它可以被任何数据处理框架所使用,包括Hadoop MapReduce和Spark。它用来支持复杂的嵌套数据结构。另外,它不仅支持多种数据编码和压缩技术,还可以按列来指定压缩方案。
Parquet实现了一个三层的层次结构来在文件中存储数据(见图1-5)。首先,和RCFile和ORC类似,它在水平方向把表格分割为行组。行组分布在整个集群上,因此可以用任何集群计算框架来并行处理。其次,在每个行组内部,它把列分割为列块。Parquet用术语“列块”来表示行组中一列的数据。一个列块在硬盘上连续存储。层次结构中的第三级是页面。Parquet把列块分割为多个页面。一个页面是编码和压缩的最小单元。一个列块可以包含多个不同类型的交错页面。因此,一个Parquet文件由行组构成,行组中包含列块,而列块中包含一个或多个页面。
1.4 消息系统
数据通常从一个应用流向另一个。一个应用产生数据,而后被一个或多个其他应用使用。一般来讲,生成或发送数据的应用叫作生产者,接收数据的则叫作消费者。
有时候,产生数据的应用数量和使用数据的应用数量会出现不对称。比如,一个应用可以产生数据,而后被多个消费者使用。同样地,一个应用也可以使用来自多个生产者的数据。
有时候应用产生数据的速率和另一个应用使用数据的速率也会出现不对称。一个应用可能产生数据的速率快于消费者使用数据的速率。
图1-5 Parquet文件结构(图片来源:parquet.apache.org)
从一个应用向另一个应用发送数据的简单方法就是把它们直接互连。然而,当生产者和消费者数量或数据生成速率和使用速率之间存在不对称时,这个方法就行不通了。另一个挑战是生产者和消费者之间的强耦合要求它们同时运行,或实现一个复杂的缓冲机制。因此,生产者和消费者之间直连无法扩展。
一个灵活且可扩展的解决方法是用一个消息代理或消息系统。应用无须直接互联,而是连接到消息代理或消息系统。这样的架构使在数据管道上添加生产者或消费者变得容易,也允许应用以不同速率来生成和使用数据。
本节讨论几个大数据应用广泛使用的消息系统。
1.4.1 Kafka
Kafka是一个分布式的消息系统或消息代理。准确来讲,它是一个分布式的、分块的、重复的提交日志服务,可以用来作为发布-订阅式消息系统。
Kafka的关键特性包括:高吞吐量、可扩展性和持久性。单个代理可以处理来自数以千计应用的每秒几百兆字节的读和写。可以通过向集群中增加更多节点来轻松扩容。关于持久性,它在硬盘上保存消息。
基于Kafka的架构中的关键实体包括:代理、生产者、消费者、主题和消息(见图1-6)。Kafka作为节点的集群来运行,每个节点叫作代理。通过Kafka发送的消息属于主题。把消息发布到Kafka主题的应用叫作生产者。消费者指的是订阅Kafka主题并处理消息的应用。
图1-6 Kafka中的消息流
Kafka把一个主题分割为多个分块。每个分块是消息的一个有序而不可变的序列。新消息被追加到一个分块。给一个分块中的每一条消息指定一个唯一的连续标识符(叫作偏移量)。各个分块分布在Kafka集群的各个节点。另外,也复制它们以提供容错功能。主题的分割有助于扩展性和并行性。一个主题不需要限制于单台机器,它可以增长到任意大小。主题大小的增长可以通过向Kafka集群中添加更多节点来解决。
发布到Kafka集群的消息中,一个重要的属性是:它在一个可配置的周期内保留所有消息。即使消费者使用了一条消息,在所配置的间隔内消息依然可以获取它。更重要的是,Kafka的性能对于数据大小实际上保持恒定。
Kafka使用一个叫作消费者组的机制来同时支持队列和发布-订阅消息模型。把发布到一个主题的每条消息发送到每一个订阅的消费者组内的一个消费者。因此,如果订阅一个主题的所有消费者属于同一个消费者组,则Kafka作为一个队列消息系统而工作,每条消息只发送到一个消费者。另一方面,如果订阅一个主题的每一个消费者属于不同的消费者组,则Kafka作为一个发布-订阅消息系统而工作,把每条消息都广播到所有订阅某主题的消费者。
1.4.2 ZeroMQ
ZeroMQ是一个轻量级的高性能消息库。它用来实现消息队列和构建可扩展的并发和分布式消息驱动的应用。它没有利用以代理为中心的架构,尽管根据需要也可以用它来构建一个消息代理。它支持大多数现代语言和操作系统。
ZeroMQ的API仿效了标准的UNIX Socket API。应用之间通过套接字互相通信。不像标准的套接字,它支持N对N连接。一个ZeroMQ套接字代表一个异步的消息队列。它用一个简单的框架在线缆上传输离散消息。消息长度可以是0字节到数吉字节。
ZeroMQ不会对消息强加任何格式,而将消息当作二进制大对象blob。可以通过序列化协议来结合它,比如用Google的Protocol Buffers来发送和接收复杂的对象。
ZeroMQ在后台线程中异步实现I/O。它会自动处理物理连接设置、重连、消息传送重试和连接清除。另外,如果接收者不可达,它会将消息排队。当队列满额时,可以将其配置为阻止发送者或丢弃消息。因此,ZeroMQ提供了一个比标准套接字更高级的抽象来发送和接收消息,使创建消息分发应用更加简单,也使得应用间发送和接收消息的松耦合成为可能。
ZeroMQ库支持多个传输协议来进行线程间、进程间和跨网络的消息传递。对于相同进程内线程间的消息传递,它支持一种不涉及任何I/O的基于内存的消息传递机制。对于运行在相同机器上的进程之间的消息传统,它使用UNIX域或IPC套接字。这种情况下,所有通信都在操作系统内核中发生,而不会使用任何网络协议。ZeroMQ支持TCP协议来实现应用间通过网络进行通信。最后,它还支持PGM来多播消息。
ZeroMQ可用来实现不同的消息传递模式,包括:请求-应答、Router-Dealer、客户端-服务器、发布-订阅和管道。比如,可以用ZeroMQ创建一个发布-订阅模式的消息传递系统来从多个发布者发送数据到多个订阅者(见图1-7)。要实现这个模式,发布者应用会创建一个ZMQ_PUB类型的套接字。在这样的套接字上发送的消息以扇出(fan-out)的方式分布到所有已连接的订阅者。订阅者应用创建一个ZMQ_SUB类型的套接字来订阅来自发布者的数据,可以指定一个过滤器来获取想要的消息。同样地,也可以用ZeroMQ创建一个管道模式来分发数据到管道上排列的各个节点。应用创建ZMQ_PUSH类型的套接字来发送消息到下游应用,下游应用则需创建ZMQ_PULL类型的套接字。
图1-7 使用ZeroMQ的发布-订阅
1.5 NoSQL
NoSQL这个术语用于非关系型的现代数据库。起初,NoSQL指的是“不支持SQL”,因为这些数据库不支持SQL。而现在,它指的是“不止SQL”,因为其中一些数据库支持SQL命令的一个子集。相对RDBMS数据库来说,NoSQL数据库有不同的设计目标。一个关系数据库保证了ACID(原子性、一致性、独立性和持久性)。而NoSQL数据库则权衡ACID对线性扩展性、性能、高可用性、灵活的模式和其他特性的兼容性。
本节讨论一些广泛使用的NoSQL数据库。
1.5.1 Cassandra
Cassandra是一个分布式、可扩展、容错的NoSQL数据库,用于存储大数据集。它是一个分块的、可调节一致性的行存储。其关键特性是动态模式,每一行可以存储不同的列,而不像关系数据库那样每行有完全相同的列。另外,Cassandra对写操作做了优化,所以插入操作是高性能的。
Cassandra是一个无主的分布式架构。因此,它没有单点故障的问题。另外,它实现了各行在集群中的自动分布。读写数据的客户端应用可以连接Cassandra集群中的任意节点。
Cassandra通过内部对数据复制的支持来提供高可用性。保存的副本数量可以配置,每个副本在集群中不同的节点上存储。如果复制因子是3,即使一或两个节点宕机,整个集群依然可用。
Cassandra中数据通过键空间(keyspace)、表、行和列形成的层级结构来建模。键空间在概念上类似于RDBMS中的数据库或模式。它是表的逻辑集合,代表一个命名空间,用来控制一组表的数据复制。表(也称为“列族”)在概念上类似于RDBMS中的表。一个列族由分块的行的集合构成。每一行由分块的键和一组列构成。特别要注意的是,尽管Cassandra中的键空间、表、行和列看起来分别和关系型数据库中的模式、表、行和列很类似,但是它们的实现和物理存储是不同的。
在Cassandra中查询模式驱动数据模型。Cassandra中的一个列族或一个表基本上就是一个物化视图。不像关系数据库那样,Cassandra不支持连接(join),这意味着相同的数据可能需要在多个列族中复制。
1.5.2 HBase
HBase也是一个分布式、可扩展、容错的NoSQL数据存储,用于存储大数据集。它运行在HDFS之上。它和Cassandra有相似的特点,二者均受启发于Bigtable(一个由Google发明的数据存储系统)。
Bigtable是一个由Google创造的分布式存储系统,用来处理跨越上千台商用服务器中拍字节级别的结构化数据。它不支持关系数据模型;相反,它提供了一种简单的数据模型,赋予客户端应用对数据存储的动态控制权。
HBase把数据存在表中。表由行组成,行由列族组成,列族由列组成。然而,HBase中的表和列与关系数据库中的表和列有很大不同。一个HBase表本质上是一个稀疏的、分布式、持久化、多维且有序的Map。
Map是一个被大多数编程语言所支持的数据结构。这是一个用于存储键值对的容器。对于通过键查找值来说,它是一种非常高效的数据结构。一般来说,键的顺序是未定义的,应用也不关心键的顺序:它提供一个键给Map,然后获取这个键所对应的值。注意,不要把Map数据结构和Hadoop MapReduce中的map函数弄混了。map函数是一个函数式编程语言的概念,用于转换数据。
Map数据结构在不同的编程语言中有不同的名字。比如,在PHP中叫作关联数组,在Python中叫作字典,在Ruby中它称为哈希,而在Java和Scala中则为映射。
HBase表是一个有序的多维或多层级的Map。第一层键是行键,它使应用能快速从数以亿计的行中读取其中一行。第二层键是列族。第三层键是列名,也称为列标识符。第四层键是时间戳。行键、列族、列名和时间戳组合起来,就唯一标识了一个单元(cell),其中包含值。值是一个未解析的字节数组。
HBase表中的行是稀疏的。不像关系数据库中的行,HBase中的每一行不必须有同样的列。每一行有同样的列族集,但一行中的某些列族可能没有存储任何内容。一个空单元不占用任何存储空间。
1.6 分布式SQL查询引擎
如前所述,SQL是最常用来查询和分析数据的语言之一。它易学且有群众基础(了解SQL的人远比了解编程语言如Java的人多)。基本上,Hive就是因此而诞生。不过,Hive依赖于MapReduce,因为它把HiveQL查询转换成MapReduce的作业任务。
MapReduce是一个强大的框架。然而,它用于处理批量数据,它有大吞吐量和高延迟。对于数据转换或者ETL(提取、转换、加载)作业来说,它的表现非常棒,但在交互式查询或实时分析方面则不是一个完美的平台。Hive继承了MapReduce的限制。这促进了使用不同架构的低延迟查询引擎的诞生。
本节讨论了几个没有使用MapReduce的开源且低延迟的分布式SQL查询引擎。Spark SQL也可以作为分布式查询引擎,但此处暂不涉及,第7章会详细讨论。
1.6.1 Impala
Impala是一个开源的数据分析软件。它提供了SQL接口来分析存储在HDFS和HBase中的大数据集,支持HiveQL以及Hive支持的类SQL语言,可用于批处理和实时查询。
Impala没有使用MapReduce。相反,它使用了一种专业的分布式查询引擎来避免高延迟。它的架构和商用数据库MPP(大规模并行处理)类似。带来的好处就是:它提供了比Hive快一个数量级的响应时间。
1.6.2 Presto
Presto也是一个用于分析大数据集的开源分布式SQL查询引擎。目前,它提供的SQL接口可以分析HDFS、Cassandra和关系数据库中的数据。它支持太字节和拍字节级数据的交互式分析查询。另外,它还支持组合多数据源进行查询。
Presto在架构上与Impala类似,没有用MapReduce来分析HDFS数据,而是实现了MPP架构。
1.6.3 Apache Drill
Apache Drill是另一个用于分析存储在HDFS或NoSQL数据库中大数据集的开源分布式SQL查询引擎,其灵感来源于Google的Dremel。它可以用来对拍字节级数据执行快速的交互式即席查询。和Presto与Impala类似,它实现了一个集群式的MPP架构。它支持ANSI SQL和JDBC/ODBC接口,所以可以使用在任何支持JDBC/ODBC的BI或数据可视化应用中。
Apache Drill的主要特性包括:动态模式发现,灵活的数据模型,去中心化的元数据和可扩展性。使用Drill查询数据集时,模式规范并不是必需的。它使用自描述的格式(如Avro、JSON、Parquet和NoSQL)所提供的信息来决定数据集的模式。它也能处理查询中模式的更改。
Drill支持层级式的数据模型来查询复杂数据。它可以查询复杂的嵌套数据结构。比如,它可以用来查询存储在JSON或Parquet中的嵌套数据而不用“铺平”它们。
在Drill中,中心化的元数据也不是必需的。它通过数据源的存储插件获取元数据。因为不依赖中心化的元数据,所以Drill可以用来从多个数据源中立即查询数据,比如,Hive、HBase和文件。因此,它可以用作一个数据可视化平台。
Dirll兼容Hive。可以在Hive环境中使用Drill来实现对现有Hive表的快速、交互式的即席查询。它支持Hive的元数据、UDF(用户定义的函数)和文件格式。
1.7 总结
近年来数据的指数级增长给许多大数据技术带来了机会。传统的专有产品要么无法处理大数据,要么代价太昂贵。这就为开源大数据技术打开了一扇门。仅仅在过去几年里,这个领域的快速创新已经催生出很多新产品。大数据领域如此之大,以至于可以写一本书专门来介绍各种各样的大数据技术。
本章仅讨论了几项与Spark相关的大数据技术,也介绍了Hadoop及其生态系统中的关键技术。Spark也是这个生态系统中的一部分。
Spark将在第3章介绍。第2章会先讨论Scala,一种集函数式编程和面向对象编程于一体的编程语言。理解Scala非常重要,因为本书中所有示例代码都用Scala编写。另外,Spark本身用Scala所写,但也支持其他语言,如Java、Python和R。