用来度量时间,基于事件时间,数据的时间戳,用来衡量事件时间进展的标记。
具体实现上,水位线可以看作一条特殊的数据记录,是插入到数据流中的一个标记点,主要内容就是一个时间戳,用来指示当前的事件时间。
水位线也是数据流的一部分,随着数据一起流动,在不同任务之间传输。
有序流中的水位线:对于水位线周期性的生成,周期时间是指处理时间(系统时间),而不是事件时间。
乱序流中的水位线:乱序数据是因为分布式系统中,数据在节点间传输,会因为网络传输延迟的不确定性,导致顺序发生改变。乱序是指数据的先后顺序不一致,基于数据的产生时间而言的。
水位线的特性:水位线代表当前事件时间时钟,可以在数据的时间戳基础上加一些延迟数据来保证不丢数据。水位线用来表示当前事件时间的进展。水位线是基于数据的时间戳生成的,水位线的时间戳单调递增,确保任务的事件时间时钟一直向前推进。水位线通过设置延迟来保证正确处理乱序数据。
水位线生成策略:---flink dataStreamAPI有一个单独的方法:assignTimestampsAndWatermarks(),主要用来为流中的数据分配时间戳,需要传入一 个WatermarkStrategy作为参数。并生成水位线来指示事件时间。读取kafka时,可以从kafka数据中直接获取时间戳,不需要单独提取字段分配。
---有序流直接调用
---无序流,设置一个固定的延迟时间(Fixed Amount of Lateness)调用 WatermarkStrategy. forBoundedOutOfOrderness()方法可以实现,传入一个maxOutOfOrderness参数,表示“最大乱序程度”,表示数据流中乱序时间戳的最大差值。
===对于watermark的迟到的数据:第一种,直接将迟到的数据丢弃,第二种,将迟到的数据输出到单独的数据流中,使用sideOutputLateData(new OutputTag<>())实现侧输出,第三种,根据迟到的事件更新并发出结果。
可以基于时间驱动(例如:每30秒钟),也可以基于数据驱动(例如:每50个元素)。
四种类型 翻动窗口(Tumbling Window,没有重叠,也叫滚动窗口)翻滚窗有固定大小。 Keyby()之前的窗口函数:api都带有‘all’,所有数据进入到一个窗口,并行度为1, keyby()之后使用:KeyedStream.window(窗口类型.of(时间参数))
滑动窗口(Sliding Window,有重叠)一个元素可以对应多个窗口,SlidingProcessingTimeWindows.of(参数1,参数2),参数1是窗口时间长度,参数2是滑动步长
会话窗口(Session Window,没有重叠,活动间隙)没有固定的开始时间和结束时间,当会话窗口在一段时间内没有接收到元素时,会关闭会话窗口,后续元素将会分配给新的会话窗口。(基于会话驱动)
全局窗口(Global Window,全局窗口)需要指定Triger来触发计算
窗口聚合函数,增量和全量,增量性能比全量性能高,增量只维护一个中间结果状态,不需要缓存所有的窗口数据。增量:ReduceFunction,AggregateFunction和FoldFunction,全量:ProcessWindowFunction。
处理时间(Processing Time):当前机器处理该事件的时间(进入某个算子时的系统时间),有最好的性能和最低的延迟。
摄入时间(Lngestion Time):数据进入Flink框架的时间,在Source Operator中设置,每个事件拿到当前时间作为时间戳,后续的时间窗口基于该时间;比处理时间提供更可预测的结果。
事件时间(Event Time):每条事件产生时候被记录的时间。跟物理时钟没有关系,存在一定的延时,要和处理时间相结合。
===作业管理器(JobManager):应用执行的主程序,先收到要执行的应用程序,作业管理器会把JobGraph转换成一个物理层面的数据流图,包含所有可以并发的任务。作业管理器会向资源管理器(rm)请求执行任务必要的资源,也就是任务管理器(tm)上面的插槽(slot)。一旦它获取到足够的资源,就会将执行图分发到真正运行它们的TaskManager上。
运行过程中,会负责所有需要中央协调的操作,比如检查点的协调。
===资源管理器(ResourceManager)(一般是yarn):负责管理任务管理器(tm)的插槽(slot),当作业管理器申请插槽资源时,rm会将有空闲的tm分配给作业管理器(jm),如果rm没有足够的插槽来满足 作业管理器(jm)的请求,可以向资源提供平台发起会话,提供启动tm进程的容器,rm还负责终止空闲的tm,释放计算资源。
===任务管理器(TaskManager):通常在flink中会有多个tm运行,每一个tm都包含了一定数量的插槽,插槽的数量限制了tm能够执行的任务数量,启动之后,tm会向资源管理器(rm)注册他的插槽,收到资源管理器(rm)的指令后,tm就会将一个或者多个插槽提供给作业管理器调用,作业管理器(jm)就可以向插槽分配任务。执行过程中,一个tm可以跟其他运行同一应用程序的tm交换数据。
===分发器(Dispatcher):可以跨作业运行,为应用提交提供了REST接口,来接收client的application提交,负责启动jm和提交application,同时运行Web UI。
Flink支持at least once(至少一次)和exactly once(精准一次)
默认情况禁用checkpoint,定期做checkpoint来实现容错和恢复,容错机制不断生成数据流的快照。自动执行的快照,为了通过checkpoint机制,可以对作业的状态和计算位置进行恢复。Checkpoint可以保证exactly once,并且不牺牲性能。
===barriers(栅栏)机制,会注入到数据流中,不会乱序到达,当某个source算子收到barrier时,就会暂停数据处理过程,将自己的状态保存快照,保存到持久化存储中,最后向checkpointcoordinator报告快照制作情况,并恢复数据处理。不断向下游广播barrier,直到传到sink算子,快照制作完成,当checkpointcoordinator收到所有算子发送的报告,说明该周期的快照制作成功,如果没有按照时间内收到所有算子到的报告,说明失败。
===1.5版本之前:通过TCP的反压机制来控制,flink在prodece产生数据后,经过netty使用socket传输,使用TCP协议,TCP自带反压机制,这个是通过callback实现的,当socket发送数据去receive buffer后,receive会反馈给send端,目前receive端的buffer还有多少剩余空间,然后send会根据剩余空间,控制发送速率 。
====跨task反压和task内反压。1.5版本之前的缺点:反压传播路径太长,生效的延迟比较大。
===1.5版本之后:在flink层面实现反压机制,每一次从上游向下游发送消息的时候会发送一个backlog size,告诉下游准备发送多少消息 ,下游就会计算有多少buffer去接收消息,如果有充足的buffer,就会返回上游一个credit告诉他可以发送消息。过一段时间由于上游的发送速率要大于下游的接收速率,下游的taskmanager的buffer已经达到申请上限,这时候就会向上游返回credit=0,resultSubPartition接收到之后就不会向netty去传输数据,上游的taskmanager的buffer也很快耗尽,达到反压效果。
优点:不用通过socket和netty一层层向上反馈,降低了反压生效的延迟,同时也解决了由于一个task反压导致taskmanager和taskmanager之间的socket阻塞的问题。
主要分为两大类:一类是基于原生State的connect算子操作,另一类是基于窗口的join操作,其中基于窗口的join可分为window join和interval join两种。底层原理是依赖state状态存储,通过将数据存储到state中进行关联join,最终输出结果。
===基于window join:利用flink的窗口机制实现双流join,将两条实时流中元素分配到同一个时间窗口中完成join。底层原理:两条实时流数据缓存在window state中,当窗口触发计算时,执行join操作。代码逻辑:定义两条输入实时流A、B,A流调用join(b流)算子,关联关系定义:where为A流关联键,equalTo为B流关联键,都是订单id,定义window窗口,apply方法定义逻辑输出。
Cogroup算子:基于window窗口机制,比join算子更加灵活,可以按照用户指定的逻辑匹配左流或右流数据并输出。两条流依然需要在一个window中定义且定义好关联条件,apply方法中自定义判断。
===基于Interval join:根据右流相对左流偏移的时间区间(interval)作为关联窗口,在偏移区间窗口中完成join操作。满足数据流stream2在数据流stream1的interval(low,higt)偏移区间内关联join,interval越大,关联上的数据就越多,超出interval的数据不再ttl关联。
实现原理:interval也是利用flink的state存储数据,不过此时存在state失效机制,触发数据清理操作。代码逻辑:订单流进入程序后,等候(low,high)时间间隔内订单明细数据流数据进行join,否则继续处理下一个流,Interval join需要在两个keyedStream之上操作,即keyby(),并在between()方法中指定偏移区间的上下界。Interval join实现的是inner join,只支持事件时间
===基于connect:对两个DataStream执行connect操作,将其转化为ConnectedStraems,生成的Streams可以调用不同方法在两个实时流上执行,且双流之间可以共享状态。两个数据流被connect之后,只是被放在同一个流中,内部依然保持各自的数据和形式,两个流相互独立。代码逻辑实现:调用connect算子,根据orderid进行分组,并使用process算子分别对两条流进行处理,process方法内部进行状态编程,初始化订单、订单明细和定时器的ValueState状态,为每个进入的数据流保存state状态并创建定时器,在时间窗口内另一个流到达时进行join并输出,完成后删除定时器,未及时到达的数据触发定时器输出到侧输出流,左流先到而右流未到,则输出左流,反之输出右连流。
===双流join优化:
为什么双流join时间到了却不触发,一直没有输出(检查watermark的设置是否合理,数据时间是否远远大于watermark和窗口时间,导致窗口数据经常为空)
State数据保存多久,会内存爆炸吗(state自带ttl机制,可以设置ttl过期策略,触发flink清理过期state数据,建议程序中的state数据结构用完后手动clear掉)
双流join倾斜了怎么办(过滤异常key、拆分表减少数据、打散key分布,加内存)
想实现多流join怎么办(先union然后再二次处理,或者先进行connnect操作再进行join操作)
Join过程延迟、没关联上的数据会丢失嘛(一般不会,join过程可以使用侧输出流存储延迟流;如果出现节点网络等异常,flink checkpoint也可以保证数据不丢失)
一个或多个由简单事件构成的事件流通过一定的规则匹配,然后输出用户想得到的数据,满足规则的复杂事件。分为单个模式,组合模式,循环模式,匹配后跳过策略,检测模式。
原因:直观表现是任务节点频繁出现反压,但是增加并行度后不能解决问题,部分节点出现OOM异常,是因为大量的数据集中在某个节点上,导致该节点内存被爆,任务失败重启。原因1:业务上有严重的数据热点,原因2:技术上大量使用了keyby,groupby等操作,错误的使用了分组key,认为产生数据热点。
解决方案:
两阶段聚合解决keyby热点:首先把分组的key打散,比如加随机后缀,对打散后的数据进行聚合,把打散的key还原为真正的key,二次keyby进行结果统计,然后输出。
3.Flink消费kafka上下游并行度不一致导致的数据倾斜:有可能会为了加快数据的处理速度,来设置flink消费者的并行度大于kafka的分区数,所以需要设置flink的Redistributing,也就是数据重分配。可以通过自定义数据分区策略来实现数据的负载均衡。
===内存优化:内存管理 :flink自身实现内存管理。内存配置:flinkJVM进程的进程总内存包含了由flink应用使用的总内存以及由运行flink的JVM使用的内存。Flink总内存包括JVM堆内存和堆外内存,其中堆外内存包括直接内存和本地内存。
===配置进程参数:flink on YARN 模式下,有jobmanager和taskmanager两种进程,这两个的参数配置对flink应用的执行有很大影响。
操作步骤:任务数变多,任务平行度增大时,jobmanager内存都需要相应增大。在资源充足的情况下,可以相应增加taskmanager的个数,以提高运行效率。配置taskmanager Slot槽数,每个taskmanager多个核同时能跑多个task,相当于增大了任务的并发度。Taskmanager的内存可以随着任务增大,资源更多的情况相应增加。
===Checkpoint优化:上游算子可能是多个数据源,对应多个barrier需要全部到齐才一次性触发checkpoint,所以在遇到checkpoint时间较长的情况时,有可能是因为数据对齐需要耗费的时间比较长造成的。可以设置最小时间间隔和数据压缩状态可以延时启动。
===Flink作业问题定位:一压二查三指标,延迟吞吐是核心,时刻关注资源量,排查首先看GC。看反压:通常最后一个被压高的subTask的下游就是job的瓶颈之一,看checkpoint时长:一定程度影响job的整体吞吐,看核心指标:延迟指标和吞吐是最关键的指标,资源的使用率:提高资源的利用率是最终的目的。===Flink代码调优:方案一:通过一些数据结构,比如set或者map来结合flink state进行去重,但是会随着数据量不断增大,导致性能急剧下降。方案二:使用布隆过滤器,采用一种hash法进行
去重的工具,缺点是返回结果是概率性的,可以选择好的hash算法和扩大bitmap的存储空间提高准确性。
===平台初始搭建,在项目开发过程中遇到的问题:hdfs写权限问题导致生产环境flink on yarn提交任务报错,报关于yarn的错误。 解决:在flink启动脚本“/data/app/flink/bin/flink”中添加配置解决该问题。#操作hdfs的用户,export HADOOP_USER_NAME=hdfs
===Flink作业频繁重启:作业频繁重启又自行恢复,无尽循环,无法正常处理数据。原因很多,程序代码问题。内存问题。网络问题。外部系统问题。集群问题。
例如异常数据造成的作业崩溃,可以在taskmanager的日志中找到报错,数据源或者数据目的等上下游系统超时也会造成作业无法启动而一直在重启。此外taskmanager full GC太久造成心跳包超时而被jobmanager踢掉也是常见的作业重启原因。如果系统内存严重匮乏,那么linux自带的OOM Killer也可能把taskmanager所在的JVM进程kill了。
第一种:At least once+去重:每个算子维护一个事务日志,跟踪已处理的事件,重放失败事件,在事件进入下一个算子之前,移除重复事件,
第二种:At least once+幂等:依赖sink端存储的去重性和数据特征,如输出到数据库,但是输出到数据库的时候是批量的,如果网络传输出现问题,还要再进行检查点恢复。
第三种:两阶段提交:一旦flink开始做checkpoint操作,那么就会进入pre-commit“预提交”阶段,同时Jobmanager的Coordinator会将Barrier注入数据流中;当所有的barrier在算子中成功进行一遍传递(就是checkpoint完成),并完成快照后,则“预提交”阶段完成;等所有的算子完成“预提交”,就会发起一个commit“提交”动作,但是任何一个“预提交”失败都会导致flink回滚到最近的checkpoint。
Broker:kafka集群包含一个或多个服务器,服务器节点称为broker
Topic:不同的topic的消息分开存储,逻辑上一个topic的消息虽然保存于一个或者多个broker上,但是用户只需要指定消息的topic即可生产或消费数据而不必关心数据存于何处。(需要创建)
Partition:topic中的数据分割为一个或多个partition,每个topic至少有一个partition,当生产者产生数据的时候,根据分配策略,选择分区,然后将消息追加到指定的分区末尾(队列)partition内部有序,不同partition间的数据无序。 每个partition中的数据使用多个segment文件存储。 如果topic有多个partition,消费数据时就不能保证数据的顺序,严格保证消息的消费顺序的场景下,需要将partition数目设置为1。
Leader:每个partition有多个副本,其中有且仅有一个作为Leader,leader是当前负责数据读写的partition。
Follower:跟随leader,与leader保持数据同步,如果leader失效,会从follower中选举一个新的leader,当follower挂掉卡住或者同步太慢,leader会把这个follower从ISR列表中删除重新创建一个follower。
Replication:数据会存放到topic的partition中,但是分区有可能损坏,所以需要对分区进行备份。Leader负责写入和读取数据,follower只负责备份,保证数据的一致性
Producer:生产者,将消息发布到kafka的topic中,broker接收到生产者发送的消息后,broker将该消息追加到当前用于追加数据的segment文件中,生产者发送的消息,存储到一个partition中,生产者也可以指定数据存储的partiiton。
Consumer:消费者,从broker中读取数据,可以消费多个topic中的数据。
Consumer Group:每个Consumer属于一个特定的Consumer Group,将多个消费者集中到一起去处理某个topic的数据,可以更快提高数据的消费能力,整个消费者组共享一组偏移量(防止数据被重复读取),因为一个topic有多个分区
Offset:偏移量,可以唯一标识一条消息,偏移量决定读取数据的位置,不会有线程安全 的问题,消费者通过偏移量来决定下次读取的消息。 消息被消费之后,并不被马上删除,这样多个业务就可以重复使用kafka的消息,偏移量由用户控制,可以修改,消息最终被删除,默认生命周期1周。
Zookeeper:来存储集群的元数据(meta)信息,参与节点选举
Ack=0,意味着producer无需等待来自leader的确认而继续发送下一批消息,(当broker故障时可能丢失数据)
Ack=1,producer在ISR中的leader成功收到数据并得到确认后发送下一条数据(如果在follower同步成功之前leader故障,那么将会丢失数据)
Ack=-1,producer需要等待ISR中所有的follower都确认接收到数据后才算一次发送完成,可靠性最高。(在broker发送ack时,leader发生故障,则会造成数据重复)
===如果follower长时间未向leader同步数据,则该follower将会被踢出ISR集合,判断标准:超过10秒没有同步数据;主副节点差4000条数据
Kafka幂等性机制保证单个分区不会重复写入数据,而实现幂等性的核心就是引入了producer id和sequence number(序列号)两个概念。Producer不论向server发送多少次重复数据,server端都只会持久化一条。
At Least Once+幂等性=Exactly Once
启用幂等性,要将producer的参数中enable.idempotence设置为true即可(此时ack=-1)。Kafka的幂等性实现其实就是将原来下游需要做的去重放在了数据上游。
原理:开启幂等性的prodecer在初始化的时候会被分配一个pid,发往同一partition的消息会附带Sequence Number(序列号)。而Broker端会对
Leader维护了一个动态的in-sync replica set(ISR:同步副本),意为和leader保持同步的follower集合。当ISR中的follower完成数据的同步之后,leader就会给producer发送ack。如果follower长时间未向leader同步数据, 则该follower将被踢出ISR,该时间阀值由replica.lag.time.max.ms参数设定。而如果Leader发生故障,就会从ISR中选举出新的leader。
Kafka事务性主要是为了解决幂等性无法跨partition运作的问题,事务性提供了多个partition写入的原子性。就是写入多个partition要么全部成功,要么全部失败,不会出现部分成功部分失败这种情况。Flink正是基于kafka的事务性,实现了端到端到的Exactly Once语义。
原理:提供了一个唯一id,即使故障恢复后也不会改变,这个id可以跟内部的pid1:1分配,这个id是用户提供的,而pid是procuder自己内部自动生成的(并且故障之后pid会变化),有了这个id就可以实现多分区,跨会话的EOS语义。
===顺序读写:kafka消息是不断追加到文件中的,充分利用磁盘的顺序读写性能,不需要硬盘磁头的寻道时间,只需要很少的扇形旋转时间,比随机读写快很多。
===零拷贝:Zero-Copy系统调用机制。直接请求系统内核将数据直接从磁盘拷贝到socket(套接字缓冲区),而不经过用户应用程序。
===页缓存(pagecache):代替JVM进程来保存内存数据,IO调度器会将连续的小的写操作处理为大的物理写操作,IO调度器将尝试重新排列写操作,以最大限度地减少磁盘头的移动,pagecache会自动使用所有可用的空闲内存。
===指明partition的情况下,直接将指明的值作为partition值
===没有指明partition值但是有key的情况下,将key的hash值与topic的partition数进行取余得到partition值
===既没有partition值又没有key值的情况下,kafka采用Sticky Partition(黏性分区器),会随机选择一个分区,并尽可能一直使用该分区,待该分区的batch已满或者已完成,kafka再随机一个分区进行使用(以前是一条条的轮询,现在是一批次的轮询)
===RangeAssignor(默认)范围分区:按照消费者总数和分区总数进行整除运算来获得一个跨度,然后将分区按照跨度进行平均分配,以保证分区尽可能均匀的分配给所有的消费者。对于每一个topic,RangeAssignor策略会将消费者组内所有订阅这个topic的消费者按照名称的字典序排序,然后为每个消费者划分固定的分区范围,如果不够平均分配,那么字典序靠前的消费者会被多分配一个分区。(分配不均匀,有可能出现部分消费者过载的情况)
===RoundRobinAssignor(轮询分配):将消费者组内所有消费者以及消费者所订阅的所有topic的partition按照字典序排序,然后通过轮询消费者方式逐个将分区分配给每个消费者。
===StickyAssignor(粘性分配):分区的分配尽可能均匀,分区的分配尽可能与上次分配的保持相同,当两者发生冲突时,第一个目标优先于第二个目标。
===Driver:解析器:将sql字符串转换成抽象语法树AST,一般使用第三方工具库完成
编译器:将AST编译生成逻辑执行计划
优化器:对逻辑执行计划进行优化
执行器:把逻辑执行计划转换成可以运行的物理计划
===Client:实现对hive访问的用户接口:一般是JDBC/ODBC:使用java的方式访问hive
===Metastore:元数据,通常使用mysql数据库存储。
===运行机制:客户端提交HQL程序发送到Driver(任何数据库驱动程序,如JDBC,ODBC)中执行,Driver根据HQL解析Query语句,验证语法,编译器将获取元数据的请求发送给Metastore,Metastore将所需的元数据作为响应发送给编译器,编译器重新检查要求,并重新发送计划给Driver,Driver将执行计划发送到执行引擎,执行引擎将作业发送到JobTracker,NameNode将作业分配到TaskTracker,DataNode执行MapReduce操作,执行的同时,执行引擎通过Matastore执行元数据操作,执行引擎接收DataNode的结果,执行引擎将结果发送到Driver,Driver将结果发送到Hive端口。
===内部表:hive管理的表,创建内部表时,数据真实存在于表的目录内,删除内部表时,物理数据和文件也一并删除。包括元数据和存储数据
===外部表:由HDFS管理,管理只是在逻辑和语法意义上,新建表仅仅指向一个外部目录,删除时不物理删除外部目录,仅仅是将引用和定义删除。HDFS上的文件不会被删除
企业内部都是使用外部表,因为会有多人操作数据仓库,可能会产生数据表误删操作,为了数据安全性,通常使用外部表。
===分区表:就是对应一个HDSF文件系统上的独立的文件夹,该文件夹是该分区所有的数据文件。Hive中的分区就是目录,把一个大的数据集根据业务需要分割成小的数据集,在查询是通过where子句中的表达式选择查询需要的指定的分区,这样查询效率会提高很多。
静态分区手动指定,动态分区通过数据来进行判断,静态分区是在编译时期,通过用户传递决定,动态分区只有在sql执行时才能决定。(是hive查询通常使用分区的列作为查询条件)
===分桶表:分桶针对的是数据文件,分区针对的是数据的存储路径,分桶表就是将表中的数据分文件管理,某个字段的hashcoin的时候相同规则的桶数据进行关联,查询数据的时候可以先确定数据的桶,加载数据;ode%桶数clustered by(name):j在实际使用时,可以分区和分桶同时使用。
===全局排序(order by):只有一个reducer,导致当输入规模较大时,需要较长的时间计算,放在select语句的结尾
===局部排序(sort by):多个reduce,数据进入reducer前完成排序。只保证每个reducer的输出有序,不保证全局有序。
===分区排序(distribute by):根据指定的字段将数据分到不同的reducer,分发算法是hash散列。类似与MR中partition进行分区,结合sortby使用(distribute by要在sort by之前),多分配reduce处理。
===分区并排序(cluster by):除了具有distribute by的功能外,还会对该字段进行排序,只能升序,不能倒序。
===聚合开窗函数:sum(求和)min(最小)max(最大)avg(平均值)count(计数)
Over():指定分析函数工作的数据窗口大小,这个数据窗口大小可能会随着行的变而变化。
Current row:当前行,n preceding:往前n行数据,n following:往后n行数据,unbounded:起点,unbounded preceding表示从前面的起点,unbounded following表示到后面的终点
Lag(col,n,default_val):往前第n行数据,col是列名,n是往上的行数,当第n行为null的时候取default_val
Lead(col,n,default_val):往后第n行数据,col是列名,n是往下的行数,当第n行为null的时候取default_val
Ntile(n):把有序分区的行分发到指定数据的组中,各个组有编号,编号从1开始,对于每一行,ntile返回此行所属的组的编号
Cume_dist(),计算某个窗口或分区中某个值的累积分布。
===排序开窗函数:rank()排序相同时,中间会出现空缺,分组内会出现同一个排名,排名次序不连续
Dense_rank()相同排序时,中间不会空缺,分组内可能会出现同样的次序,排序名次也是连续的
Row_number()排名不一致,只会累加,不会出现同一排名
Percent_rank()计算给定行的百分比排名,可以计算超过了百分之多少的人
===自定义UDF:单行函数,一进一出,自定义类继承UDF类,然后重写evaluate方法,其中evaluate可以被多次重载来实现不同的功能,将写的类打成jar包上传到hdfs上的指定jar包目录
===自定义UDAF:聚集函数,多进一出,让类继承UDAF类,重新实现静态内部类UDAFEvaluator
===自定义UDTF:一进多出,用于处理单行数据,并生成多个数据行,差别在于需要继承的是GeneriUDTF,然后需要覆盖重写父类的三个抽象方法,输出后有几列,在initialize中定义,主要处理逻辑在process中实现
===表现:执行任务时候,任务进度长时间维持在99%左右,查看stage的执行情况时,卡在最后一个task长时间不动,查看task监控页面,发现某个或者某两三个task运行的时间远远大于其他task的运行时间,这些task处理的数据量也远远大于其他task,一个spark任务运行时间是由最后一个执行成功的task决定的,如果某个task发生了数据倾斜,会拖慢整个spark任务执行效率,即便其他没有倾斜的task已经执行完毕,甚至会导致OOM。
===查看数据倾斜方法:在yarn界面查看是否产生数据倾斜,举例:大部分task的执行时间是25秒,处理记录数为几万到几十万不等(几兆到几十兆),但是有一个task处理时间为1小时处理量高达72G还没执行完,可以通过group by或者抽样找出倾斜key。
===数据倾斜条件:数据计算时发生了shuffle,就是对数据进行了重新分区,如join,某个或者某几个key比较集中,或者存在大量null key,分发到某一个或者某几个reduce上的数据远高于平均值;如group by,维度过小,某值的数量过多,某个维度或者某几个维度且维度的数据量特别大,集中在reduce
===数据倾斜解决方案:
特殊情形处理://同数据类型关联产生数据倾斜,把数字类型转换成字符串类型
//Null key不参与关联
//数据加盐:赋予null值随机值,把倾斜的数据分到不同的reduce上
//提高reduce并行度,set mapred.reduce.tasks=15,修改参数
Group by导致://开启负载均衡
//Group by双重聚合,当使用group by进行聚合统计时,如果存在某个或者某几个key发生了倾斜,会导致某个倾斜key shuffle到一个reduce。先将倾斜key打上随机数,让倾斜key分散到不同的reduce进行聚合,然后去掉随机数,对倾斜key再聚合,此时虽然倾斜的key仍然会shuffle到一个reduce,但是此时key是第一步聚合之后的。
Join导致的:reduce join转换成map join(此方案适合小表join大表的时候)
过滤倾斜join单独进行join,所以如果把倾斜key过滤出来单独去join,这个倾斜key就会分散到多个task去进行join操作,最后union all。
===建表设计层面://使用分区表优化:分区表是在某一个或者几个维度上对数据进行分类存储,一个分区对应一个目录。如果筛选条件里有分区字段,那么hive只需要遍历对应分区目录下的文件即可,不需要遍历全局数据,使得处理的数据量大大减少,从而提高查询效率。
//使用分桶表优化:指将数据以指定列的值为key进行hash,hash到指定数目的桶中,只需要遍历所在的桶就可以了。
//选择合适的文件存储格式:根据业务选择,创建表时,尽量使用ORC、ParquetFile这些列式存储格式,因为每一列的数据在物理上是存储在一起的,hive查询时会只遍历需要列数据,大大减少处理的数据量。ORC会基于列创建索引,查询很快。Parquet对于大型查询的类型是高效的,还有对于特定列查询,使用snappy、gzip压缩,默认前者,支持Impala查询引擎。解决mr性能瓶颈(主要由网络io和磁盘io),最主要就是减少数据量,所以数据压缩,但是要消耗cpu,但是在hadoop中,性能瓶颈不在于cpu,cpu压力并不大,所以没问题。
===sql语法和运行参数层面://列裁剪:在查询时只读取需要的列,分区裁剪就是只读取需要的分区,当列很多或者数据量很大时,如果select*或者 不指定分区,全列扫描和全表扫描效率都很低,hive在读数据时,可以只读取查询中所需要的列,忽略其他列,节省读取开销,中间表存储开销和数据整合开销。
//谓词下推:将sql语句中的where谓词逻辑都尽可能提前执行,减少下游处理的数据量,对应逻辑优化器是PredicatePushDown(PPD),设置参数set hive.optimize.ppd=true;
//合并小文件:执行mr程序时,一个文件的一个数据分块需要一个maptask来处理,但是如果数据源是大量的小文件这样启动大量的maptask任务,浪费资源,在map执行前合并小文件,减少maptask任务数量,SET hive.merge.mapredfiles = true;默认256M.
//合理控制map tsak和reduce task的数量:mapjoin是将join双方比较小的表直接分发到各个map进程的内存中,在map进程中进行join操作,避免reduce处理。
//join数据倾斜优化:开启set hive.optimize.skewjoin=true;在join过程中hive会将计数超过阈值hive.skewjoin.key(默认100000)的倾斜key对应的行临时写入文件中,然后再启动另一个job做map join生成结果。通过hive.skewjoin.mapjoin.map.tasks参数还可以控制第二个job的mapper数量,默认10000.
//CBO优化:join时候表的顺序是前面的表会被加载到内存中,后面的表进行磁盘扫描。Cbo是成本优化器,作用:如何排序连接,执行哪种类型的连接,并行度,是属于hive底层的优化,设置参数就ok。
===hive架构层面://启用本地抓取,hive从hdfs读取数据有两种方式,启用mapreduce读取和直接抓取,直接抓取数据更快很多,但是只有少数操作可以使用直接抓取方式,hive.fetch.conversion参数配置,只是select*,where条件针对分区字段进行筛选过滤时,带有limit分支语句时,可以采用直接抓取。
//JVM重用:maptask和reducetask都会启动JVM进程,执行完毕之后JVM进程就会退出,但是要多次启动jvm的情况下,消耗很大,开启jvm重用会一直占用使用到的task的插槽,以便进行重用,直到任务完成才会释放,所以也有缺点。
//并行执行 :如果某些执行阶段不是互相依赖,就可以让多个不互相依赖的stage并发执行,节约时间提高执行速度,缺点:如果集群资源匮乏,会导致各个job相互抢占资源导致整体执行性能下降。set hive.exec.parallel=true设置并行度(默认是8)set hive.exec.parallel.thread.number=16;
都是分布式计算框架
===spark计算中间结果基于内存缓存,mr基于硬盘,在hdfs做存储和读取
===迭代式计算差距明显,最大是100多倍
===基于内存计算快的原因是使用了DAG有向无环图来切分任务的执行先后顺序
RDD是弹性式分布式数据集 ,每个RDD都是由一系列的partition组成的,每个task运行在自己的partition上,RDD之间互相依赖,分区器作用在(k,v)格式的RDD上,RDD默认寻找最优的计算位置,计算向数据靠拢,尽可能少做数据拉取操作。
RDD的弹性(容错):partition数量大小没有限制,RDD之间依赖关系,可以基于上一个RDD重新计算出RDD
RDD的分布式:RDD由partition组成partition分布在不同的节点上RDD提供最佳计算位置
分区原因:单节点处理不了大量的数据
分区方式:hashpartition(哈希分区),可能产生数据倾斜
Rangepartiiton(范围分区),解决哈希分区倾斜问题,随机抽样方式
Partition是RDD的最小单元,同一份数据的partition大小不一,数量不定,是根据application里的算子和最初读入的数据分块数量决定的。Partition位于计算空间,partition没有冗余,丢失之后重新计算得到。
Master:资源管理的主节点(进程)
Cluster Manager:集群获取资源的外部服务器(yarn,mesos,standalone)
Worker:资源管理的从节点(进程)
Application:基于spark的用户程序,包含driver程序和executor程序
Driver:用来连接工作进程(worker)的进程
Executor:这个进程负责运行任务,负责数据存在内存或者磁盘上
Task:被发送到executor上的工作单元
Job:包含很多任务(task)的并行计算,和action算子对应
Stage:一个job会被拆分成很多组任务,每组任务被称为stage
控制算子:cache:默认将RDD的数据持久化到内存,懒执行
Persist:可以指定持久化的级别,最常用的是memory_only和memory_and_disk
Checkpoint:将RDD持久化到磁盘,还可以切断RDD之间的依赖关系,懒执行
多文件转换算子:join:leftouterjoin,rightouterjoin,fullouterjoin,根据key值进行连接,比如(k,v)join(k,w)返回(k(v,w))
Union:合并两个数据集,两个数据集的类型要一致,返回新的RDD的分区数是合并RDD分区数的总和
Intersection:取两个数据集的交集
Subtract:取两个数据集的差集
Distinct:对RDD内数据去重,分区数不会变化,进行整个对象的匹配,完全相同的时候才会去重
Cogroup:当调用类型(k,v)和(k,w)的数据上时,返回一个数据集(k,(iterable
Yarn-cluster:执行流程:客户机提交application应用程序,发送请求到resourcemanager,请求启动applicationmaster;rm收到请求后随机在一台nodemanager上启动am,am启动driver线程,并且发送请求到rm,请求一批container用于启动excutor,rm返回一批nodemanager节点给am,am连接到nodemanager,发送请求到nodemanager启动excutor,excutor反向注册到am所在的节点的driver,driver发送task到excutor。
(driver运行在yarn集群中某一台nodemanager中,每次提交任务的driver所在的机器都是不再提交任务的客户端机器,而是多个nodemanager节点中的一台,不会产生某一台机器网卡流量激增的现象,但同样也有缺点,任务提交后不能看到日志,只能通过yarn查看日志)
===启动集群后,worker节点会向master节点汇报资源情况,master掌握了集群资源情况
===当spark提交一个application后,根据RDD之间的依赖关系将application形成一个DAG有向无环图
===任务提交后,spark会在driver端创建两个对象:DAGScheduler和taskScheduler,DAGscheduler是任务调度的高层调度器,是一个对象。
===DAGscheduler的主要作用就是将DAG根据RDD之间的宽窄依赖关系划分为一个个stage,然后将这些stage以taskset的形式提交给taskscheduler(taskscheduler是任务调度的低层调度器,这里封装的就是一个个的task任务,也就是stage中并行的tsak任务)
===taskscheduler会遍历taskset集合,拿到每个task后会将task发送到executor中去执行(其实就是发送到executor中的线程池threadpool去执行)
===task在executor线程池中的运行情况会向taskscheduler反馈,当task执行失败时,则由taskscheduler负责重试,将task重新发送给executor去执行,默认重试3次,如果重试3次依然失败,那么这个task所在的stage就失败了。
===stage失败了则由DAGscheduler来负责重试,重新发送taskset到taskscheduler,stage默认重试4次,如果重试4次以后依然失败,那么这个job就失败了,job失败,application就失败了。
===taskscheduler不仅能重试失败的task,还会重试straggling(落后,缓慢)task(就是执行速度比其他task慢太多的task)。如果有运行缓慢 的task那么taskscheduler会启动一个新的task来与这个运行缓慢的task执行相同的处理逻辑,两个task哪个先执行完,就以哪个task的执行结果为准,这就是spark的推测执行机制,在spark中推测执行机制默认关闭,通过spark.speculation属性来配置。(对于ETL类型要入数据库的业务要关闭推测执行机制,避免重复数据入库)
广播变量:主要用于节点间高效分发对象
List会封装到每个task,发送到executor中,使用广播变量将list广播出去,会放在executor中的blockmanager中,一个executor里只有一个。广播变量只能在driver端定义,不能在executor端定义,在driver端可以修改广播变量的值,在executor端无法修改广播变量的值。
累加器:用来对信息进行聚合,主要用于累计计数等场景。累加器在driver端定义赋初始值,累加器只能在driver端读取,在executor端更新。
===Dataset:分布式的数据集合,dataset提供强类型支持,在RDD的每行数据加了类型约束。是spark1.6中新添加的接口,集中了rdd的优点以及使用了sparksql优化的执行引擎。
Dataset底层封装的是RDD,当rdd泛型是row类型的时候,也可以称dataset为dataframe。
===Dataframe:rdd+schema元数据信息。类似于传统数据库的二维表格。
===Sparksql数据源:可以是json类型的字符串,jdbc,parquet,hive,hdfs等
===资源调优:
1.1分配资源:executor-memory、executor-cores、driver-memory
如何设置:实际生产中,提交spark任务时,使用spark-submit shell脚本,在里面调整对应的参数。
参数大小:yarn模式:先计算出yarn集群的所有大小,比如一共500g内存,100个cpu,这时候可以分配最大资源是给定50个executor、每个executor的内存大小10g,每个executor使用的cpu个数是2。
使用原则:资源充足,尽量调节最大
1.2提高并行度:各个stage的task的数量代表spark作业在各个阶段stage的并行度,分配完资源就可以设置程序的并行度
设置task的数量:至少设置成application的总cpu core数量相同,比如150个core分配150个task,官方推荐task数量设置成application总cpu core数量的2~3倍。
设置参数spark.defalut.parallelism来设置task数量:默认没有值,可以通过构建sparkconf对象的时候设置,例如new sparkconf().set(“spark.defalut.parallelism”,”500”)
给RDD重新设置partition的数量:设置参数spark.sql.shuffle.partition=500,默认200。
===开发调优:
2.1RDD重用和持久化:可以把多次使用的RDD进行持久化,避免后续需要,再次重新计算,提升效率。调用rdd的cache或者persist方法进行持久化,使用序列化方式持久化到内存,将RDD的每个partition的数据序列化成一个字节数组,大大减少内存空间占用,在获取数据的时候再反序列化,可以使用双副本机制持久化。
2.2广播变量:通过driver把共享数据转换成广播变量,广播变量数量等于executor数量,广播变量只能在driver端定义,可以修改广播变量的值。通过Sparkcontext的broadcast方法把数据转换成广播变量 ,通过调用value方法获取广播变量的值。
2.3尽量避免使用shuffle类算子:shuffle过程就是将分布在集群中多个节点上的同一个key,拉取到同一个节点上,进行聚合或者join操作,shuffle涉及到数据要进行大量的网络传输,使用reducebykey、join、distinct、repartition等算子操作都会产生shuffle,broadcast+map的join操作不会导致shuffle操作 。使用map-side预聚合的shuffle操作,减少数据的传输量,提升性能。(建议使用reducebykey或者aggregatebykey算子来替代掉groupbykey算子 ,因为这两个都会使用用户自定义的函数对每个节点 本地的相同key进行预聚合)
2.4使用高性能算子:reducebykey或者aggregatebykey代替groupbykey;
Mappartitions代替普通map,mappartition一次函数调用会处理一个partition所有的数据,使用foreachpartitions代替foreach,使用filter之后进行coalesce操作,(coalesce算子手动减少RDD的partition数量),如果需要在repartition重分区之后,还要进行排序,建议直接使用repartitionAndSortWithPartitions算子(可以一边进行重分区 ,一边进行排序)
2.5使用Kryo优化序列化性能:
Kyno序列化机制启用后生效的地方:算子函数中使用到的外部变量;持久化RDD时进行序列化,产生shuffle的地方;
开启:创建sparkconf对象,设置序列化器为KynoSerializer,注册要序列化的自定义类型
2.6使用fastutil优化数据格式:
Fastutil扩展了java标准集合框架的类库;
Fastutil集合类,可以减小内存的占用,更快的存取速度;
使用list(interger)的替换成intlist即可使用fastutil
2.7调节数据本地化等待时长:
数据本地化:数据在哪里就在对应的机器上开启计算任务,减少网络传输
本地化级别:process_local:进程本地化,代码和数据在同一个进程中,在同一个executor中,性能最好。
Node_local:节点本地化,代码和数据在同一个节点中,数据需要在进程间进行传输,性能其次;
Rank_local:机架本地化,数据和tsak在一个机架的两个节点上,数据需要通过网络在节点之间进行传输,性能较差;
Any:无限制,数据和tsak可能集中在集群的任何地方,性能最差
数据本地化等待时长:默认3秒,首先采用最佳方式,等待3秒后降级,不行继续降。
调节参数并测试:修改spark.locality.wait参数,默认3秒,可以增加。在代码中设置:new SparkConf().set(“spark.locality.wait”,”10”)
===基于spark内存模型调优:
3.1spark中executor内存划分:task执行时自己编写代码使用;task通过shuffle过程拉取了上一个stage的task的输出后,进行聚合等操作时使用;让RDD缓存时使用。
3.2spark内存模型:
静态内存模型:一个executor分为三部分,一部分是storage内存区域,默认0.6,一部分是execution区域,默认0.2,还有一部分是其他区域,默认0.2,使用参数控制:spark.storage.memoryFraction:
(cache数据大,则应调高),spark.shuffle.memoryFraction:(shuffle多,则应调高)。缺点:storage内存区域和execution区域后,假设一个任务execution内存不够用了,但是它的storage内存区域是空闲的,两个之间不能互相借用。
统一内存模型:动态内存模型先是预留300m内存,防止内存溢出。动态内存模型把整体内存分成了两部分,统一内存(60%)和其他内存(40%)统一内存又划分为两个小部分,storage内存和execution内存,两者各占50%
统一内存模型特点:storage内存和execution内存可以相互借用。
===shuffle相关参数调优:
4.1spark的shuffle原理:对数据的重组,在整个shuffle过程中,伴随着大量的磁盘和网络IO
4.2Hashshuffle两种运行机制,合并的运行机制:通过复用buffer来优化shuffle过程中产生的小文件的数量。开启合并机制的配置是spark.shuffle.consolidateFiles,默认是fasle,设置为true就是开启。并行任务或者是数据分片过多也会产生很多小文件。普通机制:shuffle写阶段:对相同的key执行hash算法,写入同一个磁盘文件中,先写到内存,内存满了,才会溢写磁盘。Shuffle读阶段:拉取与shuffle read task自己的buffer缓冲大小相同的数据,然后通过内存中的一个map进行聚合操作。但是会产生海量的小文件,可能导致OOM。
4.3sortshuffle:普通机制:写入磁盘时会进行排序,也会生成索引文件。优点:小文件变少,一个task只生成一个file文件,file文件整体有序,加上索引文件辅助,查找变快
Bypass运行机制:在shufflemap task数量小于默认值200时,启用bypass机制的sortshuffle(数据量少,没必要sort全排序)
4.4spark shuffle参数调优:spark.shuffle.file.buffer默认32k,buffer缓冲大小可以适当增加(比如64k)
Spark.reducer.maxSizeInFlight默认48m,决定buffer缓冲每次拉取多少数据,适当增大
Spark.shuffle.io.maxRetries,默认3,拉取失败重试最大次数,适当增加,(比如60)
Spark.shuffle.io.retryWait默认5s,每次重试拉取数据的等待间隔
===数据倾斜调优:
5.1现象:个别task执行极慢。有的task直接报OOM
5.2原理:某一个task数据量比较大,运行时间很长,导致整个spark作业运行时间很长
5.3定位:一:根据log日志信息定位:数据倾斜只会发生在shuffle过程中,可能是代码中使用了触发shuffle的算子,因为某些key对应的数据远远高于其他的key
二:分析定位逻辑:一个job会划分成很多个stage,首先看数据倾斜发生在第几个stage中,可以在任务运行过程中,观察任务的UI界面,可以观察到每一个stage中运行task的数据量,进一步确定是不是task分配的数据不均匀导致数据倾斜(每个task的运行时间和处理的数据量)
三:某个task莫名内存溢出情况:通过yarn查看yarn-cluster模式下log的异常栈
四:查看导致数据倾斜的key的数据分布情况
5.4原因:一:数据本身问题,key分布不均衡(包括大量key为空),key的设置不合理 二:spark使用不当,shuffle时的并发度不够,计算方式有误
5.5后果:一:spark中的stage的执行时间受限于最后那个执行完成的task,因此运行缓慢的任务会拖垮整个程序的运行速度(分布式程序运行的速度是由最慢的那个task决定的)
二:过多的数据在同一个task中运行,将会把executor内存撑爆,导致OOM内存溢出。
5.6调优:一:使用hive ETL预处理数据,适用于导致数据倾斜的是hive表,就是hiveETL预先对数据按照key进行聚合,或者是预先和其他表进行join。(预处理过程可能倾斜)
二:过滤少数导致倾斜的key,适用于导致倾斜的key就少数几个,(key很多)
三:提高shuffle操作的并行度(效果差)。提高shuffle read task的数量
四:两阶段聚合(局部+全局)。分组聚合的场景,第一次局部聚合,先给key打上随机数前缀,进行局部聚合,然后去掉前缀,再进行全局聚合。
五:将reduce join转为map join。适用于在RDD使用join类操作,或者在spark sql中使用join语句时,而且join操作中的一个RDD或者表的数据量比较小,使用Broadcast变量与map类算子实现join操作,完全规避掉shuffle类操作。(适用于一个大表和一个小表的情况 ,需要将小表进行广播,消耗内存资源)
六:采样倾斜key并拆分join操作:适用于其中某一个RDD/hive表中的少数几个key的数据量过大,而另一个RDD/hive表中的所有key分布都均匀。对那个RDD通过sample算子采样出一份样本,统计每个key的数量,计算是哪几个key,然后将这几个key对应的数据拆分出来,形成一个单独的RDD,并打上随机前缀,不会导致倾斜的大部分key形成另外一个RDD。打散成n份去进行join,不会集中在少数 task上,分散到多个task进行join。
七:使用随机前缀和扩容RDD进行join。Join操作时。扩容就是将每条数据都扩容成n条数据,扩容出来的每条数据都依次打上一个0~n的前缀,最后将两个处理后的RDD进行join即可。(针对有大量倾斜key的情况,但是对内存资源要求很高)。
旧版本:通过设置参数spark.streaming.receiver.maxRate来限制Receiver的数据接收速率。
新版本:1.5版本引入反压机制,通过动态控制数据接收速率来适配集群数据处理能力。
Spark Streaming Backpressure:根据JobScheduler反馈作业的执行信息来动态调整Receiver数据接收率。令牌桶机制。
容量大:单表可以有百亿行百万列
面向列:面向列的存储和权限控制,并支持独立检索,可以动态增加列
多版本:hbase的每一个列的数据存储都有多个version
稀疏性:为空的列不占用存储空间,表可以设计的非常稀疏,不用像关系型数据库那样需要预先知道所有列名然后在进行null填充。
拓展性:底层依赖hdfs,当磁盘空间不足的时候,只需要动态增加datanode节点服务就可以了。
高可靠性:WAL机制,保证数据写入的时候不会因为集群异常而导致写入数据丢失,replication机制保证集群在出现严重问题时候,数据不丢失或损坏,底层用hdfs,有备份
高性能:底层的LSM数据结构和Rowkey有序排列,让hbase写入性能非常高。Region切分,主键索引、缓存机制使hbase在海量数据下具备一定的随机读取性能,针对rowkey的查询能达到毫秒级别。LSM树形结构,最末端的子节点是以内存的方式进行存储的,内存中的小树会flush到磁盘中(当子节点达到一定的阈值后,会放到磁盘中,存入的过程会进行实时merge成一个主节点,然后磁盘中的树定期会做merge操作,合并成一颗大树,以优化读性能)
NameSpace命名空间:相当于mysql中的database,每个命名空间下有多个表,hbase和default是HBase自带的命名空间,hbase命名空间中是HBase的内置表,default是用户默认使用的命名空间。
Region:类似于mysql中表的概念,但是这里相当于表的切片(表数据量到一定程度时,就会自动切片)。Hbase中定义表的时候,只需要声明列族,而不需要声明具体的列。HBase写入数据时,字段是可以按需要变化的。
RowKey:用来检索记录的主键,是一行数据的唯一标识,任意字符串(最大64kb),以字节数组保存,存储时,数据按照rowkey的字典序排序存储
Column:HBase中每个列都是由Column Family和Column Qualifier进行限定,例如:info:name,info:age。
TimeStamp:用于标识数据的不同版本,数据写入时,如果不指定时间 ,HBase会自动写入,其值为数据写入时间。
Cell:由rowkey,Column Family:Column Qualifier,TimeStamp唯一确定的单元,Cell中存储的数据类型都是字节码,即字节数组。
===Client:客户端发送请求到数据库,连接方式可以是hbase shell或者类JDBC。发送的请求包括DDL(数据库定义语言,表的建立,删除,添加删除列族,控制版本),DML(数据库操作语言,增删改),DQL(数据库查询语言 ,查询--全表扫描--基于主键--基于过滤器)。Client维护着一些cache来加快对hbase的访问,比如region的位置信息。
===HMaster:集群主节点,可以实现高可用,通过zk来维护主副节点的切换。为region server分配region并负责region server的负载均衡,管理用户对table的结构DDL操作。发现失效的region,并将失效的region分配到正常的regionserver上,并且在region server失效的时候,协调对应的hlog进行任务的拆分。
===HRegion Server:直接对接用户的读写请求,真正干活的节点,主要负责管理hmaster为其分配的region,处理对这些region的IO请求;负责与底层的HDFS交互,存储数据到HDFS;负责region变大以后的拆分以及storefile的合并工作;会实时的和HMaster保持心跳,汇报当前节点的信息;HRegionServer负责和客户端建立连接;当意外关闭的时候,当前节点的Region会被其他HRegionServer管理
===HRegion:是hbase中分布式存储和负载均衡的最小单元 。不同的region可以分布在不同的regionserver上。Hbase自动把表水平划分成多个region,每个region会保存一个表里面某段连续的数据。Region达到阈值10G的时候会被平分,切分后的其中一个region转移到其他的hregionserver上管理。
===store:hregion是表获取和分布的基本元素,由一个或者多个store组成,每个store保存一个columns family。每个store又由一个memstore和0或多个storefile组成。
Memstore:作为hbase的内存数据存储,数据的写操作会先到memstore中。达到阈值(64k)后,regionserver启动flasheatch进程将memstore中的数据写入storefile持久化存储,每次写入后形成一个单独的storefile,当客户检索数据 时,先在memstore中查找,如果memstore中不存在,会在storefile中继续查找 。
Storefile:底层是以hfile的格式保存,hbase以store的大小来判断是否需要切分region
Hfile:站在hdfs角度叫hfile,站在hbase角度叫storefile
Hlog:负责记录数据的操作日志,hbase出现故障时可以进行日志重放和故障恢复,例如磁盘掉电导致memstore中的数据没有持久化存储到storefile。
===zookeeper:选举hmaster,监控region server,维护元数据集群配置工作。
===HDFS:提供底层数据存储服务,提供高可用支持,hbase将hlog存储在HDFS上,当服务器发生异常宕机时,可以重放 hlog恢复数据。
读:找到要读取数据的region所在的regionserver,然后按照以下顺序进行读取:先去blockcache读取,若blockcache(缓存)没有,就到memstore中读取,若memstore中没有,就到hfile中读取。
写:找到要写入数据的region所在的regionserver,然后将数据先写到WAL(预写日志)中,然后再将数据写到memstore等待刷新,回复客户端写入完成。
触发时机:一:region中所有的memstore占用内存超过相关阈值
二:整个regionserver的memstore占用内存和大于相关阈值
三:WAL数量大于相关阈值
四:定期自动刷写。默认1小时,建议调大,10小时(刷写频繁导致产生小文件,影响随机读性能)
五:数据更新超过一定阈值
六:手动触发刷写,shell命令
刷写策略:1.1之前:如果刷写某个memstore,它所在的region中其他的memstore也会被一起刷写
刷写流程:prepareflush(准备刷写)阶段:对memstore做snapshot(快照),在创建snapshot期间持有updateLock
Flushcache阶段:如果创建快照没问题,返回的result.result将为null,然后iternalFlushCacheAndCommit,包含flushCache和commit阶段。Flushcache就是将准阶段创建好的快照写到临时文件里,临时文件存放在对应region文件夹下面的.tmp目录
Commit阶段就是把flushcache阶段产生的临时文件移到对应的列族目录下面,删除第一步生成的snapshot
合并分类:minor compaction:选取一些小的、相邻的storefile合并成一个更大的storefile
Major compaction:将所有的storefile合并成一个storefile,清理被删除的数据,TTL过期数据,版本号超过设定版本号的数据,时间比较长,关闭自动触发,手动在业务低峰期触发
合并时机:memstore刷盘,后台线程周期性检查,手动触发
切分原因:数据分布不均匀
compaction性能损耗严重(合并占用大量内存)
资源耗费严重(数据写入量大不做切分可能一个热点region新增数据就有几十G)
触发时机:每次数据合并之后针对相应的region生成一个requestsplit请求 ,检测filesize是否达到阈值。检测方法:系统遍历region所有的store文件大小,如果大于最大filesize(默认10G)就会触发切分
切分流程:寻找切分点:系统遍历所有store,找到最大的,在这个store里找最大的hfile,定位这个文件中心位置对应的rowkey,作为切分点。
开启切分事物:prepare---execute---rollback三个阶段
切分优化:region预分配,数据被均衡到多台机器上
===表优化:预分区:rowkey用来检索表中的记录,rowkey按照字典序存储,rowkey设计原则,ColumnFamily不要定义太多,version版本设置最大,。。。
===写入优化:多table并发写,WAL Flag日志,批量写(调用Table.put(Put)方法),HTable参数设置(Auto Flush,Write Buffer,多线程并发写)
===读取优化:显示的指定列,关闭ResultScanner(释放资源),查询结果(先查缓存)
===缓存优化:设置Scan缓存,禁用块缓存,缓存查询结果
MergeTree是最基础的表引擎,
Mergetree在写入一批数据时,会以数据片段的形式写入磁盘,且数据片段不可修改,为了避免片段过多,clickhouse会通过后台线程,定期合并这些数据片段,属于相同分区的数据片段会被合成一个新的片段,这种数据片段往复合并的特点,也是合并树名称的由来
存储格式:拥有物理存储,数据会按照分区目录形式保存到磁盘上,
===一级索引:mergetree主键使用primary key定义,索引文件按照primary key排序,更常用order by代替主键,
稀疏索引:primary.index文件内的一级索引采用稀疏索引方式实现,稠密索引每一个索引都会对应一条数据记录,一条稀疏索引标记对应一段数据,稀疏索引就像一本书的目录,每个目录只会对应章节的开始位置,不会对应到具体的每个字,少量的稀疏索引就能标记大量的数据记录的区间位置,数据量越大优势越明显,稀疏索引占用空间小,所以primary.idx中的数据常驻内存的,读取速度非常快
索引粒度:会标注整个数据的长度,最终数据会形成多个间隔的小段
索引数据的生成规则:一级索引属于稀疏索引,索引值根据声明的主键字段获取,
索引的查询过程:markrange是clickhouse中用于定义标记区间的对象,markrange与索引编号对应,使用start和end两个属性表示区间范围,通过start和end对应的索引编号的取值,既能得到它对应的数值区间,而数值区间表示了此markrange包含的数据范围,
===二级索引:跳数索引,由数据的聚合信息构成,为了查询时减少扫描数据的范围。默认关闭,四种跳数索引,minmax、set、ngrambf_v1和tokenbf_v1,一张数据表支持同时声明多个跳数索引。
===列式存储,更好的进行数据压缩,最小化数据的扫描范围,
===数据压缩,一个压缩数据块由头信息和压缩数据两部分组成,头信息9个字节,
能够按需加载数据并解压,读取粒度进一步精确到压缩数据块
数据标记和索引区间是对齐的,按照index_granularity的粒度间隔,
Mergetree在读取数据时,必须通过标记数据的位置信息才能找到所需要的数据,分为两个步骤,读取压缩数据块和读取数据两个步骤,
多对一:多个数据标记对应一个压缩数据块,一对一,一对多,
写入:先生成分区目录,每一批数据的写入,都会生成一个新的分区目录,属于相同分区的目录会依照规则合并到一起,然后按照index_granularity索引粒度,会分别生成primary.idx一级索引、每一个列字段的.mrk数据标记和.bin压缩数据文件。
查询:不断减小数据范围的过程,首先可以依次借助分区索引、一级索引和二级索引,将数据扫描范围缩至最小,然后再借助数据标记,将需要解压与计算的数据范围缩至最小。
===优点:---为了高效使用CPU,数据不仅按列式存储,同时还按向量进行处理
---数据压缩空间大,减少IO;处理单查询高吞吐量,每台服务器每秒最多数十亿行
---索引非B树结构,不需要满足最左原则,只要过滤条件在索引列中包含即可,即使在使用的数据不在索引中,由于各种并行处理机制ClickHouse全表扫描的速度也很快。
---写入速度非常快,50-200M/s,对于大量的数据更新非常适用
===缺点:---不支持事务,不支持真正的删除/更新;
---不支持高并发,官方建议qps为100,可以通过修改配置文件增加连接数,但是在服务器足够好的情况下;
---sql满足日常使用80%以上的语法,join写法比较特殊,
---尽量做1000条以上批量写入,避免小批量的数据进行插入更新删除操作,因为ck底层不断的做异步数据合并,会影响查询性能,在做实时数据写入的时候一定要避开
---并行处理机制,即使一个查询,也会用服务器一半的cpu去执行,所以clickhouse不能支持高并发的使用场景,默认单查询使用cpu核数为服务器核数的一半,安装时会自动识别服务器核数,可以通过配置文件修改核数。
===shared nothing 一种分布式计算架构,每一个节点都是独立的,在系统中不存在单点竞争,没有节点共享存储和硬盘。
===并行计算:多核多节点并行化大型查询,也便于扩容
===列式存储:每一列单独存放,数据即是索引,一行数据包含一个列或者多个列,每一列单独用一个cell来存储数据。
===Merge Tree:clickhouse支持主键表,为了快速执行对主键范围的查询,数据使用合并树进行递增排序,所以数据可以不断的添加到表中,添加数据时无锁处理
===稀疏索引:在稀疏索引中,只为索引码的某些值建立索引项,也是聚集索引,每一个索引项包括索引值以及指向该搜索码值的第一条数据记录的指针
===数据压缩:一些面向列的DBMS不使用数据压缩,但是数据压缩确实提高性能,ck两种数据压缩方式:LZ4和ZSTD,默认使用LZ4压缩方式
===sql支持:语法基本和sql语法兼容,支持join/from/in和join子句及标量子查询支持子查询
===索引:带有主键可以在特定的时间范围内为特定客户抽取数据,并且延迟时间小于几十毫秒
===支持在线查询:低延迟意味着可以无延迟的实时处理查询,而Yandex.Metrica界面页面加载使用此功能
===数据复制和对数据完整性的支持:使用异步多主复制,写入任何可用的副本后,数据将分发到所有剩余的副本,系统在不同的副本上保持相同的数据,数据在失败后自动恢复,或对复杂情况使用“按钮”
===基于cpu指令的向量化执行
===TinyLog:以列文件的形式保存在磁盘上,不支持索引,没有并发控制。一般保存少量数据的小表,生产环境上作用有限,用于平时练习测试
===Memory:内存引擎,数据以未压缩的原始形式直接保存在内存当中,服务器重启数据就会消失。读写操作不会相互阻塞,不支持索引,简单查询下有非常高的性能表现(用于测试)
===MergeTree:支持索引和分区
===ReplacingMergeTree(保证最终一致性):比MergeTree多去重功能
===SummingMergeTree:只关系以维度进行汇总聚合结果的场景,如果只使用普通的MergeTree的话,无论是存储空间的开销,还是查询时临时聚合的开销都比较大。所以这个引擎提供了“预聚合”。
数据仓库
OLTP:联机事务处理,特点是高并发且数据量级不大的查询,最主要用于管理事务,这种系统中的数据都是以实体对象模型来存储数据,并满足三范式。主要是为了操作数据而设计,用于处理已知的任务和负载:最常见的优化在于主码索引和散列,检索特定的记录,去优化某一些特定的查询语句
OLAP:联机分析处理,特点是查询频率较OLTP系统更低,通常会涉及到非常复杂的聚合计算。OLAP系统以维度模型来存储历史数据,其主要存储描述性的数据并且在结构上都是同质的。
区别:OLTP针对的是业务开发人员,OLAP针对的是分析决策人员;功能上OLTP针对的是日常事务处理,OLAP是面向分析决策的;模型上OLTP是关系模型,OLAP是多维模型;数据量上OLTP几条或者几十条记录,OLAP大于百万条记录;操作类型上OLTP是增删查改,OLAP以查询为主
---orc不支持嵌套结构(但可以通过复杂数据类型如map
---orc与hive更兼容,作为hive的常用存储格式,但是parquet与spark更兼容
---orc比parquet的存储压缩率较高
---orc导入数据和数据查询的速度比parquet快
小文件进行合并(减少map数)----在map执行前合并小文件,减少map数,
Java:CombineHiveInputFormat 具有对小文件进行合并的功能(系统默认的格式)
在 map-only 任务结束时合并小文件,默认 true
SET hive.merge.mapfiles = true;
在 map-reduce 任务结束时合并小文件,默认 false
SET hive.merge.mapredfiles = true;
合并文件的大小,默认 256M
SET hive.merge.size.per.task = 268435456;
当输出文件的平均大小小于该值时,启动一个独立的 map-reduce 任务进行文件 merge
SET hive.merge.smallfiles.avgsize = 16777216;
管理多个 Redis 服务器,检查master和slave是否运作正常,当监控某个redis出现问题,哨兵可以通过api向管理员或者其他应用程序发送通知,自动故障迁移,
哨兵机制为了解决主从复制的缺点,如果主节点出现问题,不能提供服务,需要人工修改配置将从变主,主节点的写能力单机,能力有限,单机节点存储能力有限
key对应的数据存在,但是在redis中过期,如果有大量请求发送过来,这些请求发现缓存过期一般都会从后端DB加载数据并回到缓存,这时候大量并发请求瞬间可以把后端DB压垮(redis某个热门数据过期,大量的合理数据请求达到数据库)
解决方案:---预先设置热门数据,在 redis 高峰访问之前,把一些热门数据提前存入到redis 里面,加大这些热门数据 key 的时长
---实时调整:现场监控哪些数据热门,实时调整 key 的过期时长
Key对应的数据在redis中并不存在,每次在针对key的请求从缓存中获取不到,请求转发到数据库,访问量太大压垮数据库,比如用一个不存在的用户id获取用户信息,(黑客访问肯定不存在的数据,造成服务器压力大)
解决方案:---对空值缓存,
---设置可访问的名单(白名单):使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
---布隆过滤器
针对很多key失效导致redis无法命中,数据库压力激增
解决方案:
---使用锁或者队列,保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求 落到底层存储系统,效率低,不适用高并发情况,
---设置过期标志更新缓存
---将缓存失效时间分散开:在原有的 失效时间基础上增加一个随机值,这样每一个缓存的过期时间的重复率就会降低,很难引发集体失效事件