要疯了要疯了要,三天要复习七万字
图片后续会补充,或者直接找我要word文件
第一章
1.1.3 数据产生方式的变革促成大数据时代的来临
- 运营式系统阶段--数据库的出现使得数据管理的复杂度大大降低,数据往往伴随着一定的运营活动而产生并记录在数据库中,数据的产生方式是被动。
- 用户原创内容阶段--数据爆发产生于Web 2.0 时代,而Web 2.0 的最重要标志就是用户原创内容·智能手机等移动设备加速内容产生·数据产生方式是主动
- 感知式系统阶段--·感知式系统的广泛使用·人类社会数据量第三次大的飞跃最终导致了大数据的产生
1.3.2 大数据对思维方式的影响
在思维方式方面,大数据完全颠覆了传统的思维方式:
- 全样而非抽样
- 效率而非精确
- 相关而非因果
- 在社会发展方面,大数据决策逐渐成为一种新的决策方式,大数据应用有力促进了信息技术与各行业的深度融合,大数据开发大大推动了新技术和新应用的不断涌现
- 在就业市场方面,大数据的兴起使得数据科学家成为热门职业
- 在人才培养方面,大数据的兴起,将在很大程度上改变中国高校信息技术相关专业的现有教学和科研体制
1.4.1 大数据对科学研究的影响
大数据无处不在,包括金融、汽车、零售、餐饮、电信、能源、政务、医疗、体育、娱乐等在内的社会各行各业都已经融入了大数据的印迹。
第一种范式:实验科学
第二种范式:理论科学
第三种范式:计算科学
第四种范式:数据密集型科学:
在大数据环境下,一切都是以数据为中心,从数据中发现问题、解决问题,真正体现数据的价值。大数据成为科学工作者的保障,从数据中可以挖掘出未知模式和有价值的信息,服务于生产和生活,推动科技创新和社会进步。
第三种范式和第四种范式的区别:
在第三种范式中,一般是先提出可能的理论,再搜集数据,然后通过计算来验证。而对于第四种范式,是先有了大量已知的数据,然后通过计算得出之前未知的结论。
1.6 大数据计算模式
大数据计算模式 | 解决问题 | 代表产品 |
批处理计算 | 针对大规模数据的批量处理 | MapReduce、Spark等 |
流计算 | 针对流数据的实时计算 | Flink、Storm、S4、Flume、Streams、Puma、DStream、Super Mario、银河流数据处理平台等 |
图计算 | 针对大规模图结构数据的处理 | Pregel、GraphX、Giraph、PowerGraph、Hama、GoldenOrb等 |
查询分析计算 | 大规模数据的存储管理和查询分析 | Dremel、Hive、Cassandra、Impala等 |
第 2 章-大数据处理架构 Hadoop
2.2 Hadoop 生态系统(1、2、3、4、7)
HDFS | 分布式文件系统 |
MapReduce | 分布式并行编程模型 |
YARN | 资源管理和调度器 |
Tez | 运行在YARN之上的下一代Hadoop查询处理框架 |
Hive | Hadoop上的数据仓库 |
HBase | Hadoop上的非关系型的分布式数据库 |
Pig | 一个基于Hadoop的大规模数据分析平台,提供类似SQL的查询语言Pig Latin |
Sqoop | 用于在Hadoop与传统数据库之间进行数据传递 |
Oozie | Hadoop上的工作流管理系统 |
Zookeeper | 提供分布式协调一致性服务 |
Storm | 流计算框架 |
Flume | 一个高可用的,高可靠的,分布式的海量日志采集、聚合和传输的系统 |
Ambari | Hadoop快速部署工具,支持Apache Hadoop集群的供应、管理和监控 |
Kafka | 一种高吞吐量的分布式发布订阅消息系统,可以处理消费者规模的网站中的所有动作流数据 |
Spark | 类似于Hadoop MapReduce的通用并行框架 |
HDFS (Hadoop Distributed File System):
概念: HDFS是一个分布式文件系统,设计用于在廉价硬件上运行,提供高吞吐量的数据访问,非常适合大规模数据集的应用。
功能和作用: HDFS将大数据分割成块,分布式存储在一个网络中的多台机器上。这增加了系统的可靠性,通过存储数据的多个副本来防止数据丢失。
HBase:
概念: HBase是一个开源、非关系型、分布式数据库,运行在HDFS之上。
功能和作用: 它用于存储和访问大量稀疏的数据集(如网络数据、电子邮件等)。HBase提供了类似于传统数据库的功能,如表格、列和版本控制。
MapReduce:
概念: MapReduce是一个编程模型,用于大数据的处理和生成。
功能和作用: 它允许分布式处理大量数据。MapReduce工作分为两个阶段:Map阶段和Reduce阶段。Map阶段处理输入数据并生成中间输出,Reduce阶段处理Map生成的数据并输出最终结果。
Hive:
概念: Hive是一个在Hadoop上构建的数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供类似SQL的查询语言,称为HQL(Hive查询语言)。
功能和作用: Hive允许用户编写HQL查询,这些查询被转换成MapReduce任务进行执行。它用于数据摘要、查询和分析。
ZooKeeper:
概念: ZooKeeper是一个开源的分布式协调服务,它用于管理大型分布式系统。
功能和作用: ZooKeeper维护配置信息、命名、提供分布式同步和提供组服务。简单来说,它负责维护和协调分布式环境中的节点,确保所有节点都是同步的。
2.3.6 Hadoop 伪分布式安装
- Hadoop 可以在单节点上以伪分布式的方式运行,Hadoop 进程以分离的 Java 进程来运行,节点既作为 NameNode 也作为 DataNode,同时,读取的是 HDFS 中的文件
- Hadoop 的配置文件位于 /usr/local/hadoop/etc/hadoop/ 中,伪分布式需要修改2个配置文件 core-site.xml 和 hdfs-site.xml
- Hadoop的配置文件是 xml 格式,每个配置以声明 property 的 name 和 value 的方式来实现
- 修改配置文件:core-site.xml,hdfs-site.xml,mapred-site.xml
- 初始化文件系统hadoop namenode -format
- 启动所有进程start-all.sh
- 访问web界面,查看Hadoop信息
- 运行实例
- <configuration>
- <property>
- <name>hadoop.tmp.dir</name>
- <value>file:/usr/local/hadoop/tmp</value>
- <description>Abase for other temporary directories.</description>
- </property>
- <property>
- <name>fs.defaultFS</name>
- <value>hdfs://localhost:9000</value>
- </property>
- </configuration>
- hadoop.tmp.dir表示存放临时数据的目录,即包括NameNode的数据,也包括DataNode的数据。该路径任意指定,只要实际存在该文件夹即可
- name为fs.defaultFS的值,表示hdfs路径的逻辑名称
----课本上详细读
第 3 章-分布式文件系统 HDFS
3.1.1 计算机集群结构
- 分布式文件系统把文件分布存储到多个计算机节点上,成千上万的计算机节点构成计算机集群
- 与之前使用多个处理器和专用高级硬件的并行化处理装置不同的是,目前的分布式文件系统所采用的计算机集群,都是由普通硬件构成的,这就大大降低了硬件上的开销
3.1.2 分布式文件系统的结构
分布式文件系统在物理结构上是由计算机集群中的多个节点构成的,这些节点分为两类,一类叫“主节点”(Master Node)或者也被称为“名称结点”(NameNode),另一类叫“从节点”(Slave Node)或者也被称为“数据节点”(DataNode)
3.2 HDFS 简介
HDFS要实现以下目标:
●兼容廉价的硬件设备
●流数据读写
●大数据集
●简单的文件模型
●强大的跨平台兼容性
局限性:
●不适合低延迟数据访问
●无法高效存储大量小文件
●不支持多用户写入及任意修改文件
3.3 HDFS 的相关概念
1.HDFS默认一个块64MB,一个文件被分成多个块,以块作为存储单位
块的大小远远大于普通文件系统,可以最小化寻址开销
HDFS采用抽象的块概念可以带来以下几个明显的好处:
● 支持大规模文件存储:文件以块为单位进行存储,一个大规模文件可以被分拆成若干个文件块,不同的文件块可以被分发到不同的节点上,因此,一个文件的大小不会受到单个节点的存储容量的限制,可以远远大于网络中任意节点的存储容量
● 简化系统设计:首先,大大简化了存储管理,因为文件块大小是固定的,这样就可以很容易计算出一个节点可以存储多少文件块;其次,方便了元数据的管理,元数据不需要和文件块一起存储,可以由其他系统负责管理元数据
● 适合数据备份:每个文件块都可以冗余存储到多个节点上,大大提高了系统的容错性和可用性
名称节点的数据结构
- 在HDFS中,名称节点(NameNode)负责管理分布式文件系统的命名空间(Namespace),保存了两个核心的数据结构,即FsImage和EditLog
- FsImage用于维护文件系统树以及文件树中所有的文件和文件夹的元数据
- 操作日志文件EditLog中记录了所有针对文件的创建、删除、重命名等操作
- 名称节点记录了每个文件中各个块所在的数据节点的位置信息
FsImage文件包含文件系统中所有目录和文件inode的序列化形式。每个inode是一个文件或目录的元数据的内部表示,并包含此类信息:文件的复制等级、修改和访问时间、访问权限、块大小以及组成文件的块。对于目录,则存储修改时间、权限和配额元数据
FsImage文件没有记录每个块存储在哪个数据节点。而是由名称节点把这些映射信息保留在内存中,当数据节点加入HDFS集群时,数据节点会把自己所包含的块列表告知给名称节点,此后会定期执行这种告知操作,以确保名称节点的块映射是最新的。
在名称节点启动的时候,它会将FsImage文件中的内容加载到内存中,之后再执行EditLog文件中的各项操作,使得内存中的元数据和实际的同步,存在内存中的元数据支持客户端的读操作。
一旦在内存中成功建立文件系统元数据的映射,则创建一个新的FsImage文件和一个空的EditLog文件
名称节点起来之后,HDFS中的更新操作会重新写到EditLog文件中,因为FsImage文件一般都很大(GB级别的很常见),如果所有的更新操作都往FsImage文件中添加,这样会导致系统运行的十分缓慢,但是,如果往EditLog文件里面写就不会这样,因为EditLog 要小很多。每次执行写操作之后,且在向客户端发送成功代码之前,edits文件都需要同步更新
名称节点运行期间EditLog不断变大的问题:
在名称节点运行期间,HDFS的所有更新操作都是直接写到EditLog中,久而久之, EditLog文件将会变得很大
虽然这对名称节点运行时候是没有什么明显影响的,但是,当名称节点重启的时候,名称节点需要先将FsImage里面的所有内容映像到内存中,然后再一条一条地执行EditLog中的记录,当EditLog文件非常大的时候,会导致名称节点启动操作非常慢,而在这段时间内HDFS系统处于安全模式,一直无法对外提供写操作,影响了用户的使用
如何解决?答案是:SecondaryNameNode第二名称节点
第二名称节点是HDFS架构中的一个组成部分,它是用来保存名称节点中对HDFS 元数据信息的备份,并减少名称节点重启的时间。SecondaryNameNode一般是单独运行在一台机器上
SecondaryNameNode的工作情况:
(1)SecondaryNameNode会定期和NameNode通信,请求其停止使用EditLog文件,暂时将新的写操作写到一个新的文件edit.new上来,这个操作是瞬间完成,上层写日志的函数完全感觉不到差别;
(2)SecondaryNameNode通过HTTP GET方式从NameNode上获取到FsImage和EditLog文件,并下载到本地的相应目录下;
(3)SecondaryNameNode将下载下来的FsImage载入到内存,然后一条一条地执行EditLog文件中的各项更新操作,使得内存中的FsImage保持最新;这个过程就是EditLog和FsImage文件合并;
(4)SecondaryNameNode执行完(3)操作之后,会通过post方式将新的FsImage文件发送到NameNode节点上
(5)NameNode将从SecondaryNameNode接收到的新的FsImage替换旧的FsImage文件,同时将edit.new替换EditLog文件,通过这个过程EditLog就变小了
- 数据节点是分布式文件系统HDFS的工作节点,负责数据的存储和读取,会根据客户端或者是名称节点的调度来进行数据的存储和检索,并且向名称节点定期发送自己所存储的块的列表
- 每个数据节点中的数据会被保存在各自节点的本地Linux文件系统中
3.5.1 数据的冗余存储
作为一个分布式文件系统,为了保证系统的容错性和可用性,HDFS采用了多副本方式对数据进行冗余存储,通常一个数据块的多个副本会被分布到不同的数据节点上,如图3-5所示,数据块1被分别存放到数据节点A和C上,数据块2被存放在数据节点A和B上。这种多副本方式具有以下几个优点:
(1)加快数据传输速度
(2)容易检查数据错误
(3)保证数据可靠性
3.5.2 数据存取策略
1.数据存放
- 第一个副本:放置在上传文件的数据节点;如果是集群外提交,则随机挑选一台磁盘不太满、CPU不太忙的节点
- 第二个副本:放置在与第一个副本不同的机架的节点上
- 第三个副本:与第一个副本相同机架的其他节点上
- 更多副本:随机节点
- 数据读取
- HDFS提供了一个API可以确定一个数据节点所属的机架ID,客户端也可以调用API获取自己所属的机架ID
- 当客户端读取数据时,从名称节点获得数据块不同副本的存放位置列表,列表中包含了副本所在的数据节点,可以调用API来确定客户端和这些数据节点所属的机架ID,当发现某个数据块副本对应的机架ID和客户端对应的机架ID相同时,就优先选择该副本读取数据,如果没有发现,就随机选择一个副本读取数据
第 4 章-分布式数据库 HBase
4.1.2 HBase 简介
HBase是一个高可靠、高性能、面向列、可伸缩的分布式数据库,是谷歌BigTable的开源实现,主要用来存储非结构化和半结构化的松散数据。HBase的目标是处理非常庞大的表,可以通过水平扩展的方式,利用廉价计算机集群处理由超过10亿行数据和数百万列元素组成的数据表。
关系数据库已经流行很多年,并且Hadoop已经有了HDFS和MapReduce,为什么需要HBase?
- Hadoop可以很好地解决大规模数据的离线批量处理问题,但是,受限于Hadoop MapReduce编程框架的高延迟数据处理机制,使得Hadoop无法满足大规模数据实时处理应用的需求
- HDFS面向批量访问模式,不是随机访问模式
- 传统的通用关系型数据库无法应对在数据规模剧增时导致的系统扩展性和性能问题(分库分表也不能很好解决)
- 传统关系数据库在数据结构变化时一般需要停机维护;空列浪费存储空间
- 因此,业界出现了一类面向半结构化数据存储和处理的高可扩展、低写入/查询延迟的系统,例如,键值数据库、文档数据库和列族数据库(如BigTable和HBase等)
- HBase已经成功应用于互联网服务领域和传统行业的众多在线式数据分析处理系统中
4.1.3 HBase 与传统关系数据库的对比分析
HBase与传统的关系数据库的区别主要体现在以下几个方面:
(1)数据类型:关系数据库采用关系模型,具有丰富的数据类型和存储方式,HBase则采用了更加简单的数据模型,它把数据存储为未经解释的字符串。
(2)数据操作:关系数据库中包含了丰富的操作,其中会涉及复杂的多表连接。HBase操作则不存在复杂的表与表之间的关系,只有简单的插入、查询、删除、清空等,因为HBase在设计上就避免了复杂的表和表之间的关系。
(3)存储模式:关系数据库是基于行模式存储的。HBase是基于列存储的,每个列族都由几个文件保存,不同列族的文件是分离的。
(4)数据索引:关系数据库通常可以针对不同列构建复杂的多个索引,以提高数据访问性能。HBase只有一个索引——行键,通过巧妙的设计,HBase中的所有访问方法,或者通过行键访问,或者通过行键扫描,从而使得整个系统不会慢下来。
(5)数据维护:在关系数据库中,更新操作会用最新的当前值去替换记录中原来的旧值,旧值被覆盖后就不会存在。而在HBase中执行更新操作时,并不会删除数据旧的版本,而是生成一个新的版本,旧有的版本仍然保留。
(6)可伸缩性:关系数据库很难实现横向扩展,纵向扩展的空间也比较有限。相反,HBase和BigTable这些分布式数据库就是为了实现灵活的水平扩展而开发的,能够轻易地通过在集群中增加或者减少硬件数量来实现性能的伸缩。
4.3.1 数据模型概述
- HBase是一个稀疏、多维度、排序的映射表,这张表的索引是行键、列族、列限定符和时间戳
- 每个值是一个未经解释的字符串,没有数据类型
- 用户在表中存储数据,每一行都有一个可排序的行键和任意多的列
- 表在水平方向由一个或者多个列族组成,一个列族中可以包含任意多个列,同一个列族里面的数据存储在一起
- 列族支持动态扩展,可以很轻松地添加一个列族或列,无需预先定义列的数量以及类型,所有列均以字符串形式存储,用户需要自行进行数据类型转换
- HBase中执行更新操作时,并不会删除数据旧的版本,而是生成一个新的版本,旧有的版本仍然保留(这是和HDFS只允许追加不允许修改的特性相关的)
4.3.2 数据模型的相关概念
- 表:HBase采用表来组织数据,表由行和列组成,列划分为若干个列族
- 行:每个HBase表都由若干行组成,每个行由行键(row key)来标识。
- 列族:一个HBase表被分组成许多“列族”(Column Family)的集合,它是基本的访问控制单元
- 列限定符:列族里的数据通过列限定符(或列)来定位
- 单元格:在HBase表中,通过行、列族和列限定符确定一个“单元格”(cell),单元格中存储的数据没有数据类型,总被视为字节数组byte[]
- 时间戳:每个单元格都保存着同一份数据的多个版本,这些版本采用时间戳进行索引
4.3.3 数据坐标
HBase中需要根据行键、列族、列限定符和时间戳来确定一个单元格,因此,可以视为一个“四维坐标”,即[行键, 列族, 列限定符, 时间戳]
4.4.3 Region 的定位
主服务器Master负责管理和维护HBase表的分区信息,维护Region服务器列表,分配Region,负载均衡。Region服务器负责存储和维护分配给自己的Region,处理来自客户端的读写请求
客户端并不是直接从Master主服务器上读取数据,而是在获得Region的存储位置信息后,直接从Region服务器上读取数据。客户端并不依赖Master,而是通过Zookeeper来获得Region位置信息,大多数客户端甚至从来不和Master通信,这种设计方式使得Master负载很小
开始只有一个Region,后来不断分裂
Region拆分操作非常快,接近瞬间,因为拆分之后的Region读取的仍然是原存储文件,直到“合并”过程把存储文件异步地写到独立的文件之后,才会读取新文件
定位:
元数据表,又名.META.表,存储了Region和Region服务器的映射关系
当HBase表很大时, .META.表也会被分裂成多个Region
根数据表,又名-ROOT-表,记录所有元数据的具体位置
-ROOT-表只有唯一一个Region,名字是在程序中被写死的
Zookeeper文件记录了-ROOT-表的位置
层次 | 名称 | 作用 | |
第一层 | Zookeeper文件 | 记录了-ROOT-表的位置信息 | |
第二层 | -ROOT-表 | 记录了.META.表的Region位置信息 -ROOT-表只能有一个Region。 通过-ROOT-表,就可以访问.META.表中的数据 | |
第三层 | .META.表 | 记录了用户数据表的Region位置信息,.META.表可以有多个Region, 保存了HBase中所有用户数据表的Region位置信息 |
为了加快访问速度,.META.表的全部Region都会被保存在内存中
假设.META.表的每行(一个映射条目)在内存中大约占用1KB,并且每个Region限制为128MB,那么,上面的三层结构可以保存的用户数据表的Region数目的计算方法是:
(-ROOT-表能够寻址的.META.表的Region个数)×(每个.META.表的 Region可以寻址的用户数据表的Region个数)
一个-ROOT-表最多只能有一个Region,也就是最多只能有128MB,按照每行(一个映射条目)占用1KB内存计算,128MB空间可以容纳128MB/1KB=2^17行,也就是说,一个-ROOT-表可以寻址2^17个.META.表的Region。
同理,每个.META.表的 Region可以寻址的用户数据表的Region个数是128MB/1KB=2^17。
最终,三层结构可以保存的Region数目是(128MB/1KB) × (128MB/1KB) = 2^34个Region
客户端访问数据时的“三级寻址”
为了加速寻址,客户端会缓存位置信息,同时,需要解决缓存失效问题
寻址过程客户端只需要询问Zookeeper服务器,不需要连接Master服务器
4.5.2 Region 服务器的工作原理 --**
1.用户读写数据过程
- 用户写入数据时,被分配到相应Region服务器去执行
- 用户数据首先被写入到MemStore和Hlog中
- 只有当操作写入Hlog之后,commit()调用才会将其返回给客户端
- 当用户读取数据时,Region服务器会首先访问MemStore缓存,如果找不到,再去磁盘上面的StoreFile中寻找
2.缓存的刷新
- 系统会周期性地把MemStore缓存里的内容刷写到磁盘的StoreFile文件中,清空缓存,并在Hlog里面写入一个标记
- 每次刷写都生成一个新的StoreFile文件,因此,每个Store包含多个StoreFile文件
- 每个Region服务器都有一个自己的HLog 文件,每次启动都检查该文件,确认最近一次执行缓存刷新操作之后是否发生新的写入操作;如果发现更新,则先写入MemStore,再刷写到StoreFile,最后删除旧的Hlog文件,开始为用户提供服务
- StoreFile的合并
- 每次刷写都生成一个新的StoreFile,数量太多,影响查找速度
- 调用Store.compact()把多个合并成一个
- 合并操作比较耗费资源,只有数量达到一个阈值才启动合并
第 5 章-NoSQL 数据库
5.1 NoSQL 简介
通常,NoSQL数据库具有以下几个特点:
(1)灵活的可扩展性
(2)灵活的数据模型
(3)与云计算紧密融合
5.2 NoSQL 兴起的原因
1.关系数据库已经无法满足Web2.0的需求。主要表现在以下几个方面:
(1)无法满足海量数据的管理需求
(2)无法满足数据高并发的需求
(3)无法满足高可扩展性和高可用性的需求
MySQL集群是否可以完全解决问题?
- 复杂性:部署、管理、配置很复杂
- 数据库复制:MySQL主备之间采用复制方式,只能是异步复制,当主库压力较大时可能产生较大延迟,主备切换可能会丢失最后一部分更新事务,这时往往需要人工介入,备份和恢复不方便
- 扩容问题:如果系统压力过大需要增加新的机器,这个过程涉及数据重新划分,整个过程比较复杂,且容易出错
- 动态数据迁移问题:如果某个数据库组压力过大,需要将其中部分数据迁移出去,迁移过程需要总控节点整体协调,以及数据库节点的配合。这个过程很难做到自动化
2、“One size fits all”模式很难适用于截然不同的业务场景
- 关系模型作为统一的数据模型既被用于数据分析,也被用于在线业务。但这两者一个强调高吞吐,一个强调低延时,已经演化出完全不同的架构。用同一套模型来抽象显然是不合适的
- Hadoop就是针对数据分析
- MongoDB、Redis等是针对在线业务,两者都抛弃了关系模型
3、关系数据库的关键特性包括完善的事务机制和高效的查询机制。但是,关系数据库引以为傲的两个关键特性,到了Web2.0时代却成了鸡肋,主要表现在以下几个方面:
(1)Web2.0网站系统通常不要求严格的数据库事务
(2)Web2.0并不要求严格的读写实时性
(3)Web2.0通常不包含大量复杂的SQL查询(去结构化,存储空间换取更好的查询性能)
比较标准 | RDBMS | NoSQL | 备注 |
数据库原理 | 完全支持 | 部分支持 | RDBMS有关系代数理论作为基础 NoSQL没有统一的理论基础 |
数据规模 | 大 | 超大 | RDBMS很难实现横向扩展,纵向扩展的空间也比较有限,性能会随着数据规模的增大而降低 NoSQL可以很容易通过添加更多设备来支持更大规模的数据 |
数据库模式 | 固定 | 灵活 | RDBMS需要定义数据库模式,严格遵守数据定义和相关约束条件 NoSQL不存在数据库模式,可以自由灵活定义并存储各种不同类型的数据 |
查询效率 | 快 | 可以实现高效的简单查询,但是不具备高度结构化查询等特性,复杂查询的性能不尽人意 | RDBMS借助于索引机制可以实现快速查询(包括记录查询和范围查询) 很多NoSQL数据库没有面向复杂查询的索引,虽然NoSQL可以使用MapReduce来加速查询,但是,在复杂查询方面的性能仍然不如RDBMS |
比较标准 | RDBMS | NoSQL | 备注 |
一致性 | 强一致性 | 弱一致性 | RDBMS严格遵守事务ACID模型,可以保证事务强一致性 很多NoSQL数据库放松了对事务ACID四性的要求,而是遵守BASE模型,只能保证最终一致性 |
数据完整性 | 容易实现 | 很难实现 | 任何一个RDBMS都可以很容易实现数据完整性,比如通过主键或者非空约束来实现实体完整性,通过主键、外键来实现参照完整性,通过约束或者触发器来实现用户自定义完整性 但是,在NoSQL数据库却无法实现 |
扩展性 | 一般 | 好 | RDBMS很难实现横向扩展,纵向扩展的空间也比较有限 NoSQL在设计之初就充分考虑了横向扩展的需求,可以很容易通过添加廉价设备实现扩展 |
可用性 | 好 | 很好 | RDBMS在任何时候都以保证数据一致性为优先目标,其次才是优化系统性能,随着数据规模的增大,RDBMS为了保证严格的一致性,只能提供相对较弱的可用性 大多数NoSQL都能提供较高的可用性 |
比较标准 | RDBMS | NoSQL | 备注 |
标准化 | 是 | 否 | RDBMS已经标准化(SQL) NoSQL还没有行业标准,不同的NoSQL数据库都有自己的查询语言,很难规范应用程序接口 StoneBraker认为:NoSQL缺乏统一查询语言,将会拖慢NoSQL发展 |
技术支持 | 高 | 低 | RDBMS经过几十年的发展,已经非常成熟,Oracle等大型厂商都可以提供很好的技术支持 NoSQL在技术支持方面仍然处于起步阶段,还不成熟,缺乏有力的技术支持 |
可维护性 | 复杂 | 复杂 | RDBMS需要专门的数据库管理员(DBA)维护 NoSQL数据库虽然没有DBMS复杂,也难以维护 |
5.5 NoSQL 的三大基石
5.5.1所谓的CAP指的是:
- C(Consistency):一致性,是指任何一个读操作总是能够读到之前完成的写操作的结果,也就是在分布式环境中,多点的数据是一致的,或者说,所有节点在同一时间具有相同的数据
- A:(Availability):可用性,是指快速获取数据,可以在确定的时间内返回操作结果,保证每个请求不管成功或者失败都有响应;
- P(Tolerance of Network Partition):分区容忍性,是指当出现网络分区的情况时(即系统中的一部分节点无法和其他节点进行通信),分离的系统也能够正常运行,也就是说,系统中任意信息的丢失或失败不会影响系统的继续运作。
CAP理论告诉我们,一个分布式系统不可能同时满足一致性、可用性和分区容忍性这三个需求,最多只能同时满足其中两个,正所谓“鱼和熊掌不可兼得”。
当处理CAP的问题时,可以有几个明显的选择:
- CA:也就是强调一致性(C)和可用性(A),放弃分区容忍性(P),最简单的做法是把所有与事务相关的内容都放到同一台机器上。很显然,这种做法会严重影响系统的可扩展性。传统的关系数据库(MySQL、SQL Server和PostgreSQL),都采用了这种设计原则,因此,扩展性都比较差
- CP:也就是强调一致性(C)和分区容忍性(P),放弃可用性(A),当出现网络分区的情况时,受影响的服务需要等待数据一致,因此在等待期间就无法对外提供服务
- AP:也就是强调可用性(A)和分区容忍性(P),放弃一致性(C),允许系统返回不一致的数据
场景:订单系统下单买了1台电脑,库存系统中电脑的数量-1。分布式系统中,系统之间需要网络通信等各种问题。无法实现买了1台电脑,库存即时-1。
- CP:订单创建后,等待库存减少后才返回结果。保证数据一致,强一致性表现,用户体验差。(类似银行存钱)
- AP:订单创建后,不等待库存减少后就返回结果。那库存数据怎么办?(异步处理后通知订单系统,若异步处理失败,有补偿机制(重新发请求,补录,校对程序)保证数据一致)。(类似淘宝)
- AC:不拆分数据库系统,在同一个数据库的同一个事务中完成操作,即单体应用。下单和减库存,存在于同一个事务。缺点:不能做分区, 分区涉及网络,进而涉及分区容错性,进而选CP,AP。
5.5.2 BASE
说起BASE(Basically Availble, Soft-state, Eventual consistency),不得不谈到ACID
ACID | BASE |
原子性(Atomicity) | 基本可用(Basically Available) |
一致性(Consistency) | 软状态/柔性事务(Soft state) |
隔离性(Isolation) | 最终一致性 (Eventual consistency) |
持久性 (Durable) |
|
一个数据库事务具有ACID四性:
- A(Atomicity):原子性,是指事务必须是原子工作单元,对于其数据修改,要么全都执行,要么全都不执行
- C(Consistency):一致性,是指事务在完成时,必须使所有的数据都保持一致状态
- I(Isolation):隔离性,是指由并发事务所做的修改必须与任何其它并发事务所做的修改隔离
- D(Durability):持久性,是指事务完成之后,它对于系统的影响是永久性的,该修改即使出现致命的系统故障也将一直保持
BASE的基本含义是基本可用(Basically Availble)、软状态(Soft-state)和最终一致性(Eventual consistency):
- 基本可用,是指一个分布式系统的一部分发生问题变得不可用时,其他部分仍然可以正常使用,也就是允许分区失败的情形出现
- “软状态(soft-state)”是与“硬状态(hard-state)”相对应的一种提法。数据库保存的数据是“硬状态”时,可以保证数据一致性,即保证数据一直是正确的。“软状态”是指状态可以有一段时间不同步,具有一定的滞后性
*最终一致性* 一致性的类型包括强一致性和弱一致性,二者的主要区别在于高并发的数据访问操作下,后续操作是否能够获取最新的数据。对于强一致性而言,当执行完一次更新操作后,后续的其他读操作就可以保证读到更新后的最新数据;反之,如果不能保证后续访问读到的都是更新后的最新数据,那么就是弱一致性。而最终一致性只不过是弱一致性的一种特例,允许后续的访问操作可以暂时读不到更新后的数据,但是经过一段时间之后,必须最终读到更新后的数据。
最常见的实现最终一致性的系统是DNS(域名系统)。一个域名更新操作根据配置的形式被分发出去,并结合有过期机制的缓存;最终所有的客户端可以看到最新的值。
场景:订单系统下单买了1台电脑,会给用户发送短信。分布式系统中,系统之间会出现网络通信等各种问题。无法实现买了1台电脑,短信立刻发送成功。
- AP表现为订单创建后无需等待短信是否发送,直接将订单创建成功结果返回。
- AP可以保证用户体验,但是,短信数据就不管了?这时,BASE(最终一致性)设计就排上用场了。
- 基本可用就是快速实现用户的基本价值与诉求,用户下单后立即返回“订单已创建”就是基本可用的体现。
- 软状态代表了在业务操作没有最终完成前的中间状态。在订单创建后,短信记录未发送成功前就属于软状态。
- 最终一致性代表通过技术手段过一段时间后让数据保持完整的状态。
5.5.3 最终一致性
最终一致性根据更新数据后各进程访问到数据的时间和方式的不同,又可以区分为:
- 因果一致性:如果进程A通知进程B它已更新了一个数据项,那么进程B的后续访问将获得A写入的最新值。而与进程A无因果关系的进程C的访问,仍然遵守一般的最终一致性规则
- “读己之所写”一致性:可以视为因果一致性的一个特例。当进程A自己执行一个更新操作之后,它自己总是可以访问到更新过的值,绝不会看到旧值
- 单调读一致性:如果进程已经看到过数据对象的某个值,那么任何后续访问都不会返回在那个值之前的值
最终一致性根据更新数据后各进程访问到数据的时间和方式的不同,又可以区分为:
- 会话一致性:它把访问存储系统的进程放到会话(session)的上下文中,只要会话还存在,系统就保证“读己之所写”一致性。如果由于某些失败情形令会话终止,就要建立新的会话,而且系统保证不会延续到新的会话
- 单调写一致性:系统保证来自同一个进程的写操作顺序执行。系统必须保证这种程度的一致性,否则就非常难以编程了
如何实现各种类型的一致性?
对于分布式数据系统:
N — 数据复制的份数
W — 更新数据是需要保证写完成的节点数
R — 读取数据的时候需要读取的节点数
如果W+R>N,写的节点和读的节点重叠,则是强一致性。例如对于典型的一主一备同步复制的关系型数据库,N=2,W=2,R=1,则不管读的是主库还是备库的数据,都是一致的。一般设定是R+W = N+1,这是保证强一致性的最小设定
如果W+R<=N,则是弱一致性。例如对于一主一备异步复制的关系型数据库,N=2,W=1,R=1,则如果读的是备库,就可能无法读取主库已经更新过的数据,所以是弱一致性。
对于分布式系统,为了保证高可用性,一般设置N>=3。不同的N,W,R组合,是在可用性和一致性之间取一个平衡,以适应不同的应用场景。
如果N=W,R=1,任何一个写节点失效,都会导致写失败,因此可用性会降低,但是由于数据分布的N个节点是同步写入的,因此可以保证强一致性。
实例:HBase是借助其底层的HDFS来实现其数据冗余备份的。HDFS采用的就是强一致性保证。在数据没有完全同步到N个节点前,写操作是不会返回成功的。也就是说它的W=N,而读操作只需要读到一个值即可,也就是说它R=1。
像Voldemort,Cassandra和Riak这些类Dynamo的系统,通常都允许用户按需要设置N,R,W三个值,即使是设置成W+R<= N也是可以的。也就是说他允许用户在强一致性和最终一致性之间自由选择。而在用户选择了最终一致性,或者是W<N的强一致性时,则总会出现一段“各个节点数据不同步导致系统处理不一致的时间”。为了提供最终一致性的支持,这些系统会提供一些工具来使数据更新被最终同步到所有相关节点。
总结:NoSQL数据库虽然数量众多,但是,归结起来,典型的NoSQL数据库通常包括键值数据库、列族数据库、文档数据库和图数据库
(1)关系数据库
优势:以完善的关系代数理论作为基础,有严格的标准,支持事务ACID四性,借助索引机制可以实现高效的查询,技术成熟,有专业公司的技术支持
劣势:可扩展性较差,无法较好支持海量数据存储,数据模型过于死板、无法较好支持Web2.0应用,事务机制影响了系统的整体性能等
(2)NoSQL数据库
优势:可以支持超大规模数据存储,灵活的数据模型可以很好地支持Web2.0应用,具有强大的横向扩展能力等
劣势:缺乏数学理论基础,复杂查询性能不高,大都不能实现事务强一致性,很难实现数据完整性,技术尚不成熟,缺乏专业团队的技术支持,维护较困难等
关系数据库和NoSQL数据库各有优缺点,彼此无法取代
关系数据库应用场景:电信、银行等领域的关键业务系统,需要保证强事务一致性
NoSQL数据库应用场景:互联网企业、传统企业的非关键业务(比如数据分析)
采用混合架构
案例:亚马逊公司就使用不同类型的数据库来支撑它的电子商务应用
对于“购物篮”这种临时性数据,采用键值存储会更加高效
当前的产品和订单信息则适合存放在关系数据库中
大量的历史订单信息则适合保存在类似MongoDB的文档数据库中
第 7 章-MapReduce
7.1.2 MapReduce 模型简介
- MapReduce将复杂的、运行于大规模集群上的并行计算过程高度地抽象到了两个函数:Map和Reduce
- 编程容易,不需要掌握分布式并行编程细节,也可以很容易把自己的程序运行在分布式系统上,完成海量数据的计算
- MapReduce采用“分而治之”策略,一个存储在分布式文件系统中的大规模数据集,会被切分成许多独立的分片(split),这些分片可以被多个Map任务并行处理
- MapReduce设计的一个理念就是“计算向数据靠拢”,而不是“数据向计算靠拢”,因为,移动数据需要大量的网络传输开销
- MapReduce框架采用了Master/Slave架构,包括一个Master和若干个Slave。Master上运行JobTracker,Slave上运行TaskTracker
- Hadoop框架是用Java实现的,但是,MapReduce应用程序则不一定要用Java来写
7.1.3 Map 和 Reduce 函数
函数 | 输入 | 输出 | 说明 |
Map | <k1,v1> 如: <行号,”a b c”> | List(<k2,v2>) 如: <“a”,1> <“b”,1> <“c”,1> | 1.将小数据集进一步解析成一批<key,value>对,输入Map函数中进行处理 2.每一个输入的<k1,v1>会输出一批<k2,v2>。<k2,v2>是计算的中间结果 |
Reduce | <k2,List(v2)> 如:<“a”,<1,1,1>> | <k3,v3> <“a”,3> | 输入的中间结果<k2,List(v2)>中的List(v2)表示是一批属于同一个k2的value |
7.2.1 工作流程概述
MapReduce体系结构主要由四个部分组成,分别是:Client、JobTracker、TaskTracker以及Task
1)Client
- 用户编写的MapReduce程序通过Client提交到JobTracker端
- 用户可通过Client提供的一些接口查看作业运行状态
2)JobTracker
- JobTracker负责资源监控和作业调度
- JobTracker 监控所有TaskTracker与Job的健康状况,一旦发现失败,就将相应的任务转移到其他节点
- JobTracker 会跟踪任务的执行进度、资源使用量等信息,并将这些信息告诉任务调度器(TaskScheduler),而调度器会在资源出现空闲时,选择合适的任务去使用这些资源
3)TaskTracker
- TaskTracker 会周期性地通过“心跳”将本节点上资源的使用情况和任务的运行进度汇报给JobTracker,同时接收JobTracker 发送过来的命令并执行相应的操作(如启动新任务、杀死任务等)
- TaskTracker 使用“slot”等量划分本节点上的资源量(CPU、内存等)。一个Task 获取到一个slot 后才有机会运行,而Hadoop调度器的作用就是将各个TaskTracker上的空闲slot分配给Task使用。slot 分为Map slot 和Reduce slot 两种,分别供MapTask 和Reduce Task 使用
4)Task
Task 分为Map Task 和Reduce Task 两种,均由TaskTracker 启动
7.3.3 Shuffle 过程详解
1. Shuffle过程简介
- Map端的Shuffle过程
- 每个Map任务分配一个缓存
- MapReduce默认100MB缓存
- 设置溢写比例0.8
- 分区默认采用哈希函数
- 排序是默认的操作
- 排序后可以合并(Combine)
- 合并不能改变最终结果
- 在Map任务全部结束之前进行归并
- 归并得到一个大的文件,放在本地磁盘
- 文件归并时,如果溢写文件数量大于预定值(默认是3)则可以再次启动Combiner,少于3不需要
- JobTracker会一直监测Map任务的执行,并通知Reduce任务来领取数据
- Reduce端的Shuffle过程
- Reduce任务通过RPC向JobTracker询问Map任务是否已经完成,若完成,则领取数据
- Reduce领取数据先放入缓存,来自不同Map机器,先归并,再合并,写入磁盘
- 多个溢写文件归并成一个或多个大文件,文件中的键值对是排序的
- 当数据很少时,不需要溢写到磁盘,直接在缓存中归并,然后输出给Reduce
第 8 章-Hadoop 架构再探讨
8.1.1 Hadoop 的局限与不足
Hadoop1.0的核心组件(仅指MapReduce和HDFS,不包括Hadoop生态系统内的Pig、Hive、HBase等其他组件),主要存在以下不足:
- 抽象层次低,需人工编码
- 表达能力有限
- 开发者自己管理作业(Job)之间的依赖关系
- 难以看到程序整体逻辑
- 执行迭代操作效率低
- 资源浪费(Map和Reduce分两阶段执行)
- 实时性差(适合批处理,不支持实时交互式)
8.3.1 MapReduce 1.0 的缺陷
(1)存在单点故障
(2)JobTracker“大包大揽”导致任务过重(任务多时内存开销大,上限4000节点)
(3)容易出现内存溢出(分配资源只考虑MapReduce任务数,不考虑CPU、内存)
(4)资源划分不合理(强制划分为slot ,包括Map slot和Reduce slot)
8.3.6 YARN 的发展目标
YARN的目标就是实现“一个集群多个框架”,为什么?
一个企业当中同时存在各种不同的业务应用场景,需要采用不同的计算框架
- MapReduce实现离线批处理
- 使用Impala实现实时交互式查询分析
- 使用Storm实现流式数据实时分析
- 使用Spark实现迭代计算
这些产品通常来自不同的开发团队,具有各自的资源调度管理机制
为了避免不同类型应用之间互相干扰,企业就需要把内部的服务器拆分成多个集群,分别安装运行不同的计算框架,即“一个框架一个集群”
导致问题
- 集群资源利用率低
- 数据无法共享
- 维护代价高
YARN的目标就是实现“一个集群多个框架”,即在一个集群上部署一个统一的资源调度管理框架YARN,在YARN之上可以部署其他各种计算框架
由YARN为这些计算框架提供统一的资源调度管理服务,并且能够根据各种计算框架的负载需求,调整各自占用的资源,实现集群资源共享和资源弹性收缩
可以实现一个集群上的不同应用负载混搭,有效提高了集群的利用率
不同计算框架可以共享底层存储,避免了数据集跨集群移动
第 10 章-Spark
10.1.1 Spark 简介
Spark最初由美国加州伯克利大学(UCBerkeley)的AMP实验室于2009年开发,是基于内存计算的大数据并行计算框架,可用于构建大型的、低延迟的数据分析应用程序
2013年Spark加入Apache孵化器项目后发展迅猛,如今已成为Apache软件基金会最重要的三大分布式计算系统开源项目之一(Hadoop、Spark、Storm)
Spark在2014年打破了Hadoop保持的基准排序纪录
Spark/206个节点/23分钟/100TB数据
Hadoop/2000个节点/72分钟/100TB数据
Spark用十分之一的计算资源,获得了比Hadoop快3倍的速度
Spark具有如下几个主要特点:
- 运行速度快:使用DAG执行引擎以支持循环数据流与内存计算
- 容易使用:支持使用Scala、Java、Python和R语言进行编程,可以通过Spark Shell进行交互式编程
- 通用性:Spark提供了完整而强大的技术栈,包括SQL查询、流式计算、机器学习和图算法组件
- 运行模式多样:可运行于独立的集群模式中,可运行于Hadoop中,也可运行于Amazon EC2等云环境中,并且可以访问HDFS、Cassandra、HBase、Hive等多种数据源
10.1.3 Spark 与 Hadoop 的对比
Hadoop存在如下一些缺点:
- 表达能力有限
- 磁盘IO开销大
- 延迟高
- 任务之间的衔接涉及IO开销
- 在前一个任务执行完成之前,其他任务就无法开始,难以胜任复杂、多阶段的计算任务
Spark在借鉴Hadoop MapReduce优点的同时,很好地解决了MapReduce所面临的问题
相比于Hadoop MapReduce,Spark主要具有如下优点:
- Spark的计算模式也属于MapReduce,但不局限于Map和Reduce操作,还提供了多种数据集操作类型,编程模型比Hadoop MapReduce更灵活
- Spark提供了内存计算,可将中间结果放到内存中,对于迭代运算效率更高
- Spark基于DAG的任务调度执行机制,要优于Hadoop MapReduce的迭代执行机制
使用Hadoop进行迭代计算非常耗资源
Spark将数据载入内存后,之后的迭代计算都可以直接使用内存中的中间结果作运算,避免了从磁盘中频繁读取数据
10.2 Spark 生态系统
在实际应用中,大数据处理主要包括以下三个类型:
- 复杂的批量数据处理:通常时间跨度在数十分钟到数小时之间
- 基于历史数据的交互式查询:通常时间跨度在数十秒到数分钟之间
- 基于实时数据流的数据处理:通常时间跨度在数百毫秒到数秒之间
当同时存在以上三种场景时,就需要同时部署三种不同的软件
比如: MapReduce / Impala / Storm
这样做难免会带来一些问题:
- 不同场景之间输入输出数据无法做到无缝共享,通常需要进行数据格式的转换
- 不同的软件需要不同的开发和维护团队,带来了较高的使用成本
- 比较难以对同一个集群中的各个系统进行统一的资源协调和分配
- Spark的设计遵循“一个软件栈满足不同应用场景”的理念,逐渐形成了一套完整的生态系统
- 既能够提供内存计算框架,也可以支持SQL即席查询、实时流式计算、机器学习和图计算等
- Spark可以部署在资源管理器YARN之上,提供一站式的大数据解决方案
- 因此,Spark所提供的生态系统足以应对上述三种场景,即同时支持批处理、交互式查询和流数据处理
Spark生态系统已经成为伯克利数据分析软件栈BDAS(Berkeley Data Analytics Stack)的重要组成部分
Spark的生态系统主要包含了Spark Core、Spark SQL、Spark Streaming、MLLib和GraphX 等组件
应用场景 | 时间跨度 | 其他框架 | Spark生态系统中的组件 |
复杂的批量数据处理 | 小时级 | MapReduce、Hive | Spark |
基于历史数据的交互式查询 | 分钟级、秒级 | Impala、Dremel、Drill | Spark SQL |
基于实时数据流的数据处理 | 毫秒、秒级 | Storm、S4 | Spark Streaming |
基于历史数据的数据挖掘 | - | Mahout | MLlib |
图结构数据的处理 | - | Pregel、Hama | GraphX |
10.3.1 基本概念
- RDD:是Resillient Distributed Dataset(弹性分布式数据集)的简称,是分布式内存的一个抽象概念,提供了一种高度受限的共享内存模型
- DAG:是Directed Acyclic Graph(有向无环图)的简称,反映RDD之间的依赖关系
- Executor:是运行在工作节点(WorkerNode)的一个进程,负责运行Task
- Application:用户编写的Spark应用程序
- Task:运行在Executor上的工作单元
- Job:一个Job包含多个RDD及作用于相应RDD上的各种操作
- Stage:是Job的基本调度单位,一个Job会分为多组Task,每组Task被称为Stage,或者也被称为TaskSet,代表了一组关联的、相互之间没有Shuffle依赖关系的任务组成的任务集
10.3.2 架构设计
Spark运行架构包括集群资源管理器(Cluster Manager)、运行作业任务的工作节点(Worker Node)、每个应用的任务控制节点(Driver)和每个工作节点上负责具体任务的执行进程(Executor),资源管理器可以自带或Mesos或YARN
与Hadoop MapReduce计算框架相比,Spark所采用的Executor有两个优点:
- 一是利用多线程来执行具体的任务,减少任务的启动开销
- 二是Executor中有一个BlockManager存储模块,会将内存和磁盘共同作为存储设备,有效减少IO开销
- 一个Application由一个Driver和若干个Job构成,一个Job由多个Stage构成,一个Stage由多个没有Shuffle关系的Task组成
- 当执行一个Application时,Driver会向集群管理器申请资源,启动Executor,并向Executor发送应用程序代码和文件,然后在Executor上执行Task,运行结束后,执行结果会返回给Driver,或者写到HDFS或者其他数据库中
10.3.4 RDD 的设计与运行原理
1.设计背景
- 许多迭代式算法(比如机器学习、图算法等)和交互式数据挖掘工具,共同之处是,不同计算阶段之间会重用中间结果
- 目前的MapReduce框架都是把中间结果写入到HDFS中,带来了大量的数据复制、磁盘IO和序列化开销
- RDD就是为了满足这种需求而出现的,它提供了一个抽象的数据架构,我们不必担心底层数据的分布式特性,只需将具体的应用逻辑表达为一系列转换处理,不同RDD之间的转换操作形成依赖关系,可以实现管道化,避免中间数据存储
2.RDD概念
- 一个RDD就是一个分布式对象集合,本质上是一个只读的分区记录集合,每个RDD可分成多个分区,每个分区就是一个数据集片段,并且一个RDD的不同分区可以被保存到集群中不同的节点上,从而可以在集群中的不同节点上进行并行计算
- RDD提供了一种高度受限的共享内存模型,即RDD是只读的记录分区的集合,不能直接修改,只能基于稳定的物理存储中的数据集创建RDD,或者通过在其他RDD上执行确定的转换操作(如map、join和group by)而创建得到新的RDD
- RDD提供了一组丰富的操作以支持常见的数据运算,分为“动作”(Action)和“转换”(Transformation)两种类型
- RDD提供的转换接口都非常简单,都是类似map、filter、groupBy、join等粗粒度的数据转换操作,而不是针对某个数据项的细粒度修改(不适合网页爬虫)
- 表面上RDD的功能很受限、不够强大,实际上RDD已经被实践证明可以高效地表达许多框架的编程模型(比如MapReduce、SQL、Pregel)
- Spark用Scala语言实现了RDD的API,程序员可以通过调用API实现对RDD的各种操作
RDD典型的执行过程如下:
- RDD读入外部数据源进行创建
- RDD经过一系列的转换(Transformation)操作,每一次都会产生不同的RDD,供给下一个转换操作使用
- 最后一个RDD经过“动作”操作进行转换,并输出到外部数据源
这一系列处理称为一个Lineage(血缘关系),即DAG拓扑排序的结果
优点:惰性调用、管道化、避免同步等待、不需要保存中间结果、每次操作变得简单
3.RDD特性
Spark采用RDD以后能够实现高效计算的原因主要在于:
(1)高效的容错性
- 现有容错机制:数据复制或者记录日志
- RDD:血缘关系、重新计算丢失分区、无需回滚系统、重算过程在不同节点之间并行、只记录粗粒度的操作
(2)中间结果持久化到内存,数据在内存中的多个RDD操作之间进行传递,避免了不必要的读写磁盘开销
(3)存放的数据可以是Java对象,避免了不必要的对象序列化和反序列化
4. RDD之间的依赖关系
Shuffle操作---什么是Shuffle操作
窄依赖和宽依赖---是否包含Shuffle操作是区分窄依赖和宽依赖的根据
- 窄依赖表现为一个父RDD的分区对应于一个子RDD的分区或多个父RDD的分区对应于一个子RDD的分区
- 宽依赖则表现为存在一个父RDD的一个分区对应一个子RDD的多个分区
5.阶段的划分
Spark 根据DAG 图中的RDD 依赖关系,把一个作业分成多个阶段。阶段划分的依据是窄依赖和宽依赖。对于宽依赖和窄依赖而言,窄依赖对于作业的优化很有利,宽依赖无法优化
逻辑上,每个RDD 操作都是一个fork/join(一种用于并行执行任务的框架),把计算fork 到每个RDD 分区,完成计算后对各个分区得到的结果进行join 操作,然后fork/join下一个RDD 操作
ork/join的优化原理
举例:一个学校(含2个班级)完成从北京到厦门的长征
窄依赖可以实现“流水线”优化
宽依赖无法实现“流水线”优化
- Spark根据DAG图中的RDD依赖关系,把一个作业分成多个阶段。对于宽依赖和窄依赖而言,窄依赖对于作业的优化很有利。只有窄依赖可以实现流水线优化,宽依赖包含Shuffle过程,无法实现流水线方式处理。
- Spark通过分析各个RDD的依赖关系生成了DAG,再通过分析各个RDD中的分区之间的依赖关系来决定如何划分Stage,具体划分方法是:
- 在DAG中进行反向解析,遇到宽依赖就断开
- 遇到窄依赖就把当前的RDD加入到Stage中
- 将窄依赖尽量划分在同一个Stage中,可以实现流
被分成三个Stage,在Stage2中,从map到union都是窄依赖,这两步操作可以形成一个流水线操作
流水线操作实例
分区7通过map操作生成的分区9,可以不用等待分区8到分区10这个map操作的计算结束,而是继续进行union操作,得到分区13,这样流水线执行大大提高了计算的效率
6.RDD运行过程
通过上述对RDD概念、依赖关系和Stage划分的介绍,结合之前介绍的Spark运行基本流程,再总结一下RDD在Spark架构中的运行过程:
(1)创建RDD对象;
(2)SparkContext负责计算RDD之间的依赖关系,构建DAG;
(3)DAGScheduler负责把DAG图分解成多个Stage,每个Stage中包含了多个Task,每个Task会被TaskScheduler分发给各个WorkerNode上的Executor去执行
10.5.2 Spark RDD 基本操作
Spark的主要操作对象是RDD,RDD可以通过多种方式灵活创建,可通过导入外部数据源建立,或者从其他的RDD转化而来。
在Spark程序中必须创建一个SparkContext对象,该对象是Spark程序的入口,负责创建RDD、启动任务等。在启动Spark Shell后,该对象会自动创建,可以通过变量sc进行访问。
作为示例,我们选择以Spark安装目录中的“README.md”文件作为数据源新建一个RDD,代码如下:
Scala > val textFile = sc.textFile("file:///usr/local/spark/README.md")
// 通过file:前缀指定读取本地文件
Spark RDD支持两种类型的操作:
动作(action):在数据集上进行运算,返回计算值
转换(transformation): 基于现有的数据集创建一个新的数据集
第 11 章-流计算
11.1 流计算概述
第 12 章-Flink
12.2.5 Flink 的优势
第 15 章-大数据在不同领域的应用
15.2 协同过滤
推荐技术从被提出到现在已有十余年,在多年的发展历程中诞生了很多新的推荐算法。协同过滤作为最早、最知名的推荐算法,不仅在学术界得到了深入研究,而且至今在业界仍有广泛的应用
协同过滤可分为基于用户的协同过滤和基于物品的协同过滤
1.基于用户的协同过滤算法(简称UserCF算法)在1992年被提出,是推荐系统中最古老的算法
UserCF算法符合人们对于“趣味相投”的认知,即兴趣相似的用户往往有相同的物品喜好:当目标用户需要个性化推荐时,可以先找到和目标用户有相似兴趣的用户群体,然后将这个用户群体喜欢的、而目标用户没有听说过的物品推荐给目标用户
UserCF算法的实现主要包括两个步骤:
第一步:找到和目标用户兴趣相似的用户集合
第二步:找到该集合中的用户所喜欢的、且目标用户没有听说过的物品推荐给目标用户
实现UserCF算法的关键步骤是计算用户与用户之间的兴趣相似度。目前较多使用的相似度算法有:
泊松相关系数(Person Correlation Coefficient)
余弦相似度(Cosine-based Similarity)
调整余弦相似度(Adjusted Cosine Similarity)
给定用户u和用户v,令N(u)表示用户u感兴趣的物品集合,令N(v)为用户v感兴趣的物品集合,则使用余弦相似度进行计算用户相似度的公式为:
由于很多用户相互之间并没有对同样的物品产生过行为,因此其相似度公式的分子为0,相似度也为0
我们可以利用物品到用户的倒排表(每个物品所对应的、对该物品感兴趣的用户列表),仅对有对相同物品产生交互行为的用户进行计算
得到用户间的相似度后,再使用如下公式来度量用户u对物品i的兴趣程度Pui:
其中,S(u, K)是和用户u兴趣最接近的K个用户的集合,N(i)是喜欢物品i的用户集合,Wuv是用户u和用户v的相似度,rvi是隐反馈信息,代表用户v对物品i的感兴趣程度,为简化计算可令rvi=1
对所有物品计算Pui后,可以对Pui进行降序处理,取前N个物品作为推荐结果展示给用户u(称为Top-N推荐)
2.基于物品的协同过滤算法(简称ItemCF算法)是目前业界应用最多的算法。无论是亚马逊还是Netflix,其推荐系统的基础都是ItemCF算法
ItemCF算法是给目标用户推荐那些和他们之前喜欢的物品相似的物品。ItemCF算法主要通过分析用户的行为记录来计算物品之间的相似度
该算法基于的假设是:物品A和物品B具有很大的相似度是因为喜欢物品A的用户大多也喜欢物品B。例如,该算法会因为你购买过《数据挖掘导论》而给你推荐《机器学习实战》,因为买过《数据挖掘导论》的用户多数也购买了《机器学习实战》
ItemCF算法与UserCF算法类似,计算也分为两步:
第一步:计算物品之间的相似度;
第二步:根据物品的相似度和用户的历史行为,给用户生成推荐列表。
2.Item-Based CF
- UserCF算法和ItemCF算法的思想、计算过程都相似
- 两者最主要的区别:
- UserCF算法推荐的是那些和目标用户有共同兴趣爱好的其他用户所喜欢的物品
- ItemCF算法推荐的是那些和目标用户之前喜欢的物品类似的其他物品
- UserCF算法的推荐更偏向社会化,而ItemCF算法的推荐更偏向于个性化
- UserCF算法的推荐更偏向社会化:适合应用于新闻推荐、微博话题推荐等应用场景,其推荐结果在新颖性方面有一定的优势
- UserCF缺点:随着用户数目的增大,用户相似度计算复杂度越来越高。而且UserCF推荐结果相关性较弱,难以对推荐结果作出解释,容易受大众影响而推荐热门物品
- ItemCF算法的推荐更偏向于个性化:适合应用于电子商务、电影、图书等应用场景,可以利用用户的历史行为给推荐结果作出解释,让用户更为信服推荐的效果
- ItemCF缺点:倾向于推荐与用户已购买商品相似的商品,往往会出现多样性不足、推荐新颖度较低的问题
《spark 编程基础(Python 版)》
第 3 章-Spark 环境搭建和使用方法
3.2.1 pyspark 命令
在Spark中采用本地模式启动pyspark的命令主要包含以下参数:
--master:这个参数表示当前的pyspark要连接到哪个master,如果是local[*],就是使用本地模式启动pyspark ,其中,中括号内的星号表示需要使用几个CPU核心(core),也就是启动几个线程模拟Spark集群。
--jars: 这个参数用于把相关的JAR包添加到CLASSPATH中;如果有多个jar包,可以使用逗号分隔符连接它们。
第 4 章-RDD 编程
4.1.1 RDD 创建
1. 从文件系统中加载数据创建RDD
Spark采用textFile()方法来从文件系统中加载数据创建RD,该方法把文件的URI作为参数,这个URI可以是:
- 本地文件系统的地址;
- 或者是分布式文件系统HDFS的地址;
- 或者是Amazon S3的地址等等
-
-
2. 通过并行集合(列表)创建RDD
4.1.2 RDD 操作
1. 转换操作
操作 | 含义 |
filter(func) | 筛选出满足函数func的元素,并返回一个新的数据集。 |
map(func) | 将每个元素传递到函数func中,并将结果返回为一个新的数据集。 |
flatMap(func) | 与map()相似,但每个输入元素都可以映射到0或多个输出结果。 |
groupByKey() | 应用于(K,V)键值对的数据集时,返回一个新的(K, Iterable)形式的数据集。 |
reduceByKey(func) | 应用于(K,V)键值对的数据集时,返回一个新的(K, V)形式的数据集,其中每个值是将每个key传递到函数func中进行聚合后的结果。 |
map(func)
map(func)操作将每个元素传递到函数func中,并将结果返回为一个新的数据集。
>>> data = [1,2,3,4,5]
>>> rdd1 = sc.parallelize(data)
>>> rdd2 = rdd1.map(lambda x:x+10)
>>> rdd2.foreach(print)
11
13
12
14
15
>>> lines = sc.textFile("file:///usr/local/spark/mycode/rdd/word.txt")
>>> words = lines.map(lambda line:line.split(" "))
>>> words.foreach(print)
['Hadoop', 'is', 'good']
['Spark', 'is', 'fast']
['Spark', 'is', 'better']
>>> lines = sc.textFile("file:///usr/local/spark/mycode/rdd/word.txt")
>>> words = lines.flatMap(lambda line:line.split(" "))
groupByKey()应用于(K,V)键值对的数据集时,返回一个新的(K, Iterable)形式的数据集
>>> words = sc.parallelize([("Hadoop",1),("is",1),("good",1), \
... ("Spark",1),("is",1),("fast",1),("Spark",1),("is",1),("better",1)])
>>> words1 = words.groupByKey()
>>> words1.foreach(print)
('Hadoop', <pyspark.resultiterable.ResultIterable object at 0x7fb210552c88>)
('better', <pyspark.resultiterable.ResultIterable object at 0x7fb210552e80>)
('fast', <pyspark.resultiterable.ResultIterable object at 0x7fb210552c88>)
('good', <pyspark.resultiterable.ResultIterable object at 0x7fb210552c88>)
('Spark', <pyspark.resultiterable.ResultIterable object at 0x7fb210552f98>)
('is', <pyspark.resultiterable.ResultIterable object at 0x7fb210552e10>)
reduceByKey(func)应用于(K,V)键值对的数据集时,返回一个新的(K, V)形式的数据集,其中的每个值是将每个key传递到函数func中进行聚合后得到的结果
>>> words = sc.parallelize([("Hadoop",1),("is",1),("good",1),("Spark",1), \
... ("is",1),("fast",1),("Spark",1),("is",1),("better",1)])
>>> words1 = words.reduceByKey(lambda a,b:a+b)
>>> words1.foreach(print)
('good', 1)
('Hadoop', 1)
('better', 1)
('Spark', 2)
('fast', 1)
('is', 3)
- 行动操作
行动操作是真正触发计算的地方。Spark程序执行到行动操作时,才会执行真正的计算,从文件中加载数据,完成一次又一次转换操作,最终,完成行动操作得到结果。
操作 | 含义 |
count() | 返回数据集中的元素个数 |
collect() | 以数组的形式返回数据集中的所有元素 |
first() | 返回数据集中的第一个元素 |
take(n) | 以数组的形式返回数据集中的前n个元素 |
reduce(func) | 通过函数func(输入两个参数并返回一个值)聚合数据集中的元素 |
foreach(func) | 将数据集中的每个元素传递到函数func中运行 |
>>> rdd = sc.parallelize([1,2,3,4,5])
>>> rdd.count()
5
>>> rdd.first()
1
>>> rdd.take(3)
[1, 2, 3]
>>> rdd.reduce(lambda a,b:a+b)
15
>>> rdd.collect()
[1, 2, 3, 4, 5]
>>> rdd.foreach(lambda elem:print(elem))
1
2
3
4
5
3. 惰性机制
所谓的“惰性机制”是指,整个转换过程只是记录了转换的轨迹,并不会发生真正的计算,只有遇到行动操作时,才会触发“从头到尾”的真正的计算。
这里给出一段简单的语句来解释Spark的惰性机制。
>>> lines = sc.textFile("file:///usr/local/spark/mycode/rdd/word.txt")
>>> lineLengths = lines.map(lambda s:len(s))
>>> totalLength = lineLengths.reduce(lambda a,b:a+b)
>>> print(totalLength)
4.1.4 分区
RDD是弹性分布式数据集,通常RDD很大,会被分成很多个分区,分别保存在不同的节点上。
1.分区的作用
(1)增加并行度
(2)减少通信开销
2.RDD分区的原则
一个是使得分区的个数尽量等于集群中的CPU核心(core)数目。
对于不同的Spark部署模式而言(本地模式、Standalone模式、YARN模式、Mesos模式),都可以通过设置spark.default.parallelism这个参数的值,来配置默认的分区数目,一般而言:
- 本地模式:默认为本地机器的CPU数目,若设置了local[N],则默认为N。
- Apache Mesos:默认的分区数为8。
- Standalone或YARN:在“集群中所有CPU核心数目总和”和“2”二者中取较大值作为默认值。
- 设置分区的个数
1)创建RDD时手动指定分区个数
在调用textFile()和parallelize()方法的时候手动指定分区个数即可,语法格式如下:
sc.textFile(path, partitionNum)
其中,path参数用于指定要加载的文件的地址,partitionNum参数用于指定分区个数。
>>> list = [1,2,3,4,5]
>>> rdd = sc.parallelize(list,2) //设置两个分区
2)使用reparititon方法重新设置分区个数
通过转换操作得到新 RDD 时,直接调用 repartition 方法即可。例如:
>>> data = sc.parallelize([1,2,3,4,5],2)
>>> len(data.glom().collect()) #显示data这个RDD的分区数量
2
>>> rdd = data.repartition(1) #对data这个RDD进行重新分区
>>> len(rdd.glom().collect()) #显示rdd这个RDD的分区数量
1
4.自定义分区方法
Spark提供了自带的HashPartitioner(哈希分区)与RangePartitioner(区域分区),能够满足大多数应用场景的需求。与此同时,Spark也支持自定义分区方式,即通过提供一个自定义的分区函数来控制RDD的分区方式,从而利用领域知识进一步减少通信开销。
实例:根据key值的最后一位数字,写到不同的文件。
例如:
10写入到part-00000
11写入到part-00001
.
.
.
19写入到part-00009
4.2.1 键值对 RDD 的创建
4.2.2 常用的键值对 RDD 转换操作
- reduceByKey(func)
- groupByKey()
- keys
- values
- sortByKey()
- mapValues(func)
- join
- combineByKey
reduceByKey(func)
reduceByKey(func)的功能是,使用func函数合并具有相同键的值
(Hadoop,1)
(Spark,1)
(Hive,1)
(Spark,1)
>>> pairRDD = sc.parallelize([("Hadoop",1),("Spark",1),("Hive",1),("Spark",1)])
>>> pairRDD.reduceByKey(lambda a,b:a+b).foreach(print)
('Spark', 2)
('Hive', 1)
('Hadoop', 1)
>>> list = [("spark",1),("spark",2),("hadoop",3),("hadoop",5)]
>>> pairRDD = sc.parallelize(list)
>>> pairRDD.groupByKey()
PythonRDD[27] at RDD at PythonRDD.scala:48
>>> pairRDD.groupByKey().foreach(print)
('hadoop', <pyspark.resultiterable.ResultIterable object at 0x7f2c1093ecf8>)
('spark', <pyspark.resultiterable.ResultIterable obj
>>> words = ["one", "two", "two", "three", "three", "three"]
>>> wordPairsRDD = sc.parallelize(words).map(lambda word:(word, 1))
>>> wordCountsWithReduce = wordPairsRDD.reduceByKey(lambda a,b:a+b)
>>> wordCountsWithReduce.foreach(print)
('one', 1)
('two', 2)
('three', 3)
>>> wordCountsWithGroup = wordPairsRDD.groupByKey(). \
... map(lambda t:(t[0],sum(t[1])))
>>> wordCountsWithGroup.foreach(print)
('two', 2)
('three', 3)
('one', 1)
上面得到的wordCountsWithReduce和wordCountsWithGroup是完全一样的,但是,它们的内部运算过程是不同的。
>>> list = [("Hadoop",1),("Spark",1),("Hive",1),("Spark",1)]
>>> pairRDD = sc.parallelize(list)
>>> pairRDD.keys().foreach(print)
Hadoop
Spark
Hive
Spark
>>> list = [("Hadoop",1),("Spark",1),("Hive",1),("Spark",1)]
>>> pairRDD = sc.parallelize(list)
>>> pairRDD.values().foreach(print)
1
1
1
1
>>> list = [("Hadoop",1),("Spark",1),("Hive",1),("Spark",1)]
>>> pairRDD = sc.parallelize(list)
>>> pairRDD.foreach(print)
('Hadoop', 1)
('Spark', 1)
('Hive', 1)
('Spark', 1)
>>> pairRDD.sortByKey().foreach(print)
('Hadoop', 1)
('Hive', 1)
('Spark', 1)
('Spark', 1)
sortByKey()和sortBy()
>>> d1 = sc.parallelize([("c",8),("b",25),("c",17),("a",42), \
... ("b",4),("d",9),("e",17),("c",2),("f",29),("g",21),("b",9)])
>>> d1.reduceByKey(lambda a,b:a+b).sortByKey(False).collect()
[('g', 21), ('f', 29), ('e', 17), ('d', 9), ('c', 27), ('b', 38), ('a', 42)]
>>> d1 = sc.parallelize([("c",8),("b",25),("c",17),("a",42), \
... ("b",4),("d",9),("e",17),("c",2),("f",29),("g",21),("b",9)])
>>> d1.reduceByKey(lambda a,b:a+b).sortBy(lambda x:x,False).collect()
[('g', 21), ('f', 29), ('e', 17), ('d', 9), ('c', 27), ('b', 38), ('a', 42)]
>>> d1.reduceByKey(lambda a,b:a+b).sortBy(lambda x:x[0],False).collect()
[('g', 21), ('f', 29), ('e', 17), ('d', 9), ('c', 27), ('b', 38), ('a', 42)]
>>> d1.reduceByKey(lambda a,b:a+b).sortBy(lambda x:x[1],False).collect()
[('a', 42), ('b', 38), ('f', 29), ('c', 27), ('g', 21), ('e', 17), ('d', 9)]
mapValues(func)
对键值对RDD中的每个value都应用一个函数,但是,key不会发生变化
(Hadoop,1)
(Spark,1)
(Hive,1)
(Spark,1)
>>> list = [("Hadoop",1),("Spark",1),("Hive",1),("Spark",1)]
>>> pairRDD = sc.parallelize(list)
>>> pairRDD1 = pairRDD.mapValues(lambda x:x+1)
>>> pairRDD1.foreach(print)
('Hadoop', 2)
('Spark', 2)
('Hive', 2)
('Spark', 2)
join就表示内连接。对于内连接,对于给定的两个输入数据集(K,V1)和(K,V2),只有在两个数据集中都存在的key才会被输出,最终得到一个(K,(V1,V2))类型的数据集。
>>> pairRDD1 = sc. \
... parallelize([("spark",1),("spark",2),("hadoop",3),("hadoop",5)])
>>> pairRDD2 = sc.parallelize([("spark","fast")])
>>> pairRDD3 = pairRDD1.join(pairRDD2)
>>> pairRDD3.foreach(print)
('spark', (1, 'fast'))
('spark', (2, 'fast'))
题目:给定一组键值对(“spark”,2)("hadoop",6)("hadoop",4)("spark",6)键值对的key表示图书名称,value表示某天图书销量,请计算每个键对应的平均值,也就是计算每种图书的每天平均销量
>>> rdd = sc.parallelize([("spark",2),("hadoop",6),("hadoop",4),("spark",6)])
>>> rdd.mapValues(lambda x:(x,1)).\
... reduceByKey(lambda x,y:(x[0]+y[0],x[1]+y[1])).\
... mapValues(lambda x:x[0]/x[1]).collect()
[('hadoop', 5.0), ('spark', 4.0)]
.
4.3.1 文件数据读写
1.本地文件系统的数据读写
(1)从文件中读取数据创建RDD
>>> textFile = sc.\
... textFile("file:///usr/local/spark/mycode/rdd/word.txt")
>>> textFile.first()
'Hadoop is good'
因为Spark采用了惰性机制,在执行转换操作的时候,即使输入了错误的语句,spark-shell也不会马上报错(假设word123.txt不存在)
>>> textFile = sc.\
... textFile("file:///usr/local/spark/mycode/wordcount/word123.txt")
- 把RDD写入到文本文件中
>>> textFile = sc.\
... textFile("file:///usr/local/spark/mycode/rdd/word.txt")
>>> textFile.\
... saveAsTextFile("file:///usr/local/spark/mycode/rdd/writeback")
$ cd /usr/local/spark/mycode/wordcount/writeback/
$ ls
如果想再次把数据加载在RDD中,只要使用writeback这个目录即可,如下:
>>> textFile = sc.\
... textFile("file:///usr/local/spark/mycode/rdd/writeback")
- 分布式文件系统HDFS的数据读写
第 5 章-Spark SQL
5.1 Spark SQL 简介
关系数据库已经很流行。
关系数据库在大数据时代已经不能满足要求。
首先,用户需要从不同数据源执行各种操作,包括结构化、半结构化和非结构化数据。
其次,用户需要执行高级分析,比如机器学习和图像处理。
在实际大数据应用中,经常需要融合关系查询和复杂分析算法(比如机器学习或图像处理),但是,缺少这样的系统。
Spark SQL填补了这个鸿沟:
- 首先,可以提供DataFrame API,可以对内部和外部各种数据源执行各种关系型操作。
- 其次,可以支持大数据中的大量数据源和数据分析算法。
- Spark SQL可以融合:传统关系数据库的结构化数据管理能力和机器学习算法的数据处理能力。
5.2 DataFrame 概述
- DataFrame的推出,让Spark具备了处理大规模结构化数据的能力,不仅比原有的RDD转化方式更加简单易用,而且获得了更高的计算性能。
- Spark能够轻松实现从MySQL到DataFrame的转化,并且支持SQL查询
- RDD是分布式的 Java对象的集合,但是,对象内部结构对于RDD而言却是不可知的。
- DataFrame是一种以RDD为基础的分布式数据集,提供了详细的结构信息。
5.3 DataFrame 的创建
在创建DataFrame时,可以使用spark.read操作,从不同类型的文件中加载数据创建DataFrame,例如:
spark.read.text(“people.txt”):读取文本文件people.txt创建DataFrame。
spark.read.json(“people.json”):读取people.json文件创建DataFrame;在读取本地文件或HDFS文件时,要注意给出正确的文件路径。
spark.read.parquet(“people.parquet”):读取people.parquet文件创建DataFrame。
或者也可以使用如下格式的语句:
spark.read.format("text").load("people.txt"):读取文本文件people.json创建DataFrame;
spark.read.format("json").load("people.json"):读取JSON文件people.json创建DataFrame;
spark.read.format("parquet").load("people.parquet"):读取Parquet文件people.parquet创建DataFrame。
>>> df = spark.read.json("file:///usr/local/spark/examples/src/main/resources/people.json")
>>> df.show()
+----+-------+
| age| name|
+----+-------+
|null|Michael|
| 30| Andy|
| 19| Justin|
+----+-------+
5.5 DataFrame 的常用操作
5.6 从 RDD 转换得到 DataFrame
利用反射机制推断RDD模式
>>> from pyspark.sql import Row
>>> people = spark.sparkContext.\
... textFile("file:///usr/local/spark/examples/src/main/resources/people.txt").\
... map(lambda line: line.split(",")).\
... map(lambda p: Row(name=p[0], age=int(p[1])))
>>> schemaPeople = spark.createDataFrame(people)
#必须注册为临时表才能供下面的查询使用
>>> schemaPeople.createOrReplaceTempView("people")
>>> personsDF = spark.sql("select name,age from people where age > 20")
#DataFrame中的每个元素都是一行记录,包含name和age两个字段,分别用p.name和p.age来获取值
>>> personsRDD=personsDF.rdd.map(lambda p:"Name: "+p.name+ ","+"Age: "+str(p.age))
>>> personsRDD.foreach(print)
Name: Michael,Age: 29
Name: Andy,Age: 30
当无法提前获知数据结构时,就需要采用编程方式定义RDD模式。
使用编程方式定义RDD模式
比如,现在需要通过编程方式把people.txt加载进来生成DataFrame,并完成SQL查询。
>>> from pyspark.sql.types import *
>>> from pyspark.sql import Row
#下面生成“表头”
>>> schemaString = "name age"
>>> fields = [StructField(field_name, StringType(), True) for field_name in schemaString.split(" ")]
>>> schema = StructType(fields)
#下面生成“表中的记录”
>>> lines = spark.sparkContext.\
... textFile("file:///usr/local/spark/examples/src/main/resources/people.txt")
>>> parts = lines.map(lambda x: x.split(","))
>>> people = parts.map(lambda p: Row(p[0], p[1].strip()))
#下面把“表头”和“表中的记录”拼装在一起
>>> schemaPeople = spark.createDataFrame(people, schema)
#注册一个临时表供下面查询使用
>>> schemaPeople.createOrReplaceTempView("people")
>>> results = spark.sql("SELECT name,age FROM people")
>>> results.show()
+-------+---+
| name|age|
+-------+---+
|Michael| 29|
| Andy| 30|
| Justin| 19|
+-------+---+
第 6 章-Spark Streaming
6.5.1 Kafka 简介
- Kafka是一种高吞吐量的分布式发布订阅消息系统,用户通过Kafka系统可以发布大量的消息,同时也能实时订阅消费消息。
- Kafka可以同时满足在线实时处理和批量离线处理。
- 在公司的大数据生态系统中,可以把Kafka作为数据交换枢纽,不同类型的分布式系统(关系数据库、NoSQL数据库、流处理系统、批处理系统等),可以统一接入到Kafka,实现和Hadoop各个组件之间的不同类型数据的实时高效交换。
第 8 章-Spark MLlib
8.1 基于大数据的机器学习
机器学习可以看做是一门人工智能的科学,该领域的主要研究对象是人工智能。机器学习利用数据或以往的经验,以此优化计算机程序的性能标准。
机器学习强调三个关键词:算法、经验、性能
- 传统的机器学习算法,由于技术和单机存储的限制,只能在少量数据上使用,依赖于数据抽样。
- 大数据技术的出现,可以支持在全量数据上进行机器学习。
- 机器学习算法涉及大量迭代计算。
- 基于磁盘的MapReduce不适合进行大量迭代计算。
- 基于内存的Spark比较适合进行大量迭代计算
Spark提供了一个基于海量数据的机器学习库,它提供了常用机器学习算法的分布式实现。
开发者只需要有 Spark 基础并且了解机器学习算法的原理,以及方法相关参数的含义,就可以轻松的通过调用相应的 API 来实现基于海量数据的机器学习过程。pyspark的即席查询也是一个关键。算法工程师可以边写代码边运行,边看结果。
- 需要注意的是,MLlib中只包含能够在集群上运行良好的并行算法,这一点很重要。
- 有些经典的机器学习算法没有包含在其中,就是因为它们不能并行执行。
- 相反地,一些较新的研究得出的算法因为适用于集群,也被包含在MLlib中,例如分布式随机森林算法、最小交替二乘算法。这样的选择使得MLlib中的每一个算法都适用于大规模数据集。
- 如果是小规模数据集上训练各机器学习模型,最好还是在各个节点上使用单节点的机器学习算法库(比如Weka)。
MLlib是Spark的机器学习(Machine Learning)库,旨在简化机器学习的工程实践工作。
MLlib由一些通用的学习算法和工具组成,包括分类、回归、聚类、协同过滤、降维等,同时还包括底层的优化原语和高层的流水线(Pipeline)API,具体如下:
算法工具:常用的学习算法,如分类、回归、聚类和协同过滤;
特征化工具:特征提取、转化、降维和选择工具;
流水线(Pipeline):用于构建、评估和调整机器学习工作流的工具;
持久性:保存和加载算法、模型和管道;
实用工具:线性代数、统计、数据处理等工具。
spark.mllib 包含基于RDD的原始算法API。Spark MLlib 历史比较长,在1.0 以前的版本即已经包含了,提供的算法实现都是基于原始的 RDD。
spark.ml 则提供了基于DataFrames 高层次的API,可以用来构建机器学习工作流(PipeLine)。ML Pipeline 弥补了原始 MLlib 库的不足,向用户提供了一个基于 DataFrame 的机器学习工作流式 API 套件。
MLlib目前支持4种常见的机器学习问题: 分类、回归、聚类和协同过滤。
8.4.1 流水线的概念
DataFrame:使用Spark SQL中的DataFrame作为数据集,它可以容纳各种数据类型。较之RDD,DataFrame包含了schema 信息,更类似传统数据库中的二维表格。
它被ML Pipeline用来存储源数据。例如,DataFrame中的列可以是存储的文本、特征向量、真实标签和预测的标签等。
Transformer:翻译成转换器,是一种可以将一个DataFrame转换为另一个DataFrame的算法。比如一个模型就是一个 Transformer。它可以把一个不包含预测标签的测试数据集 DataFrame 打上标签,转化成另一个包含预测标签的 DataFrame。
技术上,Transformer实现了一个方法transform(),它通过附加一个或多个列将一个DataFrame转换为另一个DataFrame。
Estimator:翻译成估计器或评估器,它是学习算法或在训练数据上的训练方法的概念抽象。在 Pipeline 里通常是被用来操作 DataFrame 数据并生成一个 Transformer。从技术上讲,Estimator实现了一个方法fit(),它接受一个DataFrame并产生一个转换器。比如,一个随机森林算法就是一个 Estimator,它可以调用fit(),通过训练特征数据而得到一个随机森林模型
Parameter:Parameter 被用来设置 Transformer 或者 Estimator 的参数。现在,所有转换器和估计器可共享用于指定参数的公共API。ParamMap是一组(参数,值)对。
PipeLine:翻译为流水线或者管道。流水线将多个工作流阶段(转换器和估计器)连接在一起,形成机器学习的工作流,并获得结果输出。
要构建一个 Pipeline流水线,首先需要定义 Pipeline 中的各个流水线阶段PipelineStage(包括转换器和评估器),比如指标提取和转换模型训练等。有了这些处理特定问题的转换器和评估器,就可以按照具体的处理逻辑有序地组织PipelineStages 并创建一个Pipeline。
>>> pipeline = Pipeline(stages=[stage1,stage2,stage3])
然后就可以把训练数据集作为输入参数,调用 Pipeline 实例的 fit 方法来开始以流的方式来处理源训练数据。这个调用会返回一个 PipelineModel 类实例,进而被用来预测测试数据的标签。
流水线的各个阶段按顺序运行,输入的DataFrame在它通过每个阶段时被转换。
值得注意的是,流水线本身也可以看做是一个估计器。在流水线的fit()方法运行之后,它产生一个PipelineModel,它是一个Transformer。 这个管道模型将在测试数据的时候使用。 下图说明了这种用法。