Hadoop生态圈中各组件工作流程分析
- HDFS YARN MapReduce三者关系
- 关系图
- 文字描述
- HDFS写数据流程
- 流程图
- 过程描述
- HDFS读数据流程
- 流程图
- 过程描述
- 切片源码
- InputFormat
- MapReduce工作流程
- 概念图
- 过程描述
- shuffle机制
- shuffle流程图解
- 文字描述
- Yarn工作机制
- 流程图
- 调度算法
- 先进先出调度器(FIFO)
- 容量调度器(Capacity Scheduler)
- 公平调度器(Fair Scheduler)
- Map Task工作机制
- 流程图
- Reduce Task 工作机制
- 流程图
- MapReduce工作机制
- 流程图
- MapReduce优化性能
- map优化
- Reduce优化
- 数据倾斜问题
- Hadoop小文件解决方案
- ZooKeeper
- ZooKeeper工作机制
- ZooKeeper特点
- Leader选举机制
- HA
- HDFS-HA核心问题
- 自动故障转移工作机制
- Yarn-HA工作机制
- Hadoop-HA集群最终规划
- Hive
- Hive架构原理
- 抽象语法树
- 逻辑计划与物理计划
- hiveserver2
- 语法
- 炸裂函数
- 窗口函数(开窗函数)
- 自定义函数
- 分区表(分目录)和分桶表(分文件)
- 二级分区表
- 动态分区
- 分桶表基本语法
- 文件格式和压缩
- Hadoop压缩概述
- Hive的企业级调优(面试重点)
- Flume
- 概述
- 组成
- 事务
- Kafka
- 生产者消息发送流程
- 生产者重要参数列表
- 生产经验--提高吞吐量
- 生产经验--数据可靠性
- 生产经验--数据去重
- 幂等性
- 生产者事务
- 生产经验--数据有序or怎么解决乱序
- Kafka Broker工作流程
- broker 重要参数
- Leader和Follower故障处理细节
- kafka文件存储机制
- 高效读写数据
- 消费者
- 消费者初始化流程
- 消费者消费详细流程
- 生产经验——分区的分配以及再平衡
- maxwell
- Maxwell原理
- Maxwell如何实现断点续传?
- datax
- Spark
- 分布式
- 计算(让CPU执行操作指令)
- 框架Frame
- Spark内置模块
- RDD
- RDD依赖关系
- 依赖分类
- Spark中的数量
- Spark分区策略
- 算子
- 转换算子
- map
- groupBy
- shuffle
- filter
- flatMap
- distinct
- sortBy
- 序列化
- 持久化
- mapValues-KV
- groupByKey-KV
- sortByKey-KV
- coalesce
- repartition
- 行动算子
- collect
- takeOrdered
- count
- first
- take
- mapToPair
- countByKey
- countByValue
- saveAsTextFile
- saveAsObjectFile
- foreachPartition
- 匿名内部类
- 其他
- parallelize
- parallelizePairs
- saveAsTextFile
- cache --优化
- persist--优化
- checkpoint--优化
- 堆内存、非堆内存、堆外内存
- Partitioner--优化
- 广播变量
- 结构化数据、半结构化数据和非结构化数据
- SparkSQL
- Spark Streaming(微批次准实时的数据处理框架)
- 数据处理方式
HDFS YARN MapReduce三者关系
关系图
文字描述
- 客户端提交文件给Hadoop -> Yarn -> ResourceManager
- ResourceManager启动App Master
- App Master 申请资源给Container
- 在Container中跑MapTask(数据分成了几片就有几个Map Task)
- Map Task 中的数据从DataNode中来
- Map Task 跑完把数据结果传给Reducer Task
- Reducer Task跑完把结果存入HDFS
HDFS写数据流程
流程图
过程描述
- 客户端创建一个 分布式文件系统对象 通过它向NameNode请求上传文件D:\io\input\a.txt
- NameNode检查目录树是否可以上传(权限、目录是否已经存在等),响应可以上传
- 客户端向NameNode请求上传Block(0-128M)
- NameNode返回dn1 dn2 dn3节点 (副本节点选择:本地节点 其他机架一个节点 其他机架另一个节点 选距离最近的 拓扑)
- 客户端和DataNode1建立传输通道,然后DataNode1和DataNode2建立传输通道,DataNode2和DataNode3建立传输通道,应答成功就可以开始传输数据了
- 从本地读取数据后开始传输,一个包一个包的传。Packet 64k 含 chunk512byte + chunksum4byte
- DataNode节点会一个一个的把Packet写在磁盘上(落盘)
- 传输完成后若还有块er没传完,回到第三步直到所以块都传输完成
HDFS读数据流程
流程图
过程描述
- 客户端创建文件系统对象 Distributed FileSystem 向NameNode发送读数据请求
- NameNode看客户端有没有读权限并看看自己有没有该数据,返回目标文件的 元数据
- 客户端创建流 读数据流
- 向目标DataNode节点发送读数据请求(块信息等)
- 目标节点向客户端传输数据
- 传输完成若还有块等待读取数据就重复步骤4直到数据都读完
- 将文件改名后关闭流
切片源码
/*
getFormatMinSplitSize() : 返回1
getMinSplitSize(job) :如果配置了mapreduce.input.fileinputformat.split.minsize参数返回参数设置的值
否则返回默认值1
*/
long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));
/*
如果设置了mapreduce.input.fileinputformat.split.maxsize参数那么返回该参数设置的值
否则返回Long.MAX_VALUE
*/
long maxSize = getMaxSplitSize(job);
//该集合用来存放切片信息(一个切片就是一个对象)
//放入到该集合的对象是FileSplit 但是泛型是InputSplit 说明FileSplit和InputSplit有继承关系
//FileSplit是InputSplit的子类
List<InputSplit> splits = new ArrayList<InputSplit>();
//获取输入目录中所有的文件或目录状态
List<FileStatus> files = listStatus(job);
//获取文件的路径
Path path = file.getPath();
//获取文件的大小
long length = file.getLen();
//块大小
long blockSize = file.getBlockSize();
/*
切片大小
默认 : 片大小 = 块大小
需求:
片大小 > 块大小 :修改minSize大小
片大小 < 块大小 :修改maxSize大小
protected long computeSplitSize(long blockSize, long minSize,
long maxSize) {
return Math.max(minSize, Math.min(maxSize, blockSize));
}
*/
long splitSize = computeSplitSize(blockSize, minSize, maxSize);
//剩余文件大小
long bytesRemaining = length;
/*
开始切片 :
((double) bytesRemaining)/splitSize > SPLIT_SLOP : 剩余文件大小 / 切片大小 > 1.1 (为了防止最后1片太小)
*/
while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
/*
makeSplit : 创建FileSplit对象
参数length-bytesRemaining :片的起始位置
参数splitSize :切片大小
将FileSplit对象放入到splits集合中
*/
splits.add(makeSplit(path, length-bytesRemaining, splitSize,
blkLocations[blkIndex].getHosts(),
blkLocations[blkIndex].getCachedHosts()));
//重新计算剩余文件大小 :将片大小从剩余文件大小减掉
bytesRemaining -= splitSize;
}
//将剩余文件大小整体切一片
if (bytesRemaining != 0) {
int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
splits.add(makeSplit(path, length-bytesRemaining, bytesRemaining,
blkLocations[blkIndex].getHosts(),
blkLocations[blkIndex].getCachedHosts()));
}
not splitable
//如果文件不可切整个文件是1片
splits.add(makeSplit(path, 0, length, blkLocations[0].getHosts(),
blkLocations[0].getCachedHosts()));
InputFormat
一 InputFormat : 抽象类
作用 :①切片 ②读取数据
//切片
public abstract
List<InputSplit> getSplits(JobContext context
) throws IOException, InterruptedException;
//创建RecordReader对象 该对象用来读取数据
public abstract
RecordReader<K,V> createRecordReader(InputSplit split,
TaskAttemptContext context
) throws IOException,
InterruptedException;
二 InputFormat的继承树
|-----InputFormat 抽象类
|------FileInputFormat 抽象类
|-------TextInputFormat 默认使用的InputFormat的类
三 FileInputFormat 抽象类
1.FileInputFormat是抽象类 继承了 InputFormat
2.FileInputFormat实现了父类的getSplits方法
四 TextInputFormat 默认使用的InputFormat的类
1.TextInputFormat是默认使用的InputFormat的类 继承了FileInputFormat
2.TextInputFormat实现了createRecordReader方法
3.createRecordReader方法返回了LineRecordReader,LineRecordReader是RecordReader的子类
4.LineRecordReader是真正用来读取文件中的数据的那个对象的所属类
public RecordReader<LongWritable, Text>
createRecordReader(InputSplit split,
TaskAttemptContext context) {
return new LineRecordReader(recordDelimiterBytes);
}
conbineTextInputFormat切片机制
将大量的小文件合并成一个大的Map Task的过程
虚拟存储过程 切片过程
MapReduce工作流程
概念图
过程描述
- 准备待处理数据a.txt 200M 块大小是128M 切两片(若想改变切片大小就设置conf参数,具体修改见切片源码)
- InputFormat -> FileInputFormat -> TextInputFormat -> getSplit 对数据进行切片处理,上传jar包,提交job信息给YarnRunner 创建App Master
- App Master 申请资源,生成两个Map Task
- Map Task 用 InputFormat -> RecordReader -> LineRecondReader 读数据
- 然后使用Mapper的map方法进行数据处理
- 接下来就是shuffle过程,在下一章节详细讲解
shuffle机制
shuffle流程图解
文字描述
- map方法写出的数据流向了缓冲区
- 该缓冲区在数据量达到80%时会进行排序,排序完成的数据会形成多个分区直接写入磁盘,每轮数据都会按该分区算法进行分区,方便下一步归并
- 所有数据都处理完成后就会进行归并排序,相同分区的数据进行归并
- 归并完成后按分区进行combiner合并压缩即:将1万条aaa 1 -> aaa 10000
- 将处理结果写入磁盘
- 进行过上述操作后将所有 Map Task 的相同分区放在一起进行的归并排序
- 最后数据进入 reduce 方法
Yarn工作机制
流程图
调度算法
先进先出调度器(FIFO)
容量调度器(Capacity Scheduler)
公平调度器(Fair Scheduler)
Map Task工作机制
流程图
Reduce Task 工作机制
流程图
MapReduce工作机制
流程图
MapReduce优化性能
map优化
Reduce优化
数据倾斜问题
Hadoop小文件解决方案
- Hadoop Archive(HAR归档)
是一个高效的将小文件放入HDFS块中的文件存档工具,能够将多个小文件打包成一个HAR文件,从而达到减少NameNode的内存使用。 - CombineTextInputFormat
CombineTextInputFormat用于将多个小文件在切片过程中生成一个单独的切片或者少量的切片。
ZooKeeper
ZooKeeper工作机制
ZooKeeper特点
Leader选举机制
zkCli.sh进入zk客户端
HA
HDFS-HA核心问题
- 怎么保证三台namenode的数据一致
- Fsimage:让一台nn生成数据,让其他机器nn同步。
- Edits:需要引进新的模块JournalNode来保证edtis的文件的数据一致性。
- 怎么让同时只有一台nn是active,其他所有是standby的
Zookeeper居中协调,选举active - 2nn在ha架构中并不存在,定期合并fsimage和edtis的活谁来干
由standby的nn来干。 - 如果nn真的发生了问题,怎么让其他的nn上位干活
自动故障转移
自动故障转移工作机制
Yarn-HA工作机制
Hadoop-HA集群最终规划
hadoop102 | hadoop103 | hadoop104 |
NameNode | NameNode | NameNode |
JournalNode | JournalNode | JournalNode |
DataNode | DataNode | DataNode |
Zookeeper | Zookeeper | Zookeeper |
ZKFC | ZKFC | ZKFC |
ResourceManager | ResourceManager | ResourceManager |
NodeManager | NodeManager | NodeManager |
- NameNode:HDFS中存储元数据信息的服务
- JournalNode:保证edtis的文件的数据一致性的服务
- DataNode:HDFS中真正存储数据的节点服务
- Zookeeper:文件系统+通知机制的服务
- ZKFC:ZooKeeper Failover Controller,监视和管理 Hadoop 的 NameNode 服务的高可用性
- ResourceManager:Yarn中处理客户端请求、监控NodeManager、启动或监控ApplicationMaster、资源的分配与调度的服务
- NodeManager:Yarn中管理单个节点上的资源、处理来自ResourceManager的命令、处理来自ApplicationMaster的命令的服务
Hive
基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张表,并提供类SQL查询功能。
Hive架构原理
抽象语法树
逻辑计划与物理计划
hiveserver2
Hive的hiveserver2服务的作用是提供jdbc/odbc接口,为用户提供远程访问Hive数据的功能,例如用户期望在个人电脑中访问远程服务中的Hive数据,就需要用到Hiveserver2。
Hive的metastore服务的作用是为Hive CLI或者Hiveserver2提供元数据访问接口。
语法
基础语法和MySQL相同
炸裂函数
案例演示:
数据准备
movie | category |
《疑犯追踪》 | 悬疑,动作,科幻,剧情 |
《Lie to me》 | 悬疑,警匪,动作,心理,剧情 |
《战狼2》 | 战争,动作,灾难 |
create table movie_info(
movie string, --电影名称
category string --电影分类
)
row format delimited fields terminated by "\t";
insert overwrite table movie_info
values ("《疑犯追踪》", "悬疑,动作,科幻,剧情"),
("《Lie to me》", "悬疑,警匪,动作,心理,剧情"),
("《战狼2》", "战争,动作,灾难");
需求说明:根据上述电影信息表,统计各分类的电影数量,期望结果如下:
类型 | 数量 |
剧情 | 2 |
动作 | 3 |
心理 | 1 |
悬疑 | 2 |
战争 | 1 |
灾难 | 1 |
科幻 | 1 |
警匪 | 1 |
select
cate,
count(*)
from
(
select
movie,
cate
from
(
select
movie,
split(category,',') cates
from movie_info
)t1 lateral view explode(cates) tmp as cate
)t2
group by cate;
窗口函数(开窗函数)
按照功能,常用窗口可划分为如下几类:聚合函数、跨行取值函数、排名函数。
1)聚合函数
max:最大值。
min:最小值。
sum:求和。
avg:平均值。
count:计数。
2)跨行取值函数
(1)lead和lag
(2)first_value last_value
(3)排名函数
自定义函数
1)Hive自带了一些函数,比如:max/min等,但是数量有限,自己可以通过自定义UDF来方便的扩展。
2)当Hive提供的内置函数无法满足你的业务处理需要时,此时就可以考虑使用用户自定义函数(UDF:user-defined function)。
3)根据用户自定义函数类别分为以下三种:
(1)UDF(User-Defined-Function)
一进一出。
(2)UDAF(User-Defined Aggregation Function)
用户自定义聚合函数,多进一出。
类似于:count/max/min
(3)UDTF(User-Defined Table-Generating Functions)
用户自定义表生成函数,一进多出。
如lateral view explode()
4)官方文档地址
https://cwiki.apache.org/confluence/display/Hive/HivePlugins
分区表(分目录)和分桶表(分文件)
create table dept_partition(
deptno int, --部门编号
dname string, --部门名称
loc string --部门位置
)
partitioned by (day string)
row format delimited fields terminated by '\t';
load data local inpath '/opt/module/hive/datas/dept_20220401.log' into table dept_partition partition(day='20220401');
按day分区,day是一个新列,里面可以是空的导数据的时候再写20220401,但创建表时必须有day这个字段类型也不能错!是string
insert
insert overwrite table dept_partition partition (day = '20220402')
select deptno, dname, loc
from dept_partition
where day = '2020-04-01';
分区表基本操作
(1)查看所有分区信息
hive> show partitions dept_partition;
(2)增加分区
①创建单个分区
hive (default)>
alter table dept_partition
add partition(day=‘20220403’);
②同时创建多个分区(分区之间不能有逗号)
hive (default)>
alter table dept_partition
add partition(day=‘20220404’) partition(day=‘20220405’);
(3)删除分区
①删除单个分区
hive (default)>
alter table dept_partition
drop partition (day=‘20220403’);
②同时删除多个分区(分区之间必须有逗号)
hive (default)>
alter table dept_partition
drop partition (day=‘20220404’), partition(day=‘20220405’);
二级分区表
思考:如果一天内的日志数据量也很大,如何再将数据拆分?答案是二级分区表,例如可以在按天分区的基础上,再对每天的数据按小时进行分区。
1)二级分区表建表语句
hive (default)>
create table dept_partition2(
deptno int, – 部门编号
dname string, – 部门名称
loc string – 部门位置
)
partitioned by (day string, hour string)
row format delimited fields terminated by ‘\t’;
2)数据装载语句
hive (default)>
load data local inpath ‘/opt/module/hive/datas/dept_20220401.log’
into table dept_partition2
partition(day=‘20220401’, hour=‘12’);
3)查询分区数据
hive (default)>
select
*
from dept_partition2
where day=‘20220401’ and hour=‘12’;
动态分区
动态分区是指向分区表insert数据时,被写往的分区不由用户指定,而是由每行数据的最后一个字段的值来动态的决定。使用动态分区,可只用一个insert语句将数据写入多个分区。
1)动态分区相关参数
(1)动态分区功能总开关(默认true,开启)
set hive.exec.dynamic.partition=true
(2)严格模式和非严格模式
动态分区的模式,默认strict(严格模式),要求必须指定至少一个分区为静态分区,nonstrict(非严格模式)允许所有的分区字段都使用动态分区。
set hive.exec.dynamic.partition.mode=nonstrict
(3)一条insert语句可同时创建的最大的分区个数,默认为1000。
set hive.exec.max.dynamic.partitions=1000
(4)单个Mapper或者Reducer可同时创建的最大的分区个数,默认为100。
set hive.exec.max.dynamic.partitions.pernode=100
(5)一条insert语句可以创建的最大的文件个数,默认100000。
hive.exec.max.created.files=100000
(6)当查询结果为空时且进行动态分区时,是否抛出异常,默认false。
hive.error.on.empty.partition=false
分桶表基本语法
1)建表语句
hive (default)>
create table stu_buck(
id int,
name string
)
clustered by(id)
into 4 buckets
row format delimited fields terminated by ‘\t’;
2)数据装载
(1)数据准备
在/opt/module/hive/datas/路径上创建student.txt文件,并输入如下内容。
1001 student1
1002 student2
1003 student3
1004 student4
1005 student5
1006 student6
1007 student7
1008 student8
1009 student9
1010 student10
1011 student11
1012 student12
1013 student13
1014 student14
1015 student15
1016 student16
(2)导入数据到分桶表中
说明:Hive新版本load数据可以直接跑MapReduce,老版的Hive需要将数据传到一张表里,再通过查询的方式导入到分桶表里面。
hive (default)>
load data local inpath ‘/opt/module/hive/datas/student.txt’
into table stu_buck;
文件格式和压缩
text file和sequence file都是基于行存储的,orc和parquet是基于列式存储的。
Hadoop压缩概述
压缩格式 | 算法 | 文件扩展名 | 是否可切分 |
DEFLATE | DEFLATE | .deflate | 否 |
Gzip | DEFLATE | .gz | 否 |
bzip2 | bzip2 | .bz2 | 是 |
LZO | LZO | .lzo | 是 |
Snappy | Snappy | .snappy | 否 |
压缩性能的比较:
压缩算法 | 原始文件大小 | 压缩文件大小 | 压缩速度 | 解压速度 |
gzip | 8.3GB | 1.8GB | 17.5MB/s | 58MB/s |
bzip2 | 8.3GB | 1.1GB | 2.4MB/s | 9.5MB/s |
LZO | 8.3GB | 2.9GB | 49.3MB/s | 74.6MB/s |
为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器,如下表所示:
Hadoop查看支持压缩的方式hadoop checknative。
Hadoop在driver端设置压缩。
压缩格式 | 对应的编码/解码器 |
DEFLATE | org.apache.hadoop.io.compress.DefaultCodec |
gzip | org.apache.hadoop.io.compress.GzipCodec |
bzip2 | org.apache.hadoop.io.compress.BZip2Codec |
LZO | com.hadoop.compression.lzo.LzopCodec |
Snappy | org.apache.hadoop.io.compress.SnappyCodec |
以TextFile为例
若一张表的文件类型为TextFile,若需要对该表中的数据进行压缩,多数情况下,无需在建表语句做出声明。直接将压缩后的文件导入到该表即可,Hive在查询表中数据时,可自动识别其压缩格式,进行解压。
需要注意的是,在执行往表中导入数据的SQL语句时,用户需设置以下参数,来保证写入表中的数据是被压缩的。
–SQL语句的最终输出结果是否压缩
set hive.exec.compress.output=true;
–输出结果的压缩格式(以下示例为snappy)
set mapreduce.output.fileoutputformat.compress.codec =org.apache.hadoop.io.compress.SnappyCodec;
2)ORC
若一张表的文件类型为ORC,若需要对该表数据进行压缩,需在建表语句中声明压缩格式如下:
create table orc_table
(column_specs)
stored as orc
tblproperties (“orc.compress”=“snappy”);
Hive的企业级调优(面试重点)
Flume
概述
组成
1.2.1 Agent
Agent是一个JVM进程,它以事件的形式将数据从源头送至目的地。
Agent主要有3个部分组成,Source、Channel、Sink。
1.2.2 Source
Source是负责接收数据到Flume Agent的组件。Source组件可以处理各种类型、各种格式的日志数据
1.2.3 Sink
Sink不断地轮询Channel中的事件且批量地移除它们,并将这些事件批量写入到存储或索引系统、或者被发送到另一个Flume Agent。
1.2.4 Channel
Channel是位于Source和Sink之间的缓冲区。因此,Channel允许Source和Sink运作在不同的速率上。Channel是线程安全的,可以同时处理几个Source的写入操作和几个Sink的读取操作。
Flume自带两种Channel:Memory Channel和File Channel。
Memory Channel是内存中的队列。Memory Channel在不需要关心数据丢失的情景下适用。如果需要关心数据丢失,那么Memory Channel就不应该使用,因为程序死亡、机器宕机或者重启都会导致数据丢失。
File Channel将所有事件写到磁盘。因此在程序关闭或机器宕机的情况下不会丢失数据。
1.2.5 Event
传输单元,Flume数据传输的基本单元,以Event的形式将数据从源头送至目的地。Event由Header和Body两部分组成,Header用来存放该event的一些属性,为K-V结构,Body用来存放该条数据,形式为字节数组。
事务
doCommit:检查channel内队列是否足够合并 的意思是 当前putlist中的数据是否可以放到channel中,可以就放,不可以就不放,然后都清空putList
https://flume.apache.org/releases/content/1.10.0/FlumeUserGuide.html
Kafka
消息队列,基础架构
- (1)Producer:消息生产者,就是向Kafka broker发消息的客户端。
- (2)Consumer:消息消费者,从Kafka broker取消息的客户端。
- (3)Consumer Group(CG):消费者组,由多个consumer组成。消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个组内消费者消费;消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。
- (4)Broker:一台Kafka服务器就是一个broker。一个集群由多个broker组成。一个broker可以容纳多个topic。
- (5)Topic:可以理解为一个队列,生产者和消费者面向的都是一个topic。
- (6)Partition:为了实现扩展性,一个非常大的topic可以分布到多个broker(即服务器)上,一个topic可以分为多个partition,每个partition是一个有序的队列。
- (7)Replica:副本。一个topic的每个分区都有若干个副本,一个Leader和若干个Follower。
- (8)Leader:每个分区多个副本的“主”,生产者发送数据的对象,以及消费者消费数据的对象都是Leader。
Follower:每个分区多个副本中的“从”,实时从Leader中同步数据,保持和Leader数据的同步。Leader发生故障时,某个Follower会成为新的Leader。
生产者消息发送流程
生产者重要参数列表
参数名称 | 描述 |
bootstrap.servers | 生产者连接集群所需的broker地址清单。例如hadoop102:9092,hadoop103:9092,hadoop104:9092,可以设置1个或者多个,中间用逗号隔开。注意这里并非需要所有的broker地址,因为生产者从给定的broker里查找到其他broker信息。 |
key.serializer和value.serializer | 指定发送消息的key和value的序列化类型。一定要写全类名。 |
buffer.memory | RecordAccumulator缓冲区总大小,默认32m。 |
batch.size | 缓冲区一批数据最大值,默认16k。适当增加该值,可以提高吞吐量,但是如果该值设置太大,会导致数据传输延迟增加。 |
linger.ms | 如果数据迟迟未达到batch.size,sender等待linger.time之后就会发送数据。单位ms,默认值是0ms,表示没有延迟。生产环境建议该值大小为5-100ms之间。 |
acks | 0:生产者发送过来的数据,不需要等数据落盘应答。1:生产者发送过来的数据,Leader收到数据后应答。-1(all):生产者发送过来的数据,Leader+和isr队列里面的所有节点收齐数据后应答。默认值是-1,-1和all是等价的。 |
max.in.flight.requests.per.connection | 允许最多没有返回ack的次数,默认为5,开启幂等性要保证该值是 1-5的数字。 |
retries | 当消息发送出现错误的时候,系统会重发消息。retries表示重试次数。默认是int最大值,2147483647。如果设置了重试,还想保证消息的有序性,需要设置MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION=1否则在重试此失败消息的时候,其他的消息可能发送成功了。 |
retry.backoff.ms | 两次重试之间的时间间隔,默认是100ms。 |
enable.idempotence | 是否开启幂等性,默认true,开启幂等性。 |
compression.type | 生产者发送的所有数据的压缩方式。默认是none,也就是不压缩。 支持压缩类型:none、gzip、snappy、lz4和zstd。 |
生产经验–提高吞吐量
生产经验–数据可靠性
ack应答
生产经验–数据去重
幂等性
1)幂等性原理
生产者事务
0.11版本的Kafka同时引入了事务的特性,为了实现跨分区跨会话的事务,需要引入一个全局唯一的Transaction ID,并将Producer获得的PID和Transaction ID绑定。这样当Producer重启后就可以通过正在进行的Transaction ID获得原来的PID。
为了管理Transaction,Kafka引入了一个新的组件Transaction Coordinator。Producer就是通过和Transaction Coordinator交互获得Transaction ID对应的任务状态。
Transaction Coordinator还负责将事务所有写入Kafka的一个内部Topic,这样即使整个服务重启,由于事务状态得到保存,进行中的事务状态可以得到恢复,从而继续进行。
注意:提前开启幂等性!!!
生产经验–数据有序or怎么解决乱序
- Kafka 最多只保证单分区内的消息是有序的,所以如果要保证业务全局严格有序,就要设置 Topic 为单分区。
- 如何保证单分区内数据有序or怎么解决乱序?
其中参数134都是保证数据安全性的,2是保证顺序的
在生产者向kafka中传输数据时
在producer中就排好序seqnum,0-5给6-11
6-9均已传输完成,传输第10个batch时发生故障写入失败,从broker返回写入失败的消息,于是producer开始重新传输batch10
但此时batch11已发出,到broker时,broker发现batch11的编号是5,而batch9的seqnum是3,5-3 不等于1,那么broker就不让batch11写入,保证了数据的有序性
等到batch10重新发送的请求成功写入了之后再允许后面的batch写入
Kafka Broker工作流程
broker启动后在zk中注册(即在集群上启动kafka)
然后controller选举leader(leader只存在于同一分区的不同副本上),谁先注册,谁说了算(当leader)选举规则如图
由leader节点上的controller监听broker节点的变化
每台节点上的controller将节点信息上传到ZooKeeper,其他节点的controller从zk同步相关信息
假设broker1(leader)挂了,controller监听到节点变化就要获取 ISR 选举新的leader:broker0,更新leader和 ISR
broker 重要参数
参数名称 | 描述 |
replica.lag.time.max.ms | ISR中,如果Follower长时间未向Leader发送通信请求或同步数据,则该Follower将被踢出ISR。该时间阈值,默认30s。 |
auto.leader.rebalance.enable | 默认是true。 自动Leader Partition 平衡。 |
leader.imbalance.per.broker.percentage | 默认是10%。每个broker允许的不平衡的leader的比率。如果每个broker超过了这个值,控制器会触发leader的平衡。 |
leader.imbalance.check.interval.seconds | 默认值300秒。检查leader负载是否平衡的间隔时间。 |
log.segment.bytes | Kafka中log日志是分成一块块存储的,此配置是指log日志划分 成块的大小,默认值1G。 |
log.index.interval.bytes | 默认4kb,kafka里面每当写入了4kb大小的日志(.log),然后就往index文件里面记录一个索引。 |
log.retention.hours | Kafka中数据保存的时间,默认7天。 |
log.retention.minutes | Kafka中数据保存的时间,分钟级别,默认关闭。 |
log.retention.ms | Kafka中数据保存的时间,毫秒级别,默认关闭。 |
log.retention.check.interval.ms | 检查数据是否保存超时的间隔,默认是5分钟。 |
log.retention.bytes | 默认等于-1,表示无穷大。超过设置的所有日志总大小,删除最早的segment。 |
log.cleanup.policy | 默认是delete,表示所有数据启用删除策略;如果设置值为compact,表示所有数据启用压缩策略。 |
num.io.threads | 默认是8。负责写磁盘的线程数。整个参数值要占总核数的50%。 |
num.replica.fetchers | 副本拉取线程数,这个参数占总核数的50%的1/3 |
num.network.threads | 默认是3。数据传输线程数,这个参数占总核数的50%的2/3 。 |
log.flush.interval.messages | 强制页缓存刷写到磁盘的条数,默认是long的最大值,9223372036854775807。一般不建议修改,交给系统自己管理。 |
log.flush.interval.ms | 每隔多久,刷数据到磁盘,默认是null。一般不建议修改,交给系统自己管理。 |
Leader和Follower故障处理细节
kafka文件存储机制
index:稀疏索引
log:offset,偏移量
timeindex:时间索引
kafka-run-class.sh kafka.tools.DumpLogSegments --files ./00000000000000000000.index
kafka-run-class.sh kafka.tools.DumpLogSegments --files ./00000000000000000000.log
kafka-run-class.sh kafka.tools.DumpLogSegments --files ./00000000000000000000.index --print-data-log
//从3往下的所有数据
kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic demo --partition 0 --offset 3
高效读写数据
消费者
消费者初始化流程
消费者消费详细流程
生产经验——分区的分配以及再平衡
就是coordinate 选出来的Leader 是如何将分区分配给组内的各个消费者的
maxwell
Maxwell 是由美国Zendesk公司开源,用Java编写的MySQL变更数据抓取软件。它会实时监控MySQL数据库的数据变更操作(包括insert、update、delete),并将变更数据以 JSON 格式发送给 Kafka、Kinesi等流数据处理平台。官网地址:http://maxwells-daemon.io/
Maxwell原理
将自己伪装成slave,并遵循MySQL主从复制的协议,从master同步数据。Maxwell的工作原理是实时读取MySQL数据库的二进制日志(Binlog),从中获取变更数据,再将变更数据以JSON格式发送至Kafka等流处理平台。
Maxwell如何实现断点续传?
在MySQL中维护了一个元数据库,在元数据库中有一个position表,在这里记录了监控的binlog位置。
datax
DataX 是阿里巴巴开源的一个异构数据源离线同步工具,致力于实现包括关系型数据库(MySQL、Oracle等)、HDFS、Hive、ODPS、HBase、FTP等各种异构数据源之间稳定高效的数据同步功能。
源码地址:https://github.com/alibaba/DataX
下载地址:http://datax-opensource.oss-cn-hangzhou.aliyuncs.com/datax.tar.gz
Spark
分布式计算框架
分布式
多进程、多节点
伪分布式:多进程、单节点
vCore:虚拟核
单进程申请资源少
进程和线程:Java中进程和线程底层都是调用JVM来抢占资源(CPU)的
栈溢出:栈里东西太多了
栈内存溢出:内存东西太多了无法新建栈了
微服务架构(可以和分布式的主从架构结合使用)
为了避免网络拥挤最好不把两台服务器放一个机房
计算(让CPU执行操作指令)
单机计算
分布式计算:核心是切片(将数据切分后给各个节点计算)
框架Frame
框架:不完整的计算机程序,无法独立运行
MR Spark
系统:不完整的计算机程序,无法独立运行
HDFS Kafka ZooKeeper
Spark(离线)&Flink(实时)
离线:以天、小时为单位
实时:以毫秒为单位延迟
MR & Spark
Hive:MR开发效率低(MR基于一次性计算的中心思想)
Spark:解决了MR运行效率低的问题,数据在中间状态(除了shuffle)不落盘,减少了IO,但过于依赖内存
Spark内置模块
RDD
Spark分布式计算核心模型
数据结构:组织和管理数据的结构和方式
数组、链表。。。
数据模型:对数据的模拟和抽象以及提炼(class)
数据模型(Bean) + 业务模型(逻辑)-----封装
RDD:对象 + 封装(分布式计算)
还有计算逻辑要自己写
数据在RDD中流转路径
RDD依赖关系
依赖关系:如果A用到了B,那么就说A依赖于B
Spark中相邻的2个RDD之间存在依赖关系:新的RDD依赖于旧的RDD
连续的依赖关系我们一般称之为血缘关系
每个RDD都会保存血缘关系
依赖分类
RDD的依赖关系(数据分区之间的关系)主要分为2种:
- OneToOneDependency ( NarrowDependency ):窄依赖
如果上游(旧)的RDD的一个分区的数据被下游(新)的RDD的一个分区所独享,这个依赖关系就称之窄依赖 - ShuffleDependency :宽依赖
如果上游(旧)的RDD的一个分区的数据被下游(新)的RDD的多个分区所共享,这个依赖关系就称之宽依赖,宽依赖会将数据打乱重新组合,所以底层会存在shuffle操作
Spark中的数量
Application的数量 :SparkContext环境对象的数量, Driver对象(线程)
Job (ActiveJob)的数量 :行动算子调用的次数
Stage(阶段)的数量 :shuffle(宽依赖)的次数 + 1
Task(任务)的数量 :每个阶段的最后的RDD的分区数量之和
Spark分区策略
/*
1A----00-03
200B--04-09
3C----10-13
4D----14-17
50E---18-22
6F----23-26
7H----27-30
89I---31-33
*/
34/5=6...4
4/6>1/10 所以剩余字符自己成为一个区
共6个区 每个区6个字符 最后一个分区4个字符
Spark 恶化Hadoop一样要读就读一整行
所以每个分区的数据就是下面展示的那样
本应读的范围:实际读到的数据
[00-05]1A 200B
[06-11]3C
[12-17]4D
[18-23]50E 6F
[24-29]7H
[30-33]89I
假设文件中34个字符,设置的最小分区为5
算子
转换算子
只要源码中被XXXX注释的方法就都可以用lamda表达式
map
A变换B => 1 => 2
RDD数据执行过程中:分区间无序,分区内有序
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.Function;
import java.util.Arrays;
public class SparkCore01_Function {
public static void main(String[] args) {
SparkConf conf = new SparkConf();
conf.setMaster("local[*]");
conf.setAppName("Spark Core RDD Function");
final JavaSparkContext jsc = new JavaSparkContext(conf);
final JavaRDD<Integer> rdd = jsc.parallelize(
Arrays.asList(1, 2, 3, 4)
);
// TODO 逻辑 : num * 2
// map : A变换B => 1 => 2
final JavaRDD<Integer> mapRDD = rdd.map(new Function<Integer, Integer>() {
@Override
public Integer call(Integer in) throws Exception {
return in * 2;
}
});
mapRDD.collect().forEach(System.out::println);
jsc.stop();
}
}
groupBy
分组
groupBy方法用于将数据按照指定的规则进行分组
方法可以传递2个参数
- 第一个参数表示分组逻辑
- 第二个参数表示分组后,分区的数量
参数可以省略,默认取值就是前一个RDD的分区数量
shuffle
Spark要求一个组的数据必须在一个分区中
Spark的分组操作会将一个分区的数据打乱和其他分区的数据重新组合在一起
这个操作称之为打乱重组,也称之为shuffle
Spark要求shuffle操作中,前一个RDD功能不执行完,后续的RDD功能不允许执行
Spark中RDD不允许存储数据,Shuffle操作又必须保证前一个RDD完全处理完,才能执行后续RDD,那么数据就需要临时保存
Spark的shuffle操作是将数据存储到磁盘中的
Spark中所有包含shuffle功能的方法,都有可以改变分区数量的能力
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import java.util.Arrays;
public class SparkCore05_Function_groupBy_2 {
public static void main(String[] args) {
SparkConf conf = new SparkConf();
conf.setMaster("local[*]");
conf.setAppName("Spark Core RDD Function");
final JavaSparkContext jsc = new JavaSparkContext(conf);
final JavaRDD<String> rdd = jsc.parallelize(
Arrays.asList("Hadoop", "spark", "hive", "Sqoop"), 2
);
rdd.groupBy(
name -> name.substring(0, 1).toUpperCase()//name.charAt(0)//name.substring(0, 1)
).collect().forEach(System.out::println);
jsc.stop();
}
}
filter
筛选过滤
filter方法可以对数据按照指定的规则进行筛选过滤
filter方法会根据规则的返回值判断数据是否保留
true -> 保留
false -> 丢弃
filter方法可能会导致数据倾斜
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import java.util.Arrays;
public class SparkCore03_Function_filter {
public static void main(String[] args) {
SparkConf conf = new SparkConf();
conf.setMaster("local[*]");
conf.setAppName("Spark Core RDD Function");
final JavaSparkContext jsc = new JavaSparkContext(conf);
final JavaRDD<Integer> rdd = jsc.parallelize(Arrays.asList(1, 2, 3, 4), 2);
final JavaRDD<Integer> filterRDD = rdd.filter(num -> {
return num % 2 == 0;
});
filterRDD.collect().forEach(System.out::println);
jsc.stop();
}
}
flatMap
将数据分解成多个数据返回,但是语法要求返回值必须为迭代器
将一个整体拆分成个体使用的操作,称之为扁平化(flat + map)操作
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import java.util.Arrays;
public class SparkCore04_Function_flatMap {
public static void main(String[] args) {
SparkConf conf = new SparkConf();
conf.setMaster("local[*]");
conf.setAppName("Spark Core RDD Function");
final JavaSparkContext jsc = new JavaSparkContext(conf);
final JavaRDD<String> rdd = jsc.textFile("data/test.txt");
// map : A -> B (1 : 1)
// flatMap : A -> (B,C,D,E) (1 : N)
// flatMap可以将数据分解成多个数据返回,但是语法要求返回值必须为迭代器
final JavaRDD<String> flatMapRDD = rdd.flatMap(
line -> Arrays.asList(line.split(" ")).iterator()
);
flatMapRDD.collect().forEach(System.out::println);
jsc.stop();
}
}
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import java.util.Arrays;
import java.util.List;
public class exer {
public static void main(String[] args) {
SparkConf conf = new SparkConf();
conf.setAppName("hahahaspark");
conf.setMaster("local[*]");
final JavaSparkContext j = new JavaSparkContext(conf);
JavaRDD<List<Integer>> rdd = j.parallelize(Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5, 6)
));
//下面这句中Arrays.asList()是个空集合,所以语法虽然没有报错但一定打印不出东西,所以flatMap函数既可以1->1、1->N、1->0
rdd.flatMap(list -> Arrays.asList().iterator()).collect().forEach(System.out::println);
//常用输出语句
rdd.flatMap(List::iterator).collect().forEach(System.out::println);
j.stop();
}
}
distinct
分布式去重
distinct也会产生数据倾斜
distinct方法可以传递参数:参数表示去重后的分区数量
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import java.util.Arrays;
public class SparkCore06_Function_distinct {
public static void main(String[] args) throws Exception {
SparkConf conf = new SparkConf();
conf.setMaster("local[*]");
conf.setAppName("Spark Core RDD Function");
final JavaSparkContext jsc = new JavaSparkContext(conf);
final JavaRDD<Integer> rdd = jsc.parallelize(
Arrays.asList(1,1,1,1), 4
);
final JavaRDD<Integer> distinctRDD = rdd.distinct();
distinctRDD.saveAsTextFile("output");//.collect().forEach(System.out::println);
jsc.stop();
}
}
sortBy
sortBy按照指定的规则对数据进行排序
sortBy方法需要传递3个参数
- 第一个参数表示 排序的规则
spark排序时,会给每一条数据增加一个排序的标记,根据标记(类型)对数据进行排序 - 第二个参数表示 排序的方式(true:升序,false:降序)
- 第三个参数表示 排序后的分区数量
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaSparkContext;
import java.util.Arrays;
public class exer {
public static void main(String[] args) {
SparkConf conf = new SparkConf();
conf.setAppName("hahahaspark");
conf.setMaster("local[*]");
final JavaSparkContext j = new JavaSparkContext(conf);
j.parallelize(Arrays.asList(1,11,22,3,2,5,7,4)).sortBy(num->num+"",true,2).collect().forEach(System.out::println);
j.stop();
}
}
使用自己写的javaBean时记得序列化和重写compareTo方法
重写compareTo方法很好理解就是为了比较排序,那么序列化呢?
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaSparkContext;
import java.io.Serializable;
import java.util.Arrays;
public class exer {
public static void main(String[] args) {
SparkConf conf = new SparkConf();
conf.setAppName("hahahaspark");
conf.setMaster("local[*]");
final JavaSparkContext j = new JavaSparkContext(conf);
for (User user : j.parallelize(Arrays.asList(
new User(20, 3000),
new User(50, 5000),
new User(30, 4000),
new User(10, 6000)
), 2).sortBy(user1 -> user1.getAge(),true,2).collect()) {
System.out.println(user);
}
j.stop();
}
}
class User implements Serializable, Comparable<User> {
private Integer age;
private Integer amount;
public User() {
}
public User(Integer age, Integer amount) {
this.age = age;
this.amount = amount;
}
@Override
public String toString() {
return "User{" +
"age=" + age +
", amount=" + amount +
'}';
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
@Override
public int compareTo(User o) {
return this.getAge()-o.getAge();
}
}
序列化
由图,不难发现我们的自定义类通过网络传输了,我们的命令(Driver)是跑在蓝色区域部分的,但是Spark是集群作业,所以Driver和Executor未必在同一节点,那么就需要通过网络传输,所以需要序列化。
Driver端将RDD计算逻辑组合,Executor端执行代码
所以User这个类要通过网络传输给Executor端
需要将Driver端的对象拉取到Executor端之后执行
拉取数据的过程需要通过网络,网络中只能传递asc码
asc码:0~255
java的Byte类型:-128-127
所以我们只要在java中将字符串或是其他的内存中的内容转成Byte数组(字节码)--------这个过程就是序列化
持久化
把数据保存到磁盘和内存都是持久化
mapValues-KV
给KV类型用的,单值类型用不了
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import scala.Tuple2;
import java.util.Arrays;
public class SparkCore09_Function_KV_mapValues {
public static void main(String[] args) throws Exception {
SparkConf conf = new SparkConf();
conf.setMaster("local[*]");
conf.setAppName("Spark Core RDD Function");
final JavaSparkContext jsc = new JavaSparkContext(conf);
final JavaRDD<Tuple2<String, Integer>> rdd = jsc.parallelize(
Arrays.asList(
new Tuple2<>("zhangsan", 3000),
new Tuple2<>("lisi", 4000),
new Tuple2<>("wangwu", 4500),
new Tuple2<>("zhaoliu", 2400)
),
2
);
// new Tuple2<>("zhangsan", 3000) -> new Tuple2<>("zhangsan", 36000)
rdd.map(
tuple2 -> {
// 元组:将无关的数据组合在一起,当成整体使用
// 如果想要访问元组的元素,可以采用顺序号。
// 元组的数据只会为了封装,无法进行修改
// 方法元组中的第二个值并进行修改
//tuple2._2 = tuple2._2 * 12;
return new Tuple2<>( tuple2._1, tuple2._2 * 12 );
}
).collect().forEach(System.out::println);
jsc.stop();
}
}
元组类型不能改变其值,只能返回一个新的
groupByKey-KV
groupByKey方法会将数据的key作为分组的标记,将V分在一个组中
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaSparkContext;
import scala.Tuple2;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
public class SparkCore10_Function_KV_groupByKey {
public static void main(String[] args) throws Exception {
SparkConf conf = new SparkConf();
conf.setMaster("local[*]");
conf.setAppName("Spark Core RDD Function");
final JavaSparkContext jsc = new JavaSparkContext(conf);
// JavaRDD <Tuple2<String, Integer>> rdd
final JavaPairRDD<String, Integer> rdd = jsc.parallelizePairs(
Arrays.asList(
new Tuple2<>("a", 3000),
new Tuple2<>("b", 4000),
new Tuple2<>("a", 4500),
new Tuple2<>("b", 2400)
),
2
);
// TODO groupBy方法会将数据按照规则进行分组
final JavaPairRDD<String, Iterable<Tuple2<String, Integer>>> rdd1 =
rdd.groupBy(
kv -> kv._1
);
// TODO groupByKey方法会将数据的key作为分组的标记,将V分在一个组中
final JavaPairRDD<String, Iterable<Integer>> rdd2 = rdd.groupByKey();
rdd1.collect().forEach(System.out::println);
System.out.println("******************************************");
rdd2.collect().forEach(System.out::println);
jsc.stop();
}
}
sortByKey-KV
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaSparkContext;
import scala.Tuple2;
import java.util.Arrays;
public class SparkCore12_Function_KV_sortByKey {
public static void main(String[] args) throws Exception {
SparkConf conf = new SparkConf();
conf.setMaster("local[*]");
conf.setAppName("Spark Core RDD Function");
final JavaSparkContext jsc = new JavaSparkContext(conf);
// JavaRDD <Tuple2<String, Integer>> rdd
final JavaPairRDD<String, Integer> rdd = jsc.parallelizePairs(
Arrays.asList(
new Tuple2<>("a", 1000),
new Tuple2<>("b", 4000),
new Tuple2<>("a", 3000),
new Tuple2<>("b", 2000)
),
2
);
// TODO sortByKey方法用于对KV类型数据中的key进行排序
// groupByKey : (k, [v, v, v,])
// reduceByKey : (k, [v + v + v])
// sortByKey :
final JavaPairRDD<String, Integer> sortRDD = rdd.sortByKey(false);
sortRDD.collect().forEach(System.out::println);
jsc.stop();
}
}
coalesce
合并(缩减)分区
repartition
增加分区,底层还是掉coalescce
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaSparkContext;
import scala.Tuple2;
import java.util.Arrays;
public class SparkCore13_Function {
public static void main(String[] args) throws Exception {
SparkConf conf = new SparkConf();
conf.setMaster("local[*]");
conf.setAppName("Spark Core RDD Function");
final JavaSparkContext jsc = new JavaSparkContext(conf);
// JavaRDD <Tuple2<String, Integer>> rdd
final JavaPairRDD<String, Integer> rdd = jsc.parallelizePairs(
Arrays.asList(
new Tuple2<>("a", 1000),
new Tuple2<>("b", 2000),
new Tuple2<>("c", 3000),
new Tuple2<>("d", 4000)
),
4
);
// TODO 直接改变分区的方法
// coalesce : 合并(缩减)分区
// 方法默认不存在shuffle操作
// repartition : 扩大分区
// 底层就是 coalesce(shuffle)
//final JavaPairRDD<String, Integer> coalesceRDD = rdd.coalesce(6, true);
final JavaPairRDD<String, Integer> repartitionRDD = rdd.repartition(6);
repartitionRDD.saveAsTextFile("output");
jsc.stop();
}
}
行动算子
collect
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import scala.Tuple2;
import java.util.Arrays;
import java.util.List;
public class SparkCore14_Function_collect {
public static void main(String[] args) throws Exception {
SparkConf conf = new SparkConf();
conf.setMaster("local[*]");
conf.setAppName("Spark Core RDD Function");
final JavaSparkContext jsc = new JavaSparkContext(conf);
final JavaRDD<Integer> rdd = jsc.parallelize(
Arrays.asList(1, 2, 3, 4), 4
);
// final JavaRDD<Integer> mapRDD = rdd.map(
// num -> {
// System.out.println("num = " + num);
// return num;
// }
// );
// TODO Spark RDD的方法主要功能不是执行逻辑,是组合逻辑
// collect方法的执行会触发Spark逻辑的执行
// RDD的方法主要分为2大类
// 1. 组合功能 : A -> B(A) -> C(B(A))
// 2. 执行功能 : collect
// 区分方式:看方法的返回值类型
// 组合功能的方法都会返回RDD对象,等同于将旧的RDD转转成新的RDD,用于组合功能
// 一般称之为:【转换算子】
// 执行功能的方法都会返回具体的执行结果,和RDD无关,称之为:【行动算子】
// 网上(书籍):判断作业是否执行
// 所谓的算子,其实就是RDD的方法, Scala集合中的方法称之为方法
final JavaRDD<Integer> sortRDD = rdd.sortBy(num -> num, true, 2);
final List<Integer> result = sortRDD.collect();
result.forEach(System.out::println);
// http://localhost:4040
Thread.sleep(10000000000L);
jsc.stop();
}
}
takeOrdered
排完序后拿
count
数个数
first
拿第一个
take
直接拿前三个
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import java.util.Arrays;
import java.util.List;
public class SparkCore15_Function_action_1 {
public static void main(String[] args) throws Exception {
SparkConf conf = new SparkConf();
conf.setMaster("local[*]");
conf.setAppName("Spark Core RDD Function");
final JavaSparkContext jsc = new JavaSparkContext(conf);
final JavaRDD<Integer> rdd = jsc.parallelize(
Arrays.asList(1, 4, 3, 2), 2
);
final long count = rdd.count(); // 4
final Integer first = rdd.first(); // 1
// take : 拿,取, Top3
final List<Integer> take = rdd.take(3); // 1,4,3
// [1,4,3] => [1,3,4]
// [1,2,3,4] => [1,2,3]
final List<Integer> integers = rdd.takeOrdered(3);
System.out.println(count);
System.out.println(first);
System.out.println(take);
System.out.println(integers);
jsc.stop();
}
}
mapToPair
countByKey
countByValue
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import scala.Tuple2;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class SparkCore15_Function_action_2 {
public static void main(String[] args) throws Exception {
SparkConf conf = new SparkConf();
conf.setMaster("local[*]");
conf.setAppName("Spark Core RDD Function");
final JavaSparkContext jsc = new JavaSparkContext(conf);
final JavaRDD<String> rdd = jsc.parallelize(
Arrays.asList("Hello", "Hello", "World", "Hello"), 2
);
// TODO 将单值数据类型转换为KV键值类型
final JavaPairRDD<String, String> pairRDD = rdd.mapToPair(
num -> new Tuple2<>(num, num)
);
// countByKey 方法用于计算数据处理结果中相同key的数量
// Hello -> (Hello, Hello)
// Hello -> (Hello, Hello)
// World -> (World, World)
// Hello -> (Hello, Hello)
// -----------------------------------------------
// (Hello, 3), (World, 1)
final Map<String, Long> map = pairRDD.countByKey();
// countByValue方法名称中的value表示的不是kv中v,表示单值类型中的value
// ((hello, hello), 3), (("world", "world“), 1)
final Map<Tuple2<String, String>, Long> tuple2LongMap = pairRDD.countByValue();
System.out.println(map);
System.out.println(tuple2LongMap);
jsc.stop();
}
}
saveAsTextFile
saveAsObjectFile
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import scala.Tuple2;
import java.util.Arrays;
import java.util.Map;
public class SparkCore15_Function_action_3 {
public static void main(String[] args) throws Exception {
SparkConf conf = new SparkConf();
conf.setMaster("local[*]");
conf.setAppName("Spark Core RDD Function");
final JavaSparkContext jsc = new JavaSparkContext(conf);
final JavaRDD<String> rdd = jsc.parallelize(
Arrays.asList("Hello", "Hello", "World", "Hello"), 2
);
// saveAsTextFile方法可以将数据保存到本地磁盘文件或分布式文件存储系统(HDFS)
// 数据的保存方式为分区保存
rdd.saveAsTextFile("output");
rdd.saveAsObjectFile("output1");
jsc.stop();
}
}
foreachPartition
foreachPartition会将一个分区的数据统一进行传递并进行处理,性能比较高,但是需要慎用,
非常依赖内存
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import java.util.Arrays;
public class SparkCore15_Function_action_4 {
public static void main(String[] args) throws Exception {
SparkConf conf = new SparkConf();
conf.setMaster("local[*]");
conf.setAppName("Spark Core RDD Function");
final JavaSparkContext jsc = new JavaSparkContext(conf);
final JavaRDD<Integer> rdd = jsc.parallelize(
Arrays.asList(3,4,1,2), 2
);
rdd.collect().forEach(
word -> System.out.println(word)
);
System.out.println("**********************************");
rdd.foreach(
word -> System.out.println(word)
);
// TODO foreachPartition会将一个分区的数据统一进行传递并进行处理,性能比较高,但是需要慎用
rdd.foreachPartition(
iter -> {
while ( iter.hasNext() ) {
System.out.println(iter.next());
}
}
);
jsc.stop();
}
}
匿名内部类
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import java.io.PrintStream;
import java.util.Arrays;
public class SparkCore15_Function_action_5 {
public static void main(String[] args) throws Exception {
SparkConf conf = new SparkConf();
conf.setMaster("local[*]");
conf.setAppName("Spark Core RDD Function");
final JavaSparkContext jsc = new JavaSparkContext(conf);
final JavaRDD<Integer> rdd = jsc.parallelize(
Arrays.asList(3,4,1,2), 2
);
// lambda表达式的本质是通过匿名函数类的方式
final PrintStream out = System.out;
rdd.collect().forEach(
out::println
);
System.out.println("**********************************");
// 能省则省,这里不能省
rdd.foreach(
num -> System.out.println(num)
);
jsc.stop();
}
}
其他
parallelize
并发
parallelizePairs
按对儿
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import scala.Tuple2;
import java.util.Arrays;
public class SparkCore08_Function_KV {
public static void main(String[] args) throws Exception {
SparkConf conf = new SparkConf();
conf.setMaster("local[*]");
conf.setAppName("Spark Core RDD Function");
final JavaSparkContext jsc = new JavaSparkContext(conf);
// TODO 数据就是单值类型
// (word, count)
// final JavaRDD<Integer> rdd = jsc.parallelize(
// Arrays.asList(new User(),new ArrayList(),"abc",new Tuple2<>(1001, "zhangsan")), 2
// );
// TODO 数据是KV (2元组)键值类型: 将数据的KEY和VALUE独立来考虑的数据类型
// 需要采用特殊方法对接数据源
// final JavaRDD<Tuple2<Integer, String>> rdd = jsc.parallelize(
// Arrays.asList(
// new Tuple2<>(1001, "zhangsan"),
// new Tuple2<>(1002, "lisi"),
// new Tuple2<>(1003, "wangwu"),
// new Tuple2<>(1004, "zhaoliu")
// ),
// 2
// );
final JavaPairRDD<Integer, String> rdd = jsc.parallelizePairs(
Arrays.asList(
new Tuple2<>(1001, "zhangsan"),
new Tuple2<>(1002, "lisi"),
new Tuple2<>(1003, "wangwu"),
new Tuple2<>(1004, "zhaoliu")
), 2
);
//rdd.mapValues();
jsc.stop();
}
}
saveAsTextFile
以文件存储
路径不能存在
cache --优化
缓存,底层调用的是持久化
reduceByKey会自动cache
不会添加血缘,只会增加一个依赖
persist–优化
持久化
有很多参数,控制存储级别
checkpoint–优化
检查点:可以跨应用缓存,一般和cache配合使用
一般保存到HDFS
会切断血缘,把自己作为头儿
堆内存、非堆内存、堆外内存
内存泄漏
堆内存 | 非堆内存 | 堆外内存 |
堆内存 | 栈、方法区 | 其他应用等的内存:可用UnSafe API unpersist()操作 |
Partitioner–优化
分区器:默认是HashPartitioner
RangePartitioner:范围分区器
自定义分区器要继承Partitioner(抽象类)
然后重写抽象方法
numPartitions:定义规则中分区数量
getPartitions :根据数据中的key返回数据所在分区
广播变量
和RDD并行使用的数据模型,
结构化数据、半结构化数据和非结构化数据
结构化数据:MySQL中的table
半结构化数据:json、xml、html
非结构化数据:文件、图片、压缩包、doc、xls
SparkSQL
Spark在结构化数据的处理场合,使用了新的模块SparkSQL
SparlSQL会将RDD数据模型进行封装,同时也对环境进行封装
Session:会话,类似于JDBC中Connection
SparkSQL的环境对象的构造方式比较复杂,所以不能直接new,采用设计模式:构建器模式
当构建对象的步骤和过程比较复杂的场合,构建器模式将步骤进行封装,变化的内容可以通过外部进行修改
StringBuilder.append().append().toString()
final SparkSession sparkSQL = SparkSession
.builder()
.appName("SparkSQL")
.master("local[*]")
.getOrCreate();
final SparkSession sparkSQL = SparkSession.builder().config(conf).getOrCreate();
import org.apache.spark.SparkConf;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SparkSession;
public class SparkSql03_SQL {
public static void main(String[] args) {
SparkConf conf = new SparkConf();
conf.setMaster("local[*]");
conf.setAppName("Spark SQL");
final SparkSession sparkSQL = SparkSession.builder().config(conf).getOrCreate();
final Dataset<Row> jsonDS = sparkSQL.read().json("data/user.json");
// TODO 数据源中的数据映射成一张表
jsonDS.createOrReplaceTempView("user");
// TODO 使用SQL的方式读取数据模型中的数据
// ParseException : select * from [age: bigint, name: string]
// AnalysisException: Table or view not found: jsonDS
// TODO 学习重点
// 1. 模型的使用方式
// 2. SQL不是万能的。
// 3. 数据源的不同
final Dataset<Row> result = sparkSQL.sql("select avg(age) from user");
result.show();
sparkSQL.stop();
}
}
Spark Streaming(微批次准实时的数据处理框架)
准实时数据处理:数据处理以秒分钟为单位
数据处理方式
流式数据处理:一条数据一条数据的处理
批量数据处理:一批数据一批数据的处理
微批量数据处理:一小批数据一小批数据的处理