读书笔记之《大数据技术原理与应用》
大数据计算模式及其代表产品
Hadoop的核心是分布式文件系统(Hadoop Distributed File System,HDFS)和MapReduce。HDFS是针对谷歌文件系统(Google File System,GFS)的开源实现,是面向普通硬件环境的分布式文件系统,具有较高的读写速度、很好的容错性和可伸缩性,支持大规模数据的分布式存储,其冗余数据存储的方式很好地保证了数据的安全性。MapReduce是针对谷歌 MapReduce 的开源实现,允许用户在不了解分布式系统底层细节的情况下开发并行应用程序,采用MapReduce来整合分布式文件系统上的数据,可保证分析和处理数据的高效性。
Hadoop生态系统
值得关注的组件
- 【Sqoop】是SQL-to-Hadoop的缩写,主要用来在Hadoop和关系数据库之间交换数据,可以改进数据的互操作性。通过Sqoop可以方便地将数据从MySQL、Oracle、PostgreSQL等关系数据库中导入Hadoop(可以导入HDFS、HBase或Hive),或者将数据从Hadoop导出到关系数据库,使得传统关系数据库和Hadoop之间的数据迁移变得非常方便。
- 【Flume】Flume是Cloudera提供的一个高可用的、高可靠的、分布式的海量日志采集、聚合和传输的系统。Flume 支持在日志系统中定制各类数据发送方,用于收集数据;
名称节点负责文件和目录的创建、删除和重命名等,同时管理着数据节点和文件块的映射关系,因此客户端只有访问名称节点才能找到请求的文件块所在的位置,进而到相应位置读取所需文件块。
数据节点负责数据的存储和读取,在存储时,由名称节点分配存储位置,然后由客户端把数据直接写入相应数据节点;在读取时,客户端从名称节点获得数据节点和文件块的映射关系,然后就可以到相应位置访问文件块。数据节点也要根据名称节点的命令创建、删除数据块和冗余复制。
HDFS 采用名称节点(NameNode)来管理文件系统的元数据,这些元数据被保存在内存中,从而使客户端可以快速获取文件实际存储位置。通常,每个文件、目录和块大约占150字节,如果有1 000万个文件,每个文件对应一个块,那么,名称节点至少要消耗3 GB的内存来保存这些元数据信息。很显然,这时元数据检索的效率就比较低了,需要花费较多的时间找到一个文件的实际存储位置。而且,如果继续扩展到数十亿个文件时,名称节点保存元数据所需要的内存空间就会大大增加,以现有的硬件水平,是无法在内存中保存如此大量的元数据的。其次,用MapReduce处理大量小文件时,会产生过多的Map任务,线程管理开销会大大增加,因此处理大量小文件的速度远远低于处理同等大小的大文件的速度。再次,访问大量小文件的速度远远低于访问几个大文件的速度,因为访问大量小文件,需要不断从一个数据节点跳到另一个数据节点,严重影响性能。
HDFS也同样采用了块的概念,默认的一个块大小是64 MB。在HDFS中的文件会被拆分成多个块,每个块作为独立的单元进行存储。块的大小也不宜设置过大,因为,通常MapReduce中的Map任务一次只处理一个块中的数据,如果启动的任务太少,就会降低作业并行处理速度。
名称节点(NameNode)负责管理分布式文件系统的命名空间(Namespace),保存了两个核心的数据结构,即FsImage和EditLog。FsImage用于维护文件系统树以及文件树中所有的文件和文件夹的元数据,操作日志文件EditLog中记录了所有针对文件的创建、删除、重命名等操作。名称节点记录了每个文件中各个块所在的数据节点的位置信息,但是并不持久化存储这些信息,而是在系统每次启动时扫描所有数据节点重构得到这些信息。
名称节点在启动时,会将 FsImage 的内容加载到内存当中,然后执行 EditLog 文件中的各项操作,使得内存中的元数据保持最新。这个操作完成以后,就会创建一个新的 FsImage 文件和一个空的EditLog文件。名称节点启动成功并进入正常运行状态以后,HDFS中的更新操作都会被写入到 EditLog,而不是直接写入FsImage,这是因为对于分布式文件系统而言,FsImage文件通常都很庞大(一般都是GB级别以上),如果所有的更新操作都直接往FsImage文件中添加,那么系统就会变得非常缓慢。相对而言,EditLog通常都要远远小于FsImage,更新操作写入到EditLog是非常高效的。名称节点在启动的过程中处于“安全模式”,只能对外提供读操作,无法提供写操作。启动过程结束后,系统就会退出安全模式,进入正常运行状态,对外提供读写操作。
在名称节点运行期间,HDFS会不断发生更新操作,这些更新操作都是直接被写入到EditLog文件,因此 EditLog 文件也会逐渐变大,如果EditLog很大,就会导致整个过程变得非常缓慢,使得名称节点在启动过程中长期处于“安全模式”,无法正常对外提供写操作,影响了用户的使用。
为了有效解决EditLog逐渐变大带来的问题,HDFS在设计中采用了第二名称节点(Secondary NameNode)。第二名称节点是HDFS架构的一个重要组成部分,具有两个方面的功能:首先,可以完成EditLog与FsImage的合并操作,减小EditLog文件大小,缩短名称节点重启时间;其次,可以作为名称节点的“检查点”,保存名称节点中的元数据信息。
每隔一段时间,第二名称节点会和名称节点通信,请求其停止使用EditLog文件,暂时将新到达的写操作添加到一个新的文件 EditLog.new 中。然后,第二名称节点把名称节点中的 FsImage 文件和EditLog文件拉回到本地,再加载到内存中;对二者执行合并操作,即在内存中逐条执行EditLog中的操作,使得FsImage保持最新。合并结束后,第二名称节点会把合并后得到的最新的FsImage文件发送到名称节点。名称节点收到后,会用最新的FsImage文件去替换旧的FsImage文件,同时用 EditLog.new 文件去替换 EditLog 文件,从而减小了 EditLog文件的大小。
第二名称节点会定期和名称节点通信,从名称节点获取 FsImage 文件和 EditLog 文件,执行合并操作得到新的FsImage 文件。从这个角度来讲,第二名称节点相当于为名称节点设置了一个“检查点(CheckPoint)”,周期性地备份名称节点中的元数据信息,当名称节点发生故障时,就可以用第二名称节点中记录的元数据信息进行系统恢复。在HDFS的设计中,也并不支持把系统直接切换到第二名称节点,因此从这个角度来讲,第二名称节点只是起到了名称节点的“检查点”作用,并不能起到“热备份”作用。即使有了第二名称节点的存在,当名称节点发生故障时,系统还是有可能会丢失部分元数据信息的。
第二名称节点工作示意图:
每个数据节点的数据实际上是保存在本地Linux文件系统中的。每个数据节点会周期性地向名称节点发送“心跳”信息,报告自己的状态,没有按时发送心跳信息的数据节点会被标记为“宕机”,不会再给它分配任何I/O请求。
当客户端需要访问一个文件时,首先把文件名发送给名称节点,名称节点根据文件名找到对应的数据块(一个文件可能包括多个数据块),再根据每个数据块信息找到实际存储各个数据块的数据节点的位置,并把数据节点位置发送给客户端,最后客户端直接访问这些数据节点获取数据。在整个访问过程中,名称节点并不参与数据的传输。这种设计方式,使得一个文件的数据能够在不同的数据节点上实现并发访问,大大提高了数据访问速度。
HDFS的命名空间包含目录、文件和块。命名空间管理是指命名空间支持对HDFS中的目录、文件和块做类似文件系统的创建、修改、删除等基本操作。在当前的HDFS体系结构中,在整个HDFS 集群中只有一个命名空间,并且只有唯一一个名称节点,该节点负责对这个命名空间进行管理。HDFS 使用的是传统的分级文件体系,因此用户可以像使用普通文件系统一样,创建、删除目录和文件,在目录间转移文件、重命名文件等。但是,HDFS 还没有实现磁盘配额和文件访问权限等功能,也不支持文件的硬连接和软连接(快捷方式)。
HDFS采用了以机架(Rack)为基础的数据存放策略。一个HDFS集群通常包含多个机架,不同机架之间的数据通信需要经过交换机或者路由器,同一个机架中不同机器之间的通信则不需要经过交换机和路由器,这意味着同一个机架中不同机器之间的通信要比不同机架之间机器的通信带宽大。
HDFS默认每个数据节点都是在不同的机架上,这种方法会存在一个缺点,那就是写入数据的时候不能充分利用同一机架内部机器之间的带宽。但是,与这点缺点相比,这种方法也带来了更多很显著的优点:首先,可以获得很高的数据可靠性,即使一个机架发生故障,位于其他机架上的数据副本仍然是可用的;其次,在读取数据的时候,可以在多个机架上并行读取数据,大大提高了数据读取速度;最后,可以更容易地实现系统内部负载均衡和错误处理。
HDFS 默认的冗余复制因子是 3,每一个文件块会被同时保存到 3 个地方,其中,有两份副本放在同一个机架的不同机器上面,第三个副本放在不同机架的机器上面,这样既可以保证机架发生异常时的数据恢复,也可以提高数据读写性能。
HDFS的数据复制采用了流水线复制的策略,大大提高了数据复制过程的效率。当客户端要往HDFS中写入一个文件时,这个文件会首先被写入本地,并被切分成若干个块,每个块的大小是由HDFS的设定值来决定的。每个块都向HDFS集群中的名称节点发起写请求,名称节点会根据系统中各个数据节点的使用情况,选择一个数据节点列表返回给客户端,然后客户端就把数据首先写入列表中的第一个数据节点,同时把列表传给第一个数据节点,当第一个数据节点接收到4 KB数据的时候,写入本地,并且向列表中的第二个数据节点发起连接请求,把自己已经接收到的4KB数据和列表传给第二个数据节点,当第二个数据节点接收到4 KB数据的时候,写入本地,并且向列表中的第三个数据节点发起连接请求,依次类推,列表中的多个数据节点形成一条数据复制的流水线。最后,当文件写完的时候,数据复制也同时完成。
即由于一些数据节点的不可用,会导致一些数据块的副本数量小于冗余因子。名称节点会定期检查这种情况,一旦发现某个数据块的副本数量小于冗余因子,就会启动数据冗余复制,为它生成新的副本。HDFS 与其他分布式文件系统的最大区别就是可以调整冗余数据的位置。
网络传输和磁盘错误等因素都会造成数据错误。客户端在读取到数据后,会采用md5和sha1对数据块进行校验,以确定读取到正确的数据。在文件被创建时,客户端就会对每一个文件块进行信息摘录,并把这些信息写入同一个路径的隐藏文件里面。当客户端读取文件的时候,会先读取该信息文件,然后利用该信息文件对每个读取的数据块进行校验,如果校验出错,客户端就会请求到另外一个数据节点读取该文件块,并且向名称节点报告这个文件块有错误,名称节点会定期检查并且重新复制这个块。
在HBase中执行更新操作时,并不会删除数据旧的版本,而是生成一个新的版本,旧有的版本仍然保留。
HBase 实际上就是一个稀疏、多维、持久化存储的映射表,它采用行键(RowKey)、列族(Column Family)、列限定符(Column Qualifier)和时间戳(Timestamp)进行索引,每个值都是未经解释的字节数组byte[]。
列族需要在表创建时就定义好,数量不能太多(HBase的一些缺陷使得列族数量只限于几十个),而且不要频繁修改。存储在一个列族当中的所有数据,通常都属于同一种数据类型,这通常意味着具有更高的压缩率。列名都以列族作为前缀。列族里的数据通过列限定符(或列)来定位。列限定符不用事先定义。
HBase中需要根据行键、列族、列限定符和时间戳来确定一个单元格,因此可以视为一个“四维坐标”,即[行键, 列族, 列限定符, 时间戳]。如果把所有坐标看成一个整体,视为“键”,把四维坐标对应的单元格中的数据视为“值”,那么,HBase也可以看成一个键值数据库。
HBase中的每个表是由许多行组成的,但是在物理存储层面,它是采用了基于列的存储方式,而不是像传统关系数据库那样采用基于行的存储方式。(面向列的存储)
HBase的实现包括一个Master主服务器和多个Region服务器。Region服务器负责存储和维护分配给自己的Region,处理来自客户端的读写请求。主服务器 Master 负责管理和维护 HBase表的分区信息,比如,一个表被分成了哪些Region,每个Region被存放在哪台Region服务器上,同时也负责维护Region服务器列表。因此,如果Master主服务器死机,那么整个系统都会无效。Master会实时监测集群中的Region服务器,把特定的Region分配到可用的Region服务器上,并确保整个集群内部不同Region服务器之间的负载均衡,当某个Region服务器因出现故障而失效时,Master会把该故障服务器上存储的Region重新分配给其他可用的Region服务器。除此以外,Master还处理模式变化,如表和列族的创建。客户端并不是直接从Master主服务器上读取数据,而是在获得Region的存储位置信息后,直接从Region服务器上读取数据。尤其需要指出的是,HBase客户端并不依赖于Master而是借助于Zookeeper来获得Region的位置信息的,所以大多数客户端从来不和主服务器Master通信,这种设计方式使Master的负载很小。
对于每个HBase表而言,表中的行是根据行键的值的字典序进行维护的,表中包含的行的数量可能非常庞大,无法存储在一台机器上,需要分布存储到多台机器上。因此,需要根据行键的值对表中的行进行分区,每个行区间构成一个分区,被称为“Region”,包含了位于某个值域区间内的所有数据,它是负载均衡和数据分发的基本单位,这些Region会被分发到不同的Region服务器上。
初始时,每个表只包含一个Region,随着数据的不断插入,Region会持续增大,当一个Region中包含的行数量达到一个阈值时,就会被自动等分成两个新的Region。随着表中行的数量继续增加,就会分裂出越来越多的Region。
每个Region的默认大小是100 MB到200 MB,是HBase中负载均衡和数据分发的基本单位。Master主服务器会把不同的Region分配到不同的Region服务器上,但是同一个Region是不会被拆分到多个Region服务器上的。每个Region服务器负责管理一个Region集合,通常在每个Region服务器上会放置10~1000个Region。
每个Region都有一个RegionID来标识它的唯一性,这样,一个Region标识符就可以表示成“表名+开始主键+RegionID”。
为了定位每个Region所在的位置,就可以构建一张映射表,映射表的每个条目(或每行)包含两项内容,一个是Region标识符,另一个是Region服务器标识,这个条目就表示Region和Region服务器之间的对应关系,从而就可以知道某个Region被保存在哪个Region服务器中。这个映射表包含了关于Region的元数据(即Region和Region服务器之间的对应关系),因此也被称为“元数据表”,又名“.META.表”。当一个HBase表中的Region数量非常庞大的时候,.META.表的条目就会非常多,一个服务器保存不下,也需要分区存储到不同的服务器上,因此.META.表也会被分裂成多个Region,这时,为了定位这些Region,就需要再构建一个新的映射表,记录所有元数据的具体位置,这个新的映射表就是“根数据表”,又名“-ROOT-表”。-ROOT-表是不能被分割的,永远只存在一个 Region用于存放-ROOT-表,因此这个用来存放-ROOT-表的唯一一个 Region,它的名字是在程序中被写死的,Master主服务器永远知道它的位置。
HBase使用类似B+树的三层结构来保存Region位置信息,为了加快访问速度,.META.表的全部Region都会被保存在内存中。
客户端访问用户数据之前,需要首先访问 Zookeeper,获取-ROOT-表的位置信息,然后访问-ROOT-表,获得.META.表的信息,接着访问.META.表,找到所需的Region具体位于哪个Region服务器,最后才会到该Region服务器读取数据。该过程需要多次网络操作,为了加速寻址过程,一般会在客户端做缓存,把查询过的位置信息缓存起来,这样以后访问相同的数据时,就可以直接从客户端缓存中获取Region的位置信息,而不需要每次都经历一个“三级寻址”过程。
需要注意的是,随着HBase中表的不断更新,Region的位置信息可能会发生变化,但是客户端缓存并不会自己检测Region位置信息是否失效,而是在需要访问数据时,从缓存中获取Region位置信息却发现不存在的时候,才会判断出缓存失效,这时,就需要再次经历上述的“三级寻址”过程,重新获取最新的 Region 位置信息去访问数据,并用最新的 Region 位置信息替换缓存中失效的信息。当一个客户端从Zookeeper服务器上拿到-ROOT-表的地址以后,就可以通过“三级寻址”找到用户数据表所在的Region服务器,并直接访问该Region服务器获得数据,没有必要再连接主服务器Master。因此,主服务器的负载相对就小了很多。
HBase系统架构
Region服务器是 HBase中最核心的模块,负责维护分配给自己的 Region,并响应用户的读写请求。Region服务器内部管理了一系列Region对象和一个HLog文件,其中HLog是磁盘上面的记录文件,它记录着所有的更新操作。每个Region对象又是由多个 Store 组成的,每个 Store 对应了表中的一个列族的存储。每个 Store 又包含了一个 MemStore和若干个StoreFile,其中,MemStore是在内存中的缓存,保存最近更新的数据;StoreFile是磁盘中的文件,这些文件都是B树结构的,方便快速读取。StoreFile在底层的实现方式是HDFS文件系统的HFile,HFile的数据块通常采用压缩方式存储,压缩之后可以大大减少网络I/O和磁盘I/O。
HBase读写过程:当用户写入数据时,会被分配到相应的 Region 服务器去执行操作。用户数据首先被写入到MemStore和HLog中,当操作写入HLog之后,commit()调用才会将其返回给客户端。当用户读取数据时,Region服务器会首先访问MemStore缓存,如果数据不在缓存中,才会到磁盘上面的StoreFile中去寻找。
HBase缓存刷新:MemStore缓存的容量有限,系统会周期性地调用Region.flushcache()把MemStore缓存里面的内容写到磁盘的StoreFile文件中,清空缓存,并在HLog文件中写入一个标记,用来表示缓存中的内容已经被写入StoreFile文件中。每次缓存刷新操作都会在磁盘上生成一个新的StoreFile文件,因此每个Store会包含多个StoreFile文件。
HLog删除重建:Region服务器都有一个自己的HLog文件,在启动的时候,每个Region服务器都会检查自己的HLog文件,确认最近一次执行缓存刷新操作之后是否发生新的写入操作。如果没有更新,说明所有数据已经被永久保存到磁盘的StoreFile文件中;如果发现更新,就先把这些更新写入MemStore,然后再刷新缓存,写入到磁盘的StoreFile文件中。最后,删除旧的HLog文件,并开始为用户提供数据访问服务。
HBase数据恢复:Zookeeper会实时监测每个Region服务器的状态,当某个Region服务器发生故障时,Zookeeper会通知Master。Master首先会处理该故障Region服务器上面遗留的HLog文件,由于一个Region服务器上面可能会维护着多个Region对象,这些Region对象共用一个HLog文件,因此这个遗留的 HLog 文件中包含了来自多个Region 对象的日志记录。系统会根据每条日志记录所属的Region对象对HLog数据进行拆分,分别放到相应Region对象的目录下,然后再将失效的Region重新分配到可用的Region服务器中,并把与该Region对象相关的HLog日志记录也发送给相应的Region服务器。Region服务器领取到分配给自己的Region对象以及与之相关的HLog日志记录以后,会重新做一遍日志记录中的各种操作,把日志记录中的数据写入 MemStore 缓存,然后刷新到磁盘的StoreFile文件中,完成数据恢复。
HBase 系统中,每个 Region 服务器只需要维护一个 HLog文件,所有Region 对象共用一个 HLog,而不是每个 Region 使用一个 HLog。
不同类型的NoSQL数据库:
数据库CAP理论
- C(Consistency):一致性。它是指任何一个读操作总是能够读到之前完成的写操作的结果,也就是在分布式环境中,多点的数据是一致的。
- A(Availability):可用性。它是指快速获取数据,可以在确定的时间内返回操作结果。
- P(Tolerance of Network Partition):分区容忍性。它是指当出现网络分区的情况时(即系统中的一部分节点无法和其他节点进行通信),分离的系统也能够正常运行 一个分布式系统不可能同时满足一致性、可用性和分区容忍性这3个需求,最多只能同时满足其中2个。
数据库事务的ACID特性:
- A(Atomicity):原子性。它是指事务必须是原子工作单元,对于其数据修改,要么全都执行,要么全都不执行。
- C(Consistency):一致性。它是指事务在完成时,必须使所有的数据都保持一致状态。
- I(Isolation):隔离性。它是指由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。
- D(Durability):持久性。它是指事务完成之后,它对于系统的影响是永久性的,该修改即使出现致命的系统故障也将一直保持。
Map&Reduce
Spark基本概念
- RDD:是弹性分布式数据集(Resilient Distributed Dataset)的英文缩写,是分布式内存的一个抽象概念,提供了一种高度受限的共享内存模型。
- DAG:是Directed Acyclic Graph(有向无环图)的英文缩写,反映RDD之间的依赖关系。
- Executor:是运行在工作节点(Worker Node)上的一个进程,负责运行任务,并为应用程序存储数据。
- 应用:用户编写的Spark应用程序
- 任务:运行在Executor上的工作单元
- 作业:一个作业包含多个RDD及作用于相应RDD上的各种操作
- 阶段:是作业的基本调度单位,一个作业会分为多组任务,每组任务被称为“阶段”,或者也被称为“任务集”
Spark运行架构 包括集群资源管理器(Cluster Manager)、运行作业任务的工作节点(Worker Node)、每个应用的任务控制节点(Driver)和每个工作节点上负责具体任务的执行进程(Executor)。
在 Spark 中,一个应用(Application)由一个任务控制节点(Driver)和若干个作业(Job)构成,一个作业由多个阶段(Stage)构成,一个阶段由多个任务(Task)组成。当执行一个应用时,任务控制节点会向集群管理器(Cluster Manager)申请资源,启动Executor,并向Executor发送应用程序代码和文件,然后在 Executor 上执行任务,运行结束后执行结果会返回给任务控制节点,或者写到HDFS或者其他数据库中。