大数据面试

HADOOP相关:

HADOOP文件夹添加限制:针对的是文件夹而不是针对账号。
hdfs dfs -setQuota 2 path:为指定HADOOP文件夹添加上传文件的数量。
hdfs dfs -setSpaceQuota 4k path:为指定HADOOP文件夹空间添加大小限制。
hdfs dfsadmin -clrSpaceQuota path:清楚指定HADOOP文件夹的空间大小限制。

HADOOP安全模式
集群启动的时候,首先会进入安全模式,处于安全模式时系统会检查数据块的完整性。比如如果我们通过参数配置副本数是3,HDFS默认的副本率是0.999,两个副本数的副本率明显小于默认副本率值
这是系统会自动复制一个副本到其他DN节点。如果设置的副本数大于默认的值,则系统会删除多余的副本。在安全模式下,文件系统只接受读数据请求,不接受删除、修改等请求。当整个系统达到安
全标准时,HDFS会自动离开安全模式。
hdfs dfsadmin -safemode get
hdfs dfsadmin -safemode enter
hdfs dfsadmin -safemode leave

Linxu本地文件拷贝:SCP
集群间文件拷贝:DISTCP

MR阶段:split->map->shuffle->reduce

MR:
自定义map实现(继承MAPPER基类)
    setup方法:主要用于一些对象的初始化
    map方法:处理数据逻辑
    cleanup方法:maptask执行完成之后会调用此方法,可用于一些清理工作
    run方法:

自定义reducer实现(继承REDUCER基类)
    setup方法:主要用于一些对象的初始化
    reducer方法:处理数据逻辑,所有从MapTask发送过来的数据都会调用此方法
    cleanup方法:maptask执行完成之后会调用此方法,可用于一些清理工作
    run方法:
    
Map Task工作机制
    1、数据文件经过split之后得到多个数据片文件。一个数据片文件对应一个Block。
    2、RecordRead对象按行读取每个数据片文件的内容给map进行处理。
    3、map处理完数据之后交给OutputCollector收集器对结果key进行分区(默认使用HashPartitioner,也就是对key求hash值然后同reduce数取模,由partitioner接口决定),然后写入memory buffer。
       每个map task都有一个内存缓冲区,存储着map的输出结果,当缓冲区快满的时候需要将缓冲区的数据以一个临时文件的方式存放到磁盘,此过程称为溢写操作,由单独的线程来完成,不影响往缓冲区
       写map task结果的线程。缓冲区默认大小是100M,溢写比列是0.8,也就是说当缓冲区达到80M时,会锁定这80M的内存,执行溢写操作。map task的输出结果可以往剩下的20M内存写数据,互不影响。
       当整个map task结束后再对磁盘中该map task产生的所有临时文件合并,生成最终的正式输出文件,等待reduce task来拉取数据。
       
    PS:内存缓冲区的作用是为了批量收集map task结果,减少磁盘IO,K-V对以及partition的结果都会被写入缓冲区,写入之前,K-V都会被序列化成字节数组。
    
Reduce Task工作机制
    1、通过HTTP方式请求map task获取属于自己的文件。
    2、将从map task Copy过来的数据先放入内存缓冲区,当内存缓冲区达到一定阈值时,对内存数据进行merge操作。merge分为三种形式:内存到内存、内存到磁盘、磁盘到磁盘。这也是溢写操作
       默认情况下第一种merge方式不启用,第二种merge方式一直在运行,直到没有map端的数据时才会结束,然后启动第三种磁盘到磁盘的merge方式生成最终的文件。
    3、将分散的数据合并成一个大的数据集然后对合并后的数据集进行数据排序。
    4、对排序后K-V对调用reduce方法,key相同的K-V对调用一次reduce方法,将结果写入到HDFS文件中。
    
Shuffle过程:从map task产生输出开始至Reduce获取数据作为输入之前的过程称为shuffle
    1、收集:将map task的结果输出到内存缓冲区,保存的是K-V、partition分区等信息。
    2、溢写:当缓冲区达到一定的阈值时,将数据以临时文件的方式写入磁盘,写入之前要对数据进行一次排序操作,如果配置了combiner,会将具有相同的分区号和key的数据进行排序。
    3、合并:将所有溢写的临时文件进行一次合并操作,以确保一个map task最终只会产生一个中间数据文件。
    4、拷贝:Reduce Task将map task产生的中间数据文件拷贝到自己的内存缓冲区,当缓冲区达到一定阈值时,将数据写入磁盘。
    5、合并:在拷贝map task中间数据文件的同时,后台会开启两个线程(inMemoryMerge和onDiskMerge)对内存到本地磁盘的数据文件进行合并。
    6、排序:在合并的同时进行排序操作,map task已经对数据进行了局部排序,reduce task只需保证拷贝的数据最终整体有效性即可。
    
    
HIVE:

内部表和外部表的区别


HQL转MR过程
    CLI->DRIVER->解析器->编译器->优化器->执行器

HIVE HA(HIVE高可用)
    在实际应用中,极少数情况下HIVE会出现端口不响应或进程丢失问题。为了解决这类问题,可以搭配HIVE HA高可用来解决。如在Hadoop集群上构建的数据仓库由多个Hive实例进行管理,这些Hive实例
被纳入一个资源池,由HAProxy提供统一的对外接口。客户端的查询请求首先访问HAProxy,由HAProxy对访问请求进行转发。HAProxy接受到请求后,会轮询(RPC)资源池中可用的Hive实例,执行逻辑可用性。
    如果某个Hive实例逻辑可用,就会把客户端的访问请求到该Hive实例上。否则就把该实例加入黑名单,并继续从资源池中取出下一个Hive实例进行逻辑可用性测试。对于黑名单中的Hive实例,Hive HA
每隔一段时间进行统一处理,首先尝试重启该实例,如果重启成功,将其再次放入资源池中。

    <property>
        <name>hive.server2.support.dynamic.service.discovery</name>
        <value>true</value>
    </property>
    
    <property>
        <name>hive.server2.zookeeper.namespace</name>
        <value>hiveserver2_zk</value>
    </property>
    
    <property>
        <name>hive.zookeeper.quorum</name>
        <value> zkNode1:2181,zkNode2:2181,zkNode3:2181</value>
    </property>
    
    <property>
        <name>hive.zookeeper.client.port</name>
        <value>2181</value>
    </property>
    
    
    <property>
        <name>hive.server2.thrift.bind.host</name>
        <value>0.0.0.0</value>
    </property>
    
    <property>
        <name>hive.server2.thrift.port</name>
        <value>10001</value> //两个HiveServer2实例的端口号要一致
    </property>

    JDBC连接的URL格式为:

    jdbc:hive2://<zookeeper quorum>/<dbName>;serviceDiscoveryMode=zooKeeper;zooKeeperNamespace=hiveserver2
    
    其中:
    
    <zookeeper quorum> 为Zookeeper的集群链接串,如zkNode1:2181,zkNode2:2181,zkNode3:2181
    
    <dbName> 为Hive数据库,默认为default
    
    serviceDiscoveryMode=zooKeeper 指定模式为zooKeeper
    
    zooKeeperNamespace=hiveserver2 指定ZK中的nameSpace,即参数hive.server2.zookeeper.namespace所定义,我定义为hiveserver2_zk
    
    
order by、sort by、distribute by、cluster by
    order by:对输入做全局排序,因此只有一个reducer,如果输入规模较大,需要耗费较长的计算时间。
    sort by:保证每个reducer的输出有序,不保证全局有序。
    distribute by:根据指定字段将数据分布到不同的reducer,且分发算法是hash散列。
    cluster by:同时具有distribute by跟sort by的功能,如果分桶和sort字段是同一个字段,可以使用cluster by替换distribute by + sort by。
    
    
行转列
    concat(A,B,C):返回输入字符串连接后的结果。
    concat_ws(separator,str1,str2...):根据指定分隔符连接字符串,可以用array的方式来替代分隔符后面的字符串列表。
    collect_set(col):将某字段的值进行去重汇总,返回一个array。
    
    select age,concat_ws("|",collect_set(name)) from tbl_name group by age --统计年龄相同的人都有哪些
    
    
列转行
    explode(col):将hive中的array或者map结构拆分成多行。
    lateral view:用于和split、explode等UDTF一起使用,它能够将一列数据拆成多行数据,在此基础上可以对拆分后的数据进行聚合。
    
    用法:lateral view udtf(expression) tableAlias AS columnAlias。如下
    lateral view explode(move) table_tmp as move_name --move字段在表中必须是被定义为array或map结构类型
    
    lateral view split(move,"separator") table_tmp as move_name --move字段是字符串类型,根据指定的分隔符将该值进行切分
    
    
分组求TOP_N
    row_number() over(distribute by colname sort by colname asc|desc)
    
    PS:over(distribute by colname1 sort by colname2 desc)意为先根据colname1进行分组,在分组内部再根据colname2进行降序排序,默认是升序。row_number()则为组内每一行记录返回一个从1开始的
    有序的顺序编号。根据顺序编号即可获取组内对应TOP_N的值。
    

Hive性能优化
    1、SQL语句优化
        使用explain结合yarn日志查看HiveSQL所有阶段的运行信息。
    
    2、文件格式优化
        执行同样的SQL语句及同样的数据,数据存储格式不同,执行时长不同。TextFile,SequenceFile,Parquet,ORC这几种存储格式中,Parquet和ORC执行时间较短。
        
        PS:四种存储格式的区别
            1、TextFile:默认的文件存储格式,行存储,数据不做压缩,磁盘开销大,数据解析开销大。建表的时候不指定文件格式时默认是该格式。导入数据时直接把数据文件拷贝到HDFS上不进行处理。
            其他存储格式不能直接从本地导入数据,需要将数据转为textFile格式,才能导入。
            2、SequenceFile:二进制文件,以K-V对的形式序列化到文件中。可分割、可压缩。压缩方式:NONE、RECORD、BLOCK。
            4、RCFile:行划分,列存储,可压缩。
            5、ORCFile:是对RCFile的优化,行划分,列存储,可压缩。
            
    3、小文件过多优化(原因、影响、解决方案)    
        HIVE中的小文件是在向HIVE表插入数据时产生的,分以下几种向HIVE中导入数据的方式
            1、直接向表插入数据:INSERT INTO TBL_NAME VALUES(...),(...)...
               该方式每次插入时都会产生一个文件,多次插入少量的数据就会出现多个小文件。这种方式生产环境上很少使用。
               
            2、通过Load方式加载数据
               当导入一个文件时,HIVE表就有一个文件。当导入一个文件夹时,HIVE表的文件数量就是该文件夹下所有文件的数量。
                
            3、通过查询方式加载数据:INSERT INTO TBL_NAME SELECT COL... FROM TBL_NAME或者FROM TBL_NAME INSERT INTO TBL_NAME SELECT COL...
               这种方式是生产环境常用的,也是最容易产生小文件的方式。INSERT导入数据时会启动MR任务,MR中REDUCE有多少个就输入多少个文件,所以,文件数量=ReduceTask数量*分区数。如果MR任务
            中没有REDUCE只有MAP阶段,则文件数量=MapTask数量*分区数。
                
        小文件如果过多,对HIVE来说,在进行查询的时候,每个小文件都会当成一个BLOCK,启动一个MAP任务来执行,而一个MAP任务的启动和初始化时间远远大于逻辑处理时间,这样就会造成很大程度上
        的资源浪费,并且可执行的MAP数量是有限的。同时对于底层存储HDFS来说,HDFS本身就不适合存储大量小文件,会导致NameNode源数据特别大,占用太多内存,严重影响HDFS的性能。
                
        解决方案:
            1、使用HIVE自带的concatenate命令自动合并小文件。如对A表进行小文件合并:alter table a concatenate;
                PS:concatenate命令只支持RCFILE和ORCFILE文件类型。不能指定合并后的文件数量,但是可以多次执行该命令。当多次使用该命令进行合并后文件数量不在变化,则跟每个文件的最小size
            参数设置有关,可以修改对应的值来修改合并后文件的数量。
            
            2、调整参数减少Map数量
                如设置执行MAP前进行小文件合并、每个MAP的最大输入、每个节点上split大小等。
                
            3、调整参数较少Reduce数量
                
            4、使用HADOOP的archive将小文件归档(在HIVE中设置相应的参数)
               归档的分区数据可以查看,但是无法对归档分区进行数据的插入,如果要对分区插入数据,需要通过alter table unarchive命令将归档分区恢复为文件。
    
    4、并行执行优化
        HIVE会将一个查询转化成一个或多个Stage,HIVE一次只会执行一个Stage。不过对于同一个JOB不同的Stage之间可能并非完全互相依赖,也就说有些Stage是可以并行执行。可以通过相应的参数配置来
    打开任务并行执行并执行最大的并行度。
    
        PS:开启并行执行也得是在系统资源比较空闲的时候才有优势,否则也并行不起来。
    
    5、JVM优化
        对于难以避免小文件或task特别多的场景,JVM重用对HIVE的性能具有非常大的影响。配置相应的参数即可。这个功能的缺点是开启JVM重用将一直占用使用到的TASK插槽以便进行重用,直到任务完成
    后才能释放。
    
    6、推测执行优化
        在分布式集群环境下,因为程序BUG(包括HADOOP本身的BUG)、负载不均衡或者资源分布不均等原因,会造成同一个作业的多个任务之间运行速度不一致,有些任务明显慢于其他任务,则整体的进度将
    会被这些任务拖慢。为了解决这一问题,可以开启推测执行,HADOOP根据一定的方式推测出执行慢的任务,并为这些任务启动一个备份任务,让该任务与原始任务同时处理同一份数据,并最终选用最先成
    功运行完成任务的计算结果为最终结果。在mapred-size.xml文件中配置相应的参数即可。
    
    
PS:对于只有一个文件的情况,假设该文件的大小为120M,里面只包含几个字段,但是却有几千万条记录,那么在不做任何处理的情况,只会有一个MAP来处理该文件,这样会很耗时。可以将该表的记录随机分散到包含10(视具体情况而定)文件的另一张表中,在用该表替代原始表进行SQL操作,这样则会用10个MAP任务完成。每个MAP任务只执行几百万的记录,效率会好很多。如下操作
    set mapreduce.job.reduces=10
    create table a_1 as
    select * from a
    distribute by rand();
    
    对于含有多个小文件的情况,则开启MAP端合并小文件,也就是在MAP执行前合并小文件及配置相应的控制参数。
    
    
    
Kafka
    传统消息队列应用场景
        同步处理:
            用户填写注册信息->将注册信息写入数据库->后续业务处理如发送短信请求等->页面响应成功
            
        异步处理:
            用户填写注册信息->将注册信息写入数据库->页面响应成功。发送短信请求写入MQ中。
            
            
    消息队列的两种模式
        点对点:一对一,消费者主动拉取数据,而不是将消息推送到客户端,一条消息只能有一个消费者消费,消费者收到消息后消息会从消息队列中清楚。
        
        发布/订阅:一对多,产生消息后推送给所有订阅者。一条消息可由多个消费者消费,不会立即删除消息,默认消息会保留7天。
        
    一个topic分为多个partition,一个partition对应一个log文件,生产的数据会不断追加到Log文件,Log文件会分为多个segment,一个segment对应两个文件:.log文件、.index文件。
    
    分区原则:
        1、指明partition时,直接将指明的值作为partition值
        2、没有指明partition值但是有key时,根据key做hash,再与partition数进行取余得到partition值
        3、没有partition值和key值时,随机生成一个整数,后面每次调用在这个整数上自增,将这个数与topic可用的partition总数取余得到partition值,也就是常说的round-robin算法。
        
        
    消费者组和消费者重平衡
        消费者组:是多个consumer组成的一个集合,订阅topic主题的消息,一般来说消费者组里的消费者数量最好和所订阅的所有topic的分区数量保持一致最好,当消费者数量小于分区数量时,那么必然
    有一个消费者需要消费多个分区的消息;当消费者数量大于分区数量的时候,必然会有消费者没有分区可消费。消费者的好处是支持多种消息模型。
    
        消费者重平衡(Rebalance):指的是有新消费者加入的情况,比如刚开始我们只有消费者A在消费消息,过了一段时间之后消费者B和C加入了,这时候分区就需要重新分配。重平衡期间组内的消费
    者都无法消费消息。因为消费者和分区数总数存在绑定的关系,所以只要消费者数量、topic数量、分区数量任何一个发生改变,都会触发重平衡。
        重平衡的过程:依赖于消费者和协调者之间的心跳来维护,消费者会有一个独立的线程去定时发送心跳给协调者(通过hearbeat.interval.ms来控制发送心跳的间隔时间)。
            1、每个消费者第一次加入组的时候都会向协调者发送JoinGroup请求,第一个发送这个请求的消费者会成为这个组的"管理者",协调者会返回组成员列表给"管理者"。
            2、"管理者"执行分区分配策略,然后把分配结果通过SyncGroup请求发送给协调者,协调者接收分区分配结果。
            3、其他consumer向协调者也发送syncGroup请求,协调者把对应的分区分配响应给各个consumer。
        
    消费者
    consumer采用pull(拉)模式从broker中读取数据而不是由broker将数据推送给consumer,由consumer根据自己的消费能力以适当的速率消费消息。pull模式下,如果kafka没有数据,consumer可能会陷入
  循环中,一直返回空数据。针对这个问题,consumer在消费数据时,传入一个时长参数timeout,如果当前没有数据可提供消费,consumer会等待一段时间之后再返回。
  
    分区分配策略:
       一个consumer group中有多个consumer,一个topic有多个partition,所以会涉及到partition的分配问题,既确定哪个partition由哪个consumer来消费。
        1、RoundRobin:将consumer group内的所有consumer以及对应consumer group所订阅的所有topic的partition按照字典序排序,然后通过轮询方式逐个将partition分配给每个消费者。
        
        2、Range:默认分区策略。首先对同一个topic中的partition按照序号进行排序,并对消费者按照字母顺序进行排序。然后用partition数除于consumer数来决定每个consumer消费几个分区。如果除不
        尽,则前面的几个consumer将会多消费一个分区。假设n=partition数/consumer数,m=partition数%consumer数,那么前m个consumer每个分配n+1个partition。后面n-m个consumer每个分配n个分区。
        
    offset维护
        由于consumer在消费过程中可能会出现断电宕机等故障,consumer恢复后,需要从故障前的位置继续消费,所以consumer要实时记录自己消费到了哪个offset,以便故障恢复后继续消费。
        在0.9版本之前,consumer默认将offset保存在ZK中,从0.9版本开始,consumer默认将offset保存在kafka的一个内置topic中,该topic为__consumer_offsets。
    
        
    数据可靠性:(生产者、消费者、kafka自身)
        为保证producer发送的数据能可靠的发送到指定的topic,topic的每个partition接收到producer发送的消息后,都需要向producer发送ack(确认收到),如果producer接收到ack则进行下一轮消息
    的发送,否则重新发送消息。
        发送ack的时机:确保有follower与Leader同步完成之后,leader再发送ack给producer,这样才能保证leader挂掉之后,能在follower中选举出新的leader。现有方案是1、半数以上的follower同步
    完成之后发送ack;2、全部的follower同步完成之后发送ack。
        ack三种应答机制:
            0:producer不等待broker的ack,broker一接收到消息但是还没有写入磁盘就已经发送ack确认,当broker发生故障时可能丢失数据。
            1:producer等待broker的ack,partition的leader落盘成功后返回ack,不等待follower同步完成。如果在follower同步成功之前leader发生故障,那么将会丢失数据。
            all:producer等待broker的ack,partition的leader和follower全部落盘成功后才返回ack。但是如果在follower同步完成后,broker发送ack之前,leader发生故障时,那么会造成数据重复。因
            为producer长时间未收到broker的ack确认,那么会重新发送该条消息。
            
        消费者数据丢失解决办法只需关闭自动提交offset即可,改为业务处理成功之后手动提交。通过enable.auto.commit=false,设置为手动提交;auto.offset.reset=earliest|latest。earliest代表
    从分区开始位置读取数据,可能会重复读取数据但不会丢失;latest标识从分区末尾读取,可能会存在丢失数据。
            
        kafka因为消息写入是通过pagecache异步写入磁盘的,所以仍然存在数据丢失的可能。可以通过配置以下三个参数来解决
            1、replication.factor=n:副本数,保证至少有2个或者以上的副本。
            2、min.insync.replicas=n:同步完成的副本数,只有完成同步的副本数大于等于该值时才认为数据写入成功。
            3、unclean.leader.election.enable=false|true:未完成同步的分区副本是否能作为leader。
    
    ISR(In-Sync Replica Set:副本同步列表)
        假设leader收到数据后,所有的follower开始同步数据,但其中一个follower因为某种故障,迟迟不能与leader进行同步,那么leader就要一直等下去,直到它完成同步之后才能发送ack。为了解决
    该问题,leader维护了一个动态的ISR,ISR是和leader同步的follower的集合。当ISR中的follower完成数据同步之后,leader就会发送ack确认。如果follower长时间未向leader同步数据,则该follower
    就将会被剔除ISR,该时间阈值由replica.lag.time.max.ms参数设定。leader发生故障之后,就会从ISR中选择新的leader继续接收数据。
            
    故障处理细节(保证副本之间的一致性:HW和LEO)
        Log文件中的HW(高水位,所有副本中最小的LEO的值,消费者能见到的最大offset)和LEO(每个副本的最后一个offset)
        1、follower故障
            follower发生故障后会被临时剔除ISR,待该follower恢复后,follower会读取本地磁盘记录的上次HW,并将log文件高于HW的部分截取掉,然后从HW处开始向leader同步。当follower的LEO大于等
        于该partition的HW时,即follower追上了leader,就可以重新加入ISR了。
        
        2、leader故障
            leader发生故障后,会从ISR中选出一个新的leader,之后,为了保证多个副本之间数据的一致性,其余的follower会先将各自的log文件高于HW的部分截掉,然后从新的leader开始同步数据。
            
        PS:这只能保证副本之间的数据一致性,并不能保证数据不丢失或者不重复。
        
        
    Excatly Once语义
        将服务器的ACK级别设置为-1,可以保证Producer到Broker之间不会丢失数据,即At Least Once语义。将服务器的ACK级别设置为0,可以保证生产者每条消息只会发送一次,即At Most Once语义。
    At Least Once可以保证补数据不丢失,但是不能保证数据不重复,At Most Once可以保证数据不重复,但是不能保证数据不丢失。对于一些数据而言,消费者要求数据既不重复也不丢失,既Excatly Once
    语义。为了实现Excatly Once语义,可以使用At Least Once + 幂等性来实现。所谓幂等性就是无论Producer向Broker发送多少次重复数据,Broker端都只会持久化一条。使用幂等性只需要将Producer中
    的enable.idompotence设置为true即可。开启幂等性的Producer在初始化的时候都会被分配一个PID,发往同一个partition的消息会附带SequenceNumber。而Broker端会对
    <PID,Partition,SequenceNumber>做缓存,当具有相同的主键的消息提交时,Broker只会持久化一条。
    
    PS:当producer重启时PID就会发生变化,同时不同的partition也具有不同的主键,所以幂等性无法保证跨分区会话的Excatly Once。
        
        
    高效读写数据:
        1、顺序写磁盘:producer生产的数据写入到log文件中一直追加到文件末端,为顺序写,而不是随机写。顺序写要比随机写高效很多,因为此省去了大量的磁盘寻址时间。
        
        2、数据持久化到Pagecache中。
        
        3、零拷贝。省去了中间的两次拷贝
            传统数据拷贝:
                先将数据从磁盘读取到内核空间进行缓存、将数据从内核空间读取到用户空间缓存、将数据从用户空间写回内核空间并放入socket缓存、将数据复制到网卡接口,此时数据才能通过网络发送
              发生复制的次数:磁盘文件到pagecache、pagecache到用户空间、用户空间到socket、socket到网卡接口一共4次。
                
            kafka数据拷贝:
                不需要将数据读取到用户空间进行缓存然后再写回内核空间缓存这两步复制操作。只需将磁盘文件的数据复制到内核空间中的pagecache缓存一次,然后将数据从pagecache直接发送到网络中
              发生复制的次数:磁盘文件到pagecache。

        4、批量处理和压缩
            kafka在发送消息的时候不是一条一条的发送的,而是会把多条消息合并成一个批次进行处理发送,消费消息也是一样,一次拉取一批次的消息进行消费。并且producer、broker、consumer都使用
        了优化后的压缩算法,发送和消费消息使用压缩节省了网络传输的开销,broker存储使用压缩则降低了磁盘存储的使用空间。
        
        如果有10个consumer,传统方式下,数据复制次数为4*10=40次,而使用零拷贝计数只需要1+10次,一次为从磁盘复制到pagecache,10次表示10个消费者各自读取一次页面缓存。
        
        
    ZK在kafka中的作用
        kafka集群中有一个broker会被选举为controller,负责管理集群中broker的上下线,所有topic的partition副本分配和leader选举等工作。controller的管理工作依赖于ZK。controller监听ZK上对应
    的brokers/ids路径,该路径下包含各个broker的ID值。当某个leader发生故障之后,该leader所处的broker对应的ID将会从brokers/ids中剔除,controller监控到改动之后,去ZK对应的/brokers/topics
    /topic_name/partitions/partition_num/state下获取ISR列表。然后根据ISR选举出新的leader,接着更新leader和ISR列表的值。
    
    Kafka通信原理
        1、kafka broker启动的时候会向ZK注册自己的ID,同时订阅ZK的brokers/ids路径。
        2、producer启动的时候会指定bootstrap.servers,通过指定的broker地址,kafka就会和这些broker创建TCP连接。
        3、连接到任何一台broker之后,发送请求获取元数据信息(如包含哪些topic、topic都有哪些partition、partition有哪些副本、partition的leader副本信息等)
        4、接着就会创建和所有broker的TCP连接即可开始发送数据。
        5、消费者和生产者一样,也会指定bootstrap.servers属性,然后选择一台broker创建TCP连接,发送请求找到协调者所在的broker
        6、然后再和协调者broker创建TCP连接,获取元数据
        7、根据分区Leader节点所在的broker节点,和这些broker分别创建连接
        8、最后开始消费消息
    
    kafka事务
        在Excatly Once语义中,无法跨分区会话。事务能够让producer和consumer可以跨分区和会话,要么全部成功要么全部失败。
    
    
    kafka API:producer API、consumer API
    
    
    kafka拦截器:所用于producer,用于在发送数据之前和接收到回调逻辑之前做数据的处理。
        
        
HBase:分布式列式存储数据库
    其主要由三部分组成(Master、Zookeeper、Region Server)
        Master(实现类是HMaster):是所有Region Server的管理者,分配Region到各个Region Server,监控集群中所有Region Server的状态(通过Heartbeat和ZK中的状态),对其进行负载均衡和故障转移;
        同时负责表的创建、删除、修改;
        
        Zookeeper:实现HMaster高可用,监控Region Server的状态,存储元数据表信息(Meta Table表,是一个所有Region地址的列表,key:tblname,region start key,region id,value:Region Server)等。元数据表实际是存储在Region Server中的,但是只有ZK知道具体在哪个Region Server上。
        
        RegionServer(实现类是HRegionServer):是Region的管理者,一个Region Server对应一个节点,由WAL(日志文件)、BlockCache和Region组成,负责数据的读取、写入、删除操作,同时切分在插入
        过程中过大的Region和压缩Region。
        
        Region:一张HBase表会被切分成若干个块 ,每块对应一个Region,存储表中某一段连续的数据,由一至多个Store组成。
        
        Store:一个Store对应表中的一个列族,由一个MemStore和零至多个StoreFile组成,StoreFile以HFile存储在HDFS的DN上,HBase写入数据到HDFS的过程其实就是不断追加HFile的过程。
        
        HFile:缓冲区的数据都是根据key进行排序的,所以在flush到HFile的时候按顺序一条一条记录往下写就可以,速度会非常快,因为不需要磁盘寻址。为了避免在查询的时候扫描整个HFile才发现数据
        不在当前HFile的情况,HFile实现了索引,使用的是多级索引,类似于B树,这样就可以确定在不扫描整个HFile的情况下就知道数据是否在当前文件中。
               当一个HFile被打开的时候,对应的Index就会被加载到BlockCache中。
        
        PS:MemStore对应的是写缓存,用于存储还没有被写入到磁盘的数据,并且是排序过的。每一个列族对应一个MemStore,数据会先写入到MemStore,然后返回ACK给客户端,当MemStore大小达到一定
        的阈值时,将数据flush到StoreFile。
            BlockCache对应的读缓存,存储经常被读取的数据,使用的是LRU算法,当缓冲区满了之后,最近最少使用的数据会被淘汰。
            
            
    HBase数据写入步骤
        1、客户端先访问ZK,获取hbase:meta表位于哪个Region Server上。
        2、访问对应的Region Server,获取hbase:meta表,然后根据请求的namespace:table/rowkey,查询出目标数据位于哪个Region Server中的那个Region上。并将该表的Region信息以及meta表的位置
        信息缓存在客户端的meta cache中,方便下次访问。
        3、与目标Region Server进行通讯。
        4、将数据顺序写入到WAL(日志文件)。
        5、将数据写入对应的MemStore,数据会在MemStore进行排序。
        6、向客户端发送ACK。
        7、等达到MemStore的flush时机时,将数据flush到HFile中。
        
        MemStore Flush时机:
            1、当某个MemStore大小达到hbase.hregion.memstore.flush.size(默认128M)时,其所在Region下的所有MemStore都会将数据flush到HFile中。当该Region的MemStore的总大小达到hbase.hregion
            .memstore.flush.size(默认128M)*hbase.hregion.memstore.block.multiplier(默认值4)时,该Region的MemStore写数据操作会被阻塞。
            
            2、当Region Server中所有MemStore总大小达到指定值时,Region会按照其所有MemStore的大小顺序(由大到小)依次进行flush。直到Region Server中所有MemStore总大小减小到指定值以下。值
            为java_heapsize * hbase.regionserver.global.memstore.size * hbase.regionserver.global.memstore.size.lower.limit。当memstore总大小达到java_heapsize * hbase.regionserver.glo
            bal.memstore.size时,该Region Server下的MemStore写数据操作会被阻塞。
            
            3、到达自动flush时间时触发MemStore的flush操作:hbase.regionserver.optionalcacheflushinterval(默认1小时)。
            
        PS:HBase表中最好不要有太多的列族,因为一个列族对应一个MemStore,如果一个MemStore满了,那么对应的Region下的所有MemStore都会将数据flush到HFile中,会导致频繁刷新缓冲区等性能问题
        
        PS:由于MemStore每次flush都会生成一个新的HFile,且同一个字段的不同版本(timestamp)和不同类型(put/delete)有可能分布在不同的HFile,因此查询时需要遍历所有的HFile。为了减少HFile的
        个数,以及清理过期和删除的数据,会进行StoreFile Compaction(压缩)。Compaction有以下两种
            1、Minor Compaction:将一个Store临近的若干个较小的HFile合并成一个较大的HFile,并清理部分过期和删除的数据。
            
            2、Majar Compaction:将一个Store下的所有的HFile合并成一个大的HFile,并且清理所有过期和删除的数据。
            
    HBase Region Split
        默认情况下,每个Table起初只有一个Region,随着数据的不断写入,Region会自动进行拆分。刚拆分时,两个子Region都位于当前的Region Server,但出于负载均衡的考虑,HMaster会将某个Region
    转移给其他的Region Server。
        Region Split的时机:
            1、当一个Region下的某个Store下的所有StoreFile总大小超过指定的大小时会进行拆分。0.94版本之前
            2、当一个Region下的某个Store下的所有StoreFile总大小超过Min(initialSize*R^3,hbase,hregion.max.filesize)时,该Region进行拆分。其中initial的默认值为2*hbase.hregion.memstore.f
            lush.size,R为当前Region Server中属于该Table的Region个数。0.94版本之后。
            3、2.0版本开始,如果当前RegionServer上该表只有一个Region,按照2*hbase.hregion.memstore.flush.size分裂,否则按照hbase.hregion.max.filesize分裂。
        
    HBase读取数据步骤
        1、客户端先访问ZK,获取hbase:meta表位于哪个Region Server上。
        2、访问对应的Region Server,获取hbase:meta表,然后根据请求的namespace:table/rowkey,查询出目标数据位于哪个Region Server中的那个Region上。并将该表的Region信息以及meta表的位置
        信息缓存在客户端的meta cache中,方便下次访问。
        3、与目标Region Server进行通讯。
        4、依次从Block Cache(读缓存)、MemStore、StoreFile中查询目标数据,并将查到的所有数据进行合并。此处所有数据指的是同一条数据的不同版本(timestamp)或者不同的类型(put/delete)。
        5、将查询到的新的数据块(Block,HFile数据存储单元,默认大小为64KB)缓存到Block Cache。
        6、将合并后的最终结果返回给客户端。
        
        
    HBase与Hive整合
        1、将Hive中与HBase进行整合的jar包拷贝到HBase的Lib目录下
        2、将HBase Lib目录下的所有jar拷贝到Hive中
        3、Hive中创建与HBase关联的表:store by xxx with serdeproperties("k","v") tblproperties("k","v")
        4、编写HQL查询
        
        
    HBase优化:预分区/RowKey设计/内存优化/基础优化
        高可用实现
            1、在conf目录下创建backup-master文件
            2、将作为standy By的HBase节点名称写入该文件中
            3、将整个conf目录copy到其他节点
            
        1、预分区
            每一个Region维护着StartRow和EndRow,如果加入的数据复合某个Region维护的RowKey范围,则该数据交给这个Region维护。可以将数据所要投放的分区提前规划好,以提高HBase性能。
            1、手动设定预分区:create 'tblname','family col','partition1',SPLITS=>['1000','2000','3000','4000']
            2、生成16进制序列预分区:create 'tblname','family col','partition2',{NUMREGIONS=>15,SPLITALGO=>'HexStringSplit'}
            3、按照文件中设置的规则预分区:create 'tblname','partition3',SPLITS=>'filename.suffix'
            
        2、RowKey设计
            一条数据的唯一标识是RowKey,数据存储于哪个分区,取决于RowKey处于哪个预分区的区间内,设计RowKey的主要目的就是让数据均匀的分布于所有的Region中,防止数据倾斜。
            1、生成随机数、HASH、散列值
            2、字符串反转
            3、字符串拼接:如时间戳_某个字段的值
            
        3、内存优化
            HBase操作过程中需要大量的内存开销,毕竟Table是可以缓存在内存中的,一般会分配整个可用内存的70%给HBase的Java堆。但也不必要分配非常大的堆内存,因为GC过程持续太久会导致Region
        Server处于长期不可用状态,一般16~48G内存就可以,如果因为框架占用内存过高导致系统内存不可用,框架一样会被系统服务拖死。
        
        4、基础优化
            1、允许在HDFS的文件中追加内容:hdfs-site.xml、hbase-site.xml->dfs.support.append,默认为true。
            
            2、优化DN允许的最大文件打开数:hdfs-site.xml->dfs.datanode.max.transfer.threads,默认为4096。
            
            3、优化延迟高的数据操作的等待时间:hdfs-site.xml->dfs.image.transfer.timeout,默认60000毫秒。延迟高,socket就需要等待更长时间,为了确保socket不会被timeout掉。
            
            4、优化数据的写入效率:mapred-site.xml->mapreduce.map.output.compress,mapreduce.map.output.compress.codec,前者为开启写入压缩,后者指定压缩方式。
            
            5、设置RPC监听数量:hbase-site.xml->hbase.regionserver.handler.count,默认值30,读写请求较多时,增加此值。
            
            6、优化Store文件大小:hbase-site.xml->hbase.hregion.max.filesize,默认10GB。如果需要运行HBase的MR任务,可以减小此值,因为一个Region对应一个MAP任务,单个Region过大,会导致
            MAP任务执行时间过长。
            
            7、优化HBase客户端缓存:hbase-site.xml->hbase.client.write.buffer。

            8、指定scan.next扫描HBase所获取的行数:hbase-site.xml->hbase.client.scanner.caching。值越大,消耗内存越大。
            
            9、制定flush、compact、split机制。

Spark
    1、窄依赖:父类RDD的一个Partition被子RDD的一个Partition所使用。
    2、宽依赖:父类RDD的一个Partition被子RDD的多个Partition所使用。
    
    划分Stage的整体思路:DAG由DAGScheduler从后往前推,遇到宽依赖就断开,划分一个Stage;遇到窄依赖就将这个RDD加入该Stage。每个stage由一组并行的task组成,将这些task以taskSet的形式提交
    给TaskScheduler执行。
    
    Task的两种类型:ShuffleMap Task和ResultTask
        DAG的最后一个阶段会为每个结果的Partition生成一个ResultTask,即每个Stage里面的Task的数量是由该Stage中最后一个RDD的Partition数量所决定的。而其余的所有阶段都会生成ShuffleMap Task
    之所以称为ShuffleMap Task是因为它需要将自己的计算结果通过shuffle到下一个Stage中;
    
    广播变量和累加器
        在Spark程序中,当一个传递给Spark操作的函数在远程节点上运行时,Spark操作实际上是操作这个函数所用变量的一个独立副本,这些变量会被复制到每台机器上,有多少个Task用到该变量就复制多
    少次(Excecutor);并且这些变量在远程机器上的所有更新都不会传递回驱动程序(Driver);为了解决这两种情况可以使用广播变量和累加器。
        1、广播变量:每个Executor拥有一份变量副本,这台Executor启动的Task都会先去自己所属的Executor获取,没有再从驱动程序获取并缓存在Executor中。由SparkContext对象创建。broadcast(v)
            注意事项:
                1、只读,不能修改
                2、不能将一个RDD座位广播变量,因为RDD不存储数据,可以将RDD的结果广播出去。
                3、广播变量只能在驱动程序(Driver)端定义,不能在Executor端定义
                4、广播变量在Driver端可以修改值,在Executor端无法修改
        
        2、累加器:解决远程机器上对变量的更新不会传递回驱动程序的问题。由SparkContext对象创建,sc.accumulator(0)
            注意事项:
                1、在Driver端定义赋初始值,只能在Driver端读取最后的值,在Executor端更新。
                2、累加器不是一个调优操作,因为如果不这样做,结果是错的。
                
                
    控制算子:cache、persist、checkpoint
        持久化的单位是Partition,cache和persist都是懒执行的,必须有一个action类算子触发执行。checkpoint算子不仅将RDD持久化到磁盘,还能切断RDD之间的依赖关系。
        
        checkpoint使用场景:
            1、如果一个RDD的计算时间比较长或者计算起来比较复杂,对该RDD调用checkpoint,将该RDD的计算结果保存到HDFS上,这样数据会更加安全。
            
            2、如果一个RDD的依赖关系非常长,也可以调用checkpoint来切断依赖关系,提高容错的效率。
        
        checkpoint执行原理:
            1、当RDD的Job执行完毕后,会从finalRDD从后往前回溯。
            2、当回溯到某一个RDD调用了checkpoint方法,会对当前的RDD做一个标记。
            3、spark框架会自动启动一个新的Job重新计算这个RDD的数据并将数据持久化到HDFS上。
            优化:对RDD执行checkpoint之前,最好对这个RDD先执行cache,这样新启动的JOB只需要将内存中的数据拷贝到HDFS上即可,省去了重新计算。如RDD.cache();RDD.checkpoint();
        
        PS:cache和persist算子后不能立即紧跟action算子,否则不是持久化RDD,如rdd.cache().count():返回的不是持久化RDD而是一个数值。
        
    Spark4种运行模式
        1、Local
        2、Standalone
        3、Yarn
        4、Mesos
        
        
    Standalone模式两种提交任务方式(client是独立于spark集群的一个节点,只需要将配置好了spark包原封不动的拷贝一个到集群外的任一节点充当client即可)
        spark集群的角色:master、worker(executor运行在worker)
        1、client执行流程
            1、Spark集群启动后,Worker节点会向Master节点汇报资源情况,这样Master即可掌握集群资源的使用情况。
            2、client提交任务后,会在client端启动Driver进程。
            3、Driver向Master申请启动Application的资源。
            4、资源申请成功后,Driver端将task发送到worker端执行。
            5、worker将task执行结果返回到Driver端。
            
        PS:适用于测试,在Client端可以看到task执行情况。此模式下,Driver在每次提交application时都会在client启动,会导致client端网卡流量暴增问题。
        
        2、cluster执行流程
            1、Spark集群启动后,Worker节点会向Master节点汇报资源情况,这样Master即可掌握集群资源的使用情况。
            2、client提交任务后,会向master申请启动Driver。
            3、master接收请求,随机在集群中的一台节点启动Driver。
            4、Driver启动后,为当前的application申请资源。
            5、资源申请成功后,Driver将task发送到worker节点执行。
            6、worker将task执行结果返回给Driver端。
            
        PS:Client端无法查看task的具体执行情况,多次提交的application都会在集群中随机选择一台节点来启动Driver,将网卡暴增问题散布在集群上而非某一个节点上。
        
        
        
    Yarn模式两种提交任务方式
        1、client模式
            1、Spark集群启动后,Worker节点会向Master节点汇报资源情况,这样Master即可掌握集群资源的使用情况。
            2、client提交任务后,会在client端启动Driver。
            3、Driver启动成功后会向ResourceManager发送启动ApplicationMaster请求。
            4、ResourceManager接受请求,在集群中随机选择一台NodeManager来启动ApplicationMaster。这里的NodeManager相当于Standalone中的worker。
            5、ApplicationMaster启动成功之后,向ResourceManager申请一批Container资源,用于启动Executor。
            6、ResourceManager返回一批可用的NodeManager节点给ApplicationMaster。
            7、ApplicationMaster向NodeManager发送启动Executor的命令。
            8、Executor启动后,会反向注册给Driver,Driver发送task到Executor执行,Executor将执行结果返回给Driver端。
            
        PS:优缺点跟Standalone模式的Client一致。ApplicationMaster有launchExecutor和申请资源的功能,但没有作业调度的功能,作业调度由NodeManager负责。
        
        2、cluster模式
            1、Spark集群启动后,Worker节点会向Master节点汇报资源情况,这样Master即可掌握集群资源的使用情况。
            2、Client提交任务之后,向ResourceManager发送启动ApplicationMaster请求。
            3、RM接收请求后在集群中随机选一台NM来启动AM。此时的AM相当于Master端。
            4、AM启动后,向RM申请一批Container资源用于启动Executor。
            5、RM返回一批可用的NM节点给AM。
            6、AM向NM发送启动Executor的请求。
            7、Executor启动后,反向注册到AM所在节点的Driver。Driver发送task给Executor,Executor将执行结果返回给Driver。
        
    Spark粗粒度资源申请和细粒度资源申请
        1、粗粒度资源申请
            在application执行之前,将所有的资源申请完毕,当资源申请成功后,才会进行任务调度,当所有的task执行完成后,才会释放这部分资源。
            
            优点:在application执行之前所有的资源都申请完毕,每一个task直接使用资源就可以,不需要task在执行前自己去申请资源,task启动变快了,那么task执行也就变快了,stage、job、applic
            ation也就变快了。
            
            缺点:直到最后一个task执行完成才会释放资源,集群的资源无法充分利用。
        
        2、细粒度资源申请
            在application执行之前不需要先去申请资源,而是直接执行,让job中的每个task在执行之前去申请资源,task执行完成就释放资源。
            
            优点:充分利用集群资源。
            
            缺点:task自己去申请资源,那么相应的执行就会变慢,相应的application就会变慢。
        
    Spark任务调度流程
        1、Spark集群启动后,Worker节点会向Master节点汇报资源情况,这样Master即可掌握集群资源的使用情况。
        2、当spark提交一个application后,根据RDD之间的依赖关系将application形成一个DAG有向无环图。
        3、任务(JOB)提交后,spark会在Driver端创建两个对象:DAGScheduler和TaskScheduler,DAGScheduler是任务调度的高层调度器,是一个对象。DAGScheduler的主要作用是将DAG根据RDD之间的宽窄
        依赖关系划分为一个个Stage,每个Stage由一组并行的task组成,以TaskSet的形式提交给TaskScheduler。
        4、TaskScheduler遍历TaskSet集合,拿到每个task后将其发送到Executor上执行(其实是发送到Executor中的线程池ThreadPool去执行)。
        5、task向TaskScheduler汇报自己的运行情况,当执行失败时,由TaskScheduler负责将task重新发送给Executor执行,默认重试3次。如果重试3次依然失败,那么task所在的stage就失败。stage失败
        则由DAGScheduler负责重试,默认重试4次。重试4次之后依然失败,那么这个JOB就执行失败。也就是Application执行失败。
        
        PS:TaskScheduler不仅能重试失败的task,还能重试执行缓慢的任务。如果有运行缓慢的task,那么TaskScheduler会启动一个备份的task来与这个运行缓慢的task执行相同的处理逻辑。哪个task先
        执行完,就以哪个task的执行结果为准。这就是Spark的推测执行机制。默认是关闭的。
        
        
        
    Spark on Hive:Hive只作为存储角色;Spark负责SQL解析优化、执行;
    
    Hive on Spark:Hive即作为存储又负责SQL解析优化;Spark负责执行;
    
    Spark on Hive搭建:
        1、在Spark客户端配置Hive on Spark
           在Spark客户端安装包下的conf文件夹中创建hive-site.xml(也可以从HIVE安装包中拷贝过来);里面只保留以下配置(指定HIVE元数据在哪个节点上)
           <configuration>
                <property>
                    <name>hive.metastore.uris</name>
                    <value>thrift://nodename:port</value>
                </property>
           </configuration>
        2、启动Hive的metastore服务
           hive --service metastore &
           
        3、启动ZK集群和HDFS集群
    
    DataFram、DataSet、RDD的区别:
        
    指定文件保存时的模式:SaveMode
        1、Overwrite:覆盖
        2、Append:追加
        3、ErrorIfExists:如果存在就报错
        4、Ignore:如果存在就忽略
        用法:df.write().mode(SaveMode.Overwrite).format("parquet").save("path") | df.write().mode(SaveMode.Overwrite).format("parquet").parquet("path")
        
    创建DF的几种方式:
        1、读取JSON格式的文件。
        2、通过JSON格式的RDD创建。
        3、动态创建SCHEMA将非JSON格式的RDD转换而来。sqlContext.createDataFrame(rdd,schema);schema=StructType(List(StructField("colname",type,false|true),...))。
        4、读取Parquet文件。
        5、读取JDBC中的数据,如MYSQL;通过sqlContext来读取;如sqlContext.read().format("jdbc").options("指定连接信息").load();
        6、读取HIVE中的数据;通过HiveContext来读取;如val hiveContext = new HiveContext(sc);hiveContext.sql("use databases|drop table|create table...");
        
    存储DF
        1、存储为parquet文件。
        2、存储到JDBC数据库。
        3、存储到HIVE表。
        
    Shuffle(以ReduceByKey为例来)
        ReduceByKey会将上一个RDD中的每一个key对应的所有value聚合成一个value,然后生成一个新的RDD。但是同一个key的value不一定都在同一个partition或者同一个节点上,为了将同一个key的所有
    value汇聚到一个节点上去做聚合,就需要跨分区或者跨节点将同一个key对应的所有value拉取到某一个节点上去做聚合操作,这就是shuffle过程。
        Shuffle分两步:
            Shuffle Write:上一个Stage的每个Map Task必须保证将自己处理的当前分区中相同的key写入一个分区文件中,可能会写入多个不同的分区文件。
            
            Shuffle Read:Reduce Task从上一个Stage的所有Task所在的机器上寻找属于自己的分区文件汇聚到同一个节点上进行处理和聚合。
            
        两种Shuffle类型:
            1、HashShuffle:1.2之前,对应的默认分区器是HashPartitioner。
                1、普通机制
                    1、每个map task将不同结果写入不同的buffer中,每个buffer对应一个磁盘小文件。
                    2、reduce task拉取属于自己的磁盘小文件进行处理。
                    产生的磁盘小文件数:M(map task个数)*R(reduce task个数)

                2、合并机制
                    1、同一Executor下同一stage的所有task任务将相同的结果写入相同的buffer中。
                    2、reduce task拉取属于自己的磁盘小文件进行处理。
                    产生的磁盘小文件数:C(core个数)*R(reduce task个数)
            
            2、SortShuffle:1.2开始,对应的默认分区器是RangePartitioner。
                map task执行完之后会将该map task产生的磁盘小文件合并和一个大的磁盘文件,同时生成一个索引文件。reduce task去map端拉取数据的时候,首先解析索引文件,根据索引文件再去拉取
            对应的数据。
            
        Shuffle寻址过程
            1、map task执行完成之后,MapOutputTrackerWorker对象将封装到MapStatus对象的执行结果及磁盘小文件地址汇报给Driver中MapOutputTrackerMaster。
            2、Reduce task拉取磁盘小文件的时候,首先会在本地的MapOutputTrackerWorker对象中获取,没有再去Driver中的MapOutputTrackerMaster请求磁盘小文件地址并存储在本地的MapOutputTracke
            rWorker里方便下次寻址。
            3、获取到磁盘小文件的地址后,通过本地的BlockManager到数据所在的节点上拉取数据。
            
            
    Spark内存管理
        Spark执行应用程序时,Spark集群会启动Driver和Executor两种JVM。内存管理分为:静态内存管理、统一内存管理。
        
        
    Spark Streaming
    
    
    Spark比MR快的原因?
        1、Spark的计算结果可以放入内存,支持基于内存的迭代,而MR不支持。
        2、Spark有DAG有向无环图,可以实现pipeline计算模式。pipeline计算模式->读取完数据后一条条的并行计算,不用等。
        3、资源调度模式:Spark是粗粒度资源调度;MR是细粒度资源调度;
        
        
    Spark中主要进程的作用?
        1、Driver进程:负责任务的分发和结果的回收。
        2、Executor进程:负责具体任务的执行。
        3、Master进程:Spark资源管理的主进程,负责资源调度。
        4、Worker进程:Spark资源管理的从进程,Worker节点主要运行Executor。
        
        
    Spark调优:
        1、开发调优:
            1、避免创建重复的RDD:对于同一份数据,不应该存在多个RDD,否则会多次重复计算,增加作业的性能开销。
            
            2、尽可能的复用同一个RDD:比如一个RDD的数据格式是K-V类型的,另一个RDD格式是单V类型的。这两个RDD的value数据是一样的。那么此时我们可以只使用K-V的RDD,因为其中已经包含了另一
            个RDD的数据。这样可以减少RDD的数量,从而减少算子的执行次数。 
            
            3、对多次使用RDD进行持久化。persist()方法可以指定持久化的级别,如persist(StorageLevel.MEMORY_AND_DISK_SER)。
            
            4、尽量避免使用Shuffle类算子:Shuffle过程是最耗性能的过程。Shuffle过程简而言之就是将分布在集群中多个节点上的同一个key,拉取到同一个节点上,进行聚合或者join操作等。Shuffle
            过程会涉及到大量的磁盘IO、网络传输。
            PS:会导致Shuffle操作的transformation算子:xxxByKey(除了countByKey)、Join(join、leftOuterJoin、rightOuterJoin、fullOuterJoin、cogroup)、distinct、intersection、subtract
            partitionBy、repartition。
            
            5、使用map-side预聚合的Shuffle操作:因为业务需要,一定要使用Shuffle操作无法使用map类算子来替代时,可以使用map-side预聚合的算子。所谓map-side预聚合就是每个节点在本地对相同
            的key进行一次聚合,类似于MR中本地的combiner。聚合之后,每个节点本地只有一条相同的key。这时在拉取数据的时候相应的磁盘IO跟网络开销也会降低。尽可能的使用reduceByKey或者aggre
            gateByKey算子来替代groupByKey。
            
            6、使用高性能算子:
                1、使用reduceByKey/aggregateByKey替代groupByKey。
                2、使用mapPartitions替代map。
                3、使用foreachPartitions替代foreach。
                4、filter之后进行coalesce操作。过滤掉部分数据后,调用coalesce将RDD中的数据压缩到更少的partition中去。
                
            7、使用广播变量
            
            8、使用Kryo优化序列化性能:Spark默认使用的是JAVA的序列化机制。
                在Spark中主要有三个地方涉及到了序列化
                    1、算子函数中使用外部的变量时,该变量会被序列化后进行网络传输。
                    2、将自定义的类型作为RDD的泛型类型时,所有自定义类型对象,都会进行序列化。
                    3、使用可序列化的持久化策略时。
                
                使用Kryo序列化的步骤
                    1、conf.set("spark.serializer","org.apache.spark.serializer.KryoSerializer") --设置序列化器。
                    2、conf.registerKryoClasses(Array(ClassOf[MyClass1],ClassOf[MyClass2]...)) --注册要序列化的自定义类型。
        
        
        2、数据倾斜调优:
            发生数据倾斜的现象:
                1、绝大多数task执行得非常快,个别task执行很慢。这种情况很常见。
                2、原本正常执行的Spark作业,某天突然报OOM(内存溢出)异常,观察异常栈。这种情况比较少见。
                
            发生数据倾斜的原理:(数据倾斜发生在shuffle中)
                在进行shuffle的时候,必须将各个节点上相同的key拉取都某一个节点上的task进行处理。如果某个key对应的数据量特别大的话,就会发生数据倾斜。比如大部分的key对应的记录只有几条
            而个别的key对应的记录有上百万条。只有几条记录的task一秒钟就执行完了,但是几百万条记录的task则需要花费一两个小时。因此,整个Spark作业的运行进度由运行时间最长的那个task决定
            
            定位数据倾斜的位置:
                1、第一种现象
                    1、client模式下提交,在本地log文件中查看当前运行到了哪个stage;cluster模式下提交通过Spark Web UI来查看当前运行到了哪个stage。无论哪种模式提交,都可以通过Spark Web
                    UI来查看当前stage下各个task分配的数据量,进而确定是不是task分配的数据不均匀导致的数据倾斜。
                    2、找到当前stage对应的代码,查看代码中使用的导致shuffle的算子,对其进行分析并调优。
                    
                2、第二种现象
                    1、直接查看log中的异常栈定位到代码中哪一行发生了OOM。
                    2、在该行代码附近查看导致shuffle的算子;当然也有可能是开发编写的代码bug造成的。
                    
            查看导致数据倾斜key的数据分布情况:
                在定位到数据倾斜发生在哪里后,接着分析执行了shuffle操作并且导致数据倾斜的RDD/Hive表。查看一下其中的key的分布,根据key的分布情况,选择具体的方案来解决。
                
            数据倾斜解决方案:
                1、Hive ETL预处理数据
                    场景:导致数据倾斜的是HIVE表。
                    方案思路:通过HIVE ETL对数据按照key进行聚合或者事先跟其他表进行Join,然后将预处理后的HIVE表作为数据源。因为HIVE端已经做了预处理,所以Spark作业中就可不使用shuffle
                    算子。
                    优缺点:避免了数据倾斜,Spark作业性能大幅提升;治标不治本,HIVE ETL还是会发生数据倾斜;
                    方案实践:当业务系统代码频繁调用Spark作业,而且对Spark作业的执行性能要求比较高时,比较实用这种方案;将数据倾斜提前到上游,每天仅执行一次,只有那么一次是执行慢的。
                    之后的每次Spark作业调用,都会非常快。
                    
                2、过滤少数导致倾斜的key
                    场景:通过查看数据分布发现只有少数几个key导致数据倾斜,且这几个key对计算影响不大,那么比较适合使用这种方案。
                    方案思路:直接过滤掉导致数据倾斜的少数key即可。
                    优缺点:避免了数据倾斜,Spark作业性能大幅提升;使用场景不多;
                    方案实践:项目中也采用过这种方案解决数据倾斜。有次发现某一天Spark作业在运行的时候突然OOM了,追查之后发现,是HIVE表中的某一个KEY在那天数据异常,导致数据量暴增。因此
                    就采取每次执行前进行采样,计算出样本中数据量最大的几个key之后,直接在程序中将那些key过滤掉。
                    
                3、提高shuffle操作的并行度
                    场景:建议优先使用这种方案,这是处理倾斜最简单的一种方案。
                    方案思路:在对RDD执行shuffle算子时,给shuffle算子传入一个参数,如reduceByKey(1000),该参数设置了这个shuffle算子执行时shuffle read task的数量。对于Spark SQL中的shuf
                    fle类语句,如group by、join等,需要设置一个参数,即spark.sql.shuffle.partitions,代表shuffle read task的并行度。
                    优缺点:实现简单,有效缓解和减轻数据倾斜的影响;只是缓解,不是彻底解决,而且效果有限;
                    
                4、两阶段聚合(局部聚合+全局聚合)
                    场景:对RDD执行如reduceByKey等聚合类shuffle算子或者在Spark SQL中使用如group by语句进行分组聚合时,比较使用这种方案。
                    方案思路:第一次局部聚合,如在原先的key上加上一个随机数前缀之后对其执行如reduceByKey等聚合操作。接着将聚合后的各个key的前缀去掉,再次执行如reduceByKey等聚合操作进
                    行全局聚合。
                    优缺点:效果明显,通常可以解决掉数据倾斜,至少是大幅度缓解数据倾斜;只适用于聚合类的shuffle操作。
                
                5、将reduce join转为map join
                    场景:在对RDD使用join操作或者在Spark SQL中使用join语句时,join操作中的其中一个RDD或者表的数据量比较小(比如几百M或者一两G),比较适合使用此方案。
                    方案思路:不使用join算子进行连接操作,而是使用broadcast变量与map类算子实现join操作,进而完全规避掉shuffle类操作,彻底避免数据倾斜的发生和出现。将数据量较小的RDD通
                    过collect()算子将全部数据拉取到Driver端的内存中,然后对其构建一个broadcast变量;接着对另一个RDD执行map类算子操作,在算子函数内部,从broadcast变量中获取数据量较小的
                    RDD的全量数据,与当前RDD的每一条数据按照连接Key进行比对,满足连接条件,就将两个RDD的数据以某种方式连接起来。
                    优缺点:对join操作导致的数据倾斜,效果非常好,因为根本就不会发生shuffle,也就根本不会发生数据倾斜;只适用于一个大表和一个小表的情况;
                    
                6、采样倾斜key并分拆join操作
                    场景:两个RDD/HIVE表进行join的时候,对应的数据量都非常大。通过查看key分布发现导致数据倾斜是因为其中一个RDD/HIVE表中少数几个key的数据量过大,而另一个RDD/HIVE表中所
                    有key分布比较均匀,使用这个方案。
                    方案思路:
                        1、对包含数据量过大的几个key的RDD通过sample算子采样出一份样本来,然后统计一下每个key的数量,计算出数据量最大的是哪几个key。
                        2、将这个几个key对应的数据从原来的RDD中拆分出来形成一个单独的RDD,并给每个key附加上一个n以内的随机数作为前缀。其他key则形成另一个RDD。
                        3、接着将key分布均匀的RDD液过滤出那几个key对应的数据形成一个单独的RDD,将每条记录增加到n条记录,并按顺序附加一个0-n的前缀,例如原本只有一条记录为hello,增加10
                        条后变为0_hello,1_hello...10_hello。其他key形成另一个RDD。
                        4、附加随机前缀的两个RDD进行join操作,此时就可以将原先相同的key打散成n峰,分散到多个task中去进行join了。另外两个普通的RDD也进行join操作。
                        5、将两次join的结果使用union算子合并起来即可,就是最终的结果。  
                    优缺点:只适用于数据量比较大的两个RDD/HIVE进行join操作时,导致数据倾斜的是其中一个RDD/HIVE中少数的key的情况。不适用于导致数据倾斜key特别多的情况。
                    
                7、使用随机前缀和扩容RDD进行join
                    场景:两个RDD/HIVE表进行join的时候,其中一个RDD/HIVE表中有大量的key导致数据倾斜的情况。
                    方案思路:
                        1、将造成数据倾斜的RDD中的每条记录添加一个n以内的随机前缀。
                        2、将另一个正常的RDD进行扩容,将每条记录扩容到N条记录。扩容出来的每条数据依次打上一个0~n的前缀。
                        3、最后将两个处理后的RDD进行join即可。
                    优缺点:对join类型的数据倾斜基本都可以处理,而且效果也比较显著,性能提升效果非常不错;只是缓解数据倾斜而不是彻底避免数据倾斜。而且需要多整个RDD进行扩容,内存要求高
                // 首先将其中一个key分布相对较为均匀的RDD膨胀100倍。
                JavaPairRDD<String, Row> expandedRDD = rdd1.flatMapToPair(
                        new PairFlatMapFunction<Tuple2<Long,Row>, String, Row>() {
                            private static final long serialVersionUID = 1L;
                            @Override
                            public Iterable<Tuple2<String, Row>> call(Tuple2<Long, Row> tuple)
                                    throws Exception {
                                List<Tuple2<String, Row>> list = new ArrayList<Tuple2<String, Row>>();
                                for(int i = 0; i < 100; i++) {
                                    list.add(new Tuple2<String, Row>(0 + "_" + tuple._1, tuple._2));
                                }
                                return list;
                            }
                        });
                
                // 其次,将另一个有数据倾斜key的RDD,每条数据都打上100以内的随机前缀。
                JavaPairRDD<String, String> mappedRDD = rdd2.mapToPair(
                        new PairFunction<Tuple2<Long,String>, String, String>() {
                            private static final long serialVersionUID = 1L;
                            @Override
                            public Tuple2<String, String> call(Tuple2<Long, String> tuple)
                                    throws Exception {
                                Random random = new Random();
                                int prefix = random.nextInt(100);
                                return new Tuple2<String, String>(prefix + "_" + tuple._1, tuple._2);
                            }
                        });
                
                // 将两个处理后的RDD进行join即可。
                JavaPairRDD<String, Tuple2<String, Row>> joinedRDD = mappedRDD.join(expandedRDD);
        
        3、资源参数调优:
            如num-executors、executor-memory等
        
        4、Shuffle参数调优:
            如spark.shuffle.file.buffer等
            

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值