字节最全数开面试题|有同学拿了字节提前批offer
推荐阅读文章列表:大数据开发面试笔记V4.0 || 面试聊数仓第一 || 小白大数据学习路线
全文1.5w字,题目比较多,请耐心阅读!!!
一、前言
之前发过一篇字节跳动面经汇总的帖子,但是当时答案并没有更新完全,最近在字节开了提前批后,我立马更新了所有的题目答案,发出来供大家面试参考!!!
赶紧投递!!!赶紧投递!!!赶紧投递!!!
二、面经汇总
Hadoop篇
1.介绍一下Hadoop hadoop是什么
Hadoop是一个由Apache基金会所开发的分布式系统基础架构,主要解决海量数据存储与计算的问题,主要包括HDFS、MapReduce和Yarn框架。
2.谷歌的三篇论文是否了解,三驾马车GFS,BigTable,MapReduce
了解(建议浅看一下)
3.hdfs源码你知道的话,讲讲元数据怎么管理的?
Namenode对元数据的管理采用了三种形式:
- 内存元数据:基于内存存储元数据,元数据比较完整
- fsimage文件:磁盘元数据镜像文件,在NameNode工作目录中,它不包含block所在的Datanode 信息
- edits文件:数据操作日志文件,用于衔接内存元数据和fsimage之间的操作日志,可通过日志运算出元数据
4.hdfs 你知道namenode的问题吗?怎么解决?应该就是联邦机制
为了能够水平扩展Namenode,HDFS2.X提供了Federation架构。Federation架构的HDFS集群可以定义多个Namenode/Namespace,这些Namenode之间相互独立,各自分工管理自己的命名空间。HDFS集群中的Datanode提供数据块的共享存储功能,每个Datanode都会向集群中所有的Namenode注册,且周期性地向所有的Namenode发送心跳和块汇报,然后执行Namenode通过响应发回的Namenode指令。
5.hdfs写数据流程
- 首先客户端会向namenode进行请求,然后namenode会检查该文件是否已经存在,如果不存在,就会允许客户端上传文件;
- 客户端再次向namenode请求第一个block上传到哪几个datanode节点上,假设namenode返回了三个datanode节点;
- 那么客户端就会向datanode1请求上传数据,然后datanode1会继续调用datanode2,datanode2会继续调用datanode3,那么这个通信管道就建立起来了,紧接着dn3,dn2,dn1逐级应答客户端;
- 然后客户端就会向datanode1上传第一个block,以packet为单位(默认64k),datanode1收到后就会传给datanode2,dn2传给dn3
- 当第一个block传输完成之后,客户端再次请求namenode上传第二个block。【写的时候,是串行的写入 数据块】
6.namenode如果挂掉了怎么办 【HA配置】
- 当namenode发生故障宕机时,secondary namenode会保存所有的元数据信息,在namenode重启的时候,secondary namenode会将元数据信息发送给namenode。
7.说一下mapredeuce
- map阶段:首先通过把输入目录下的文件进行逻辑切片,默认大小等于block大小,并且每一个切片由一个maptask来处理,同时将切片中的数据解析成<key,value>的键值对,k表示偏移量,v表示一行内容;紧接着调用Mapper类中的map方法。将每一行内容进行处理,解析为<k,v>的键值对,在wordCount案例中,k表示单词,v表示数字1 ;
- shuffle阶段:map端shuffle:将map后的<k,v>写入环形缓冲区【默认100m】,一半写元数据信息(key的起始位置,value的起始位置,value的长度,partition号),一半写<k,v>数据,等到达80%的时候,就要进行spill溢写操作,溢写之前需要对key按照【分区算法默认是,分区号是根据key的hashcode对reduce task个数取模得到的。这时候有一个优化方法可选,combiner合并,就是预聚合的操作,将有相同Key 的Value 合并起来, 减少溢写到磁盘的数据量,只能用来累加、最大值使用,不能在求平均值的时候使用】;然后到文件中,并且进行(多个溢写文件);reduce端shuffle:reduce会同一分区的各个maptask的结果到内存中,如果放不下,就会溢写到磁盘上;然后对内存和磁盘上的数据进行(这样就可以满足将key相同的数据聚在一起); 【Merge有3种形式,分别是内存到内存,内存到磁盘,磁盘到磁盘。默认情况下第一种形式不启用,第二种Merge方式一直在运行(spill阶段)直到结束,然后启用第三种磁盘到磁盘的Merge方式生成最终的文件。】
- reduce阶段:key相同的数据会调用一次reduce方法,每次调用产生一个键值对,最后将这些键值对写入到HDFS文件中。
8.哪个阶段最费时间,环形缓冲区的调优以及什么时候需要调
shuffle:排序和溢写磁盘 原则上说,缓冲区越大,磁盘 io 的次数越少,执行速度就越快
9.环形缓冲区了不了解?说一下他的那个阈值高低的影响
- 环形缓冲区底层就是一个数组,默认大小是100M
- 数组中存放着key和value的数据,以及关于key和value的元数据信息。每个key, value对应一个元数据,元数据由4个int组成,第一个int存放value的起始位置,第二个int存放key的起始位置,第三个int存放partition,第四个int存放value的长度。
- key/value数据和元数据在环形缓冲区中的存储是由equator(赤道)分隔的,key/value按照索引递增的方向存储,元数据则按照索引递减的方向存储。将数组抽象为一个环形结构之后,以equator为界,key/value顺时针存储,元数据逆时针存储。
10.写一个wordcount
public class WordcountMapper extends Mapper<LongWritable, Text, Text, IntWritable>{ Text k = new Text(); IntWritable v = new IntWritable(1); @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { // 1 获取一行 String line = value.toString(); // 2 切割 String[] words = line.split(" "); // 3 输出 for (String word : words) { k.set(word); context.write(k, v); } } } public class WordcountReducer extends Reducer<Text, IntWritable, Text, IntWritable>{ int sum; IntWritable v = new IntWritable(); @Override protected void reduce(Text key, Iterable<IntWritable> values,Context context) throws IOException, InterruptedException { // 1 累加求和 sum = 0; for (IntWritable count : values) { sum += count.get(); } // 2 输出 v.set(sum); context.write(key,v); } }
11.WordCount在MapReduce中键值对变化
<偏移量, 一行数据> => <单词1, 1> <单词2, 1> .... => <单词1,10> <单词2,15>
12.map端为什么要排序?
是为了通过外排(外部排序)降低内存的使用量:因为reduce阶段需要分组,将key相同的放在一起进行规约,使用了两种算法:hashmap和sort,如果在reduce阶段sort排序(内部排序),太消耗内存,而map阶段的输出是要溢写到磁盘的,在磁盘中外排可以对任意数据量分组(只要磁盘够大),所以,map端排序(shuffle阶段),是为了减轻reduce端排序的压力。
13.map端输出的文件组织形式是什么样的?
多个溢写文件合并后的大文件
14.reduce怎么知道从哪里下载map输出的文件
通过MRAPPMaster获取哪些节点有map输出,当map执行结束后,会汇报给MRAPPMaster。reduce中的一个线程会定期询问MRAPPMaster以便获取map输出的位置
15.如果map输出太多小文件怎么办
开启combiner合并,但是在求平均值的时候是不能使用的
16.MapReduce优化的case
- 输入端:合并小文件 combineinputformat
- map端:提高环形缓冲区的大小,减少IO次数 开启combiner
zookeeper篇
1.zookeeper简单介绍一下,为什么要用zk?
Zookeeper是一个开源的分布式的,为分布式应用提供协调服务的Apache项目(文件系统+通知机制)
zk的应用场景:
- 统一命名服务:在分布式环境下,经常需要对服务进行统一命名,便于识别,例如ip地址
- 统一配置管理:在一个集群中,要求所有节点的配置信息是一致的
- 统一集群管理:在一个集群中,需要实时监控每个节点的状态变化
- 负载均衡:在zookeeper中记录每台服务器的访问数,再次请求的时候,让访问最少的服务器去处理当前请求
2.zk的数据存储,当重启后怎么重构zk的数据模型
zk中的数据是保存在节点上的,节点就是znode,多个znode之间构成一棵树的目录结构
zk的数据是运行在内存中,zk提供了两种持久化机制:
- 事务日志:zk把执行的命令以日志形式保存在dataLogDir指定的路径中的文件中(如果没有指定dataLogDir,则按照 dataDir指定的路径)。
- 数据快照:zk会在一定的时间间隔内做一次内存数据快照,把时刻的内存数据保存在快照文件中。
zk通过两种形式的持久化,在恢复时先恢复快照文件中的数据到内存中,再用日志文件中的数据做增量恢复,这样恢复的速度更快
3.zk的选举机制
- zookeeper刚启动的时候:投票过半数时,服务器id大的胜出
- 我举个例子吧,假设有3台服务器,服务器1先启动,此时只有它一台服务器启动了,没有任何服务器可以进行通信,因此处于Looking状态,紧接着服务器2启动,它就会和1进行通信,交换选举结果,此时id较大的2胜出,并且满足半数以上的服务器同意选举2,所以2就成为了leader,最后服务器3启动,虽然自己的id大一些,但是前面已经选出了leader,因此自己就成为了follower
4.follower和observer的区别
- follower与老大Leader保持心跳连接,当Leader挂了的时候,经过投票后成为新的leader
5.zk基于什么协议,zab和raft的区别
zookeeper基于zab协议
zab和raft主要有四点区别:
6.zk怎么扩容,zk机房扩容有什么要注意的吗
zookeeper集群的数量应为奇数:因为根据paxos理论,只有集群中超过半数的节点还存活才能保证集群的一致性。
7.cap原则
- C表示Consitency(一致性,也就是从每个节点读取的数据是一样的),A表示Avaliablity(可用性,也就是整个系统一直处于可用的状态),P表示Partition tolerance(分区容错性,分布式系统在 任何网络分区故障问题的时候,仍然能正常工作),对于一个分布式系统来说的话,最多只能满足其中的两项,并且满足P是必须的,所以往往选择就在CP或者AP中。而zookeeper就是满足了一致性和分区容错性。因为leader节点挂掉的时候,集群会重新选举出leader,在这个期间集群是不满足可用性的
Flume篇
1.Flume都有什么组件,channel的特性以及什么时候该用什么类型的channel,除了Flume还有什么数据收集工具
DataX,Sqoop
Kafka篇
1.Kafka在项目中起到的作用,如果挂掉怎么保证数据不丢失,不使用Kafka会怎样
kafka提供了3种ack应答级别:ack=0,生产者发送过来的数据,不需要等数据落盘就会应答,一般不会使用;ack=1,生产者发送过来的数据,Leader收到数据后就会应答,因为这个级别也会丢失数据,所以一般用于传输普通日志;ack=-1(默认级别),生产者发送过来的数据,Leader和ISR队列里面的所有节点收到数据后才会应答,不会丢失数据,一般用于传输和钱相关的数据,那么为什么提出了这个ISR呢?因为如果我们等待所有的follower都同步完成,才发送ack,假设有一个follower迟迟不能同步,那怎么办呢?难道要一直等吗?因此就出现了ISR队列,这里面会存放和leader保持同步的follower集合,如果长时间(30s)未和leader通信或者同步数据,就会被踢出去。
2.Kafka呢 怎么保证数据一致性 引申到exactly once
分别从如何保证数据不丢失以及数据不重复两个方面来回答:
数据不丢失:......
数据不重复:0.11版本之后,kafka提出了一个非常重要的特性,幂等性(默认是开启的),也就是说无论producer发送多少次重复的数据,kafka只会持久化一条数据,把这个特性和至少一次语义(ack级别设置为-1+副本数>=2+ISR最小副本数>=2)结合在一起,就可以实现精确一次性(既不丢失又不重复)。我大致介绍一下它的底层原理:在producer刚启动的时候会分配一个PID,然后发送到同一个分区的消息都会携带一个SequenceNum(单调自增的),broker会对<PID,partition,SeqNum>做缓存,也就是把它当做主键,如果有相同主键的消息提交时,broker只会持久化一条数据。但是这个机制只能保证单会话的精准一次性,如果想要保证跨会话的精准一次性,那么就需要事务的机制来进行保证(producer在使用事务功能之前,必须先自定义一个唯一的事务id,这样,即使客户端重启,也能继续处理未完成的事务;并且这个事务的信息会持久化到一个特殊的主题当中)
3.Kafka通过哪些机制实现了高吞吐量?
- kafka本身是分布式集群,并且采用分区技术,并行度高
- 读数据采用稀疏索引,可以快速定位要消费的数据
- 写log文件的时候,一直是追加到文件末端,是顺序写的方式,官网中说了,同样的磁盘,顺序写能达到600M/s,而随机写只有100K/s
- 实现了零拷贝技术,只用将磁盘文件的数据复制到页面缓冲区一次,然后将数据从页面缓冲区直接发送到网络中,这样就避免了在内核空间和用户空间之间的拷贝
Hive篇
1.如何理解Hive,为什么使用Hive
hive就是一款构建数据仓库的工具,它可以将结构化的数据映射为一张表,并且可以通过SQL语句进行查询分析。本质上是将SQL转换为MapReduce或者spark来进行计算,数据是存储在hdfs上,简单理解来说hive就是MapReduce的一个客户端工具。
2.Hive的实现逻辑,为什么处理小表延迟比较高
因为其计算是通过MapReduce,MapReduce是批处理,高延迟的。小文件也要执行MapReduce。Hive的优势在于处理大数据,对于处理小数据没有优势
HBase篇
1.Hbase的架构
HBase主要包括region server和master,region server主要用于region的管理,而master主要用于管理region server,另外还有zookeeper和hdfs,zookeeper主要是用来保证master的高可用,hdfs提供存储服务。
2.Hbase的读写流程
写流程:
- 客户端先访问zookeeper,获取元数据表hbase:meta在哪个region server中
- 访问对应的region server,获取到元数据表,根据写的请求,确定数据应该写到哪个region server的哪个region中,然后将region信息和元数据表的信息缓存在客户端的meta cache中,以便下次访问
- 与对应的region server进行通讯
- 将数据先写入到WAL文件中,然后再写入memstore中,数据会在memstore中进行排序
- 写入完成后,region server会向客户端发送ack
- 等到达memstore的刷写时机(达到一个默认值大小或者达到刷写的时间),将数据刷写到HFile中
读流程:
- 客户端先访问zookeeper,获取元数据表hbase:meta在哪个region server中
- 访问对应的region server,获取到元数据表,根据读的请求,确定数据位于哪个region server的哪个region中,然后将region信息和元数据表的信息缓存在客户端的meta cache中,以便下次访问
- 与对应的region server进行通讯
- 先在block cache(读缓存)中读,如果没有,然后去memstore和hfile中读取,将读到的数据返回给客户端并且写入block cache中,方便下一次读取
3.blockcache的底层实现?你提到了LRU那除了LRU还可以有什么方案?
三种实现方案:
LRUBlockCache
SlabCache
BucketCache
4.Hbase有哪几种写入方式,应用场景
- 单条put:线上业务
- 批量put:数据量较大
- 使用Mapreduce:数据量非常大
- bluckload:速度快
Spark篇
1.Spark有哪几种部署模式
- 本地模式:常用于本地开发程序与测试
- 集群模式:
- standalone模式:只使用spark自身节点的运行模式
- yarn模式:yarn作为资源调度框架的运行模式
- mesos模式:Mesos与Yarn同样是一款资源调度管理系统,可以为Spark提供服务
2.spark on yarn的工作流程
- 首先spark的客户端将作业提交给yarn的RM,然后RM会分配container,并且选择合适的NM启动ApplicationMaster,然后AM启动Driver,紧接着向RM申请资源启动executor,Executor 进程启动后会向 Driver 反向注册,全部注册完成后 Driver 开始执行main 函数,当执行到行动算子,触发一个 Job,并根据宽依赖开始划分 stage(阶段的划分),每个 stage 生成对应的 TaskSet(任务的切分),之后将 task 分发到各个 Executor 上执行。
3.怎样提高并行度 相关参数
spark.defalut.parallelism
4.client和cluster模式的区别
- client模式下,driver运行在客户端;
- cluster模式下,driver运行在yarn集群
5.Spark shuffle的过程
- spark的shuffle分为两种实现,分别为HashShuffle(spark1.2以前)和SortShuffle(spark1.2以后)
- HashShuffle分为普通机制和合并机制,分为write阶段和read阶段,write阶段就是根据key进行分区,开始先将数据写入对应的buffer中,当buffer满了之后就会溢写到磁盘上,这个时候会产生mapper的数量*reduer的数量的小文件,这样就会产生大量的磁盘IO,read阶段就是 reduce去拉取各个maptask产生的同一个分区的数据;HashShuffle的合并机制就是让多个mapper共享buffer,这时候落盘的数量等于reducer的数量*core的个数,从而可以减少落盘的小文件数量,但是当Reducer有很多的时候,依然会产生大量的磁盘小文件。
- SortShuffle分为普通机制和bypass机制
- 普通机制:map task计算的结果数据会先写入一个(默认5M)中,每写一条数据之后,就会判断一下,是否达到了阈值,如果达到阈值的话,会先尝试增加内存到当前内存的2倍,如果申请不到才会溢写,溢写的时候先按照key进行分区和排序,然后将数据溢写到磁盘,最后会将所有的临时磁盘文件合并为一个大的磁盘文件,同时生成一个索引文件,然后reduce task去map端拉取数据的时候,首先解析索引文件,根据索引文件再去拉取对应的数据。
- bypass机制:将普通机制的排序过程去掉了,它的触发条件是而当shuffle map task数量小于200(配置参数)并且算子不是聚合类的shuffle算子(比如reduceByKey)的时候,该机制不会进行排序,极大的提高了其性能。
6.讲讲Spark为什么比Hadoop快
- MapReduce需要将计算的中间结果写入磁盘,然后还要读取磁盘,从而导致了;而Spark不需要将计算的中间结果写入磁盘,这得益于Spark的RDD弹性分布式数据集和DAG有向无环图,中间结果能够以RDD的形式存放在内存中,这样大大减少了磁盘IO。(假设有多个转换操作,那么spark是不需要将第一个job的结果写入磁盘,然后再读入磁盘进行第二个job的,它是直接将结果缓存在内存中)
- MapReduce在shuffle时需要花费大量时间排序,而spark在shuffle时如果选择基于hash的计算引擎,是不需要排序的,这样就会节省大量时间。
- MapReduce是多进程模型,每个task会运行在一个独立的JVM进程中,每次启动都需要重新申请资源,消耗了大量的时间;而Spark是多线程模型,每个executor会单独运行在一个JVM进程中,每个task则是运行在executor中的一个线程。
7.RDD是什么,有什么特点
- 它翻译过来就叫做,是一种数据结构,可以理解成是一个集合。在代码中的话,RDD是一个抽象类。还有一个非常重要的特点:RDD是不保存数据的,仅仅封装了计算逻辑,也就是你直接打印RDD是看不见具体值的。
8.RDD的血缘
- 多个连续的RDD的依赖关系,称之为血缘关系;每个RDD会保存血缘关系
9.宽窄依赖
- 宽依赖:父的RDD的一个分区的数据会被子RDD的多个分区依赖,涉及到Shuffle
- 窄依赖:父的RDD的一个分区的数据只会被子RDD的一个分区依赖
10.stage划分
- 对于窄依赖,不会进行划分,也就是将这些转换操作尽量放在在同一个 stage中,可以实现流水线并行计算。 对于宽依赖,由于有 shuffle 的存在,只能在父 RDD 处理完成后,才能开始接下来的计算,也就是说需要要划分 stage(从后往前,遇到宽依赖就切割stage)
11.Transform和Action算子分别列举一些常用的,他们的区别是什么
- 转换算子主要是将旧的RDD包装成新的RDD,行动算子就是触发任务的调度和作业的执行。
- 转换算子:map flatMap filter reducebykey union
- 行动算子:collect foreach take
12.Spark 能产生shuffle的算子
- reduceByKey、sortByKey
- repartition、coalesce
- join、cogroup
13.Spark里的reduce by key和group by key两个算子在实现上的区别并且说一下性能
- groupByKey只能分组,不能聚合,而reduceByKey包含分组和聚合两个功能
- reduceByKey会在shuffle前对分区内相同key的数据进行预聚合(类似于MapReduce的combiner),减少落盘的数据量,而groupByKey只是shuffle,不会进行预聚合的操作,因此reduceByKey的会高一些
14.Spark内存管理
- spark分为堆内内存和堆外内存,堆内内存由JVM统一管理,而堆外内存直接向操作系统进行内存的申请,不受JVM控制
spark.executor.memory
和spark.memory.offHeap.size
- 堆内内存又分为存储内存和执行内存和其他内存和预留内存,存储内存主要存放缓存数据和广播变量,执行内存主要存放shuffle过程的数据,其他内存主要存放rdd的元数据信息,预留内存和其他内存作用相同。
15.看过Spark底层源码没有
- 看过,比如spark shuffle源码,...
16.Spark 数据倾斜
见之前的文章
17.用Spark遇到了哪些问题
- 聚合函数导致内存溢出
- 广播join导致内存溢出
- 数据倾斜
18.Spark join的有几种实现 *
- 包括 broadcast hash join,shuffle hash join,sort merge join,前两种都是基于hash join;broadcast 适合一张很小的表和一张大表进行join,shuffle适合一张较大的小表和一张大表进行join,sort适合两张较大的表进行join。
- 先说一下hash join吧,这个算法主要分为三步,首先确定哪张表是build table和哪张表是probe table,这个是由spark决定的,通常情况下,小表会作为build table,大表会作为probe table;然后构建hash table,遍历build table中的数据,对于每一条数据,根据join的字段进行hash,存放到hashtable中;最后遍历probe table中的数据,使用同样的hash函数,在hashtable中寻找join字段相同的数据,如果匹配成功就join到一起。这就是hash join的过程
- broadcast hash join分为broadcast阶段和hash join阶段,broadcast阶段就是 将小表广播到所有的executor上,hash join阶段就是在每个executor上执行hash join,小表构建为hash table,大表作为probe table
- shuffle hash join分为shuffle阶段和hash join阶段,shuffle阶段就是 对两张表分别按照join字段进行重分区,让相同key的数据进入同一个分区中;hash join阶段就是 对每个分区中的数据执行hash join
- sort merge join分为shuffle阶段,sort阶段和merge阶段,shuffle阶段就是 将两张表按照join字段进行重分区,让相同key的数据进入同一个分区中;sort阶段就是 对每个分区内的数据进行排序;merge阶段就是 对排好序的分区表进行join,分别遍历两张表,key相同就join输出,如果不同,左边小,就继续遍历左边的表,反之,遍历右边的表
19.背压机制应用场景 底层实现
- 背压机制就是根据JobScheduler 反馈作业的执行信息来动态调整receiver的数据接收率
20.spark RDD持久化
- 因为RDD实际上是不存储数据的,那么如果RDD要想重用,那么就需要重头开始再执行一遍,所以为了提高RDD的重用性,就有了RDD持久化
- 分类:缓存和检查点
Flink篇
1.Flink的组成
- jobmanager:相当于一个集群的Master,是整个集群的协调者,负责接收job
- taskmanager:实际负责的Worker
- client:flink程序提交的客户端,当用户提交一个Flink程序时,会首先创建一个Client
2.Flink流批一体解释一下
- Flink 使用一个引擎就支持了DataSet API 和 DataStream API。其中DataSet API用来处理有界流,DataStream API既可以处理有界流又可以处理无界流,这样就实现了流批一体
3.Flink和SparkStreaming区别
- 第一,计算速度的不同,flink是真正的实时计算框架,而sparkstreaming是一个准实时微批次的计算框架,也就是说,sparkstreaming的实时性比起flink,差了一大截。
- 第二,架构模型的不同,Spark Streaming 在运行时的主要角色包括:Driver、Executor,而Flink 在运行时主要包含:Jobmanager、Taskmanager。
- 第三,时间机制的不用,Spark Streaming 只支持处理时间,而Flink支持的时间语义包括处理时间、事件时间、注入时间,并且还提供了watermark机制来处理迟到数据。
4.那Flink shuffle呢?你了解吗?
- 其实就是redistribute,一对多
5.watermark用过吗
- 它就是一种特殊的时间戳,作用就是为了让事件时间慢一点,等迟到的数据都到了,才触发窗口计算
- 当watermark等于窗口时间的时候,就会触发计算
6.checkpoint Chandy-Lamport算法
- flink应用在启动的时候,flink的JobManager创建CheckpointCoordinator
- CheckpointCoordinator(检查点协调器) 周期性的向该流应用的所有source算子发送 barrier(屏障)。
- 当某个source算子收到一个barrier时,便暂停数据处理过程,然后将自己的当前状态制作成快照,并保存到指定的持久化存储(hdfs)中,最后向CheckpointCoordinator报告自己快照制作情况,同时向自身所有下游算子广播该barrier,恢复数据处理
- 下游算子收到barrier之后,会暂停自己的数据处理过程,然后将自身的相关状态制作成快照,并保存到指定的持久化存储中,最后向CheckpointCoordinator报告自身快照情况,同时向自身所有下游算子广播该barrier,恢复数据处理。
- 每个算子按照 上面这个操作 不断制作快照并向下游广播,直到最后barrier传递到sink算子,快照制作完成。
- 当CheckpointCoordinator收到所有算子的报告之后,认为该周期的快照制作成功; 否则,如果在规定的时间内没有收到所有算子的报告,则认为本周期快照制作失败。
7.如何用checkpoint和watermark防止读到乱序数据
- watermark设置延迟时间
- checkpoint进行持久化
8.Kafka和Flink分别怎么实现exactly once,问的比较深入,我只回答了一些用法,二阶段提交说了流程,没说出来机制。
kafka:ack=-1+幂等性
flink利用checkpoint检查点保证精准一次性【超级重点,checkpoint的细节需要知道】
java基础篇
1.java限定词(private那些)
public > protected > default > private
2.ArrayList原理,为什么初始是10,为什么扩容1.5倍
ArrayList是基于动态数组的数据结构;
因为对于1.2倍来说,它的扩容空间很小,很容易就会被装满,就需要不停的去扩容,而对于2倍来说,它的扩容空间很大,反而很难被装满,就很难被扩容。所以最终选择了1.5倍最为扩容的倍数。
3.hashmap的实现原理
在jdk1.8之前,hashmap由 数组-链表数据结构组成,在jdk1.8之后hashmap由 数组-链表-红黑树数据结构组成;当我们创建hashmap对象的时候,jdk1.8以前会创建一个长度为16的Entry数组,jdk1.8以后就不是初始化对象的时候创建数组了,而是在第一次put元素的时候,创建一个长度为16的Node数组;当我们向对象中插入数据的时候,首先调用hashcode方法计算出key的hash值,然后对数组长度取余((n-1)&hash(key)等价于hash值对数组取余)计算出向Node数组中存储数据的索引值;如果计算出的索引位置处没有数据,则直接将数据存储到数组中;如果计算出的索引位置处已经有数据了,此时会比较两个key的hash值是否相同,如果不相同,那么在此位置划出一个节点来存储该数据(拉链法);如果相同,此时发生hash碰撞,那么底层就会调用equals方法比较两个key的内容是否相同,如果相同,就将后添加的数据的value覆盖之前的value;如果不相同,就继续向下和其他数据的key进行比较,如果都不相等,就划出一个节点存储数据;如果链表长度大于阈值8(链表长度符合泊松分布,而长度为8个命中概率很小),并且数组长度大于64,则将链表变为红黑树,并且当长度小于等于6(不选择7是防止频繁的发生转换)的时候将红黑树退化为链表。
4.实现单例模式
// 饿汉式-静态常量方式 public class Singleton { private static Singleton instance = new Singleton(); private Singleton() {} public static Singleton getSingleton () { return instance; } } // 饿汉式-静态代码块 public class Singleton { private static Singleton instance; static { instance = new Singleton(); } private Singleton() {} public static Singleton getSingleton () { return instance; } } // 懒汉式 第一次调用才初始化,实现了懒加载特性,线程不安全 public class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getSingleton() { if (instance == null) { instance = new Singleton(); } return instance; } } // 懒汉式 线程安全 public class Singleton { private static Singleton instance; private Singleton() {} public static synchronized Singleton getSingleton() { if (instance == null) { instance = new Singleton(); } return instance; } } // 双重校验锁 线程安全,效率高 public class Singleton { private volatile static Singleton instance; private Singleton() {} public static Singleton getSingleton() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } // 静态内部类 线程安全,效率高 public class Singleton { private static class SingleHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton() {} public static Singleton getSingleton() { return SingleHolder.INSTANCE; } }
5.多路复用,NIO这些了解过吗?
BIO是同步阻塞的IO模型,NIO是同步非阻塞的IO模型,IO多路复用是异步阻塞的IO模型,AIO是异步非阻塞的IO模型
6.100M的数组 随机查快还是顺序查快 解释为什么?
CPU内部的数据预取机制,会假设短时间内的读取最可能就会发生在下一个(或后边离得不远)的地址上。顺序读取最有利于命中CPU的一二三级缓存。
并发编程篇
1.如何实现多线程 写过多线程吗
class RunnableDemo implements Runnable { private String threadName; public RunnableDemo(String name) { threadName = name; } @Override public void run() { System.out.println(threadName); } } public class Main { public static void main(String[] args) { RunnableDemo thread01 = new RunnableDemo("thread01"); new Thread(thread01).start(); RunnableDemo thread02 = new RunnableDemo("thread02"); new Thread(thread02).start(); RunnableDemo thread03 = new RunnableDemo("thread03"); new Thread(thread03).start(); } }
2.4种线程池功能
- 通过ThreadPoolExecutor构造函数实现(推荐)
- CachedThreadPool:返回一个可根据实际情况调整线程数量的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
- FixedThreadPool:返回一个固定线程数量的线程池,可控制线程最大并发数,超过的线程会在队列中等待
- SingleThreadExecutor:返回一个只有一个线程的线程池,它只会用唯一的线程来执行任务,保证所有任务按照执行顺序执行。
3.java内存模型
java内存模型是一套规范,描述了java程序中各种变量(线程共享变量)的访问规则,其规定所有的共享变量都存储在主内存中,每一个线程都有自己的工作内存,工作内存中只存储该线程对共享变量的副本,线程对变量的所有操作都必须在工作内存中完成,而不能直接读写主内存的变量。
4.volatile的作用
- 保证可见性:如果变量被 volatile 修饰,那么每次修改之后,接下来在读取这个变量的时候一定能读取到该变量最新的值
- 保证有序性:因为编译器或 CPU会对代码的执行顺序进行优化,导致代码的实际执行顺序可能与我们编写的顺序是不同的,在多线程的场景下就会出现问题,利用volatile可以禁止这种重排序的发生
5.synchronized和volatile的区别
- volatile只能修饰变量,synchronized可以用来代码块或者方法
- volatile不能保证数据的原子性,synchronized可以保证原子性
- volatile不会造成线程的阻塞,synchronized会造成线程的阻塞
- volatile主要用于解决变量在多个线程之间的可见性,synchronized主要用于解决多个线程之间访问资源的同步性
6.公平锁与非公平锁的区别
- 公平锁:每个线程获取锁的顺序是按照线程访问锁的先后顺序获取的,最前面的线程总是最先获取到锁。
- 非公平锁:每个线程获取锁的顺序是随机的,并不会遵循先来先得的规则,所有线程会竞争获取锁。
7.lock是公平的还是非公平的
可以根据逻辑自己实现是否公平
8.sychornized怎么优化
- 减少synchronized的范围:同步代码块中尽量短
- 降低synchronized锁的粒度:将一个锁拆分为多个锁提高并行度 (HashTable -> ConcurrentHashMap)
- 读写分离:读取时不加锁,写入和删除时加锁 (ConcurrentHashMap)
9.volatile可以保证原子性吗?
不能
10.reentrantlock底层原理
ReentrantLock先通过CAS尝试获取锁,如果获取了就将锁状态state设置为1(state=0表示无锁),但是如果锁已经被占用,先判断当前的锁是否是自己占用了,如果是的话就将锁计数器会state++(可重入性),如果是被其他线程占用,那么将该线程加入AQS队列并wait()
11.synchronize底层实现
synchronized的锁对象会关联一个monitor(监视器,它才是真正的锁对象),这个monitor不是我们主动创建的,是JVM的线程执行到这个同步代码块,发现锁对象没有monitor就会创建monitor,monitor内部有两个重要的成员变量owner:拥有这把锁的线程,recursions:记录线程拥有锁的次数,当一个线程拥有monitor后,其他线程只能等待。当执行到monitorexit时,recursions会减1,当计数器为0时,这个线程会释放锁
算法篇
都是网上的一些原题,自行寻找答案
1.岛屿问题
2.矩阵最小路径和问题 求矩阵最短路径
3.判断一棵二叉树是否镜像对称
4.判定二叉排序树
5.二叉树之Z遍历
6.非递归实现中序遍历
7.二叉搜索树查找第k个
8.堆排序
9.桶排序
10.股票交易1 2
11.二分查找
12.k个一组反转
13.重排链表
14.链表排序(归并排序实现)
15.包含min函数的栈 O(1)
16.搜索旋转排序数组
17.最长回文子串
18.LRU
19.数据结构 让你设计一个hash表 怎么设计?
20.那设计一个hashtable
21.string转int
---------------------------------------------------------
最全的大数据笔记点击下方 ↓
2023最新大数据开发面试笔记V4.0
----------------------------------------------------------
#数据人的面试交流地##2024提前批##大数据开发##字节跳动##面经#