大数据复习
概念
巨量数据集合,指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长率和多样化的信息资产。
特点
大量化Volume 、 价值化Value 、 多样化Variety 、 真实性Veracity
Linux操作系统
版本
CentOS7.2
指令
文件操作相关
ls 、ll、mkdir、cp、mv、rm、pwd、find、touch、rmdir、rm 、scp远程拷贝等
- ll 列出来的结果详细,有时间,是否可读写等信息 ,象windows里的 详细信息
- ls 只列出文件名或目录名 就象windows里的 列表
- Find / -mtime 1 查找一天前修改过的文件
- touch命令用于修改文件或者目录的时间属性,包括存取时间和更改时间(若文件不存在,系统会建立一个新的文件 )
查看文件内容相关
cat、more、head -n、less、tail(tail -n 行数 | tail -f )、vim、vi等
- tail -f :动态监测文本行 tail -n:看文本前几行 head -n:看文本后几行
- more :分页(通过光标往下分页) less:分页(可以上下分页)
权限组相关
-
chown、chmod、useradd、grpadd等
chown 修改属主属组
chmod 修改权限 drwxr-xr-x 7 [4,2,1]
chmod [a|u|g|o]
[+|-][rwx] 文件名
、chown / chgroup
- [a|u|g|o] a:所有 u:当前用户 g:当主用户 o:其它用户
- [rwx] 读操作
服务进程相关
-
ps、kill、jps、du | df(磁盘使用情况 空间或者使用率)、top(动态查看系统运行情况,常用于应用故障监控)等
-
systemctl 动作 服务名
软件安装相关
-
rpm | tar | yum
rpm: JDK
tar: Redis\Tomcat\Nginx
yum: mysql\git
-
tar解压安装 tar -zxf 文件 tomcat ,创建压缩 tar -czf 压缩文件名 需要压缩的文件
-
rpm安装
rpm -ivh rpm包
|rpm -e xxx
|rpm -qa |grep 安装包
-i | -U -
yum安装:联网下载rpm包 ,解析rpm、依赖关系
-
编译安装:一般需要下载gcc-c++/gcc
- ./configure --prefix=安装目录 --add-modual=安装模块 #检查安装环境,产生MakeFile文件
- make 编译、产生可执行文件 | make clean 清除编译结果
- make install 将make后的结果复制到安装目录下,创建程序执行连接
网络相关
ip a | ping | ifup | ifdown等
- ifup | ifdown系统启动异常诊断
补充
配置环境变量(家目录:home):~/.bashrc|.bash_profile 用户变量、/etc/profile
系统变量
网卡配置:/etc/sysconfig/network-script/ifcfg-ethx (基于Centos6)
去除服务自启动:chkconfig 服务名 off|on,查看所有系统服务:chkconfig --list
关闭防火墙:service iptables stop | chkconfig iptables off
查找看系统进程: ps -aux
文本内容查找:grep [-i] 搜索关键字 --color=always 搜索目标文件
查找指定进程:ps -aux | grep -i ‘sbin/sshd’ --color
只查询指定进程进程号:ps -aux | grep -i ‘sshd’ --color | grep sbin| awk ‘{print $2}’
杀死进程:kill -9 进程号 强制退出、kill -s TERM 进程号 优雅退出
查看端口:netstat -anp
查看磁盘容量: du -h /root/ #disk useage
df -h /root/ #disk free
常用软件
Tomcat | Nginx
- tomcat: 处理动态请求,会话管理(session复制、ip黏着、redis|memcached分布式会话服务器)
- nginx(静态): 高性能的web服务器和反向代理服务器
MySQL
- 主从复制(网络同步binlog写指令日志文件)
- 读写分离(数据库中间件mycat、动态数据源、支持读写操作的MySQL驱动)
Redis
ElasticSearch
端口大全
50070:HDFSwebUI的端口号
8485:journalnode默认的端口号
9000:HDFS服务端口
8020:高可用访问数据rpc
8088:yarn的webUI的端口号
7077:spark基于standalone的提交任务的端口号
8081:worker的webUI的端口号
18080:historyServer的webUI的端口号
4040:application的webUI的端口号
2181:zookeeper的rpc端口号
9083:hive的metastore的端口号
60010:Hbase的webUI的端口号
6379:Redis的端口号
8080:sparkwebUI的端口号(master的webUI,Tomcat的端口号)
9092:kafka broker的端口
Hadoop生态系统
Hadoop(分布式存储和计算平台)
两个核心组件:MapReduce和Hadoop Distributed File System(HDFS)
HDFS:负责将海量数据进行分布式存储
MapReduce:负责提供对数据的计算结果的汇总
生态系统:
HDFS
:Hadoop Distribute File Sysytem
Map Reduce
:Hadoop 中的分布式计算框架 实现对海量数据并行分析和计算
HBase
:是一款基于列式存储的NOSql
Hive
:是一款sql解释引擎,可以将SQL语句翻译成MR 代码,并在集群中运行
Flume
:分布式日志收集系统
Kafka
: 消息队列 ,分布式的消息系统
Zookeeper
:分布式协调服务 用于 注册中心 配置中心 集群选举 状态监测 分布式锁
HDFS(Hadoop分布式文件系统)
Hadoop版本2.9.2/2.6.0 Web UI端口:50070 操作端口:9000
擅长大数据集的可靠存储
基本Shell命令
hadoop fs -x * /* *代表文件 x代表shell命令
-put(上传) -get(下载)-ls(显示)-cp(复制) -mkdir(创建文件夹)
-moveFromLocal(从本地移动文件)-copyToLocal(从Hdfs复制到本地)
-rm -r -f(删除文件)-rmdir(删除文件夹)-cat(显示文件内容)
-tail -f(显示文件最新内容)-appendToFile(追加文件内容)
基本架构
HDFS具有主/从体系结构。 HDFS群集由单个NameNode和管理文件系统名称空间并控制客户端对文件的访问的主服务器组成。此外,还有许多数据节点,通常是集群中每个节点一个,用于管理与它们所运行的节点相连的存储。 HDFS公开了文件系统名称空间,并允许用户数据存储在文件中。在内部,文件被分成一个或多个块,这些块存储在一组DataNode中。 NameNode执行文件系统名称空间操作,例如打开,关闭和重命名文件和目录。它还确定块到DataNode的映射。 DataNode负责处理来自文件系统客户端的读写请求。 DataNode还根据NameNode的指令执行块创建,删除和复制
检查节点机制
- dfs.namenode.checkpoint.period(默认设置为1小时)指定两个连续检查点之间的最大延迟
- dfs.namenode.checkpoint.txns(默认设置为100万)
- 定义了NameNode上的非检查点事务数,即使尚未达到检查点期限,该事务也会强制执行紧急检查点。
安全模式(默认开启,只读模式)
发现绝大多数的Block块可用的时候,会自动退出安全模式。
常见问题
-
NameNode和SecondaryNameNode关系?
伪分布式集群;
主要作用:用以合并NameNode产生的
editslog(写命令日志文件)+fsimage(内存快照文件)
,加快NameNode在启动时元数据恢复速度 -
HDFS不擅长小文件的存储?
- 小文件过多导致NameNode的内存浪费
- 不符合HDFS的设计原则,寻道时间大于IO读写时间
HA集群
解决NameNode单点故障问题
架构
-
namenode
:存储系统的元数据(用于描述数据的数据),例如 文件命名空间、block到DataNode的映射,负责管理DataNodedatanode
: 用于存储数据的节点 负责相应客户端读写请求 向NameNode 汇报块信息block
:数据块 是对文件拆分的最小单元 表示 一个默认为128MB的切分 尺度,每个数据块副本,默认的副本因子为3,通过dfs.replication进行配置,另外用户还可以通过 dfs.blcoksize 设置块的大小rack
:机架 使用机架对存储节点做物理编排 用于优化存储和计算 -
Zookeeper(配置维护、域名服务、分布式同步、组服务):一个分布式的、开放源码的分布式应用协调服务,是Hadoop和HBase的重要组件。
-
JournalNode:日志服务,主要用于主备NameNode元数据的同步;运行的JournalNode进程非常轻量,可以部署在其他的服务器上。注意:节点数至少3个
-
ZKFC:ZooKeeperFailoverController作为一个ZK集群的客户端,主要用来监控NameNode的状态信息
-
fsimage:元数据信息的备份,会被加载到内存中
-
editlog:Edits文件帮助记录文件增加和更新的操作 提高效率
什么是Rack机架感知?
在集群规模比较大时,机架感知能告诉集群哪台机器属于哪台机架。
Hadoop集群分辨某台lave机器属于哪个机架是需要Hadoop的管理者人为地告知Hadoop哪台机器属于哪个机架,会将这些机器与Rack的对应信息保存在内存中,用来作为对接下来所有的HDFS的写块操作分配datanode列表时的选择策略(尽量将多个副本分布在不同的Rack)。
机架感知需要考虑的情况:
-
不同节点的通信能够尽量发生在同一个机架之内
-
为了提高容错能力,namenode节点会尽可能把数据的副本放到多个机架上
HDFS读写流程?
写
读
Data工作机制
YARN(分布式资源管理调度系统)
Apache Hadoop YARN (Yet Another Resource Negotiator,另一种资源协调者)是一种新的 Hadoop 资源管理器,它是一个通用资源管理系统,可为上层应用提供统一的资源管理和调度,它的引入为集群在利用率、资源统一管理和数据共享等方面带来了巨大好处。
架构
YARN的基本思想是将资源管理和作业调度/监视的功能拆分为单独的守护程序。这个想法是拥有一个全局ResourceManager(RM)和每个应用程序ApplicationMaster(AM)。应用程序可以是单个作业,也可以是作业的DAG。
ResourceManager具有两个主要组件:Scheduler和ApplicationsManager
NodeManager
:管理主机上计算资源,是每台机器的框架代理,向RM 汇报自身的状态信息。
ResourceManager
:负责集群计算资源的统筹规划,拥有着集群资源的最终决策权。
ApplicationMaster
:计算任务的Master,负责申请资源,协调计算任务。
YARNChild
:负责最实际的计算任务(MapTask|ReduceTask)
Container
:是计算资源的抽象,代表着一组内存、CPU、网络的占用,无论是ApplicationMaster和YARNChild都需要消耗一个Container。
HA集群
由ZooKepper集群选举实现RM的主备服务切换;
MapReduce(分布式并行计算框架)
五大核心组件
InputFormat、Mapper、Partitioner(默认HashPartitioner)、Reducer、OutPutForMat
Combiner:用于优化MR程序,需要根据具体业务场景而定。
对与每一个MapTask的输出做局部汇总,以减少网络使用量
是MR程序中Mapper和Reducer之外的一种组件
父类就是Reducer
Combiner和Reducer 的区别在于运行位置
- Combiner 是在每一个Task所在的节点上运行
- Reducer 是在接收全局所有的Mapper的输出结果
适合累加 不适合求平均数
Combiner输出的KV 要与Reducer 的KV相对应
使用
- 新建CombinerClass 继承Reducer
- 直接使用Redcuer
核心思想
化繁为简、分而治之
工作原理图
MapTask
映射
将输入数据映射为KV结构
InputFormat
数据的输入格式- 决定了如何对数据集进行**逻辑(start-end)**切割;如:TextInputFormat,
数据集剩余大小 > 128MB*1.1
- 数据切片(Split)对应一个MapTask进行处理;
- 决定了如何读取数据切片中的数据
RecordReader
- 决定了MapTask中的keyIn和valueIn类型
- 决定了如何对数据集进行**逻辑(start-end)**切割;如:TextInputFormat,
MapTask
- 根据业务需求,将数据映射kv
- 将kv输出
Shuffle
洗牌
-
Map端Shuffle
- 将映射KV首先存放到内存缓冲区(100MB)中,一旦到达阈值100*0.8,产生溢写
- 对数据进行分区(Hash分区器,k.hashCode%reduceTaskNum = 分区中)、排序、合并并溢写到磁盘,产生小的溢写文件;
- MapTask在运行中可能会产生多次溢写,MapTask运行结束后会将内存缓冲区中剩余数据和小的溢写文件合并,合并最终的结果文件;
-
Reduce端Shuffle
- ReduceTask拉取当前任务所负责的分区数据,最终将小的分区数据进行排序合并产生最终的输入文件;
- 将最终处理的结果作为ReduceTask任务输入
ReduceTask
计算
ReduceTask
- 根据输入的K,对VList进行统计计算,得到一个最终K,V结果
- 将结果通过OutputFormat输出到指定的存储系统
OutputFormat
- 决定了计算结果的输出格式
MapReduce 优缺点
- 优点
- 易于编写分布式应用程序
- 有良好的拓展机制
- 适合离线批处理
- 高容错性
- 缺点
- 不适合流处理
- 不擅长图计算
常见问题
MR应用执行流程?
-
首先通过程序员所编写的MR程序通过命令行提交
-
开始运行后称之为Job(一个MR程序就是Job),Job会向RM申请注册(会带一些信息)
-
如果RM注册通过,Job会从共享的文件系统中拷贝相关信息
-
Job紧着着会向RM提交完整的应用信息
-
a RM会计算出Job启动所需要的资源
b.连接到对应的NM启动一个MRAppMaster
-
MRAppMaster 在启动的时候会初始化Job
-
初始化Job 后,会去共享文件系统中回去切片
-
MRAppMaster 向RM 请求计算资源
-
连接到对应的NM ,消耗Container 启动YARNChild
-
获取完整的Job资源
-
计算
Job类的书写步䠫
-
封装Job对象
-
设置读入写出的格式
-
设置写入写出的路径
-
设置计算逻辑
-
设置Map和Reduce的泛型
-
提交Job
Job提交流程
- Check Space 校验空间
- 创建资源路径:staging 路径
- 创建Job 路径 (在staging路径下创建以JobID 为名字的文件夹)
- 拷贝相关资源到集群
- 计算切片 ,生成切片规划文件
- 向Staging 路径写入配置文件
数据倾斜怎么处理?
由于某些key设计不合理,导致了数据负载不均衡的现象
- 自定义分区规则
- 调整ReduceTask任务数量
- 调整任务节点JVM空间大小
优化?
参考资料:https://blog.csdn.net/qq_43193797/article/details/86005144
- 数据输入端:将小文件划分到一个数据切片中;建议CombineTextInputFormat
- Map任务:减少溢写次数,减少溢写文件合并,合理使用combiner
- Reduce任务:合理设置任务数量,设置map和reduce共存,合理设置reduce buffer大小等;
- IO传输:map端压缩(gzip)和序列化文件
常用的InputFormat和OutputFormat?
- TextInputFormat和TextOutputFormat(文本)
- DBInputFormat和DBOutputFormat (关系型数据库)
- TableInputFormat和TableOutFormat(HBase)
- …
序列化类型?
implements Writable
- IntWritable
- Text
- NullWritable
- LongWritable
MapTask并行度&Reduce阶段并行度控制?
- MapTask,由InputFormat#getSplits方法计算的切片数目所决定
- ReduceTask:由job.setNumberReduceTask(3)//默认值1
HBase(基于列存储非关系数据库)
版本1.2.4 WebUI http://hostname:16010
解决了大规模结构化数据的存储
是一个基于Google BigTable论文设计的高可靠性、高性能、可伸缩的分布式存储系统
HDFS只能存储大文本文件,最多算是文件系统,文件系统对数据的管理粒度比较粗糙,无法完成单条记录级别的数据CRUD,因此当需要对海量数据进行随机读写的时候,HDFS就无能为力。因此HBase作为构建在HDFS之上一款NoSQL数据服务,可实现对海量数据集高效的随机读写。
基本名词
- NameSpace:类似于关系型数据库的DataBase
- BigTable:大表
- RowKey:主键,读写操作时唯一依据
- ColumnFamily:列簇,组织管理列
- Timestamp:时间戳,数据版本
- Cell:单元格, rowkey + cf:cloumn + timestamp
HBase和关系数据库区别
-
数据库类型:HBase中的数据类型都是字符串类型(string)
-
数据操作:HBase只有普通的增删改查等操作,没有表之间的关联查询
-
存储模式:HBase是基于列式存储模式,而RDBMS是基于行式存储的
-
应用场景:HBase适合存储大量数据,查询效率极高
基本操作
略
架构原理
https://www.cnblogs.com/frankdeng/p/9310278.html
Zookeeper
HBase通过zk来做Master的高可用,RegionServer的监控、元数据的入口以及集群配置的维护工作
HMaster
-
为 RegionServer 分配 Region
-
负责 RegionServer 的负载均衡,发现失效的 RegionServer 并重新分配其上的 Region
发现失效的Region,将失效的Region分配到正常的RegionServer上,当RegionServer失效时,协调对应的HLog的拆分
- HDFS 上的垃圾文件(HBase)回收
- 处理 Schema 更新请求(表的创建,删除,修改,列簇的增加等等)
HRegionServer
- RegionServer 维护 Master 分配给它的 Region,处理对这些 Region 的 IO 请求
- 处理来自客户端的读写请求,负责与HDFS进行交互,负责Region变大后的拆分
HRegion
- HBase 表根据Rowkey 划分成Region,理论上一个Region包含该表格从起始行到结束之间的所有行。
- 当一个Region没有办法存储这个表中所有的行,会进行切分
数据大小>=N^2*128MB
(N为Region数量),当N=9时,切分大小会超过10GB,此时就按照10GB进行切分。
- Region由Store 组成
- Region会被分配到称之为“HRegionServer”的节点上
HStore
- 每一个Region由一个或者多个Store组成,至少是一个Store 。HBase会把经常访问的数据放在一个Store里面,即一个列簇组成一个Store ,有多少个列簇就多少个Store 。
- 在Store 中,由一个memStore 和 0个或者多个StoreFile 组成
MemStore
-
memStore 是放在内存里的。保存修改的数据即keyValues。当memStore的大小达到一个阀值(默认128MB)时,memStore会被flush到文 件,即生成一个快照。目前hbase 会有一个线程来负责memStore的flush操作。
-
Flush机制
- MemStore级别:当Region中人一个MemStore的大小达到上限(默认为128MB ),会触发MemStore刷新
- Region级别:当Region中所有的MemStore大小总和达到的上限(默认256M)
- RegionServer级别:当RegionServer中所有的MemStore大小总和达到上限(默认 40%的JVM内存使用量),会触发部分Memstore刷新。Flush顺序是按照Memstore由大到小执行,先Flush Memstore最大的Region,再执行次大的,直至总体Memstore内存使用量低于阈值(默认 38%的JVM内存使用量)。
- 当一个RegionServer中HLog数量达到了上限(可通过参数hbase.regionserver.maxlogs配置)时,系统会选取最早的一个 HLog对应的一个或多个Region进行flush
- HBase定期刷新Memstore:默认周期为1小时,确保Memstore不会长时间没有持久化。为避免所有的MemStore在同一时间都进行flush导致的问题,定期的flush操作有20000左右的随机延时。
- 手动执行flush:用户可以通过shell命令 flush ‘tablename’或者flush ‘region name’分别对一个表或者一个Region进行flush。
HLog或者WAL
- 记录RegionServer 上所有的编辑信息(Puts/Deletes,属于哪个Region),在写入MemStore之前。
- 理论上一个RegionServer上只有一个WAL实例,数据操作为串行,造成性能瓶颈,在1.0之后 ,可以通过使用底层HDFS的多管道实现了多WAL并行写入,提高了吞吐量
StoreFile|HFile
HFile(StoreFile) 用于存储HBase的数据(Cell/KeyValue)。在HFile中的数据是按RowKey、Column Family、 Column排序,对相同的Cell(即这三个值都一样),则按timestamp倒序排列。
由于MemStore中存储的Cell遵循相同的排列顺序,因而Flush过程是顺序写,由于不需要不停的移动磁盘指针,因此磁盘的顺序写性能很高。
BlockCache|HBase Block(了解)
- 读缓存,数据被读取之后仍然缓存在内存中
- 有LruBlockCache(效率较高,GC压力大)和BucketCache(效率较低,没有GC压力)两种BlockCache,默认为LruBlockCache
- 每个RegionServer中只有一个BlockCache实例
常见问题
RowKey设计?
https://blog.csdn.net/qq_38180223/article/details/80864778
-
长度:建议在16个字节以内
-
唯一:唯一不重复(加盐、预分区、反转、哈希)
-
散列:尽量分配均匀
用户标识:时间:随机盐
HBase的Rowkey设计原则
- Row格式必须固定,不可以使用UUID或者hash
- Rowkey一般由查询条件构成
HBase 读写过程
写过程
-
Client访问ZK,根据ROOT表获取meta表所在Region的位置信息,并将该位置信息写入Client Cache。(注:为了加快数据访问速度,我们将元数据、Region位置等信息缓存在Client Cache中。)
-
Client读取meta表,再根据meta表中查询得到的Namespace、表名和RowKey等相关信息,获取将要写入Region的位置信息(此过程即Region三层定位,如下图),最后client端会将meta表写入Client Cache。
-
Client向上一步HRegionServer发出写请求,HRegionServer先将操作和数据写入HLog(预写日志,Write Ahead Log,WAL),再将数据写入MemStore,并保持有序。(联想:HDFS中也是如此,EditLog写入时机也是在真实读写之前发生)
-
当MemStore的数据量超过阈值时,将数据溢写磁盘,生成一个StoreFile文件。
-
当Store中StoreFile的数量超过阈值时,将若干小StoreFile合并(Compact)为一个大StoreFile。
-
当Region中最大Store的大小超过阈值时,Region分裂(Split),等分成两个子Region。
读过程
- 获取将要读取Region的位置信息(同读的1、2步)。
- Client向HRegionServer发出读请求。
- HRegionServer先从MemStore读取数据,如未找到,再从StoreFile中读取。
StoreFile合并(Compaction)
目的:减少StoreFile数量,提升数据读取效率。
Compaction分为两种:
-
major compaction
将Store下面所有StoreFile合并为一个StoreFile,此操作会删除其他版本的数据(不同时间戳的) -
minor compaction
选取Store下的部分StoreFile,将它们合并为一个StoreFile,此操作不会删除其他版本数据。
Flume(分布式数据采集聚合工具)
1.9.0
架构
Agent
:代表一个Flume服务实例,多个Agent构成一个服务集群
Source
:接受获取采集数据源数据,将数据封装为Event事件对象
Channel
:事件队列(缓冲区),主要负责数据临时存储
Sink
:将Channel中数据输出保存到指定存储位置;
Interceptor
: 拦截器,对Source拉取的数据进行预处理
ChannelSelector
:通道选择器,主要选择通道,分发和复制
Flume的Selector的形式有2种:
①Replication 形式 数据同步给多个Channel
②Multiplexing形式 数据分流
SinkGroup
:负载均衡或者容错
常用组件类型
Source
:exec、spooldir(读取并添加后缀)、avro(一次性)、netcat(Tcp协议)、taidir、kafka等
TAILDIR:读取文件完成后,会接续读取此文件,如果有最新的文件内容会对此文件的新的内容进行读取在/root/.flume/taildir_position.json文件记录着当前文件夹每一个文件的读取的偏移量(pos),所以在检测到当前文件和其它的文件的内容有更新时,会从上次记录的偏移量的最后一个加1进行读取
Channel
:memory、file、jdbc、kakfa等
Sink
:HDFS、HBase、Kafka、File Roll、Avro Sink、Logger等
Interceptor
: UUID、HOST、Timestamp、Regex_filter、Static等
Kafka(流数据平台/消息队列)【重点】
为什么使用Kafka
参考: https://blog.csdn.net/qq_38019655/article/details/84112210
- 高吞吐量:Kafka 每秒可以生产约 25 万消息(50 MB),每秒处理 55 万消息(110 MB)
- 持久化数据存储:可进行持久化操作。将消息持久化到磁盘,因此可用于批量消费,例如 ETL,以及实时应用程序。通过将数据持久化到硬盘以及replication 防止数据丢失。
- 分布式系统易于扩展:所有的 producer、broker 和 consumer 都会有多个,均为分布式的。无需停机即可扩展机器。
- 客户端状态维护:消息被处理的状态是在 consumer 端维护,而不是由 server 端维护。当失败时能自动平衡。
版本2.11-0.11.0.0
FIFO 先进先出
系统解耦和削峰填谷
消息的是系统间通信的载体,是分布式应用不可获缺的一部分。目前系统间发送消息有两种种类。
同步消息
即使消息:打电话、表达提交、WebService、Dubbo|SpringCloud
要求消息发送方和接受放必须同时在线,一般都需要和接收方建立会话。
异步消息
发送方不理会对方是否在线,一般不需要和接收方建立会话,在接受方上线后,一般会获取发送方发送的消息。
显而易见,可以看出消息队列就是使用的异步消息的模型。
架构
名词解析
Broker
- 每个Kafka Server称之为一个Broker,多个Broker可以组成Kafka Clutser
- 一个节点上可以有一个或者多个Broker,多个Broker连接到相同的ZK组成了Kafka集群
Topic
- Topic就是消息类名,一个Topic中通常存放一类消息,每一个Topic都有一个或者多个订阅者,也就是消费者
Topic 与 Broker
- 一个Broker 上可以创建一个或者多个Topic,一个Topic可以同一集群下的多个Broker
Partition
- kafka会为每个Topic维护多个分区(partititon),每个分区会映射到一个逻辑的日志(log)文件
- 对于同一个partiton,它所在任何一个broker,都有能扮演两种角色:leader、follower
- 每个partiton的Leader用于处理到该partiton的读写请求。
- 每个partiton的followers是用于异步的从它的leader中复制数据。
- Kafka会动态维护一个与Leader保持一致的同步副本(in-sync replicas (ISR))集合,并且会将最新的同步副本(ISR )集合持久化到zookeeper。如果leader出现问题了,就会从该partition的followers中选举一个作为新的leader。
Offset(偏移量)
- Topic以日志分区形式存储,分区中每一则Record(记录)都有个offset编号用于标示record的顺序.所有存储在kafka中record是允许用户重复消费的。
基本特性
消费组
同组负载均衡,不同组广播
生产者发布策略
- key为空,轮询分区
- key不为空,hash取模
- 指定record存放分区
首次订阅topic策略
earliest
:如果当前消费组没有已提交的offset,则从分区队列头部拉取消息latest
(默认):如果当前消费组没有已提交的offset,则从分区队列的尾部拉取消息
生产者批量发送
- 将多个Record存放一个缓冲区,当满足条件(
缓冲区大小 | 逗留时间
)时以批量的形式发送存放kafka集群;
ACK机制
数据写入到kafka集群时,kafka服务会给生产者返回应答信息;
- all 或者 -1,数据写入到主分区并且同步到复制分区后再进行应答
- 1, 数据写入到主分区
- 0,无需应答
生产者的幂等写入
幂等:相同数据多次写入只会保留一个结果
只能保证单个Producer对于同一个<Topic, Partition>的Exactly Once语义
props.put("enable.idempotence",true);
Kafka事务
只支持两种事务格式级别:read_uncommited(默认)
、read_commited
生产者事务
保证Producer对于topic所有分区的Exactly Once语义
消费生产并存事务
消费和生产在同一个事务环境中;
消费方式
- 订阅(topic的所有分区)
- 指定分区
- 手动控制消费位置
偏移量控制
- 手动: 手动通过同步或者异步的方式,提交消费位置;
- 自动(默认):消费数据后自动提交消费位置offset
Kafka数据同步机制
https://www.cnblogs.com/yoke/p/11486196.html
https://blog.csdn.net/lukabruce/article/details/101012815
- LEO(Log end Offset):每个分区日志位置的最后一个offset
- HW(High WaterMark):消费者只能够可见高水位线以前的消息,并且高水位线等于所有分区中最小LEO的Offset
- ISR(In-Sync-Replicas):正在同步中的分区信息
Kafka Streams
架构
略
知识点
- 分布式并行实时计算类库
- 拓扑任务(Topology)等价于输入Topic分区数量
- 任务并行度(线程数量 <= Toplogy Task)
- 状态管理(本地状态
rocksdb
+ 远程状态changlog topic
) - Shuffle(
xxx-reparation topic
) - 窗口计算(翻滚、滑动、会话)
ZooKeeper(分布式服务协调系统)
版本:3.4.6 端口:2181
Apache ZooKeeper致力于开发和维护可实现高度可靠的分布式协调的开源服务器。
是Google的Chubby的开源实现。
CAP定理?
对于任何的分布式系统,不能同时满足一致性、可用性、分区容错性,只能够同时满足其中两个特性;
ZooKeeper CP系统(强一致性),不论访问ZooKeeper哪一个节点都可以获取到相同的数据;
节点?
ZK提供的命名空间与标准文件系统非常相似,一个名称由斜线分割开来的路径的进行表示,ZK 中每一个节点都通过路径来表示。
ZK 通过像树一样的结构来进行维护,并且每个节点通过路径来访问和标示。而且每个节点上还存储着相应的数据,这个数据不仅包含本身用户存储的数据,还有数据长度,数据创建时间,数据修改时间。
维护树形层次空间,每一个节点称为ZNode
,类型:
- 临时:依赖于创建会话,会话结束临时节点自动删除
- 永久:不依赖与创建会话,永久保留,只能客户端执行删除指令
- 临时顺序:临时 + 序号
- 永久顺序:永久 + 序号
Watcher?
Zookeeper原生API支持通过注册Watcher 来实现事件监听,但是Watcher 通知机制是一次性的。因此如果使用原生的API去实现Watcher 机制,需要反复注册,比较繁琐。
观察或者监控ZooKeeper节点信息的改变
- 指定节点的子节点数量的改变
- 指定节点的数据的改变
- 指定节点的状态的改变
应用场景
- 统一命名服务:Dubbo服务注册中心
- 集群选举:HDFS/YARN/HBase 等HA集群的主备服务切换
- 分布式锁:解决分布式系统中的线程安全问题【DB\Redis\ZK】
- 管理集群元数据
- …等
Paxos算法的原理及过程透彻理解
https://blog.csdn.net/u013679744/article/details/79222103
ZAB协议
https://www.jianshu.com/p/2bceacd60b8a
Hive(数仓工具)
可以将结构化的数据文件映射为一张数据库表,并提供简单的sql查询功能,可以将sql语句转换为MapReduce任务进行运行。
表的类型?
- 外部表:在删除外表时,只删除表的元数据,而HDFS上的数据不会删除的
- 内部表:删除内部表时,删除表的元数据和HDFS上数据
- 分区表:根据分区规则,将数据分目录存储
- 分桶表:根据列值取哈希,将不同的数据放到不同文件中存储
- 临时表:依赖于会话,测试数据临时存储;
HQL语法
select
all[distinct] 字段列表...
from 表名
where 过滤条件
group by 分组字段
having 筛选条件
order by 排序字段 asc[desc] # 全局排序 1个reduceTask
[sort by 排序字段] # 分区内排序
[partition by 分区字段] # 分区时所使用的key
[cluster by 字段] # 排序字段和分区字段一致
limit n
表连接查询
- 内连接
- 外连接
- 左半开连接
- 笛卡尔连接
- Map端Join
ETL基本了解
ETL,是英文Extract-Transform-Load的缩写,用来描述将数据从来源端经过抽取(extract)、转换(transform)、加载(load)至目的端的过程。ETL一词较常用在数据仓库,但其对象并不限于数据仓库。
https://baike.baidu.com/item/ETL/1251949?fr=aladdin
数据仓库模型设计?
参阅专业论文
面试题
https://blog.csdn.net/qq_36174081/article/details/89945050?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
新一代大数据解决方案
Scala编程语言
专门为计算而生的语言,Scala将(Java后者C++)
面向对象设计
和函数式编程
结合在一起的简洁的高级编程语言。
Java编程语言
平台无关性 平台无关性是指Java能运行于不同的平台
安全性 Java的编程类似C++,学习过C++的读者将很快掌握Java的精髓
面向对象 Java吸取了C++面向对象的概念
分布式 Java建立在扩展TCP/IP网络平台上
健壮性 Java致力于检查程序在编译和运行时的错误
- HelloWorld入门
- 变量
类型转换
通俗理解: 自动类型提升
规则:
Byte ---> Short ---> Int ---> Long ---> Float ---> Double
char
值类型可以按照下面的方向进行转换:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MI6AQxDU-1586439283542)(C:\Users\战神\AppData\Roaming\Typora\typora-user-images\1584448676269.png)]
Scala类型层次结构【重点】
在Scala中,所有的值都有类型,包括数值和函数。下图阐述了类型层次结构的一个子集。
-
Any
是所有类型的超类型,也称为顶级类型。它定义了一些通用的方法如equals
、hashCode
和toString
。Any
有两个直接子类:AnyVal
和AnyRef
。AnyVal
代表值类型。有9个预定义的非空的值类型分别是:Double
、Float
、Long
、Int
、Short
、Byte
、Char
、Unit
和Boolean
。Unit
(类似于java中的void
)是不带任何意义的值类型,它仅有一个实例可以像这样声明:()
。所有的函数必须有返回,所以说有时候Unit
也是有用的返回类型。AnyRef
代表引用类型。所有非值类型都被定义为引用类型。在Scala中,每个用户自定义的类型都是AnyRef
的子类型。如果Scala被应用在Java的运行环境中,AnyRef
相当于java.lang.Object
。Nothing
是所有类型的子类型,也称为底部类型。没有一个值是Nothing
类型的。它的用途之一是给出非正常终止的信号,如抛出异常、程序退出或者一个无限循环(可以理解为它是一个不对值进行定义的表达式的类型,或者是一个不能正常返回的方法)。Null
是所有引用类型的子类型(即AnyRef
的任意子类型)。它有一个单例值由关键字null
所定义。Null
主要是使得Scala满足和其他JVM语言的互操作性,但是几乎不应该在Scala代码中使用。我们将在后面的章节中介绍null
的替代方案。
- 分支循环语句
break
Java break: 表示结束循环
注意:scala中没有
break
和continue
关键字
yield
for 循环中的 yield 会把当前的元素记下来,保存在集合中,循环结束后将返回该集合
首先会将
yield
表达式的值会被加入存放到一个缓冲区中在遍历结束之后,会返回一个基于缓冲区的新集合,类型为
Vector
新集合的元素的类型和遍历集合的类型是一致的
-
函数
柯里化函数:接受单个参数并且返回一个函数对象的过程
函数对象:function0~function22
闭包:函数引用到了一个它作用域外的变量,称为函数的闭包;
-
数组
-
对象和类
apply 方法是object类型中的一个方法,通常作为工厂方法创建伴生类的对象【简化方式】
结论:
** 调用的伴生对象中的apply方法,用以创建伴生类**
unapply: 伴生对象中的方法,作用和apply方法相反,接受一个伴生类对象,返回伴生类对象的成员
@BeanProperty
此注解加载成员的前面,scala编译时,会自动生产java版本和scala版本的getter/setter方法
辅助构造器
类似于java重载的构造方法
Java中Override(覆盖)和Overload(重载)的区别?
-
Override:
(1)返回值类型、方法名、形参列表和父类相同
(2)访问修饰符与父类相同或者更宽
-
Overload
(1)方法名相同
(2)形参类表不同
(3)与返回值类型和修饰符无关
注意:scala的一个类中,可以有一个主构造器和若干个辅助构造器
-
-
特质
特质: 类似于Java中的接口
语法
-
特质关键词trait
-
特质可以同时用于抽象方法和具体方法
-
无父类 实现特质
extends trait1 with trait2 with trait3 ...
-
有父类 实现特质
extends 父类 with 特质1 with 特质2 ...
-
-
异常处理
类似于Java中的异常处理
-
集合
Scala 集合分为可变的和不可变的集合:
- 可变集合(
mutable
) 可以在适当的地方被更新或扩展。 - 不可变集合(默认
immutable
)类永远不会改变。
- Java集合和Scala中集合相互转换(面试考点)
import scala.collection.JavaConverters._ //隐式增强
- 可变集合(
-
隐式转换
在编译不通过时,自动寻找隐式转换尝试将一种类型转换为另外一种类型
常用使用方式:
- 隐式值
- 隐式参数
- 隐式函数
- 隐式增强
- 隐式类
Scala会考虑如下位置的隐式转换函数:
- 位于源或目标类型的伴生对象中的隐式函数
- 位于当前作用域可以以单个标识符指代的隐式函数
隐式转换在如下三种不同情况下会被考虑:【知道】
- 当表达式类型与预期类型不同时【隐式值的使用】
- 当对象访问一个不存在成员时【隐式增强】
- 当对象调用某个方法,而这个方法的参数声明与传入参数不匹配时【隐式函数】
有三种情况编译器不会尝试使用隐式转换: 【知道】
- 如果代码能够在不使用隐式转换的前提下通过编译,则不会使用隐式转换
- 编译器不会尝试同时执行多个转换
- 存在二义性的转换是错误
-
模式匹配
-
高阶函数(重点)
scala的函数实现wordcount
val conf = new SparkConf().setMaster("local[*]").setAppName("transformation test")
val sc = new SparkContext(conf)
sc
.makeRDD(List("Hello Spark", "Hello Scala", "Hello Hello Spark", "Scala very good"))
.flatMap(_.split(" "))
.map((_, 1))
.groupByKey()
.map(t2 => (t2._1, t2._2.size))
.foreachPartition(itar => { // 对RDD的每一个分区(Task, 保证Task共享一个连接对象)应用迭代操作
// itar代表的是每一个分区所有元素的迭代器
classOf[com.mysql.jdbc.Driver]
val connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "1234")
val pstm = connection.prepareStatement("insert into t_word values(?,?)")
itar.foreach(t2 => {
pstm.setString(1, t2._1)
pstm.setInt(2, t2._2)
pstm.executeUpdate()
})
pstm.close()
connection.close()
})
-
泛型
sealed、var、val、lazy区别?
lazy:在scala中变量或者常量可以被声明为懒加载的值,值在第一次使用时,才会进行初始化
sealed(密封类)
所有子类都必须在与该密封类相同的文件中定义,主要作用:
- 防止滥用继承:sealed关键字可以修饰类和特质。密封类提供了一种约束:**不能在类定义的文件之外定义任何新的子类。如:scala的List的实现使用了sealed关键字
上下文限定[重点]
语法:
[T:M]
上下文界定的形式为T:M,其中M是另一个泛型类,它要求必须存在一个类型为M[T]的隐式值
Spark(快如闪电统一分析引擎)【重点】
版本2.4.4 Web UI:8080 独立模式端口号(服务端口):7077
Windows操作系统配置主机名映射
C:\Windows\System32\drivers\etc\HOSTS
快如闪电:
- 内存式计算框架
- 任务细粒度划分
- 中间结果缓存
- 底层进行大量优化(查询优化、物理引擎、内存管理等)
统一:
- 流处理(代替Storm)
- 批处理(代替MR)
- 交互式查询(Spark SQL,代替Hive)
- 图形计算(GraphX)
- 机器学习( MLLib)
- 其它Spark第三方生态库
计算引擎:
- 分布式并行计算框架,没有提供大数据存储的解决方案
特点
- 高性能:Spark提供了一个先进的计算模型
- 易用性:Spark应用可以基于多种编程语言开发
- 通用性:Spark拥有一个强大生态库,可以解决大数据的批、流、SQL等一系列问题
- 运行环境:Spark应用可以运行在多种集群中环境中
Spark架构
分析问题(MapReduce)
- MapReduce任务分为粗粒度的MapTask和ReduceTask,并且计算针对于进程(JVM)的,并不能发挥多核CPU的优势
- MapReduce慢,MapTask映射的结果需要溢写在磁盘中存储,Reduce任务计算时需要通过网络从磁盘拉取负责计算分区数据,造成的大量资源开销
- MapReduce完成复杂的科学计算,可能需要将多个任务串联起来,多个任务的数据通过HDFS这样的共享文件系统进行共享(MR—> R1 —> MR2 —> R2 —>MR3 —>R3 …), 计算延迟较高。本来就很慢,串联更慢。
- MapReduce只能够进行Batch(大数据的批处理计算),不支持(Streaming,SQL【借助于Hive】、ML【借助于Mahout】等)
- MapReduce计算中间结果不支持缓存(Cache)
Spark On Standalone工作原理图
Spark自带资源管理调度系统(Standalone)
工作步骤:
-
第一步:在提交Spark应用时会初始化Driver(JVM进程),并且创建SparkContext
-
第二步:Driver会根据任务需要的资源信息,请求资源管理器(ClusterManager)分配计算资源
-
第三步:ClusterManager会将分配的计算资源反向注册给Driver
-
第四步:Driver端的DAGScheduler划分Stage,将一个复杂的计算任务肢解为若个小的任务,每一个Stage阶段都包含一个TaskSet
-
第五步:Driver端的TaskScheduler会根据阶段的划分,逐一提交Stage的TaskSet,运行在预支的计算节点。Spark计算节点在运行任务时,TaskSet中的每一个Task运行在Thread线程中,进行分布式并行计算。
-
第六步:当所有阶段的任务运行结束后,通过SparkContext释放占用的计算资源,通知ClusterManager回收
Spark RDD(批处理)
RDD:弹性分布式数据集对象,是Spark中最为核心的抽象,主要对各种数据源统一抽象,不可变、可分区、容错、支持并行处理计算数据集合;
创建方式
- 集合元素
- 文件系统(Local和HDFS)
- JDBC
- Hadoop InputFormat
使用
- 转换算子(Transformation):
lazy
,将一个RDD转换为另外一个RDD-
结论:
- RDD的转换算子中,如果允许设置分区数量,则此转换算子为宽依赖的算子
- RDD的宽依赖的转换算子,如果没有设定分区数量,则分区数量和父RDD的分区数量保持一致,也可以手动设定子RDD的分区数量
-
- 行动算子(Action):触发RDD处理,提交任务;通常返回一个Scala类型
- 通常情况下Action算子的返回值为Unit或者结果的集合
- 而Transformation算的返回值为新的RDD
RDD依赖(血统 | 血缘)
- 窄依赖(NarrowDependency):父RDD的一个分区或者父RDD的多个分区指向子RDD的一个分区;(一对一或者多对一)
- 宽依赖(
WidthDependency
):父RDD的一个分区指向了多个子RDD的分区;(一对多)
RDD源码剖析
总结如下:
- Spark RDD存在血统(lineage 父RDD—> 子RDD)关系,分为窄(Narrow)和宽(Width)依赖
- Spark运行时,会找到最后的一个RDD反向推导划分Stage;如果是窄依赖,则将RDD划分同一个Stage,如果是宽依赖,则立即划分产生新的Stage
- 对于每一个Stage,都包含了一个TaskSet(任务集); TaskSet中的Task的数量和分区数量一样的(1:1);Spark在进行计算时,逐一提交Stage
- Stage中的TaskSet在提交时,会以一种负载均衡方式提交给多个计算节点实现并行计算
源码追踪 > 任务提交流程
流程: ① 提交任务 —> ②划分阶段 —> ③ 封装TaskSet —> ④ 提交TaskSet
# 第一步:
SparkContext # runJob // spark应用的运行入口
dagScheduler.runJob
# 第二步:
DAGScheduler # runJob
submitJob // 提交任务
# 第三步:
DAGScheduler # submitJob
eventProcessLoop # post(JobSubmitted) // 将提交任务的事件 存放到事件处理器中
# 第四步:
DAGSchedulerEventProcessLoop # onReceive(Event) // DAG调用器接受一个事件,并对事件进行处理
doOnReceive(event)
# 第五步:
DAGSchedulerEventProcessLoop # doOnReceive(event)
// scala样例类的模式匹配 匹配到第一个case语句
case JobSubmitted => dagScheduler.handleJobSubmitted() // 处理任务提交的事件
# 第六步:
DAGScheduler # handleJobSubmitted
var finalStage: ResultStage = null // 最终(最后一个)Stage
// 通过finalRDD(最后一个RDD)创建ResultStage
finalStage = createResultStage(finalRDD, func, partitions, jobId, callSite)
// 最后一行 提交最后一个阶段
submitStage(finalStage)
# 第七步:
DAGScheduler # submitStage(finalStage)
if (!waitingStages(stage) && !runningStages(stage) && !failedStages(stage)) {
val missing = getMissingParentStages(stage).sortBy(_.id) // 获取当前stage的父Stage【重点,宽窄依赖】
logDebug("missing: " + missing)
if (missing.isEmpty) { // 判断父stage是否为空,如果为空,表示到血统最顶端,则开始进行阶段提交
logInfo("Submitting " + stage + " (" + stage.rdd + "), which has no missing parents")
submitMissingTasks(stage, jobId.get) // 提交当前阶段的TaskSet
} else { // 判断父stage是否为空,如果不为空,则通过递归方式,继续寻找父Stage
for (parent <- missing) {
submitStage(parent)
}
waitingStages += stage
}
}
# getMissingParentStages(stage) // 根据依赖类型不同,划分stage
// 如果是 ShuffleDependency【宽依赖】 则创建ShuffleMapStage划分阶段
// 如果是 NarrrowDependency【窄依赖】 则将窄依赖的RDD存放到stack,等待后续访问
for (dep <- rdd.dependencies) {
dep match {
case shufDep: ShuffleDependency[_, _, _] =>
val mapStage = getOrCreateShuffleMapStage(shufDep, stage.firstJobId)
if (!mapStage.isAvailable) {
missing += mapStage
}
case narrowDep: NarrowDependency[_] =>
waitingForVisit.push(narrowDep.rdd)
}
}
# 第八步:
DAGScheduler # submitMissingTasks(stage, jobId.get) // 通过DAGScheduler逐一提交划分好的阶段
val tasks: Seq[Task[_]] = try {
val serializedTaskMetrics = closureSerializer.serialize(stage.latestInfo.taskMetrics).array()
stage match {
case stage: ShuffleMapStage => // 根据stage类型不同,封装不同的Task实例,并且Task数量由当前stage的分区数量决定
stage.pendingPartitions.clear()
partitionsToCompute.map { id =>
val locs = taskIdToLocations(id)
val part = partitions(id)
stage.pendingPartitions += id
new ShuffleMapTask(stage.id, stage.latestInfo.attemptNumber,
taskBinary, part, locs, properties, serializedTaskMetrics, Option(jobId),
Option(sc.applicationId), sc.applicationAttemptId, stage.rdd.isBarrier())
}
case stage: ResultStage =>
partitionsToCompute.map { id =>
val p: Int = stage.partitions(id)
val part = partitions(p)
val locs = taskIdToLocations(id)
new ResultTask(stage.id, stage.latestInfo.attemptNumber,
taskBinary, part, locs, id, properties, serializedTaskMetrics,
Option(jobId), Option(sc.applicationId), sc.applicationAttemptId,
stage.rdd.isBarrier())
}
}
}
// 首先封装了TaskSet(tasks.toArray) 将上面的Seq[Task]转为定长数组 封装到TaskSet中
// taskScheduler.submitTasks(taskSet)
taskScheduler.submitTasks(new TaskSet(
tasks.toArray, stage.id, stage.latestInfo.attemptNumber, jobId, properties))
Spark的计算核心是抽象了一个RDD,RDD存在分区和依赖关系。根据RDD的分区和子RDD的分区的 映射关系将RDD依赖分为宽依赖和窄依赖(ShuffleDependency | NarrowDependency)。系统在任务提交的时候会调用DAGScheduler计算任务的state方法。首先会根据FinalRDD(最终RDD)创建一个ResultStage,然后调用DAGScheduler#submitStage(state)方法,在Stage提交阶段,系统在提交当前Stage时,需要逆向反推出当前ResultStage的所有父Stage(ShuffleMapStage),在寻找父Stage的时候,系统会根据RDD的宽窄依赖做Stage划分。如果当前Stage没有父Stage的时候,系统会将当前Stage所对应的任务提交;如果是ShuffleMapStage,系统会根据分区计算出ShuffleMapTask,并且将该Task封装成TaskSet提交;如果是ResultStage,系统会根据分区计算出ResultTask,并且将该Task封装成TaskSet提交。
RDD缓存
-
cache() :
MEMORY_ONLY
-
persistent()
StorageLevel.MEMORY_ONLY # 直接将RDD只存储到内存,效率高,占用空间大 StorageLevel.MEMORY_ONLY_2 # 直接将RDD只存储到内存,效率高,占用空间大,并且存储两份 StorageLevel.MEMORY_ONLY_SER # 将RDD先进行序列化,效率相对较低,占用空间稍微小 StorageLevel.MEMORY_ONLY_SER_2 # 将RDD先进行序列化,效率相对较低,占用空间稍微小,并且存储 两份 StorageLevel.MEMORY_AND_DISK StorageLevel.MEMORY_AND_DISK_2 StorageLevel.MEMORY_AND_DISK_SER StorageLevel.MEMORY_AND_DISK_SER_2 # 不确定情况下,一般使用该种缓存策略 StorageLevel.DISK_ONLY # 基于磁盘存储 StorageLevel.DISK_ONLY_2 StorageLevel.DISK_ONLY_SER StorageLevel.DISK_ONLY_SER_2
RDD容错
-
重新计算(默认):从头再次计算一遍
- 如果是窄依赖,只需要计算当前未完成的分区即可
- 如果是宽依赖,父RDD的所有分区都需要进行重新计算(不可避免的会造成多余计算)
-
缓存:可以通过RDD缓存恢复某一个阶段的结算数据;
-
检查点(checkpoint):某一个阶段计算状态持久化保存到磁盘(HDFS)中;链路长的推荐使用
- ① 初始化 —> ② 标记 (Mark) —> ③ 处理Checkpoint RDD —> ④ 处理完成后 checkpoint RDD之前血统中止
广播变量和累加器
当算子函数使用到一个外部变量时,会自动将外部变量序列化并且拷贝到每一个Task任务中;
广播变量(优化策略)
- 只读的,计算节点只有一个广播变量副本,所有的Task共享同一个广播变量;
- 好处:减少了内存空间的浪费,提高数据网络传输效率
- 大数据RDD和小批次数据(广播变量)Join
累加器
- (共享性)累加操作,提供Long、Double累加器
- 自定义累加器
- 累加结果可以返回给Driver端
foreach方法输出计算结果
-
常规
-
优化(每一个RDD分区创建一个连接对象,减少资源浪费开销)
Spark应用优化
https://blog.csdn.net/lukabruce/article/details/81504220
Shuffle调优
- 是适当时机对RDD分区进行缩放
- 如果计算结果被多次使用,可以考虑使用cache
- 针对于Join 优化策略
- 大数据集合和小数据集 ,不建议使用join,广播策略
- 先对两边的数据做分区,注意分区数目必须一致,且分区依据必须是Join key,然后在调用join
collect和foreach区别
- 都是Action算子
- collect执行是在Driver端,foreach是在计算节点完成
Reduce和ReduceByKey
- Reduce是动作
- ReduceByKey转换算子
sample转换、takeSample动作
统计系统访问PV(page view)
- 系统每日访问量,换句话来说PV = 访问记录数 页面展示(柱状图或者折线图)
**系统UV(Unique view) **
- 系统每日独立用户的访问量
Spark Streaming
Spark Streaming是Spark Core扩展(RDD),可以对实时流数据进行可靠、高吞吐、容错的流数据处理。
① 构建数据源: Spark Streaming在计算时,输入数据(数据源Sources)可以有多种类型,如:Kafka【重点】、Flume、TCP;
② 通过Streaming中提供高阶函数,如map、reduceByKey、join、window等,对流数据进行处理
③ 将流数据处理结果写出到存储系统(FS、DB)或者仪表盘(数据展示系统)
DStream理解?
离散数据流,是Spark Streaming 中核心抽象,建立Spark RDD微批基础之上,代表是连续的数据流。
注意DStreaming不支持延迟数据,水位线、EventTime处理,窗口长度和滑动间隔必须微批的间隔整数倍
批数据&流数据
- 批数据(batch): 有界、范围的数据,通常按照某种规则(时间、文件大小等)将数据划分为一个个批次数据
- 流数据(stream): 无界、没有范围的数据;通常是有起始,永远不会有结束。 类似于水流
架构
创建
- Basic Sources(基本数据源)
- 通过TCP服务构建
- FileSystem
- Queue RDD
- Advanced Sources(高级数据源)
- Kafka Source(重点)
- Flume
状态
-
状态管理
mapWtihState(增量更新)
和updateStateWithKey(全量更新)
-
状态数据的恢复
StreamingCotnext.getOrCreate(checkpointpath)
-
检查点数据
- 元数据(metadata)检查点:
- Spark Streaming应用的配置信息
- Spark Streaming未完成的微批的RDD
- Spark Streaming应用的代码 计算逻辑
- 数据检查点: 持久化保存DStream中的微批RDD数据
- 元数据(metadata)检查点:
-
只支持处理时间,不支持事件时间
窗口
- 翻滚(Tumbling)
- 滑动(Sliding)
JOIN
- Stream-Batch Join
- Stream-Stream Join
Spark SQL
对批次数据或者结构化流数据的处理工具;
类似于Hive,数据仓库(Data WareHourse)工具,简化Spark应用开发
提供了两种交互方式:1) SQL 脚本 ,2) Dataset API (strong-typed类型、untyped类型操作)
DataSet和DataFrame
DataSet分布式数据集对象,增强版本的RDD,通过RDD隐式转换获得DS
DataFrame分布式数据帧对象,特殊的DataSet[Row]
创建
DataSet
Dataset支持无类型操作,用户无需获取操作的类型,操作仅仅是列名
- scala集合(元组)
- 样例类(case class)
- JSON文件
- RDD
- DF转换
DataFrame
是一个命名列的数据集,用户可以直接操作column 因此几乎所有Dataframe推荐操作都是 无类型操作
- JSON文件
- 样例类
- CSV文件
- Tuple
- RDD
- DS转换
强类型(strong typed)和无类型(
untyped`)操作
- 操作类型对象,需要通过函数自定义操作规则; 在实际开发中,强类型几乎不会使用(强类型)
- 操作字段(无类型)
推荐DF无类型操作
SQL语法
- 支持标准SQL语法
- Join
【重点】
(表连接)
- Pivot(透视) 行转列
- Cube 多维度分组
- Na 对Null值
- drop : 丢弃空值行
- fill : 填充,默认值
-
Over 窗口函数(
重点
TopN):求每个分类商品热卖榜、时间排序窗口(开窗)函数
作用: 窗口函数使用over,对一组数据进行操作,返回普通列和聚合列
DB 聚合查询:
sum count avg
返回一行结果窗口函数分为3大类:
- 聚合函数
- 排名函数
- 分析函数
语法:
窗口函数名() over([partition by 分区字段] order by 字段 asc | desc [range | rows between unbounded preceding and unbounded following])
val df2 = List( (1, "zs", true, 1, 15000), (2, "ls", false, 2, 18000), (3, "ww", false, 2, 14000), (4, "zl", false, 1, 18000), (5, "win7", false, 1, 16000)) .toDF("id", "name", "sex", "dept", "salary") //************************************************************************************ # 使用窗口函数 每一个用户新增聚合列 代表的当前用户所在部门的员工的最高工资 1 "zs" true 1 15000 18000 2 "ls" false 2 18000 18000 2 "ww" false 2 14000 18000 select id,name,sex,dept,salary, max(salary) over(partition by dept order by salary desc rows between unbounded preceding and unbounded following) as dept_max_salary from t_user //************************************************************************************ val df2 = List( (1, "zs", true, 1, 15000), (2, "ls", false, 2, 17000), (3, "ww", false, 2, 14000), (4, "zl", false, 1, 18000), (6, "zl2", false, 1, 18000), (5, "win7", false, 1, 16000)) .toDF("id", "name", "sex", "dept", "salary") df2.createOrReplaceTempView("t_user") // 窗口函数1: 当前行为基准在 分区内数据可视范围 unbounded preceding(-2^63) and unbounded following (2^63-1) // 窗口函数2: 当前行为基准在 分区内数据可视范围 rowsBetween(start = -1,end = 1) 1 preceding and 1 following // 窗口函数3: 分区内数据可视范围 rowsBetween(start = -1,end = 0) 当前行 + 上一行 // 窗口函数4: 分区内数据可视范围 rowsBetween(start = 0,end = 1) 当前行 + 下一行 // 窗口函数5: 分区内数据可视范围 rangeBetween(start,end) 通过排序字段 动态计算范围区间[基准行的排序字段的值 - start,基准行的排序字段的值 + end] // 窗口函数6: 排行窗口函数(非连续排名) // 窗口函数7: 排行窗口函数(连续排名) // 窗口函数8: lead(列名,n) over(partition by ... order by ...)-- 取出基准行后n行数据作为聚合列的结果。 // 窗口函数9: lag(列名,n) over(partition by ... order by ...)-- 取出基准行前n行数据作为聚合列的结果。 spark .sql( """ |select | id,name,sex,dept,salary, | max(salary) over(partition by dept order by salary desc rows between unbounded preceding and unbounded following) as dept_max_salary, | max(salary) over(partition by dept order by salary desc rows between 1 preceding and 1 following) as dept_max_salary2, | max(salary) over(partition by dept order by salary desc rows between 1 preceding and 0 following) as dept_max_salary3, | max(salary) over(partition by dept order by salary desc rows between 0 preceding and 1 following) as dept_max_salary4, | max(salary) over(partition by dept order by salary desc range between 2000 preceding and 2000 following) as dept_max_salary5, | rank() over(partition by dept order by salary desc) as rank1, | dense_rank() over(partition by dept order by salary desc) as rank2, | lead(salary,2) over(partition by dept order by salary desc) as next, | lag(salary,1) over(partition by dept order by salary desc) as before |from | t_user """.stripMargin) .show() //------------------------------------------------------------------------------------ 略
Untyped(纯SQL)
建议在工作中使用DataFrame的纯SQL操作(无类型的)
给dataFrame起别名(视图名)
- GlobalTempView 创建全局视图,跨多个spark session会话,有效范围是整个Application
- TempView 创建会话视图,只能够被创建它的spark session会话使用,有效范围是SparkSession
- createOrReplace 如果视图存在则替换 如果不存在创建
- 注意:
- TempView 会话视图 视图表会存放在default数据库中;
- GlobalTempView 全局视图 视图表存放在global_temp数据库中
自定义函数
- 单行函数
- 聚合函数:自定义的sum函数
Load&Save操作
- 加载数据创建DS或者DF
- 保存DS或者DF到不同存储系统
Spark Structured Streaming
建立在Spark SQL基础之上的一个用于进行流数据处理的引擎
流数据进行处理,处理容错语义:
- at exactly once: 精确一次; 流数据不论处理成功还是失败 一定能够精确处理1次
- at least once:最少一次; 流数据处理成功(1次),处理失败(n次)
- at most once: 最多一次;流数据处理成功(1次),处理失败(0次)
优点:
- 支持多种数据端(流数据的输入和输出可以有多种方式)
- 应用Spark SQL操作,可以通过SQL语法计算流数据;
select word,num(word) from t_word group by word
- 支持容错语义:
at exactly once
, Spark 2.3版本之后,提供端对端低于1ms的处理延迟和at least once
- 借助于Spark SQL底层优化,保证对流数据以高效方式处理
工作原理图
核心思想将流数据视为一个持续追加的数据库表; Spark结构流处理类似于Spark SQL的批处理操作
-
InputTable:输入表,代表反映的是流数据
-
ResultTable(结果表,也是状态表):对输入表应用query结果表
-
OutputMode:
- Complete:将结果表中的内容全量式输出;
- Append: 只会将结果表中新追加的内容输出
- Update:只会将结果表中更新(新增和修改)的内容输出
聚合操作:Complete/ Update 无聚合操作:Append/Update
注意:
- Spark结构化流并不会长时间持有InputTable中内容,实际上流数据产生后会应用增量更新,完成后流数据丢弃;这样设计目的是为了保证对于内存的使用保证在一个合理的范围;只使用内存存放ResultTable(状态表)
- Spark结构化流模型不同于其它流数据处理引擎,状态管理是自动处理
Event Time 语义处理
提供了基于EventTime语义的处理,在使用的时候需要用户指定时间戳字段。
val wordCounts=df.select("value").as[String].map(_.split(","))
.map(tokens=>(tokens(0),new Timestamp(tokens(1).toLong)))
.toDF("word","timestamp")
.groupBy(
window($"timestamp","4 seconds","2 seconds"),
$"word"
)
Fault Tolerance Semantics(容错语义)
Spark结构化流实现端对端精确一次处理语义;
输入端: 通常是Kafka,处理引擎正常处理流数据则提交消费位置offset;如果未正常处理流数据则不提交消费位置offset,下一次消费数据时,还会重新拉取这条记录;
输出端: 实现幂等写操作(写出一次或者多次影响结果是一致的)
延迟数据和水位线
延迟数据:Late Data,先生产后抵达或者数据乱序问题; 处理方案:参与计算、丢弃(Flink 旁路输出)
水位线:
- 鉴别数据是否是有效数据
wm = max event time - late threshold
, - ResultTable状态表体积在一个合理的范围内
- 两种不同的输出模式:
- Update:数据抵达立即触发计算,当WM淹没过窗口的endtime后删除结果表中窗口的状态数据
- Append: 不会立即触发计算,直到WM淹没过了窗口的endtime后才会触发整个窗口计算,输出完成后删除表中窗口的状态数据
Flink(流批一体统一分析引擎)
版本:1.8.1 WebUI:8081
Apache Flink 是一个框架和分布式处理引擎,用于在无边界和有边界数据流上进行有状态的计算。Flink 能在所有常见集群环境中运行,并能以内存速度和任意规模进行计算。
Flink发展史
第一代大数据处理方案:2006年Hadoop的MapReduce-批/HDFS, 2014年9月份 apache Storm-流
第二代大数据处理方案:2014年2月 Spark RDD -批处理 ,DStream - 流 (批模拟流 )延迟高
第三代大数据处理方案:2014年12月 Flink DataStream-流,Dataset- 批 吞吐量高,低延迟特点。
Flink和Spark相似采用先进的DAG模型做任务拆分完成数据的内存计算,但是Flink是一个纯流式
计算引擎。不同于Spark在批处理之上构建流处理,Flink设计恰恰和Spark相反,Flink是在流计算上构建批处理
。
Flink计算特点 & Spark Streaming区别?
- Flink纯粹流计算框架(低延迟)、Spark Streaming 微批模拟流实现 (延迟高)。
- Flink所有的计算都是有状态的计算,无需人工干预。提供丰富数据模型存储状态。
- Flink 支持状态的TTL(Time To Live)管理,但是Spark没有。
- Flink支持基于ProcessingTime、Ingestion Time(摄取时间)、EventTime 窗口计算 。
- Spark默认支持ProcessingTime(DStream)。
- Flink支持滑动、滚动、会话 、全局 ,spark仅仅支持 滑动和滚动。
- Flink对窗口的控制更为便捷,提供触发器、Evictor(驱逐器)
- Flink提供了Watermarker,并且提出 乱序和迟到语义的处理,严格意义上讲Spark 无法处理迟到数据。
架构
https://ci.apache.org/projects/flink/flink-docs-release-1.10/zh/concepts/runtime.html
- 部署方式支持多种环境:Local模式(测试)、Standalone&Yarn、云计算
- 底层计算引擎:数据流的基础之上实现了有状态计算
- 目前Flink提供两套基础API:DataSet API(批处理)和DataStream API(流处理)
- 高级抽象API:CEP、Dynamic Table、MLLib、Relationnal Table等;
Job Managers, Task Managers, Clients
JobManagers:称为Master,负责分布式任务调度,调度Task执行,协调checkpoint,实现故障恢复。
TaskManagers :称为Worker节点,负责执行Dataflow Graph(DAG)中SubTask,负责数据缓冲和交换。主动连接JobManager,声明自身状态信息和汇报应经分配的任务。
client :并不是运行时一个部分(和Spark Driver不同),负责生成或者发送dataflow给JobManager。
Task&Operator chain(操作链)
“Oprator Chain”:将多个操作符合并到一个Task中,减少不必要的任务间网络通信。
“Task”:类似于Spark中Stage,将任务拆分成若干个阶段。
“SubTaks”:每个Task都有任务执行并行度,每个Task根据并行度拆分成若干个SubTask(等价于线程)
当前的flink应用由3个Task,5个SubTask构成,每一个SubTask会由1个Thread处理
注:
-
Flink中的Task等价于Spark中的Stage
-
Flink根据Operator Chain划分任务Task,两种依据:Forward和Hash | Rebalance
Task Slots 和资源
每个 worker(TaskManager)都是一个 JVM 进程,并且可以在不同的线程中执行一个或多个 subtasks。为了控制 worker 接收 task 的数量,worker 拥有所谓的 task slots (至少一个)。
**每个 task slots 代表 TaskManager 的一份固定资源子集。**例如,具有三个 slots 的 TaskManager 会将其管理的内存资源分成三等份给每个 slot。 划分资源意味着 subtask 之间不会竞争资源,但是也意味着它们只拥有固定的资源。注意这里并没有 CPU 隔离,当前 slots 之间只是划分任务的内存资源。
**通过调整 slot 的数量,用户可以决定 subtasks 的隔离方式。**每个 TaskManager 有一个 slot 意味着每组 task 在一个单独的 JVM 中运行(例如,在一个单独的容器中启动)。拥有多个 slots 意味着多个 subtasks 共享同一个 JVM。 Tasks 在同一个 JVM 中共享 TCP 连接(通过多路复用技术)和心跳信息(heartbeat messages)。它们还可能共享数据集和数据结构,从而降低每个 task 的开销。
TaskSlot会对计算节点内存进行均分,不同的Job持有不同TaskSlot,继而程序在运行时实现内存隔离。任意job在执行之前都必须分配额定数据的TaskSlot,这些TaskSlot和该job中最大的Task并行度相等。
-
不同Job间通过TaskSlot进行隔离
-
同一个Job的不同Task的SubTask之间可以共享slot
-
同一个Job的相同Task的SubTask之间不可以共享slot
默认情况下,Flink 允许 subtasks 共享 slots,即使它们是不同 tasks 的 subtasks,只要它们来自同一个 job。因此,一个 slot 可能会负责这个 job 的整个管道(pipeline)。允许 slot sharing 有两个好处:- Flink 集群需要与 job 中使用的最高并行度一样多的 slots。这样不需要计算作业总共包含多少个 tasks(具有不同并行度)。
- 更好的资源利用率。在没有 slot sharing 的情况下,简单的 subtasks(source/map())将会占用和复杂的 subtasks (window)一样多的资源。通过 slot sharing,将示例中的并行度从 2 增加到 6 可以充分利用 slot 的资源,同时确保繁重的 subtask 在 TaskManagers 之间公平地获取资源。
APIs 还包含了 resource group 机制,它可以用来防止不必要的 slot sharing。根据经验,合理的 slots 数量应该和 CPU 核数相同。在使用超线程(hyper-threading)时,每个 slot 将会占用 2 个或更多的硬件线程上下文(hardware thread contexts)。
-
slot 是指 taskmanager 的并发执行能力;
如上图所示:taskmanager.numberOfTaskSlots:3;即每一个 taskmanager 中的分配 3 个 TaskSlot, 3 个 taskmanager 一共有 9 个 TaskSlot。
-
parallelism 是指 taskmanager 实际使用的并发能力
如上图所示:parallelism.default:1;即运行程序默认的并行度为 1,9 个 TaskSlot 只用了 1 个,有 8 个空闲。设置合适的并行度才能提高效率。
-
parallelism 是可配置、可指定的;
上图中 example2 每个算子设置的并行度是 2, example3 每个算子设置的并行度是 9。
分析:
详细剖析:
Flink On Yarn运行原理
https://ci.apache.org/projects/flink/flink-docs-release-1.10/zh/ops/deployment/yarn_setup.html
状态管理
有状态操作或者操作算子在处理DataStream的元素或者事件的时候需要存储计算的中间状态,这就使得状态在整个Flink的精细化计算中有着非常重要的地位
如:
- 记录保存某个时间节点到当前时间节点的状态数据
- 在每分钟/小时/天汇总事件时,状态将保留待处理的汇总。
- 在数据点流上训练机器学习模型时,状态保持模型参数的当前版本。
- 当需要管理历史数据时,状态允许有效访问过去发生的事件。
在学习使用flink时,需要掌握了解状态,以便使用检查点状态容错并设置流应用保存点;flink同样支持多种状态备份,如:内存、文件系统、RocksDB等
- Keyed State
通常和 key 相关,仅可使用在
KeyedStream
的方法和算子中。
你可以把 Keyed State 看作分区或者共享的 Operator State, 而且每个 key 仅出现在一个分区内。 逻辑上每个 keyed-state 和唯一元组 <算子并发实例, key> 绑定,由于每个 key 仅”属于” 算子的一个并发,因此简化为 <算子, key>。
Keyed State 会按照 Key Group 进行管理。Key Group 是 Flink 分发 Keyed State 的最小单元; Key Group 的数目等于作业的最大并发数。在执行过程中,每个 keyed operator 会对应到一个或多个 Key Group
- Operator State( non-keyed state)
Operator State 在 Flink 作业的并发改变后,会重新分发状态,分发的策略和 Keyed State 不一样。
对于 Operator State (或者 non-keyed state) 来说,每个 operator state 和一个并发实例进行绑定。 Kafka Connector 是 Flink 中使用 operator state 的一个很好的示例。 每个 Kafka 消费者的并发在 Operator State 中维护一个 topic partition 到 offset 的映射关系。
- Raw State 与 Managed State
Keyed State 和 Operator State 分别有两种存在形式:managed and raw.
Managed State 由 Flink 运行时控制的数据结构表示,比如内部的 hash table 或者 RocksDB。 比如 “ValueState”, “ListState” 等。Flink runtime 会对这些状态进行编码并写入 checkpoint。
Raw State 则保存在算子自己的数据结构中。checkpoint 的时候,Flink 并不知晓具体的内容,仅仅写入一串字节序列到 checkpoint。
总结:在flink中无论是Keyed State或者是Operator State都可以两种形式存在Managed State
和Raw State
,推荐使用Managed State
,由于 Flink 可以在修改并发时更好的分发状态数据,并且能够更好的管理内存,因此建议使用 managed state(而不是 raw state)。
容错(检查点)
savepoint和checkpoint区别
savepoint: 手动执行,触发Flink创建还原点
checkpoint:系统定义的checkpoint数据存储到HDFS
state backend分类
Flink是一个有状态的计算框架,所有的计算状态数据存储在内存中或者是RocksDB中,这取决于state backend策略的选取。默认情况下Flink的backend状态数据存在JobManager的内存中,一般用作测试环境(数据集非常小)。如果在生产环境下一般会采取以下两种状态后端:filesysterm,rocksdb.由于流计算在JobManager的管理下会定期的进行checkpoint机制,存储计算的快照信息,快照信息会根据state backend后端实现,将状态数据持久化起来。
- memory|JobManager:状态存储在JobManager的内存,测试
- filesystem:状态存储在TaskManager中,备份在HDFS上,在超大规模计算,会导致计算节点oom(out of memory)
- rocksdb::状态存储在TaskManager内存和磁盘中(rocksdb),同时会备份数据到HDFS上,一般用在超大规模计算中
转换函数
fold
使用一个初始值,滚动折叠一个KV DataStream
union
将两个或者多个DataSream组合为一个
connect
连接两个流,允许类型不一致,可以共享状态
split
分流
select
对一个split后的流进行选择操作
iterate
基本概念:在流中创建“反馈(feedback)”循环,通过将一个算子的输出重定向到某个先前的算子。这对于定义不断更新模型的算法特别有用。
迭代的数据流向:DataStream → IterativeStream → DataStream
以下代码以流开始并连续应用迭代体。性别为male的元素将被发送回反馈(feedback)通道,继续迭代,其余元素将向下游转发,离开迭代。
val dataStream = env.socketTextStream("localhost", 8888)
dataStream
.map(line => {
val arr = line.split("\\s")
(arr(0), arr(1), arr(2))
})
.iterate(
iteration => {
var count = 0
iteration.map(t3 => {
count += 1
println(t3 + "\t" + count)
t3
})
(iteration.filter(_._3.equals("male")), iteration.filter(_._3.equals("female")))
})
.print()
结果:
(1,zs,male) 149
(1,zs,male) 150
(1,zs,male) 151
(3,ww,female) 1
16> (3,ww,female)
(1,zs,male) 152
(1,zs,male) 153
(1,zs,male) 154
Flink物理分区
Rebalancing (Round-robin partitioning) 默认策略
轮询,会将数据轮询发送给下游任务
Random partitioning
随机将数据发送给下游
Rescaling
上游分区的数据会轮询方式发送给下游的子分区,上下游任务并行度呈现整数倍
Broadcasting
将上游数据广播给下游所有分区
任务划分
- Spark 中使用宽窄依赖实现stage划分、每个stage有分区-并行度 ,也就意味着每个阶段并行对应着一组TaskSet
- Flink 使用Operator Chain将job划分为多个Task.每个Task都有并行度-分区,也就意味着每个Task对应着一组SubTask
在Spark中 每一个Task(线程)执行需要消耗一个 Core 线程资源。在Flink中每个SubTask代表着一个线程,每个Task的SubTask会分别运行在不同Task Slot中。但是允许TaskSlot 在不同Task中的SubTask共享。
人工干预Flink的OperatorChain
Flink再部署上都有哪些形式
- Spark 支持 本地仿真、支持 spark-submit提交 ,不支持WebUI提交、一般不支持跨平台提交(实现SparkSubmite)
- Flink 支持 本地仿真、支持flink run、支持webUI、支持远端提交。
Flink流算子和Spark区别
- Side out /Split Stream Flink特有
- Spark groupBy算子 Flink keyBy
状态有效期 (TTL)
任何类型的 keyed state 都可以有 有效期 (TTL)。如果配置了 TTL 且状态值已过期,则会尽最大可能清除对应的值;在使用状态 TTL 前,需要先构建一个StateTtlConfig
配置对象。 然后把配置传递到 state descriptor 中enableTimeToLive启用 TTL 功能:
注意:
- 开启TTL增加state存储,因为每个状态都要额外存储8bytes的long类型的时间戳
- 状态上次的修改时间会和数据一起保存在 state backend 中,因此开启该特性会增加状态数据的存储。
- 暂时只支持基于 processing time 的 TTL
- 如果用户以前没有开启TTL配置,在启动之前修改代码开启了TTL,在做状态恢复的时候系统启动不起来,会抛出兼容性失败以及StateMigrationException的异常。
- TTL 的配置并不会保存在 checkpoint/savepoint 中,仅对当前 Job 有效
- 当前开启 TTL 的 map state 仅在用户值序列化器支持 null 的情况下,才支持用户值为 null。如果用户值序列化器不支持 null, 可以用
NullableSerializer
包装一层。
Cleanup of Expired State(清除过期状态)
Flink默认情况下并不会主动的删除过期的state,只用使用到该state的时候flink才会对State实行过期检查,将过期数据清除。这可能导致一些不经常使用的数据可能已经过期很长时间,但是因为没有使用的机会导致长时间驻留在Flink的内存中,带来内存浪费。
Cleanup in full snapshot(完整快照中清除)
仅仅是在服务重启的时候,回去加载状态快照信息,在加载的时候检查过期的数据,并且删除数据,但是在程序的运行期间并不会主动的删除过期数据,一般运维人员只能通过定期创建savepoint或者checkpoint然后执行故障恢复才可以释放内存。
注意:如果用户使用的是RocksDB的增量式的检查点机制,该种机制就不起作用。
Cleanup in background(后台清理)
用户除了可以开启cleanupFullSnapshot在系统快照的时候清除过期的数据,同时还可以开启cleanupInBackground策略,该策略会根据用户使用state backend存储策略自动选择一种后台清理模式。
目前Flink的state backend实现统共分为两大类:heap(堆) state backend和 RocksDB backend,其中基于heap(堆) state backend使用的是
incremental cleanup (增量清理)
而 RocksDB backend使用的是compaction filter(压实过滤器)
清理策略。
窗口触发器(Trigger
)
触发器(Trigger
)决定了窗口什么时候准备好被窗口函数处理。每个窗口分配器都带有一个默认的 Trigger
。如果默认触发器不能满足你的要求,可以使用 trigger(...)
指定自定义的触发器。
触发器接口有五个方法来对不同的事件做出响应:
- TriggerResult onElement - 只要有元素落入到窗口中,就会回调。
- TriggerResult onProcessingTime - 当用户注册的ProcessingTime定时器时间到达,系统回调。
- TriggerResult onEventTime - 当用户注册的EventTime定时器时间到达,系统回调
- onMerge - 当用户使用SessionWindow的时候,系统在做窗口合并的时候会回调该方法。
- clear() - 当窗口被删除的时候系统会回调
前三个函数决定了如何通过返回一个
TriggerResult
来对其调用事件采取什么操作。TriggerResult
可以是以下之一:
- CONTINUE 什么都不做
- FIRE_AND_PURGE 触发计算,然后清除窗口中的元素
- FIRE 触发计算
- PURGE 清除窗口中的元素
窗口分配器的默认触发器
- GlobalWindow的默认触发器是永不会被触发的 NeverTrigger
- Event-time Window是EventTimeTrigger
- Processing-time Window是ProcessingTimeTrigger
Evictors(驱逐器)
可以使用evictor(…)方法完成操作。驱逐者可以在触发器触发后,应用窗口功能之前或之后从窗口中删除元素
Flink附带了三个预先实施的驱逐程序。这些是:
- CountEvictor:从窗口中保留用户指定数量的元素,并从窗口缓冲区的开头丢弃其余的元素。
- DeltaEvictor:采用DeltaFunction和阈值,计算窗口缓冲区中最后一个元素与其余每个元素之间的增量,并删除增量大于或等于阈值的元素。
- TimeEvictor:以毫秒为单位的间隔作为参数,对于给定的窗口,它将在其元素中找到最大时间戳max_ts,并删除所有时间戳小于max_ts-interval的元素。
Task Failure Recovery(任务失败恢复)
RestartStrategy(重启策略)
重启策略描述的是程序在故障的时候何时进行重启策略。目前Flink给用户提供了以下几种策略:
-
noRestart - 失败就会终止服务
-
fixedDelayRestart - 固定重启次数,用户可以设定时间间隔
//每间隔5秒中重启1次,总共尝试5次 fsEnv.setRestartStrategy(RestartStrategies.fixedDelayRestart(5,Time.seconds(5)))
-
failureRateRestart - 在规定时间间隔内,出错次数达到固定值,认定任务失败
//1分钟内总共失败5次,每次尝试间隔5秒 fsEnv.setRestartStrategy(RestartStrategies.failureRateRestart(5,Time.minutes(1),Time.seconds(5)))
-
fallBackRestart - 如果集群配置重启策略则使用集群配置策略,如果没有配置默认策略,系统会使用fixedDelayRestart
fsEnv.setRestartStrategy(RestartStrategies.fallBackRestart())
Failover Strategies(故障转移策略)
该配置配置系统已何种方式做故障重启,目前Flink支持两种重启策略region
-局部| full
-全部重启,需要用户配置flink-conf。yaml
jobmanager.execution.failover-strategy: region
CEP Pattern
FlinkCEP是构建在Flink流处理之上的的 Complex Event Processing (CEP)库。允许用户在无止境的数据流中检测到用户所关注的事件模型,并且将关注的事件模型数据抽取出来。
Flink计算发布之后是否还能够修改计算算子?
首先,这在Spark中是不允许的,因为Spark会持久化代码片段,一旦修改代码,必须删除Checkpoint,但是Flink仅仅存储各个算子的计算状态,如果用户修改代码,需要用户在有状态的操作算子上指定uid属性。
env.addSource(new FlinkKafkaConsumer[String]("topic01",new SimpleStringSchema(),props))
.uid("kakfa-consumer")
.flatMap(line => line.split("\\s+"))
.map((_,1))
.keyBy(0) //只可以写一个参数
.sum(1)
.uid("word-count") //唯一
.map(t=>t._1+"->"+t._2)
.print()
Flink Kafka如何保证精准一次的语义操作?
- https://www.cnblogs.com/ooffff/p/9482873.html
- https://www.jianshu.com/p/8cf344bb729a
- https://www.jianshu.com/p/de35bf649293
- https://blog.csdn.net/justlpf/article/details/80292375
- < https://www.jianshu.com/p/b48f3ae30f23 > (面试题)
Flink如何保证精准一次状态更新
https://www.jianshu.com/p/c0af87078b9c
比如我们在平时的开发中,需要对数据进行count,sum,max等操作,这些中间的结果(即是状态)是需要保存的,因为要不断的更新,这些值或者变量就可以理解为是一种状态,拿读取kafka为例,我们需要记录数据读取的位置(即是偏移量),并保存offset,这时offest也可以理解为是一种状态.
Flink是怎么保证容错恢复的时候保证数据没有丢失也没有数据的冗余呢?
checkpoint是使Flink 能从故障恢复的一种内部机制。检查点是 Flink 应用状态的一个一致性副本,包括了输入的读取位点。在发生故障时,Flink 通过从检查点加载应用程序状态来恢复,并从恢复的读取位点继续处理,就好像什么事情都没发生一样。Flink的状态存储在Flink的内部,这样做的好处就是不再依赖外部系统,降低了对外部系统的依赖,在Flink的内部,通过自身的进程去访问状态变量.同时会定期的做checkpoint持久化,把checkpoint存储在一个分布式的持久化系统中,如果发生故障,就会从最近的一次checkpoint中将整个流的状态进行恢复.
#### 如何拿到窗口元数据信息
ProcessWindowFunction| WindowFunction (遗产,无法获取窗口 状态信息)
谈一谈Flink如何出口迟到数据
延迟数据处理方法
有两种:
默认丢弃
旁路输出
将延迟数据加入到一个数据流中进行处理
触发水位线的计算方式
有两种:
①一种是基于定时Interval(推荐)
②通过记录触发,每来一条记录系统会立即更新水位线。
watermarker是一种机制,告知流计算何时触发窗口(Event Time),
w= max(EventTime)- maxOrderness
watermarker(T) > wondow end (T’)窗口触发。
水位线触发时机:AssignerWithPunctuatedWatermarks 数据触发 、AssignerWithPeriodicWatermarks 定时触发
Flink通过设置.allowedLateness(Time.seconds(2))设置数据最大迟到时间maxlate 。watermarker(T)-maxlate <= wondow end (T’) ,这些数据还可加入窗口计算。默认情况下,如果watermarker(T) -maxlate > window end (T’) ,此时再有数据落入窗口该数据默认会被丢弃。可以通过设置Sideout,完成对迟到数据的处理。
如何拿到窗口元数据信息
ProcessWindowFunction| WindowFunction (遗产,无法获取窗口 状态信息)