hadoop各组件工作流程分析

Hadoop生态圈中各组件工作流程分析

HDFS YARN MapReduce三者关系

关系图

在这里插入图片描述

文字描述

  1. 客户端提交文件给Hadoop -> Yarn -> ResourceManager
  2. ResourceManager启动App Master
  3. App Master 申请资源给Container
  4. 在Container中跑MapTask(数据分成了几片就有几个Map Task)
  5. Map Task 中的数据从DataNode中来
  6. Map Task 跑完把数据结果传给Reducer Task
  7. Reducer Task跑完把结果存入HDFS

HDFS写数据流程

流程图

HDFS写数据流程

过程描述

  1. 客户端创建一个 分布式文件系统对象 通过它向NameNode请求上传文件D:\io\input\a.txt
  2. NameNode检查目录树是否可以上传(权限、目录是否已经存在等),响应可以上传
  3. 客户端向NameNode请求上传Block(0-128M)
  4. NameNode返回dn1 dn2 dn3节点 (副本节点选择:本地节点 其他机架一个节点 其他机架另一个节点 选距离最近的 拓扑)
  5. 客户端和DataNode1建立传输通道,然后DataNode1和DataNode2建立传输通道,DataNode2和DataNode3建立传输通道,应答成功就可以开始传输数据了
  6. 从本地读取数据后开始传输,一个包一个包的传。Packet 64k 含 chunk512byte + chunksum4byte
  7. DataNode节点会一个一个的把Packet写在磁盘上(落盘)
  8. 传输完成后若还有块er没传完,回到第三步直到所以块都传输完成

HDFS读数据流程

流程图

读数据流程图

过程描述

  1. 客户端创建文件系统对象 Distributed FileSystem 向NameNode发送读数据请求
  2. NameNode看客户端有没有读权限并看看自己有没有该数据,返回目标文件的 元数据
  3. 客户端创建流 读数据流
  4. 向目标DataNode节点发送读数据请求(块信息等)
  5. 目标节点向客户端传输数据
  6. 传输完成若还有块等待读取数据就重复步骤4直到数据都读完
  7. 将文件改名后关闭流

切片源码

/*
 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方法返回了LineRecordReaderLineRecordReaderRecordReader的子类
	4.LineRecordReader是真正用来读取文件中的数据的那个对象的所属类
	
	public RecordReader<LongWritable, Text> 
		createRecordReader(InputSplit split,
						   TaskAttemptContext context) {
		return new LineRecordReader(recordDelimiterBytes);
   }	

conbineTextInputFormat切片机制
将大量的小文件合并成一个大的Map Task的过程
虚拟存储过程 切片过程

MapReduce工作流程

概念图

maperduce详细工作流程

过程描述

  1. 准备待处理数据a.txt 200M 块大小是128M 切两片(若想改变切片大小就设置conf参数,具体修改见切片源码)
  2. InputFormat -> FileInputFormat -> TextInputFormat -> getSplit 对数据进行切片处理,上传jar包,提交job信息给YarnRunner 创建App Master
  3. App Master 申请资源,生成两个Map Task
  4. Map Task 用 InputFormat -> RecordReader -> LineRecondReader 读数据
  5. 然后使用Mapper的map方法进行数据处理
  6. 接下来就是shuffle过程,在下一章节详细讲解

shuffle机制

shuffle流程图解

黑夜

文字描述

  1. map方法写出的数据流向了缓冲区
    • 该缓冲区在数据量达到80%时会进行排序,排序完成的数据会形成多个分区直接写入磁盘,每轮数据都会按该分区算法进行分区,方便下一步归并
    • 所有数据都处理完成后就会进行归并排序,相同分区的数据进行归并
    • 归并完成后按分区进行combiner合并压缩即:将1万条aaa 1 -> aaa 10000
    • 将处理结果写入磁盘
  2. 进行过上述操作后将所有 Map Task 的相同分区放在一起进行的归并排序
  3. 最后数据进入 reduce 方法

Yarn工作机制

流程图

在这里插入图片描述

调度算法

先进先出调度器(FIFO)

容量调度器(Capacity Scheduler)

在这里插入图片描述

在这里插入图片描述

公平调度器(Fair Scheduler)

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Map Task工作机制

流程图

在这里插入图片描述

Reduce Task 工作机制

流程图

在这里插入图片描述

MapReduce工作机制

流程图

在这里插入图片描述
在这里插入图片描述

MapReduce优化性能

map优化

在这里插入图片描述

Reduce优化

在这里插入图片描述

数据倾斜问题

在这里插入图片描述
在这里插入图片描述

Hadoop小文件解决方案

  1. Hadoop Archive(HAR归档)
    是一个高效的将小文件放入HDFS块中的文件存档工具,能够将多个小文件打包成一个HAR文件,从而达到减少NameNode的内存使用。
  2. CombineTextInputFormat
    CombineTextInputFormat用于将多个小文件在切片过程中生成一个单独的切片或者少量的切片。

ZooKeeper

ZooKeeper工作机制

在这里插入图片描述

ZooKeeper特点

在这里插入图片描述

Leader选举机制

在这里插入图片描述
zkCli.sh进入zk客户端

HA

HDFS-HA核心问题

  1. 怎么保证三台namenode的数据一致
  • Fsimage:让一台nn生成数据,让其他机器nn同步。
  • Edits:需要引进新的模块JournalNode来保证edtis的文件的数据一致性。
  1. 怎么让同时只有一台nn是active,其他所有是standby的
    Zookeeper居中协调,选举active
  2. 2nn在ha架构中并不存在,定期合并fsimage和edtis的活谁来干
    由standby的nn来干。
  3. 如果nn真的发生了问题,怎么让其他的nn上位干活
    自动故障转移

自动故障转移工作机制

在这里插入图片描述

Yarn-HA工作机制

在这里插入图片描述

Hadoop-HA集群最终规划

hadoop102hadoop103hadoop104
NameNodeNameNodeNameNode
JournalNodeJournalNodeJournalNode
DataNodeDataNodeDataNode
ZookeeperZookeeperZookeeper
ZKFCZKFCZKFC
ResourceManagerResourceManagerResourceManager
NodeManagerNodeManagerNodeManager
  • 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相同

炸裂函数

在这里插入图片描述
案例演示:
数据准备

moviecategory
《疑犯追踪》悬疑,动作,科幻,剧情
《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压缩概述

压缩格式算法文件扩展名是否可切分
DEFLATEDEFLATE.deflate
GzipDEFLATE.gz
bzip2bzip2.bz2
LZOLZO.lzo
SnappySnappy.snappy

压缩性能的比较:

压缩算法原始文件大小压缩文件大小压缩速度解压速度
gzip8.3GB1.8GB17.5MB/s58MB/s
bzip28.3GB1.1GB2.4MB/s9.5MB/s
LZO8.3GB2.9GB49.3MB/s74.6MB/s

为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器,如下表所示:
Hadoop查看支持压缩的方式hadoop checknative。
Hadoop在driver端设置压缩。

压缩格式对应的编码/解码器
DEFLATEorg.apache.hadoop.io.compress.DefaultCodec
gziporg.apache.hadoop.io.compress.GzipCodec
bzip2org.apache.hadoop.io.compress.BZip2Codec
LZOcom.hadoop.compression.lzo.LzopCodec
Snappyorg.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.memoryRecordAccumulator缓冲区总大小,默认32m。
batch.size缓冲区一批数据最大值,默认16k。适当增加该值,可以提高吞吐量,但是如果该值设置太大,会导致数据传输延迟增加。
linger.ms如果数据迟迟未达到batch.size,sender等待linger.time之后就会发送数据。单位ms,默认值是0ms,表示没有延迟。生产环境建议该值大小为5-100ms之间。
acks0:生产者发送过来的数据,不需要等数据落盘应答。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.msISR中,如果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.bytesKafka中log日志是分成一块块存储的,此配置是指log日志划分 成块的大小,默认值1G。
log.index.interval.bytes默认4kb,kafka里面每当写入了4kb大小的日志(.log),然后就往index文件里面记录一个索引。
log.retention.hoursKafka中数据保存的时间,默认7天。
log.retention.minutesKafka中数据保存的时间,分钟级别,默认关闭。
log.retention.msKafka中数据保存的时间,毫秒级别,默认关闭。
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种:

  1. OneToOneDependency ( NarrowDependency ):窄依赖
    如果上游(旧)的RDD的一个分区的数据被下游(新)的RDD的一个分区所独享,这个依赖关系就称之窄依赖
  2. 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个参数

  1. 第一个参数表示分组逻辑
  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个参数

  1. 第一个参数表示 排序的规则
    spark排序时,会给每一条数据增加一个排序的标记,根据标记(类型)对数据进行排序
  2. 第二个参数表示 排序的方式(true:升序,false:降序)
  3. 第三个参数表示 排序后的分区数量
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(微批次准实时的数据处理框架)

数据流:

  1. 无界数据流 :有开头,没有结束(结尾)的数据流,没有办法进行计算
    网络数据:Socket (无界数据流 如果想要进行计算,必须要切分成有界数据流)
  2. 有界数据流 :有开头,有结束的数据流,可以进行计算
    内存的数据 : List
    磁盘文件的数据 : File

准实时数据处理:数据处理以秒分钟为单位

数据处理方式

流式数据处理:一条数据一条数据的处理
批量数据处理:一批数据一批数据的处理

微批量数据处理:一小批数据一小批数据的处理

在这里插入图片描述

窗口:
在这里插入图片描述

import org.apache.spark.streaming.Duration;
import org.apache.spark.streaming.api.java.JavaPairDStream;
import org.apache.spark.streaming.api.java.JavaReceiverInputDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;
import scala.Tuple2;

public class SparkStreaming05_Window {
    public static void main(String[] args) throws InterruptedException {
        // 创建流环境
        JavaStreamingContext jsc = new JavaStreamingContext("local[*]", "Spark Streaming", Duration.apply(3000));

		//网络传输
        JavaReceiverInputDStream<String> dStream = jsc.socketTextStream("hadoop102", 9999);
        //wordCount代码
        JavaPairDStream<String, Integer> pairDStream = dStream.mapToPair(
                s -> new Tuple2<>(s, 1)
        );
        //窗口配置:窗口长度 3秒,滑动步长 6秒
        pairDStream.window(new Duration(3*1000),new Duration(6*1000));
        pairDStream.reduceByKey(Integer::sum).print();

        // 执行流的任务
        jsc.start();
        jsc.awaitTermination();
    }
}

Spark内核

Spark内核是Scala语言写的,所以这里只分析我们调用了什么类实现了什么功能

Spark运行流程

  1. 图示
bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master yarn \
./examples/jars/spark-examples_2.12-3.3.1.jar \
10

在这里插入图片描述

  • Spark Submit 是整个程序的入口,是个类org.apache.spark.deploy.SparkSubmit
  • 可以申请JVM运行程序,运行在Yarn环境上,所以要和Yarn关联 RM + NM
  • 申请到资源后从本地提交到Yarn上运行
  • SparkSubmit(main) => doSubmit(args) -> parseArguments(args) (解析命令中传过来的参数) -> parse(List args) -> compile(“(–[^=]+)=(.+)”)(正则) => Submit -> doRunMain -> runMain -> start(通过反射得到要跑的类) -> new Client(YarnClient) -> yarnClient.submitApplication(appContext) appContext中包含containerContext(ContainerLaunchContext JVM参数 )-> 拼接出字符串给Yarn中的RM
  • RM给NM去真正执行 ApplicationMaster (main) -> master.run() -> runDriver() -> startUserApplication(跑我们放进来的class) -> awaitResult(sparkContextPromise)这个是看我们在程序里有没有new 一个 jsc -> new Thread(Driver)
  • -> createAllocator 向RM申请资源
  • -> allocateResources() 获取资源响应 -> getAllocatedContainers -> handleAllocatedContainers 处理可用资源
  • -> ExecutorBackend
  1. 解析
    new SparkContext() -> 申请资源 -> 启动Executor -> 执行RDD转换算子
    转换算子 -> 行动算子 -> Job -> Stage -> Task
    Driver -> Task -> Executor

  2. Spark核心对象
    在这里插入图片描述

  3. Spark对象的通信

  • 通信是网络通信,用的不是普通网络通信方式(Socket)
  • Spark采用NIO的通信方式(Netty),类似生活中的收发邮件
    在这里插入图片描述
  1. Spark shuffle
    第一代:
    在这里插入图片描述

第二代:
在这里插入图片描述
第三代:
在这里插入图片描述
当前代:
在这里插入图片描述
在这里插入图片描述

  1. Spark内存
    类似于Java的内存管理机制
    JVM:
    在这里插入图片描述
    Spark内存:
    在这里插入图片描述
    在这里插入图片描述

数据仓库data warehouse

核心作用就是统计分析数据,也能存,数据仓库不是数据流转的终点 :可视化才是数据的终点
数据库:用于存储企业基础,核心的业务数据
数据建模

数仓业务逻辑

Spark : 数据的统计分析
在这里插入图片描述

数据仓库:数据的统计分析
在这里插入图片描述

数仓是数据采集的下游

在这里插入图片描述
数据仓库不能直接对接MySQL数据库作为数据源!

  1. 数据库不是为了数据仓库服务的
    数据仓库如果直接对象数据库,会导致数据库的性能降低(MySQL能承受的访问量是有限的,如果都被工作人员占用了会大大降低软件的使用体验)
  2. 数据库不能存储海量数据
    数据仓库必须获取海量数据
  3. 数据库采用行式存储
    数据仓库为了提高统计分析效率,所以需要列式存储

数据仓库应该增加自己的数据源
数据仓库的数据源中的数据应该和MySQL数据库中的数据保持一致
数据仓库的数据源应该不断融合(汇总)MySQL数据库中的数据
将数据库的数据汇总的到数据仓库数据源的过程,一般称之为数据同步,也称之为数据采集
在这里插入图片描述

数据仓库分层架构

在这里插入图片描述

第一层:数据源(ODS ER模型)

功能:

  • 为整个数据仓库作为数据来源
  • 不断汇总业务数据和日志数据
    数据量非常大:海量数据 -> 考虑资源问题:使用最少的资源存储最多的资源(考虑使用压缩算法gzip、lzo、snappy);考虑网络资源:考虑传输方式,数据尽可能不变(格式、压缩方式、存储方式)
-- ODS
    -- 1. ODS层表建模方式:ER模型
    -- 2. 数据格式不变,数据压缩方式 gzip
    -- 3. 表名
        -- 分层标记(ods_) + 同步数据的表名 + 增量/全量(inc/full)
            -- 增量,全量

在这里插入图片描述

第二层:数据加工(DWD data warehouse detail)

功能:将数据源中的数据进行加工处理(判空、无效)
为了后续统计分析做数据准备
数据量非常大,所以分离出了DIM层将数据整合
压缩方式:snappy

事实表设计(事务型事实表)
-- DWD
    -- Data Warehouse Detail
        -- detail : 详细,明细
        -- DWD层表主要设计的目的为了统计分析做准备
            -- 表中主要保存的是行为数据
            -- 多个行为数据中如果存在共通性的内容,那么可以提炼出来形成DIM层维度表的数据
        -- 表的设计要点
            -- 表的设计要依据维度建模理论中的事实表
            -- 表设计时需要orc列式存储以及snappy压缩
            -- 命名规范:
                -- 分层标记(dwd_) + 数据域(分类) + 原子性行为名称 + 增量/全量(inc/full)
                    -- 绝大多数的行为数据都是增量数据采集
                    -- 特殊情况例外,可以采用全量方式实现。
                -- dwd_user_login_success_inc

-- 事实表
    -- 维度引用 + 度量值(行为产生时可以用于统计分析的数值:金额,数量,个数)
    -- 事实表会根据场景分为3大类:
        -- 1. 事务型事实表
            -- 行为是原子性
                -- 用户登录(非原子)
                    -- 用户登录成功(原子)
                    -- 用户登录失败(原子)
            -- 粒度:描述一行数据的详细程度
                -- 描述的越详细(维度越多),粒度越细
                -- 描述的越简单(维度越多),粒度越粗
            -- 设计步骤:
                -- 1. 选择业务过程 :确定表
                -- 2. 声明粒度:确定行
                -- 3. 确认维度:确定列
                -- 4. 确认事实:确定度量值
        -- 2. 周期快照事实表
        -- 3. 累积快照事实表

-- 交易域加购事务事实表
    -- 交易域 : trade
    -- 加购 : 行为
        -- 将商品加入到购物车中的行为
            -- 购物车中没有这个商品,往购物车中增加商品
            -- 购物车中有这个商品,继续往购物车中增加该商品
    -- 事务事实表
        -- 原子性
            -- 时间(行为时间) + 用户 + 商品 + 数量
        -- 表的字段结构:必要的维度属性 + 度量值 + 可选的维度属性
    -- 建表语句
        -- 分区策略:哪一天的行为数据存放到哪一天分区
事务的原子性

登录成功(OK) 登录失败(OK)
下单成功(OK) 下单失败(非正常业务行为,不需要再创建一张表)
支付成功(OK) 支付失败(OK)

事实表设计(周期型快照事实表)

全量

-- 事务性事实表局限性
-- 事实表只针对于当前行为进行的统计分析时,性能可以得到保障。
-- 当前行为事实表和其他行为数据进行关联时,数据量会几何爆炸性增长,性能会急剧下降。
-- 存量性统计指标使用事务性事实表效率太低,所以一般会采用其他事实表的设计方式
    -- 2. 周期型快照事实表

-- 交易域购物车周期快照事实表
    -- 交易域
    -- 购物车 : cart_info
    -- 周期快照事实表

从当前表中取数据后再放回去需考虑去重问题,增加retry的容错性

事实表设计(累积型快照事实表)
-- 多行为统计指标使用事务性事实表效率太低,所以一般会采用其他事实表的设计方式
-- 3. 累积型快照事实表
    -- 使用一张表保存多个行为的状态数据

-- 交易域交易流程累积快照事实表
    -- 交易域
    -- 交易流程 : 以订单为基础的交易流程
    -- 累积快照事实表
分区策略
-- 事务性事实表:哪一天的行为数据存放到哪一天的分区
-- 周期性事实表:每一天存储一份数据
-- 累积快照事实表:从业务流程中获取最后一个业务行为时间作为分区字段
    -- 下单时间 (X)
    -- 支付时间 (X)
    -- 收货时间 (OK)

第三层:数据统计(DWS data warehouse summary 提高性能的关键层)

功能:将加工后的数据进行统计
数据量非常大
压缩方式:snappy

在这里插入图片描述
在这里插入图片描述

第四层:数据分析(ADS application data service)

功能:将统计结果进行分析,为用户提供经营决策
压缩方式:gzip
数据格式:tsv

优化

Spark:

  • reduceByKey(函数内部combine减少落盘数据量)和groupByKey
  • cache、persist和checkpoint
  • DWS

第五层:共通层(DIM dimension)

功能:将共同的数据放在共通的表中,可在多个统计需求中使用
dimension:维度,分析数据的角度

维度表设计
-- DIM层
    -- 共通维度层
        -- 设计要点
            -- 建模理论为维度建模,DIM层保存的表就是维度模型中的维度表
            -- 表的设计的目的是为了统计分析,所以需要采用列式存储(orc) + snappy压缩
            -- 命名规范
                -- 分层标记(dim_) + 维度名称 + 全量表/拉链表(full/zip)
                    -- 绝大多数的维度表都是全量表
        -- 维度表的设计:
            -- 主键(唯一)
            -- 表字段
            -- 设计步骤
                -- 1. 确定维度表:确定维度的表是否该创建
                    -- 原则上来讲,每一个分析数据的角度(维度)都应该创建一张表
                        -- 案例:统计各个省份,各个品牌的订单总销量
                            -- 订单属于事实(行为)表,省份和品牌就是维度表
                        -- 案例:统计各个性别不同年龄段的订单总销量
                            -- 订单属于事实(行为)表, 性别和年龄就是维度表
                    -- 如果多个维度存在关联,那么一般就会只创建一张表,表中包含了多个关联的维度
                    -- 如果分析数据的角度应用场景少,而且数据量小,不需要创建专门的维度表
                        -- 案例:支付方式(微信支付,支付宝支付)
                -- 2. 确定主维表和相关维表(用于分析维度表的列)
                    -- 确定表中的列
                        -- 案例:省份维度表
                            -- 列:名称
                        -- 数据仓库的数据都来自于MySQL业务数据,
                            -- 维度表的列的声明可以参考业务数据库表的字段
                        -- MySQL业务数据库中具有唯一性字段的那个业务表称之为主维表
                            -- 其他的表称之为相关维表。
                -- 3. 确定表中的列
                    -- 尽可能丰富(多)
                    -- 编码和文字共存
                    -- 沉淀通用属性 :tel, xxx
                        -- 计算或转换

-- 商品维度表 :dim_sku_full
    -- 确定维度表
    -- 主维表和相关维表
        -- 主维表和相关维表都是MySQL业务表
            -- 主要用于分析列的表称之主维表(主键)
                -- sku_info
            -- 其他用于分析列的表称之相关维表
                -- sku_attr_value
                -- sku_sale_attr_value
    -- 确定表的列
    -- 建表语句

其中日期维度表不需要从MySQL中导,而是从文件中另行导入,也不需要每天导入,每年导入一次即可

拉链表设计

-- 数据装载
    -- load
    -- save
    -- 增量表得数据操作一般都会写2个
        -- 首日数据装载
        -- 每日数据装载
-- 首日数据装载
    -- 同步方式:maxwell - 全量 - bootstrap - select * from user_info
        -- MySQL不保存行为数据,也就意味着不保存历史行为数据
    -- 拉链表会在当前表得字段得基础上,额外添加两个字段(start, end),用于标记状态得有效范围
        -- start : 无法判断开始范围
        -- end   : 无法判断
        -- 折中地考虑
            -- 从当天开始,结束时间取时间极大值(避免数据频繁修改)
        -- 分区策略
            -- 绝大多数得维度表得分区策略都是以天为单位
                -- 分区不能采用开始日期作为分区字段
                    -- 无法判断数据是否为历史状态还是最新状态
                    -- 好得方式是使用结束时间为分区字段

任务调度器

保证每一层的SQL跑完再跑下一层
在这里插入图片描述

数据仓库方法论

建模理论:

ER模型:Entity Relationship实体关系,entity=bean

采用面向对象的方式设计表,将对象理解为表,将对象之间的关系理解为表之间的关系

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

函数依赖

完全函数依赖
在这里插入图片描述

部分函数依赖
在这里插入图片描述

传递(间接)函数依赖
在这里插入图片描述

建模范式(ER模型遵守)

第一范式:属性(表字段)不可切割
第二范式:不能存在部分函数依赖
第三范式:不能存在传递函数依赖

全量和增量

行为:login,reg,order,pay => 增量
状态:sex,tm,category => 全量
统计本质上是对行为数据进行统计
分析本质上是站在什么角度对统计结果进行分析

内部表和外部表

EXTERNAL TABLE:外部表
如果数据只是Hive自身用,声明内部表
如果数据不只是Hive来用,声明外部表,删除时只能删除元数据,不删表数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值