mr 将得到的split 分配对应的 task,每个任务处理相对应的 split,将 split 以 line 方式读取每一行数据,将数据依次读取到100M(maprdeuce.task.io.sort.mb)的环形缓冲区读取过程中一旦到达阈值(mapreduce.map.sort.spill.percent)80M进行溢写操作,spiller线程溢写到磁盘(mapreduce.cluster.local.dir)目录中,期间会进行kv分区(分区数由reduce数来决定)默认使用hashpartition,再将分区中数据进行key的排序(默认排序规则是字典和升序),如果设置了setCombinerClass 则会对每个分区中的数据进行 combiner 操作,如果设置了output.compress压缩格式会对溢写的数据进行压缩,最后merge根据分区规则将数据归并到同一个文件中等待reduce的拉取,nodemanger将启动一个mapreduce_shuffle服务将数据以http方式拉取到reduce端,reduce处理阶段当达到阈值(默认0.66)或map输出数的阈值(默认100)会进行merge(同一分区的一组数据会先进行归并)|sort(将归并好的数据进行排序)|group(判断迭代器中的元素是否可以迭代),处理完成mr将同一个分区内的数据,在hdfs中以文件形式体现出来,几个分区就会创建几个文件。
其中reduce端的merge达到阈值会触发,sort则是维持其map阶段顺序,而group是设置( setGroupingComparatorClass)后才会触发。
有效的理解mr工作流程可大大提升程序运行效率,其中 mr 的 shuffle 也被称为奇迹开始的地方
spilit 是在mr 处理的map端之前产生的概念,split切片大小,默认等于block*1.1,在FileInputFormat中计算切片大小的逻辑:
blocksize:默认是 128M,可通过 dfs.blocksize 修改
minSize:默认是 1,可通过 mapreduce.input.fileinputformat.split.minsize 修改
maxsize:默认是 Long.MaxValue,可通过 mapreduce.input.fileinputformat.split.maxsize 修改
Hadoop FileInputFormat 源码:
public static final String SPLIT_MAXSIZE = "mapreduce.input.fileinputformat.split.maxsize";
public static final String SPLIT_MINSIZE = "mapreduce.input.fileinputformat.split.minsize";
protected long computeSplitSize(long blockSize, long minSize, long maxSize) {
return Math.max(minSize, Math.min(maxSize, blockSize));
}
为什么split不是与block 一一对应的?
大量小文件场景,map进程造成资源严重浪费。
针对大小文件场景可以手动配置。
namenode,在基于主从架构的hdfs文件系统中是主节点,其主要职责就是对hdfs中的文件的元信息,副本数,文件目录树,block 数据节点信息;
datanode,它是从节点也是数据节点,基于本地磁盘存储 block(文件的形式),有相关数据块的长度、效验和、时间戳,与namnode保持心跳,汇报 block 状态。
secondaryNameNode,检查点节点,namenode 日志高可用的关键,其主要作用就是将namenode的元数据日志信息合并后备份,防止元数据丢失。
元信息:是数据文件的block大小,文件副本存储位置,副本数量,block 数量,主要体现在edits文件和fsimage文件。
副本数:hdfs 中同一个文件在多个节点中所存储的总数量,也是实现持久化和保证安全性的关键。
文件目录树:hdfs提供了一个可以维护的文件目录,该文件目录下存储着有关所有hdfs的文件。
block 数据节点信息:如a文件在01和02节点中存储,该信息称为数据节点信息。
edits:记录 client
执行创建,移动,修改文件的信息,同时体现了 HDFS
的最新的状态(二进制文件)。
它分布在磁盘上的多个文件,名称由前缀 edits
及后缀组成.后缀值是该文件包含的事务 ID,同一时刻只有一个文件处于可读写状态.为避免数据丢失,事务完成后 client
端在执行成功前,文件会进行更新和同步,当 NN
向多个目录写数据时,只有在所有操作更新并同步到每个副本之后执行才成功。
fsimage:记录的是数据块的位置信息、数据块的冗余信息(二进制文件)
由于 edits
文件记录了最新状态信息,并且随着操作越多,edits
文件就会越大,把 edits
文件中最新的信息写到 fsimage
文件中就解决了 edits
文件数量多不方便管理的情况。
没有体现 HDFS
的最新状态。
每个 fsimage
文件都是文件系统元数据的一个完整的永久性的检查点。
为什么引入 secondaryNameNode?
由于只有在重启时 fsimage
和 edits
才会进行合并,得到一个新的 fsimage
文件,但是在实际生产环境中很少会重启集群,NN
的重启需要花费很长时间,因为会有很多改动需要合并到 fsimage 文件上,如果 NN
挂掉,fsimage
文件没有更新内容,从而丢失很多改动。
但 editlog 日志大小会随着时间变的越来越大,导致系统重启,根据日志恢复元数据的时间会越来越长;
为了避免这种情况,引入检查点机制checkpoint,命名空间镜像 fsimage 就是 HDFS 元数据的持久性检查点,即将内存中的元数据落磁盘生成的文件;
了解详细可以访问我另一个博客:hdfs详细
https://blog.csdn.net/qq_43259670/article/details/105882983
1、向client端提交MapReduce job.
2、随后yarn的ResourceManager进行资源的分配.
3、由NodeManager进行加载与监控containers.
4、通过applicationMaster与ResourceManager进行资源的申请及状态的交互,由NodeManagers进行MapReduce运行时job的管理.
5、通过hdfs进行job配置文件、jar包的各节点分发。
edits:记录 client
执行创建,移动,修改文件的信息,同时体现了 HDFS
的最新的状态(二进制文件)。
它分布在磁盘上的多个文件,名称由前缀 edits
及后缀组成.后缀值是该文件包含的事务 ID,同一时刻只有一个文件处于可读写状态.为避免数据丢失,事务完成后 client
端在执行成功前,文件会进行更新和同步,当 NN
向多个目录写数据时,只有在所有操作更新并同步到每个副本之后执行才成功。
fsimage:记录的是数据块的位置信息、数据块的冗余信息(二进制文件)
由于 edits
文件记录了最新状态信息,并且随着操作越多,edits
文件就会越大,把 edits
文件中最新的信息写到 fsimage
文件中就解决了 edits
文件数量多不方便管理的情况。
没有体现 HDFS
的最新状态。
每个 fsimage
文件都是文件系统元数据的一个完整的永久性的检查点。
为什么使用?
NN
使用了 FsImage
+EditLog
整合的方案;
滚动将增量的 EditLog
更新到 FsImage
,以保证更近时点的 FsImage
和更小的 EditLog
体积
一般就是读写的工作流程,因为hdfs 主要还是对文件存储与读写。
读流程:
client端创建一个代理对象与namenode进行rpc通信,拿到namenode对象后请求获取文件的元信息,namenode效验无误后将元信息返回,client获取到元信息之后根据元信息读取相应datanode的block块,将block合并成一个文件进行返回。
写流程:
client端创建一个代理对象与namenode进行rpc通信,拿到namenode对象后请求创建文件的元信息,namenode触发副本放置策略,返回元数据信息,client和datanode建立piepline连接,client将packet放入一个队列中,并向第一个datanode发送packet这一过程中上游节点同时发送下一个packet,当 block
传输完成,DN
们各自向 NN
汇报,同时 Client
继续传输下一个 block
所以,Client
的传输和 block
的汇报也是并行的
1.x
DataNode
;如果时集群外提交,则随机挑选一台磁盘不太满,CPU 不太忙的节点。可能产生的问题是前两个副本在同一机架当机架出现问题时会丢失两个副本
2.x
DataNode
;如果时集群外提交,则随机挑选一台磁盘不太满,CPU 不太忙的节点。了解详细可以访问我另一个博客:hdfs详细
https://blog.csdn.net/qq_43259670/article/details/105882983
1、执行查询:Hive接口,命令行或 web UI发送查询驱动程序
2、get Plan:驱动程序查询编译器
3、词法分析/语法分析
4、语义分析
5、逻辑计划产生
6、逻辑计划优化
7、物理计划生成
8、物理计划优化
9、物理计划执行
10、查询结果返回
提示:以上是hive的大致工作原理流程,一般面试问到这里就算比较深入了
创建表时:创建内部表时,会将数据移动到数据仓库指向的路径;若创建外部表,仅记录数据所在的路径, 不对数据的位置做任何改变。
删除表时:在删除表的时候,内部表的元数据和数据会被一起删除, 而外部表只删除元数据,不删除数据。这样外部表相对来说更加安全些,数据组织也更加灵活,方便共享源数据。
提示:内部表与外部表的区别一定要掌握,通常情况下我们都会使用外部表保证数据安全性,但是像中间表,结果表这种我们就会考虑使用内部表(管理表)
是指按照数据表的某列或某些列分为多个区,区从形式上可以理解为文件夹,比如我们要收集某个大型网站的日志数据,一个网站每天的日志数据存在同一张表上,由于每天会生成大量的日志,导致数据表的内容巨大,在查询时进行全表扫描耗费的资源非常多。
那其实这个情况下,我们可以按照日期对数据表进行分区,不同日期的数据存放在不同的分区,在查询时只要指定分区字段的值就可以直接从该分区查找
分桶是相对分区进行更细粒度的划分。
分桶将整个数据内容安装某列属性值得hash值进行区分,如要按照name属性分为3个桶,就是对name属性值的hash值对3取摸,按照取模结果对数据分桶。
如取模结果为0的数据记录存放到一个文件,取模为1的数据存放到一个文件,取模为2的数据存放到一个文件
总结:分区就是在hdfs上分目录(文件夹),分桶就是分文件。
方案一:上传数据后修复表
dfs -mkdir -p 分区目录
dfs -put 分区目录
msck repair table 表名
方案二:上传数据后添加分区
dfs -mkdir -p 分区目录
dfs -put 分区目录
alter table 表名 add partition();
提示:这里我们如果直接将新的分区文件上传到hdfs上,因为hive没有对应的元数据所以是无法查询到数据的,所以我们要进行表修复或者添加分区。
不可以,因为load数据的话hdfs下只会有一个文件无法完成分桶的效果,分桶和mapredue中的分区是一样的道理,所以我们要借助中间表导入数据。
order by会对所给的全部数据进行全局排序 ,不管来多少数据,都只启动一个reducer来处理 。
sort by是局部排序 ,sort by会根据数据量的大小启动一到多个reducer来干活,并且,它会在进入reduce之前为每个reducer都产生一个排序文件 。
distribute by 控制map结果的分发,它会将具有相同字段的map输出分发到一个reduce节点上做处理 。
cluster by 可以理解为一个特殊的distribute by和sort by的结合,当distribute by和sort by后面所跟的列名相同时,就等同于直接使用cluster by 跟上该列名。但是被cluster by指定的列最终的排序结果只能是降序,而且无法指定asc和desc。
提示:这个问题面试问的频率很高,大家一定要注意区分以下。
不可以。
原因:执行顺序!!!order by的执行顺序在select之后,所以需使用重新定义的列名进行排序。
提示:理解sql的执行顺序更加有利于大家写sql
(1)from
(2)join
(3)on
(4)where
(5)select
(6)group by
(7)having
(8)order by
(9)limit
数据倾斜就是数据的分布不平衡,某些地方特别多,某些地方又特别少,导致的在处理数据的时候,有些很快就处理完了,而有些又迟迟未能处理完,导致整体任务最终迟迟无法完成,这种现象就是数据倾斜。
针对mapreduce的过程来说就是,有多个reduce,其中有一个或者若干个reduce要处理的数据量特别大,而其他的reduce处理的数据量则比较小,那么这些数据量小的reduce很快就可以完成,而数据量大的则需要很多时间,导致整个任务一直在等它而迟迟无法完成。
跑mr任务时常见的reduce的进度总是卡在99%,这种现象很大可能就是数据倾斜造成的。
比如某些业务数据作为key的字段本就很集中,那么结果肯定会导致数据倾斜啊。
还有其他的一些原因,但是,根本原因还是key的分布不均匀,而其他的原因就是会造成key不均匀,进而导致数据倾斜的后果,所以说根本原因是key的分布不均匀。
既然有数据倾斜这种现象,就必须要有数据倾斜对应的处理方案啊。
简单地说数据倾斜这种现象导致的任务迟迟不能完成,耗费了太多时间,极大地影响了性能,所以我们数据倾斜的解决方案设计思路就是往如何提高性能,即如何缩短任务的处理时间这方面考虑的,而要提高性能,就要让key分布相对均衡,所以我们的终极目标就是考虑如何预处理数据才能够使得它的key分布均匀。
通过 yarn 监控平台的 task 信息查看到有个别的 task 执行时间过于缓慢,甚至还会挂掉。
1) 通常情况下,作业会通过input的目录产生一个或者多个map任务。
主要的决定因素有:input的文件总个数,input的文件大小,集群设置的文件块大小。
举例:
a) 假设input目录下有1个文件a,大小为780M,那么hadoop会将该文件a分隔成7个块(6个128m的块和1个12m的块),从而产生7个map数。
b) 假设input目录下有3个文件a,b,c大小分别为10m,20m,150m,那么hadoop会分隔成4个块(10m,20m,128m,22m),从而产生4个map数。即,如果文件大于块大小(128m),那么会拆分,如果小于块大小,则把该文件当成一个块。
2) 是不是map数越多越好?
答案是否定的。如果一个任务有很多小文件(远远小于块大小128m),则每个小文件也会被当做一个块,用一个map任务来完成,而一个map任务启动和初始化的时间远远大于逻辑处理的时间,就会造成很大的资源浪费。而且,同时可执行的map数是受限的。
3) 是不是保证每个map处理接近128m的文件块,就高枕无忧了?
答案也是不一定。比如有一个127m的文件,正常会用一个map去完成,但这个文件只有一个或者两个小字段,却有几千万的记录,如果map处理的逻辑比较复杂,用一个map任务去做,肯定也比较耗时。
针对上面的问题2和3,我们需要采取两种方式来解决:即减少map数和增加map数;
在map执行前合并小文件,减少map数:
CombineHiveInputFormat 具有对小文件进行合并的功能(系统默认的格式)
set mapred.max.split.size=112345600;
set mapred.min.split.size.per.node=112345600;
set mapred.min.split.size.per.rack=112345600;
set hive.input.format= org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
这个参数表示执行前进行小文件合并,前面三个参数确定合并文件块的大小,大于文件块大小128m的,按照128m来分隔,小于128m,大于100m的,按照100m来分隔,把那些小于100m的(包括小文件和分隔大文件剩下的),进行合并。
当input的文件都很大,任务逻辑复杂,map执行非常慢的时候,可以考虑增加Map数,来使得每个map处理的数据量减少,从而提高任务的执行效率。
增加map的方法为
mapreduce.input.fileinputformat.split.minsize=1 默认值为1
mapreduce.input.fileinputformat.split.maxsize=Long.MAXValue 默认值Long.MAXValue因此,默认情况下,切片大小=blocksize
maxsize(切片最大值): 参数如果调到比blocksize小,则会让切片变小,而且就等于配置的这个参数的值。
minsize(切片最小值): 参数调的比blockSize大,则可以让切片变得比blocksize还大。
--设置maxsize大小为10M,也就是说一个fileSplit的大小为10M
set mapreduce.input.fileinputformat.split.maxsize=10485760;
1、调整reduce个数方法一
1)每个Reduce处理的数据量默认是256MB
set hive.exec.reducers.bytes.per.reducer=256000000;
2)每个任务最大的reduce数,默认为1009
set hive.exec.reducers.max=1009;
3)计算reducer数的公式
N=min(参数2,总输入数据量/参数1)
2、调整reduce个数方法二
--设置每一个job中reduce个数
set mapreduce.job.reduces=3;
3、reduce个数并不是越多越好
过多的启动和初始化reduce也会消耗时间和资源;
同时过多的reduce会生成很多个文件,也有可能出现小文件问题
总的来说就是,数据倾斜的根源是key分布不均匀,所以应对方案要么是从源头解决(不让数据分区,直接在map端搞定),要么就是在分区时将这些集中却无效的key过滤(清洗)掉,或者是想办法将这些key打乱(给key加上标签),让它们进入到不同的reduce中。
order by
order by 会对输入做全局排序,因此只有一个reducer(多个reducer无法保证全局有序)
只有一个reducer,会导致当输入规模比较大时,需要较长的时间。
set hive.mapred.mode=nonstrict; (default value / 默认值)
set hive.mapred.mode=strict;
order by 和数据库中的order by功能一致按照某一项&几项排序输出。
与数据库中order by的区别在于hive.mapred.mode = strict模式下 必须指定limit否则执行会报错
原因:在order by状态下所有的数据都会到一台服务器进行reduce操作也就是只有一个reduce, 如果在数据量大的情况下会出现无果的情况,如果进行limit n,那只有n * map
number 条记录而已。只有一个reduce也可以出来里过来
sort by
sort by不是全局排序,其在数据进入reducer前完成排序
因此,如果用sort by进行排序,并且设置mapred.reduce.tasks>1,则sort by只保证每个reducer 的输出有序,不保证全局有序。
sort by 不受hive.mapred.mode是否为strict,nostrict的影响。
sort by的数据只能保证在同一个reduce中的数据可以按指定字段排序。
使用sort by你可以指定执行的reduce个数(set mapred.reduce.tasks=),对输出的 数据在执行归并排序,即可以得到全部结果。
注意:可以用limit子句大大减少数据量。使用limit n后,传输到reduce端(单机)的数据记录就 减少到n*(map个数)。否则由于数据过大可能出不了结果。
distribute by
按照指定的字段对数据进行划分到不同的输出reduce / 文件中。
insert overwrite local directory ‘/home/hadoop/out’ select * from test order by name
distribute by length(name);
此方法会根据name的长度划分到不同的reduce中,最终输出到不同的文件中。
length 是内建函数,也可以指定其他的函数或这使用自定义函数。
Cluster By
cluster by 除了具有 distribute by 的功能外还兼具 sort by 的功能。
但是排序只能是倒序排序,不能指定排序规则为asc 或者desc。
我们发现其实桶的概念就是MapReduce的分区的概念,两者完全相同。物理上每个桶就是目录里的一个文件,一个作业产生的桶(输出文件)数量和reduce任务个数相同。
而分区表的概念,则是新的概念。分区代表了数据的仓库,也就是文件夹目录。每个文件夹下面可以放不同的数据文件。通过文件夹可以查询里面存放的文件。但文件夹本身和数据的内容毫无关系。
桶则是按照数据内容的某个值进行分桶,把一个大文件散列称为一个个小文件。 这些小文件可以单独排序。如果另外一个表也按照同样的规则分成了一个个小文件。两个表join的时候,就不必要扫描整个表,只需要匹配相同分桶的数据即可。效率当然大大提升。
同样,对数据抽样的时候,也不需要扫描整个文件。只需要对每个分区按照相同规则抽取一部分数 据即可。
• 分区表
如果在建表时使用了 PARTITIONED BY,表即为分区表。分区表下的数据按分区键的值(或值的范围)放在HDFS下的不同目录中,可以有效减少查询时扫描的数据量,提升查询效率。
• 非分区表
非分区表即除分区表之外的表。
按表是否分桶分类
按表是否分桶可以将表分为两类:分桶表和非分桶表。
• 分桶表
如果在建表时使用了 CLUSTERED BY … INTO … BUCKETS,表即为分桶表。分桶表下的数据按
分桶键的哈希值放在HDFS下的不同目录中,可以有效减少查询时扫描的数据量,提升查询效率。
• 非分桶表
非分桶表即除分桶表之外的表
1.hive如果有过多的分区,由于底层是存储在HDFS上,HDFS上只用于存储大文件 而非小文件,因为过多的分区会增加namenode的负担。
2.hive会转化为mapreduce,mapreduce会转化为多个task。过多小文件的话,每个文件一个task,每个task一个JVM实例,JVM的开启与销毁会降低系统效率。
注意:合理的分区不应该有过多的分区和文件目录,并且每个目录下的文件应该足够大
<configuration>
<property>
<name>javax.jdo.option.ConnectionURLname>
<value>jdbc:mysql://bd01:3306/hive?createDatabaseIfNotExist=truevalue>
<description>JDBC connect string for a JDBC metastoredescription>
property>
<property>
<name>hive.execution.enginename>
<value>sparkvalue>
property>
<property>
<name>ngmr.partition.automergename>
<value>truevalue>
property>
<property>
<name>ngmr.partition.mergesize.mbname>
<value>3value>
property>
<property>
<name>hive.merge.sparkfilesname>
<value>truevalue>
property>
<property>
<name>hive.map.aggname>
<value>truevalue>
property>
<property>
<name>hive.vectorized.execution.enabledname>
<value>truevalue>
property>
<property>
<name>hive.cbo.enablename>
<value>truevalue>
property>
<property>
<name>hive.stats.fetch.column.statsname>
<value>truevalue>
property>
<property>
<name>hive.stats.fetch.partition.statsname>
<value>truevalue>
property>
<property>
<name>hive.compute.query.using.statsname>
<value>truevalue>
property>
<property>
<name>hive.exec.compress.intermediatename>
<value>truevalue>
property>
<property>
<name>hive.exec.compress.outputname>
<value>truevalue>
property>
<property>
<name>hive.fetch.task.conversionname>
<value>morevalue>
property>
<property>
<name>hive.groupby.skewindataname>
<value>truevalue>
property>
<property>
<name>hive.optimize.cpname>
<value>truevalue>
property>
<property>
<name>mapreduce.job.jvm.numtasksname>
<value>10value>
<description>How many tasks to run per jvm. If set to -1, there is no limit.description>
property>
<property>
<name>javax.jdo.option.ConnectionDriverNamename>
<value>com.mysql.jdbc.Drivervalue>
<description>Driver class name for a JDBC metastoredescription>
property>
<property>
<name>javax.jdo.option.ConnectionUserNamename>
<value>rootvalue>
<description>username to use against metastore databasedescription>
property>
<property>
<name>javax.jdo.option.ConnectionPasswordname>
<value>rootvalue>
<description>password to use against metastore databasedescription>
property>
configuration>
hive cli 中设置调优参数
// 合并 block 减少 task 数量
set ngmr.partition.automerge = true;
// jvm 重用
set mapreduce.job.jvm.numtasks=10;
// 表示将 n 个 block 安排给单个线程处理。
set ngmr.partition.mergesize.mb =3;
// 开启小文件合并
set hive.merge.sparkfiles = true;
// 开启小文件合并
set hive.map.agg = true;
// 使用向量化查询
set hive.vectorized.execution.enabled = true;
// cbo可以优化hive的每次查询
set hive.cbo.enable = true;
set hive.stats.fetch.column.stats = true;
set hive.stats.fetch.partition.stats = true;
set hive.compute.query.using.stats = true;
// 开启数据压缩
set hive.exec.compress.intermediate = true;
set hive.exec.compress.output = true;
// 有数据倾斜的时候进行负载均衡group by操作是否允许数据倾斜,默认是false,当设置为true时,执行计划会生成两个map/reduce作业,第一个MR中会将map的结果随机分布到reduce中,达到负载均衡的目的来解决数据倾斜,
set hive.groupby.skewindata = true;
// 列裁剪,默认开启true,在做查询时只读取用到的列,这个是个有用的优化;
set hive.optimize.cp = true;
压缩模式评价
常见压缩格式
压缩方式 | 压缩比 | 压缩速度 | 解压缩速度 | 是否可分割 |
---|---|---|---|---|
gzip | 13.4% | 21 MB/s | 118 MB/s | 否 |
bzip2 | 13.2% | 2.4MB/s | 9.5MB/s | 是 |
lzo | 20.5% | 135 MB/s | 410 MB/s | 是 |
snappy | 22.2% | 172 MB/s | 409 MB/s | 否 |
压缩格式 | 对应的编码/解码器 |
---|---|
DEFLATE | org.apache.hadoop.io.compress.DefaultCodec |
Gzip | org.apache.hadoop.io.compress.GzipCodec |
BZip2 | org.apache.hadoop.io.compress.BZip2Codec |
LZO | com.hadoop.compress.lzo.LzopCodec |
Snappy | org.apache.hadoop.io.compress.SnappyCodec |
压缩性能的比较
压缩算法 | 原始文件大小 | 压缩文件大小 | 压缩速度 | 解压速度 |
---|---|---|---|---|
gzip | 8.3GB | 1.8GB | 17.5MB/s | 58MB/s |
bzip2 | 8.3GB | 1.1GB | 2.4MB/s | 9.5MB/s |
LZO | 8.3GB | 2.9GB | 49.3MB/s | 74.6MB/s |
http://google.github.io/snappy/
On a single core of a Core i7 processor in 64-bit mode, Snappy compresses at about 250 MB/sec or more and decompresses at about 500 MB/sec or more.
要在Hadoop中启用压缩,可以配置如下参数(mapred-site.xml文件中):
参数 | 默认值 | 阶段 | 建议 |
---|---|---|---|
io.compression.codecs (在core-site.xml中配置) | org.apache.hadoop.io.compress.DefaultCodec, org.apache.hadoop.io.compress.GzipCodec, org.apache.hadoop.io.compress.BZip2Codec, org.apache.hadoop.io.compress.Lz4Codec | 输入压缩 | Hadoop使用文件扩展名判断是否支持某种编解码器 |
mapreduce.map.output.compress | false | mapper输出 | 这个参数设为true启用压缩 |
mapreduce.map.output.compress.codec | org.apache.hadoop.io.compress.DefaultCodec | mapper输出 | 使用LZO、LZ4或snappy编解码器在此阶段压缩数据 |
mapreduce.output.fileoutputformat.compress | false | reducer输出 | 这个参数设为true启用压缩 |
mapreduce.output.fileoutputformat.compress.codec | org.apache.hadoop.io.compress. DefaultCodec | reducer输出 | 使用标准工具或者编解码器,如gzip和bzip2 |
mapreduce.output.fileoutputformat.compress.type | RECORD |
Hive支持的存储数的格式主要有:TEXTFILE(行式存储) 、SEQUENCEFILE(行式存储)、ORC(列式存储)、PARQUET(列式存储)。
上图左边为逻辑表,右边第一个为行式存储,第二个为列式存储。
行存储的特点: 查询满足条件的一整行数据的时候,列存储则需要去每个聚集的字段找到对应的每个列的值,行存储只需要找到其中一个值,其余的值都在相邻地方,所以此时行存储查询的速度更快。select *
列存储的特点: 因为每个字段的数据聚集存储,在查询只需要少数几个字段的时候,能大大减少读取的数据量;每个字段的数据类型一定是相同的,列式存储可以针对性的设计更好的设计压缩算法。 select 某些字段效率更高
TEXTFILE和SEQUENCEFILE的存储格式都是基于行存储的;
ORC和PARQUET是基于列式存储的。
默认格式,数据不做压缩,磁盘开销大,数据解析开销大。可结合Gzip、Bzip2使用(系统自动检查,执行查询时自动解压),但使用这种方式,hive不会对数据进行切分,从而无法对数据进行并行操作。
Orc (Optimized Row Columnar)是hive 0.11版里引入的新的存储格式。
可以看到每个Orc文件由1个或多个stripe组成,每个stripe250MB大小,这个Stripe实际相当于RowGroup概念,不过大小由4MB->250MB,这样能提升顺序读的吞吐率。每个Stripe里有三部分组成,分别是Index Data,Row Data,Stripe Footer:
一个orc文件可以分为若干个Stripe
一个stripe可以分为三个部分
indexData:某些列的索引数据
rowData :真正的数据存储
StripFooter:stripe的元数据信息
1)Index Data:一个轻量级的index,默认是每隔1W行做一个索引。这里做的索引只是记录某行的各字段在Row Data中的offset。
2)Row Data:存的是具体的数据,先取部分行,然后对这些行按列进行存储。对每个列进行了编码,分成多个Stream来存储。
3)Stripe Footer:存的是各个stripe的元数据信息
每个文件有一个File Footer,这里面存的是每个Stripe的行数,每个Column的数据类型信息等;每个文件的尾部是一个PostScript,这里面记录了整个文件的压缩类型以及FileFooter的长度信息等。在读取文件时,会seek到文件尾部读PostScript,从里面解析到File Footer长度,再读FileFooter,从里面解析到各个Stripe信息,再读各个Stripe,即从后往前读。
Parquet是面向分析型业务的列式存储格式,由Twitter和Cloudera合作开发,2015年5月从Apache的孵化器里毕业成为Apache顶级项目。
Parquet文件是以二进制方式存储的,所以是不可以直接读取的,文件中包括该文件的数据和元数据,因此Parquet格式文件是自解析的。
通常情况下,在存储Parquet数据的时候会按照Block大小设置行组的大小,由于一般情况下每一个Mapper任务处理数据的最小单位是一个Block,这样可以把每一个行组由一个Mapper任务处理,增大任务执行并行度。Parquet文件的格式如下图所示。
上图展示了一个Parquet文件的内容,一个文件中可以存储多个行组,文件的首位都是该文件的Magic Code,用于校验它是否是一个Parquet文件,Footer length记录了文件元数据的大小,通过该值和文件长度可以计算出元数据的偏移量,文件的元数据中包括每一个行组的元数据信息和该文件存储数据的Schema信息。除了文件中每一个行组的元数据,每一页的开始都会存储该页的元数据,在Parquet中,有三种类型的页:数据页、字典页和索引页。数据页用于存储当前行组中该列的值,字典页存储该列值的编码字典,每一个列块中最多包含一个字典页,索引页用来存储当前行组下该列的索引,目前Parquet中还不支持索引页。
存储文件的查询速度测试:
1)TextFile
hive (default)> select count(*) from log_text;
_c0
100000
Time taken: 21.54 seconds, Fetched: 1 row(s)
2)ORC
hive (default)> select count(*) from log_orc;
_c0
100000
Time taken: 20.867 seconds, Fetched: 1 row(s)
3)Parquet
hive (default)> select count(*) from log_parquet;
_c0
100000
Time taken: 22.922 seconds, Fetched: 1 row(s)
存储文件的查询速度总结:
ORC > TextFile > Parquet
map,filter,flatmap,groupbykey,repartition
reduce,collect,count,take,saveAsTextFile
rdd即弹性分布式数据集,是spark中最基本的数据抽象,它代表一个不可变的、可分区的内部元素可并行计算 的集合。
使用makeRDD通过集合创建。由本地核数来决定分区数量
使用外部数据源创建如hdfs。由block的数量来决定的,通常默认为2个分区最低也是2个。
由另一个rdd得出的结果创建,即转换时创建。根据父rdd的 reduceTask数量
简单的说,有效分别各个算子之间的关系有利于生成dag图形,以及程序运行过程中产生的多次依赖变化的监察,窄依赖就是将依赖较为单一依赖视为一种方式,如果将宽窄依赖混为一谈,处理、区分、运行都会导致效率的分配不均。
即 stage 下的一个任务执行单元,一般来说,一个 rdd 有多少个 partition,就 会有多少个 task,因为每一个 task 只是处理一个partition 上的数据。
一个Job会被拆分为多组Task,每组任务被称为一个stage,每一次数据的shuffle都会产生一个stage。
每触发一次action操作就会生成一个job,
由于划分完 stage 之后,在同一个stage中只有窄依赖,没有宽依赖,可以实现流水线计算,stage中的每一个分区对应一个task,在同一个stage中就有很多可以并行运行的task。
主要通过cache,persist,checkpoint来实现RDD 的缓存持久化机制
conf.set("spark.serializer","org.apache.spark.serializer.KryoSerializer ")
提交任务的脚本:
spark-submit \
--master spark://node1:7077 \
--class com.kaikeba.WordCount \
--num-executors 3 \ 配置executor的数量
--driver-memory 1g \ 配置driver的内存(影响不大)
--executor-memory 1g \ 配置每一个executor的内存大小
--executor-cores 3 \ 配置每一个executor的cpu个数
/export/servers/wordcount.jar
设置task的数量
spark.defalut.parallelism
默认是没有值的,如果设置了值为10,它会在shuffle的过程才会起作用。
比如 val rdd2 = rdd1.reduceByKey(+)
此时rdd2的分区数就是10
可以通过在构建SparkConf对象的时候设置,例如:
new SparkConf().set("spark.defalut.parallelism","500")
给RDD重新设置partition的数量
使用rdd.repartition 来重新分区,该方法会生成一个新的rdd,使其分区数 变大。
此时由于一个partition对应一个task,那么对应的task个数越多,通过这 种方式也可以提高并行度。
通过设置参数sql.shuffle.partitions=500 默认为200;
可以适当增大,来提高并行度。 比如设置为 spark.sql.shuffle.partitions=500
持久化的双副本机制,持久化后的一个副本,因为机器宕机了,副本丢了,就还是得重新计算一次;
持久化的每个数据单元,存储一份副本,放在其他节点上面,从而进行容错;一个副本丢了,不用重新计算,还可以使用另外一份副本。这种方式,仅仅针对你的内存资源极度充足。
比如: StorageLevel.MEMORY_ONLY_2
比如一个任务需要50个executor,1000个task,共享数据为100M。
1.通过sparkContext的broadcast方法把数据转换成广播变量,类型为Broadcast,
val broadcastArray: Broadcast[Array[Int]] = sc.broadcast(Array(1,2,3,4,5,6))
sc.broadcast(Array(1,2,3,4,5,6))
2.然后executor上的BlockManager就可以拉取该广播变量的副本获取具体的数据。
获取广播变量中的值可以通过调用其value方法
val array: Array[Int] = broadcastArray.value
总结:
不使用广播变量的内存开销为100G,使用后的内存开销5G,这里就相差了20 倍左右的网络传输性能损耗和内存开销,使用广播变量后对于性能的提升和影响,还是很可观的。
广播变量的使用不一定会对性能产生决定性的作用。比如运行30分钟的spark作业,可能做了广播变量以后,速度快了2分钟,或者5分钟。但是一点一滴的调优,积少成多。最后还是会有效果的。
注意事项:
有效的避免shuffle可以减少网络间io和各个分区之间传输。
如何避免?
案例:
//错误的做法:
// 传统的join操作会导致shuffle操作。
// 因为两个RDD中,相同的key都需要通过网络拉取到一个节点上,由一个task进行join操作。
val rdd3 = rdd1.join(rdd2)
//正确的做法:
// Broadcast+map的join操作,不会导致shuffle操作。
// 使用Broadcast将一个数据量较小的RDD作为广播变量。
val rdd2Data = rdd2.collect()
val rdd2DataBroadcast = sc.broadcast(rdd2Data)
// 在rdd1.map算子中,可以从rdd2DataBroadcast中,获取rdd2的所有数据。
// 然后进行遍历,如果发现rdd2中某条数据的key与rdd1的当前数据的key是相同的,那么就判定可以进行join。
// 此时就可以根据自己需要的方式,将rdd1当前数据与rdd2中可以连接的数据,拼接在一起(String或Tuple)。
val rdd3 = rdd1.map(rdd2DataBroadcast...)
// 注意,以上操作,建议仅仅在rdd2的数据量比较少(比如几百M,或者一两G)的情况下使用。
// 因为每个Executor的内存中,都会驻留一份rdd2的全量数据。
如果因为业务需要,一定要使用shuffle操作,无法用map类的算子来替代,那么尽量使用可以map-side预聚合的算子。
所谓的map-side预聚合,说的是在每个节点本地对相同的key进行一次聚合操作,类似于MapReduce中的本地combiner。
map-side预聚合之后,每个节点本地就只会有一条相同的key,因为多条相同的key都被聚合起来了。其他节点在拉取所有节点上的相同key时,就会大大减少需 要拉取的数据数量,从而也就减少了磁盘IO以及网络传输开销。
通常来说,在可能的情况下,建议使用reduceByKey或者aggregateByKey算子来 替代掉groupByKey算子。因为reduceByKey和aggregateByKey算子都会使用用户 自定义的函数对每个节点本地的相同key进行预聚合。
而groupByKey算子是不会进行预聚合的,全量的数据会在集群的各个节点之间分 发和传输,性能相对来说比较差。
比如如下两幅图,就是典型的例子,分别基于reduceByKey和groupByKey进行单 词计数。其中第一张图是groupByKey的原理图,可以看到,没有进行任何本地聚 合时,所有数据都会在集群节点之间传输;第二张图是reduceByKey的原理图,可以看到,每个节点本地的相同key数据,都进行了预聚合,然后才传输到其他节点上进行全局聚合。
reduceByKey/aggregateByKey 可以进行预聚合操作,减少数据的传输量,提升性能
groupByKey 不会进行预聚合操作,进行数据的全量拉取,性能比较低
mapPartitions类的算子,一次函数调用会处理一个partition所有的数据,而不 是一次函数调用处理一条,性能相对来说会高一些。
但是有的时候,使用mapPartitions会出现OOM(内存溢出)的问题。因为单次函 数调用就要处理掉一个partition所有的数据,如果内存不够,垃圾回收时是无法 回收掉太多对象的,很可能出现OOM异常。所以使用这类操作时要慎重!
原理类似于“使用mapPartitions替代map”,也是一次函数调用处理一个partition的所有数据,而不是一次函数调用处理一条数据。在实践中发现,foreachPartitions类的算子,对性能的提升还是很有帮助的。比 如在foreach函数中,将RDD中所有数据写MySQL,那么如果是普通的foreach算子,就会一条数据一条数据地写,每次函数调用可能就会创建一个数据库连接,此时就势必会频繁地创建和销毁数据库连接,性能是非常低下;但是如果用foreachPartitions算子一次性处理一个partition的数据,那么对于每个partition,只要创建一个数据库连接即可,然后执行批量插入操作,此时性能是比较高的。实践中发现,对于1万条左右的数据量写MySQL,性能可以提升30%以上。
通常对一个RDD执行filter算子过滤掉RDD中较多数据后(比如30%以上的数据),建议使用coalesce算子,手动减少RDD的partition数量,将RDD中的数据压缩到更少的partition中去。
因为filter之后,RDD的每个partition中都会有很多数据被过滤掉,此时如果照常进行后续的计算,其实每个task处理的partition中的数据量并不是很多,有一点资源浪费,而且此时处理的task越多,可能速度反而越慢。
因此用coalesce减少partition数量,将RDD中的数据压缩到更少的partition 之后,只要使用更少的task即可处理完所有的partition。在某些场景下,对于 性能的提升会有一定的帮助。
repartitionAndSortWithinPartitions是Spark官网推荐的一个算子,官方建议,如果需
要在repartition重分区之后,还要进行排序,建议直接使用repartitionAndSortWithinPartitions算子。
因为该算子可以一边进行重分区的shuffle操作,一边进行排序。shuffle与sort两个操作同时进行,比先shuffle再sort来说,性能可能是要高的。
fastutil是扩展了Java标准集合框架(Map、List、Set;HashMap、ArrayList、 HashSet)的类库,提供了特殊类型的map、set、list和queue;
fastutil能够提供更小的内存占用,更快的存取速度;我们使用fastutil提供的集合类,来替代自己平时使用的JDK的原生的Map、List、Set.
Spark中应用fastutil的场景和使用
算子函数使用了外部变量
1.你可以使用Broadcast广播变量优化;
2.可以使用Kryo序列化类库,提升序列化性能和效率;
3.如果外部变量是某种比较大的集合,那么可以考虑使用fastutil改写外部 变量;
首先从源头上就减少内存的占用(fastutil),通过广播变量进一步减少内存占用,再通过Kryo序列化类库进一步减少内存占用。
算子函数里使用了比较大的集合Map/List
在你的算子函数里,也就是task要执行的计算逻辑里面,如果有逻辑中,出现,要创建比较大的Map、List等集合,
可能会占用较大的内存空间,而且可能涉及到消耗性能的遍历、存取等集合操作;那么此时,可以考虑将这些集合类型使用fastutil类库重写,使用了fastutil集合类以后,就可以在一定程度上,减少task创建出来的集合类型的内存占用。
避免executor内存频繁占满,频繁唤起GC,导致性能下降。
fastutil的使用
第一步:在pom.xml中引用fastutil的包
<dependency>
<groupId>fastutilgroupId>
<artifactId>fastutilartifactId>
<version>5.0.9version>
dependency>
第二步:平时使用List (Integer)的替换成IntList即可。
List的list对应的到fastutil就是IntList类型
使用说明:
基本都是类似于IntList的格式,前缀就是集合的元素类型;
特殊的就是Map,Int2IntMap,代表了key-value映射的元素类型。
reduce,collect,count,take,saveAsTextFile
核心概念是agent,里面包括source、chanel和sink三个组件。
source运行在日志收集节点进行日志采集,之后临时存储在chanel中,sink负责将chanel中的数据发送到目的地。
只有成功发送之后chanel中的数据才会被删除。
首先书写flume配置文件,定义agent、source、chanel和sink然后将其组装,执行flume-ng命令。
不会,Channel存储可以存储在File中,数据传输自身有事务。
也可以使用断点续传TailDir Source,防止传输时出现网络问题导致数据丢失。
FileChannel优化
通过配置dataDirs指向多个路径,每个路径对应不同的硬盘,增大Flume吞吐量。
checkpointDir 和 backupCheckpointDir 也尽量配置在不同硬盘对应的目录中,保证 checkpoint 坏掉后,可以快速使用 backupCheckpointDir 恢复数据
核心配置
当采集数据的节点发生故障将流入到另一个节点上保证数据不出现断传现象
# 定义了2个sink
a1.sinks = k1 k2
#set gruop
# 设置一个sink组,一个sink组下可以包含很多个sink
a1.sinkgroups = g1
#set sink group
# 指定g1这个sink组下有k1 k2 这2个sink
a1.sinkgroups.g1.sinks = k1 k2
配置多个 sinks让数据流入一个 sinkgroups中
核心配置
# set failover
# 指定sink组高可用的策略---failover故障转移
a1.sinkgroups.g1.processor.type = failover
# 指定k1这个sink的优先级 优先向k1传输数据优先级为10
a1.sinkgroups.g1.processor.priority.k1 = 10
# 指定k2这个sink的优先级
a1.sinkgroups.g1.processor.priority.k2 = 5
# 指定故障转移的最大时间,如果超时会出现异常 10000毫秒后没有响应就向k2传输数据
a1.sinkgroups.g1.processor.maxpenalty = 10000
说明:
这里首先要申明一个sinkgroups,然后再设置2个sink ,k1与k2,其中2个优先级是10和5。
而processor的maxpenalty被设置为10秒,默认是30秒.表示故障转移的最大时间
实现多个flume采集数据的时候避免单个flume的负载比较高,实现多个flume采集器负载均衡。
核心配置
#set load-balance
# 指定sink组高可用的策略---load_balance负载均衡
a1.sinkgroups.g1.processor.type =load_balance
# 默认是 round_robin轮询,还可以选择 random
a1.sinkgroups.g1.processor.selector = round_robin
# 如果backoff被开启,则sink processor会屏蔽故障的sink
a1.sinkgroups.g1.processor.backoff = true
调整hdfs.rollInterval(间隔)、hdfs.rollSize(大小)、hdfs.rollCount(数量)这三个参数的值。
开发中在flume-env.sh中设置JVM heap为4G或更高,部署在单独的服务器上(4核8线程16G内存)
-Xmx与-Xms最好设置一致,减少内存抖动带来的性能影响,如果设置不一致容易导致频繁fullgc。
Flume 的事务机制(类似数据库的事务机制):Flume 使用两个独立的事务分别负责从
Soucrce 到 Channel,以及从 Channel 到 Sink 的事件传递。
比如 spooling directory source 为 文件的每一行创建一个事件,一旦事务中所有的事件全部传递到 Channel 且提交成功,那么 Soucrce 就将该文件标记为完成。
同理,事务以类似的方式处理从 Channel 到 Sink 的传递过程,如果因为某种原因使得事件无法记录,那么事务将会回滚。且所有的事件都会保持到
Channel 中,等待重新传递。
1)Flume事务组成,Put事务,Take事务
Taildir Source:断点续传、多目录。Flume1.6以前需要自己自定义Source记录每次读取文件位置,实现断点续传。
File Channel:数据存储在磁盘,宕机数据可以保存。但是传输速率慢。适合对数据传输可靠性要 求高的场景,比如,金融数据。
Memory Channel:数据存储在内存中,宕机数据丢失。传输速率快。适合对数据传输可靠性要求 不高的场景,比如,普通的日志数据。
Kafka Channel:减少了Flume的Sink阶段,提高了传输效率。
Source到Channel是Put事务
Channel到Sink是Take事务
使用第三方框架 Ganglia 实时监控 Flume
1、作用
(1)Source 组件是专门用来收集数据的,可以处理各种类型、各种格式的日志数据,
包括 avro、thrift、exec、jms、spooling directory、netcat、sequence generator、syslog、http、
legacy
(2)Channel 采集的数据进行缓存,存入Memory 或 File 。
(3)Sink 组件是用于把数据发送到目的地的组件,包括 Hdfs、Logger、avro、
thrift、ipc、file、Hbase、solr、自定义。
2、我公司采用的 Source 类型为:
(1)监控后台日志:exec
(2)监控后台产生日志的端口:netcat
Source
增加 Source 个(使用 Tair Dir Source 时可增加 FileGroups 个数)可以增大 Source 的读
取数据的能力。例如:当某一个目录产生的文件过多时需要将这个文件目录拆分成多个文件
目录,同时配置好多个 Source 以保证 Source 有足够的能力获取到新产生的数据。
batchSize 参数决定 Source 一次批量运输到 Channel 的 event 条数,适当调大这个参数可
以提高 Source 搬运 Event 到 Channel 时的性能。
Channel
type 选择 memory 时 Channel 的性能最好,但是如果 Flume 进程意外挂掉可能会丢失数
据。
type 选择 file 时 Channel 的容错性更好,但是性能上会比 memory channel 差。
使用 file Channel 时 dataDirs 配置多个不同盘下的目录可以提高性能。
Capacity 参数决定 Channel 可容纳最大的 event 条数。
transactionCapacity 参数决定每次 Source 往 channel 里面写的最大 event 条数和每次 Sink 从 channel 里面读的最大 event 条数。
transactionCapacity 需要大于 Source 和 Sink 的 batchSize 参数。
Sink
增加 Sink 的个数可以增加 Sink 消费 event 的能力。Sink 也不是越多越好够用就行,过
多的 Sink 会占用系统资源,造成系统资源不必要的浪费。
1)可以考虑增加Topic的分区数,并且同时提升消费组的消费者数量,消费者数=分区数。(两者缺一不可)
2)如果是下游的数据处理不及时:提高每批次拉取的数量。批次拉取数据过少(拉取数据/处理时间<生产速度),使处理的数据小于生产的数据,也会造成数据积压。
消费者 (offset手动提交,业务逻辑成功处理后,提交offset)
保证不重复消费:落表(主键或者唯一索引的方式,避免重复数据)
业务逻辑处理(选择唯一主键存储到Redis或者mongdb中,先查询是否存在,若存在则不处理;若不存在,先插入Redis或Mongdb,再进行业务逻辑处理)
自主维护 offset 判断不存在才插入值
消费者消费后没有commit offset(程序崩溃/强行kill/消费耗时/自动提交偏移情况下unscrible)
先提交offset,后消费,有可能造成数据的重复
ISR:与leader保持同步的follower集合(保持所有同步的副本包括leader)
AR:分区的所有副本
LEO:每个副本的最后一条消息的offset
HW:一个分区中所有副本最小的offset
相同订单的数据,发送同一个分区中去
采用kafka分区策略
第一种分区策略:给定了分区号,直接将数据发送到指定的分区里面去
第二种分区策略:没有给定分区号,给定数据的key值,通过key取上hashCode进行分区
第三种分区策略:既没有给定分区号,也没有给定key值,直接轮循进行分区
第四种分区策略:自定义分区
producer.send(new ProducerRecord("test", Integer.toString(i), Integer.toString(i)));
//kafka的第一种分区方式,如果给定了分区号,那么就直接将数据发送到指定的分区号里面去
producer.send(new ProducerRecord("test",2,"helloworld",i+""));
//kafka的第二种分区策略,没有给定分区号,给定了数据的key,那么就通过key取hashcode,将数据均匀的发送到三台机器里面去
//注意如果实际工作当中,要通过key取上hashcode来进行分区,那么就一定要 保证key的变化,否则,数据就会全部去往一个分区里面
producer.send(new ProducerRecord("test",i+"",i+""));
//kafka的第三种分区策略,既没有给定分区号,也没有给定数据的key值,那么就会按照轮循的方式进行数的发送
producer.send(new ProducerRecord("test",i+""));
//kafka的第四种分区策略,自定义分区类,实现我们数据的分区
Flume 整合 Kafka 顺序性
kafka sink
defalutPartitonId
将数据发送到指定的分区中去,例如:将数据发送到 3 号分区,指定 3 就可以了
partitionIdHeader
自定义分区策略指定一个类继承 kafka.partitioner.class
详细配置:
http://flume.apache.org/releases/content/1.9.0/FlumeUserGuide.html
每个分区内,每条消息都有一个offset,故只能保证分区内有序。
如果为了保证topic整个有序,那么将partition调整为1.
拦截器 -> 序列化器 -> 分区器
1)会在zookeeper中的/brokers/topics节点下创建一个新的topic节点,如:/brokers/topics/first
2)触发Controller的监听程序
3)kafka Controller 负责topic的创建工作,并更新metadata cache
可以增加
bin/kafka-topics.sh --zookeeper localhost:2181/kafka --alter --topic topic-config --partitions 3
不可以减少,被删除的分区数据难以处理。
__consumer_offsets,保存消费者offset
一个topic多个分区,一个消费者组多个消费者,故需要将分区分配个消费者(roundrobin、range)
partition leader(ISR),controller(先到先得)
分区,顺序写磁盘,0-copy 零拷贝、利用操作系统页缓存、磁盘顺序写
kafka零拷贝原理
分区、分段、建立索引
生产者、消费者批处理
1.长度限制(64k)
2.散列性(rowkey 尽量打散,防止 regionserver 产生热点问题)
3.唯一性(字段唯一)
热点
热点发生在大量的client直接访问集群的一个或极少数个节点(访问可能是读,写或者其他操作)。大量访问会使热点region所在的单个机器超出自身承受能力,引起性能下降甚至region不可用,这也会影响同一个RegionServer上的其他region,由于主机无法服务其他region的请求。设计良好的数据访问模式以使集群被充分,均衡的利用。
使用数据库中(主键+时间戳).md5加密
主键+|
而|
是字典的最大值,而 Hbase rowkey是按照字典顺序进行排序
结果就是
““ 0000|
0000| 0001|
0001| 0002|
0002| 0003|
0003| 0004|
依次类推,就可以实现预分区防止 regionserver 产生热点问题
最后 rowkey 就为:
主键+|+加密后的时间戳
0001 | asdaswiwq
写方面 : 1. 批量写 2. 多线程并发写 3. BulkLoad 4. 预拆分Region
读方面 : 1. 多线程并发读 2.读多写少业务可以将BlockCache占比调大 3.设置Bloomfilter 4. 预拆分Region
Hbase主要包含HMaster/HRegionServer/Zookeeper
HRegionServer 负责实际数据的读写. 当访问数据时, 客户端直接与RegionServer通信.
HBase的表根据Row Key的区域分成多个Region, 一个Region包含这这个区域内所有数据. 而Region server负责管理多个Region, 负责在这个Region server上的所有region的读写操作.
HMaster 负责管理Region的位置, DDL(新增和删除表结构)
Zookeeper 负责维护和记录整个Hbase集群的状态
zookeeper探测和记录Hbase集群中服务器的状态信息.如果zookeeper发现服务器宕机,它会通知Hbase的master节点.
Hbase的优点及应用场景:
Hbase的缺点:
① Token 令牌生成,替代 Session,Session只存在于当前的 jvm 中,将 Token 存入 redis 里实现共享。
② 短信验证码 code。
③ 热点 key 预热,数据库第一次请求速度非常慢,热点数据(经常会被查询,但是不经常被修改或者删除的数据)放入redis。
④ 网页 PVUV 计数,由于 redis 是单线程线程安全可以保证计数的原子性。
⑤ 分布式锁 setnx、使用框架 resdison。
⑥ 订单的有效期 、使用 redis key 的有效期,key 失效监听,进行回调。
⑦ 实现注册中心、分布式配置中心
安全,依靠 IO 多路复用提高处理效率
redis 官方不支持 windows 版本,windows 不支持 epoll
Redis的底层采用Nio中的多路复用的机制,能够非常好的支持这样的并发,从而保证
线程安全问题;。
Redis单线程,也就是底层采用一个线程维护多个不同的客户端io操作。.
但是 NIO 在不同的操作系统上实现的方式有所不同,在我们windows操作系统使用select
实现轮训时间复杂度是为o(n),而且还存在空轮训的情况,效率非常低,其次是 默认对我
们轮训的数据有一定限制,所以支持上万的 tcp 连接是非常难。
所以在 linux 操作系统采用 epoll 实现事件驱动回调,不会存在空轮训的情况,只对活跃的
socket 连接实现主动回调这样在性能上有大大的提升,所以时间复杂度是为o(1)。
所以为什么nginx redis.都能够非常高支持高并发,最终都是linux中的 IO 多路复用机制
epoll.
服务器端只有一个线程去处理 n 多个请求,判断是否有数据,有数据进行流入 buffer 中写满后由才进行操作。
字节传输与 buffer 传输
字节流传输效率比较低,传输字节一个一个写入
buffer 传输效率比较高,写满后以一个块的形式把字节写入
redis 不存在事务回滚。
回滚的目的就是将mysql的行锁进行释放,而 redis 不存在行锁,但是 redis 有取消事务,使用discard 会放弃事务块的所有命令。
通过 redis 发布订阅,去订阅到数据库的 binlog 保持和mysql数据一致性。
采用增量 binlog 同步的方案
不会,因为有 RDB 和AOF
开启AOF 持久化最多可能丢失 1s 数据。
RDB 是以二进制文件,是在某个时间 点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。
优点:使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
缺点:RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候
这里说的这个执行数据写入到临时文件的时间点是可以通过配置来自己确定的,通过配置redis 在 n 秒内如果超过 m 个 key 被修改这执行一次 RDB 操作。这个操作就类似于在这个时间点来保存一次 Redis 的所有数据,一次快照数据。所有这个持久化方法也通常叫做 snapshots。
开启 RDB
# dbfilename:持久化数据存储在本地的文件
dbfilename dump.rdb
# dir:持久化数据存储在本地的路径,如果是在/redis/redis-3.0.6/src下启动的redis-cli,则数据会存储在当前src目录下
dir ./
## snapshot触发的时机,save
## 如下为900秒后,至少有一个变更操作,才会snapshot
## 对于此值的设置,需要谨慎,评估系统的变更操作密集程度
## 可以通过“save “””来关闭snapshot功能
# save时间,以下分别表示更改了1个key时间隔900s进行持久化存储;更改了10个key300s进行存储;更改10000个key60s进行存储。
save 900 1
save 300 10
save 60 10000
## 当snapshot时出现错误无法继续时,是否阻塞客户端“变更操作”,“错误”可能因为磁盘已满/磁盘故障/OS级别异常等
stop-writes-on-bgsave-error yes
## 是否启用rdb文件压缩,默认为“yes”,压缩往往意味着“额外的cpu消耗”,同时也意味这较小的文件尺寸以及较短的网络传输时间
rdbcompression yes
Append-only file,将“操作 + 数据”以格式化指令的方式追加到操作日志文件的尾部,在 append 操作返回后(已经写入到文件或者即将写入),才进行实际的数据变更,“日志文件”保存了历史所有的操作过程;当 server 需要数据恢复时,可以直接 replay 此日志文件,即可还原所有的操作过程。AOF 相对可靠,它和 mysql 中 bin.log、apache.log、zookeeper 中 txn-log 简直异曲同工。AOF 文件内容是字符串,非常容易阅读和解析。
优点:可以保持更高的数据完整性,如果设置追加 file 的时间是 1s,如果 redis 发生故障,最多会丢失 1s 的数据;且如果日志写入不完整支持 redis-check-aof 来进行日志修复;AOF 文件没被 rewrite 之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的 flushall)。
缺点:AOF 文件比 RDB 文件大,且恢复速度慢。
我们可以简单的认为 AOF 就是日志文件,此文件只会记录“变更操作”(例如:set/del 等),如果 server 中持续的大量变更操作,将会导致 AOF 文件非常的庞大,意味着 server 失效后,数据恢复的过程将会很长;事实上,一条数据经过多次变更,将会产生多条 AOF 记录,其实只要保存当前的状态,历史的操作记录是可以抛弃的;因为 AOF 持久化模式还伴生了“AOF rewrite”。
AOF 的特性决定了它相对比较安全,如果你期望数据更少的丢失,那么可以采用 AOF 模式。如果 AOF 文件正在被写入时突然 server 失效,有可能导致文件的最后一次记录是不完整,你可以通过手工或者程序的方式去检测并修正不完整的记录,以便通过 aof 文件恢复能够正常;同时需要提醒,如果你的 redis 持久化手段中有 aof,那么在 server 故障失效后再次启动前,需要检测 aof 文件的完整性。
AOF 默认关闭,开启方法,修改配置文件 reds.conf:appendonly yes
开启 AOF(Append-only file)
## 此选项为aof功能的开关,默认为“no”,可以通过“yes”来开启aof功能
## 只有在“yes”下,aof重写/文件同步等特性才会生效
appendonly yes
## 指定aof文件名称
appendfilename appendonly.aof
## 指定aof操作中文件同步策略,有三个合法值:always everysec no,默认为everysec
appendfsync everysec
## 在aof-rewrite期间,appendfsync是否暂缓文件同步,"no"表示“不暂缓”,“yes”表示“暂缓”,默认为“no”
no-appendfsync-on-rewrite no
## aof文件rewrite触发的最小文件尺寸(mb,gb),只有大于此aof文件大于此尺寸是才会触发rewrite,默认“64mb”,建议“512mb”
auto-aof-rewrite-min-size 64mb
## 相对于“上一次”rewrite,本次rewrite触发时aof文件应该增长的百分比。
## 每一次rewrite之后,redis都会记录下此时“新aof”文件的大小(例如A),那么当aof文件增长到A*(1 + p)之后
## 触发下一次rewrite,每一次aof记录的添加,都会检测当前aof文件的尺寸。
auto-aof-rewrite-percentage 100
redis 提供了 3 种AOF 记录同步选项:
直接修改 redis.conf 中 appendonly yes
建议最好还是使用 everysec 既能够保证数据的同步、效率也还可以。
RDB 属于全量同步,把所有数据生成二进制文件保存到磁盘中,属于定时性(规定时间的操作)
效率比较高 但是可能丢失数据
AOF 属于增量同步,把 set 操作记录到日志文件中,效率比较低,最多只会丢失 1s 数据,数据恢复较慢
存放 json 、存放二进制
json 阅读性比较强
二进制 阅读性比较差(不能跨语言)
使用淘汰策略,将一些不使用的 key 提前释放掉
voltile-lru 从已经设置过期时间的数据集中挑选最近最少使用的数据淘汰
voltile-ttl 从已经设置过期时间的数据库集当中挑选将要过期的数据
voltile-random 从已经设置过期时间的数据集任意选择淘汰数据
allkeys-lru 从数据集中挑选最近最少使用的数据淘汰
allkeys-random 从数据集中任意选择淘汰的数据
no-eviction 禁止驱逐数据
为什么要做高可用集群?
redis 可能出现单点故障,如果采用主从复制一旦宕机,需要手动的去选择一个主节点出来效率极低。
因此引用哨兵机制,让哨兵进程监控 master 状态,当它存在宕机会自动的选出一个从节点作为新的主节点。
哨兵机制,比较类似ZK的设计思路。
哨兵数量与Redis节点数量一致,单个哨兵监控主节点,发现主节点出现故障或者宕机会让另一个哨兵对主节点进行检查如果发现主节点失效,会选择新一个主节点。
1、哨兵集群中只要有一台宕了,整个集群的故障转移机制就失效了。
2、哨兵启动时候redis主库必需运行正常,否则故障转移机制也失效。
3、应用端需要先询问哨兵才能访问到redis主库,是否对效率也有明显的影响。
4、只能存在一个master节点不能存在多个,数据可能产生冗余,数据同步效率较低,浪费资源。
缓存穿透:指查询一个一定不存在的数据,如果从存储层查不到数据则不写入缓存,这将
导致这个不存在的数据每次请求都要到 DB 去查询,可能导致 DB 挂掉。
解决方案:1.查询返回的数据为空,仍把这个空结果进行缓存,但过期时间会比较短;2.布
隆过滤器:将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据
会被这个 bitmap 拦截掉,从而避免了对 DB 的查询。
缓存击穿:对于设置了过期时间的 key,缓存在某个时间点过期的时候,恰好这时间点对
这个 Key 有大量的并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并
回设到缓存,这个时候大并发的请求可能会瞬间把 DB 压垮。解决方案:1.使用互斥锁:当缓存失效时,不立即去 load db,先使用如 Redis 的 setnx 去设
置一个互斥锁,当操作成功返回时再进行 load db 的操作并回设缓存,否则重试 get 缓存的
方法。2.永远不过期:物理不过期,但逻辑过期(后台异步线程去刷新)。
缓存雪崩:设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部
转发到 DB,DB 瞬时压力过重雪崩。与缓存击穿的区别:雪崩是很多 key,击穿是某一个
key 缓存。
解决方案:将缓存失效时间分散开,比如可以在原有的失效时间基础上增加一个随机值,
比如 1-5 分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效
的事件。
先拿 setnx 来争抢锁,抢到之后,再用 expire 给锁加一个过期时间防止锁忘记了释放。
如果在 setnx 之后执行 expire 之前进程意外 crash 或者要重启维护了,那会怎么样?
set 指令有非常复杂的参数,这个应该是可以同时把 setnx 和 expire 合成一条指令来用的!
优点:
- 开发简单,对应用几乎透明
- 历史悠久,方案成熟
缺点:
- 代理影响性能
- lvs和twemproxy会有节点性能瓶颈
- redis扩容非常麻烦
- twitter内部已放弃使用该方案,新使用的架构未开源
优点:
- 开发简单,对应用几乎透明
- 性能比Twemproxy好
- 有图形化界面,扩容容易,运维方便
缺点:
- 代理依旧影响性能
- 组件过多,需要很多机器资源
- 修改了redis代码,导致和官方无法同步,新特性跟进缓慢
- 开发团队准备主推基于redis改造的reborndb
优点:
- 组件all-in-box,部署简单,节约机器资源
- 性能比proxy模式好
- 自动故障转移、Slot迁移中数据可用
- 官方原生集群方案,更新与支持有保障
缺点:
- 架构比较新,最佳实践较少
- 多键操作支持有限(驱动可以曲线救国)
- 为了性能提升,客户端需要缓存路由表信息
- 节点发现、reshard操作不够自动化
字符串 String、字典 Hash、列表 List、集合 Set、有序集合 SortedSet。如果是高级用户,那
么还会有,如果你是 Redis 中高级用户,还需要加上下面几种数据结构 HyperLogLog、
Geo、Pub/Sub。
使用 Token 当 Token 失效时会走客户端的回调方法,检测订单是否支付,没有支付直接提示订单超时。
不能解决属于强一致性问题,只能采用最终一致性方案,人工进行数据迁移,如果做了持久化将持久化文件传到从节点中。
1、强一致性:在任何时刻所有的用户或者进程查询到的都是最近一次成功更新的数据。强一致性是程度最高一致性要求,也是最难实现的。关系型数据库更新操作就是这个案例。
2、最终一致性:和强一致性相对,在某一时刻用户或者进程查询到的数据可能都不同,但是最终成功更新的数据都会被所有用户或者进程查询到。当前主流的nosql数据库都是采用这种一致性策略。
采用多主多从或者是树状形式实现同步。
对key进行分片存储在不同卡槽,分摊 key 存放
卡槽逻辑根据 crc16(meite)=50018%16384=9666
快速进行扩容和缩容,同步效率比较高
(1)集群监控,负责监控redis master 和slave进程是否正常工作。
(2)消息通知,如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
(3)故障转移,如果master node挂掉了,会自动转移到slave node上。
(4)配置中心,如果故障转移发生了,通知client客户端新的master地址。
(1)故障转移时,判断一个master node宕机了,需要大部分哨兵都同意才行,涉及到分布式选举问题。
(2)即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身就是单点,那么就不靠谱。
哨兵的核心知识
Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。
所以 redis 具有快速和数据持久化的特征,如果不将数据放在内存中,磁盘 I/O 速度为严重影响 redis 的性能。
在内存越来越便宜的今天,redis 将会越来越受欢迎, 如果设置了最大使用的内存,则数据已有记录数达
到内存限值后不能继续插入新值。
侵权请联系删除~~~~~
-------------------------------------------end----------------------------------------------------