面试总结
一、简单说一下数据仓库分层架构?
ODS数据缓冲层:存放的是原始数据,包括业务数据、日志数据、第三方数据等。
DWD数据明细层:对ODS层的数据做一定的清洗和转换。
DWS数据服务层:对DWD层的数据做轻度的汇总,得到业务汇总表或宽表。
ADS数据应用层:汇总得到业务相关的指标或数据。
二、MapReduce的运行流程
MapReduce的运行流程主要包括以下几个阶段:数据预处理、任务分配、Map任务执行、缓存文件定位、Reducer拉取文件、Reduce任务执行和结束。
- 数据预处理:在任务开始前,首先调用类库将输入文件分为多个分片。
- 任务分配:JobTracker为集群中空闲的节点分配Map任务或Reduce任务,通常有M个Map任务和R个Reduce任务,其中Reduce任务数通常小于Map任务数。
- Map任务:Mapper读取所属的文件分片,将每一条输入数据转换为键值对,使用Map函数处理后得到新的键值对作为中间结果缓存在当前节点。
- 缓存文件定位:Map任务得到的中间结果被周期性地写入Mapper所在的本地硬盘中,并把文件的存储位置信息经由JobTracker传递给Reducer。
- Reducer拉取文件:Reducer通过位置信息到相应的Mapper处拉取这些文件,将同一key对应的所有取值合并得到键值组。
- Reduce任务:Reducer使用Reduce函数计算键值组,得到最终结果并将其输出。
- 结束:当所有的Map任务和Reduce任务运行完毕后,系统会自动结束各个节点上的对应进程并将任务的执行情况反馈给用户。
此外,Reduce阶段还包括shuffle过程,主要分为复制Map输出、排序合并两个阶段。Reduce任务通过HTTP向各个Map任务拖取所需数据,数据先放入内存缓冲区,当内存缓冲区达到一定程度时,启动内存中merge操作,最终生成合并后的文件供Reduce函数处理,生成最终的输出文件。这个过程涉及到数据的复制、排序、合并等操作,确保了数据的正确性和效率
三、Spark的Shuffle过程?
Spark的shuffle过程主要包括两个阶段:shuffle write和shuffle read。
-
Shuffle Write阶段:在这个阶段,每个task会为每个reduce端的task创建一个临时的内存缓冲和磁盘文件,并将数据按key进行hash处理后写入对应的内存缓冲或磁盘文件中。当内存缓冲写满后,数据会被溢写到磁盘文件,并将所有临时磁盘文件合并成一个磁盘文件,同时创建一个单独的索引文件。这个阶段的主要目的是将数据按照key的hash值进行分组,为后续的reduce阶段做准备。
-
Shuffle Read阶段:在shuffle read阶段,数据根据索引文件被读取并进行处理。每个reduce任务根据其编号从相应的磁盘文件中获取数据,这些数据随后被处理以生成最终的输出。这个阶段涉及数据的读取、解序列化以及通过网络或本地文件系统传输到reduce任务进行处理。
此外,Spark还提供了不同的shuffle管理器来处理数据的排序和分组,例如hash shuffle和sort shuffle。Hash shuffle主要用于不需要排序的场景,而sort shuffle则适用于需要全局排序的情况。
四、RDD是什么?它的特性有哪些?
RDD是一个弹性分布式数据集。
特性:
-
分布式:RDD的数据可以分布在集群中的不同节点上。
-
弹性:RDD在执行过程中可以根据需要重新分区、重新计算。
-
不可变:RDD是只读的,对RDD的改变唯一方式是执行一个转换操作,产生一个新的RDD。
-
分区:RDD是分区的,每个分区的数据是独立处理的。
-
依赖:RDD之间有着复杂的依赖关系,RDD的转换是依赖于其父RDD的。
五、transformation算子和action算子的区别?分别有哪些?哪些算子会导致shuffle?
1、transformation算子和action算子的区别:
Transformation算子的主要作用是将一个RDD转换生成另一个RDD,例如map、filter等操作,这些操作并不立即执行,而是等到有Action算子时才会真正触发运算。这种延迟计算的模式可以理解为懒加载,提高了计算效率。
Action算子则代表一个具体的行为,它们返回的值不是RDD类型,可能是一个对象、数值或无返回值(Unit)。Action算子的主要作用是对RDD进行累加、合并、保存等操作,例如saveAsTextFile、count等操作,这些操作会立即执行,并且会触发Spark集群的计算任务。
简而言之,Transformation算子主要负责数据的转换和链式处理,而Action算子则负责触发计算并返回结果。这两种算子的协同工作使得Spark能够高效地处理大规模数据集,实现分布式计算的优势
2、transformation算子:
(2)flatMap(func):比map多一步合并操作,首先将数组元素进行映射,然后合并压平所有的数组。
(3)mapPartitions(func):函数中传入的参数是迭代器,迭代器里面保存的是一个分区里面的数据。
(4)distinct:对RDD中的元素进行去重操作。
(5)reduceByKey(func,[numTask]):找到相同的key,对其进行聚合,聚合的规则由func指定。reduce任务的数量可以由numTask指定。
(6)groupByKey():对相同的key进行分组。
(7)aggregateByKey(zeroValue: U, numPartitions: Int)(seqOp: (U, V) => U, combOp: (U, U) => U)
第一个参数代表着 初始值
第二个参数是中间聚合,在每个分区内部按照key执行聚合操作。这个分两步,
第一步先将每个value和初始值作为函数参数进行计算,返回的结果作为新的kv对。然后在对结果再带入到函数中计算。
第三个参数是最终聚合,对中间聚合结果进行最终聚合。
(8)combineByKey ( createCombiner: V=>C, mergeValue: (C, V) =>C, mergeCombiners: (C,C) =>C ):
主要分为三步,
第一步,对value进行初始化处理;
第二步,在分区内部对(key,value)进行处理,
第三步,所有分区间对(key,value)进行处理。
(9)sortBy():排序操作。
3、action算子:
基本RDD的action操作
1、reduce():接收一个函数作为参数,这个函数操作两个相同元素类型的RDD并返回一个同样类型的新元素。
val sum=rdd.reduce( (x,y) => x+y )
2、aggregate(zeroValue)(seqOp, combOp):期待返回的类型的初始值。然后通过一个函数把RDD中的元素合并起来并放入累加器。考虑到每个节点是在本地进行累加的,最终,还需要提供第二个函数来将累加器两两合并。
val result = input.aggregate((0,0))(
(acc, value) => (acc._1 + value, acc._2+1),
(acc1, acc2) => (acc1._1 + acc2._1, acc1._2 + acc2._2))
val avg = result._1 / result._2.toDouble
3、collect():以普通集合或者值的形式,把数据返回驱动器程序,它会将整个RDD的内容返回。通常在单元测试中使用,由于需要将数据复制到驱动器进程中,collect()要求所有的数据都必须能一同放入单台机器的内存中。
rdd.collect()
结果:{1,2,3,3}
Spark的collect方法,是Action类型的一个算子,会从远程集群拉取数据到driver端。最后,将大量数据汇集到一个driver节点上,将数据用数组存放,占用了jvm堆内存,非常容易造成内存溢出,只用作小型数据的观察。
如何避免使用collect:
若需要遍历RDD中元素,可以使用foreach语句;
若需要打印RDD中元素,可用take语句,返回数据集前n个元素,data.take(1000).foreach(println),这点官方文档里有说明;
若需要查看其中内容,可用saveAsTextFile方法;
总之,单机环境下使用collect问题并不大,但分布式环境下尽量规避,如有其他需要,手动编写代码实现相应功能就好。
4、first:返回RDD中的第一个元素,不排序。
scala> var rdd1 = sc.makeRDD(Array(("A","1"),("B","2"),("C","3")),2)
rdd1: org.apache.spark.rdd.RDD[(String, String)] = ParallelCollectionRDD[33] at makeRDD at :21
scala> rdd1.first
res14: (String, String) = (A,1)
5、take():返回RDD中的n个元素,并尝试只访问尽量少的分区,因此该操作会得到一个不均衡的集合。
rdd.take(2)
结果:{1,2}
6、foreach(fuc):对RDD中的每个元素使用给定的函数。
rdd.foreach(func)
7、saveAsTextFile:saveAsTextFile用于将RDD以文本文件的格式存储到文件系统中。
var rdd1 = sc.makeRDD(1 to 10,2)
scala> rdd1.saveAsTextFile("hdfs://cdh5/tmp/lxw1234.com/") //保存到HDFS
hadoop fs -ls /tmp/lxw1234.com
Found 2 items
-rw-r--r-- 2 lxw1234 supergroup 0 2015-07-10 09:15 /tmp/lxw1234.com/_SUCCESS
-rw-r--r-- 2 lxw1234 supergroup 21 2015-07-10 09:15 /tmp/lxw1234.com/part-00000
hadoop fs -cat /tmp/lxw1234.com/part-00000
1
2
3
4
5
Pair RDD的action操作:
1、countByKey ():对每个键对应的元素分别计数
rdd.countBykey()
结果:{(1,1),(3,2)}
2、collectAsMap():将结果以映射的形式返回,以便查询。
rdd.collectAsMap()
结果:Map{(1,2),(3,6)}
3、lookup(key)返回给定键对应的所有值
rdd.lookup(3)
结果:[4, 6]
4、会导致shuffle算子
在Spark中,shuffle操作通常发生在需要重新分区或排序数据的情况下,例如使用groupBy、reduceByKey、sortBy等算子时,Spark会打乱原有的数据分区,重新分配数据到新的分区中,以便进行聚合或排序操作。这种数据的重新分配过程就是shuffle。
1、byKey类的算子:比如reduceByKey、groupByKey、sortByKey、aggregateByKey、combineByKey
2、repartition类的算子:比如repartition(少量分区变成多个分区会发生shuffle)、repartitionAndSortWithinPartitions、coalesce(需要指定是否发生shuffle)、partitionBy
3、join类的算子:比如join(先groupByKey后再join就不会发生shuffle)、cogroup
注意:首先对于上述操作,能不用shuffle操作,就尽量不用,尽量使用不发生shuffle的操作。
其次,如果使用了shuffle操作,那么肯定要进行shuffle的调优,甚至是解决遇到的数据倾斜问题。
六、简述面向对象的特性?
-
封装:封装是面向对象编程的核心思想之一,它将数据和对数据的操作封装在一起,隐藏内部实现细节,确保数据的安全性和完整性。
-
继承:继承性是类与类之间的一种关系,它允许子类继承父类的属性和方法,从而实现代码的重用和扩展。
-
多态:可以理解为一个事物的多种形态。同一方法调用可以根据实际调用对象的不同而采用多种不同的行为方式。
七、java的集合有哪些?聊一聊你对map的理解
Java的集合主要有四大类:List、Set、Map 和 Queue。
- List:有序集合,可以包含重复元素。实现类有 ArrayList 和 LinkedList。
List<String> list = new ArrayList<>();
list.add("Element1");
list.add("Element2");
-
Set:不可包含重复元素。实现类有 HashSet 和 LinkedHashSet。
Set<String> set = new HashSet<>();
set.add("Element1");
set.add("Element2");
-
Map:键值对集合。实现类有 HashMap 和 LinkedHashMap。
Map<String, String> map = new HashMap<>();
map.put("Key1", "Value1");
map.put("Key2", "Value2");
-
Queue:队列,按照 FIFO(先进先出)原则排序。实现类有 PriorityQueue 和 LinkedList(作为队列使用)。
Queue<String> queue = new PriorityQueue<>();
queue.offer("Element1");
queue.offer("Element2");
Map是Java集合框架中的一部分,它提供了一种映射关系的数据结构,即键值对(key-value)映射。在Map中,每个键映射到一个值。
Map接口提供了三种集合视图,它们分别是键集、值集合和键-值映射关系集合。
Map接口的常用实现类有HashMap、TreeMap、LinkedHashMap和Properties。
- HashMap:是一个散列表,它存储的内容是键值对映射。HashMap中的键可以是null,但是只能有一个键为null。HashMap中的元素在插入和检索时是无序的。
示例代码:
Map<String, Integer> map = new HashMap<>(); map.put("one", 1); map.put("two", 2); map.put("three", 3); System.out.println(map); - TreeMap:实现了SortedMap接口,能够把它保存的记录根据键排序。默认是按照键的升序排序。
示例代码:
Map<String, Integer> map = new TreeMap<>(); map.put("one", 1); map.put("two", 2); map.put("three", 3); System.out.println(map); - LinkedHashMap:保存了记录的插入顺序,在用Iteraor遍历LinkedHashMap时,先得到的是先插入的记录。
示例代码:
Map<String, Integer> map = new LinkedHashMap<>(); map.put("one", 1); map.put("two", 2); map.put("three", 3); System.out.println(map); - Properties:该类是HashTable的子类,主要用于读取系统属性,不过在Properties中,键和值都是字符串类型。
示例代码:
Properties properties = new Properties(); properties.put("one", 1); properties.put("two", 2); properties.put("three", 3); System.out.println(properties); - HashTable:是线程安全的一个map实现类,它实现线程安全的方法是在各个方法上添加了synchronized关键字。但是现在已经不再推荐使用HashTable了,因为现在有了ConcurrentHashMap这个专门用于多线程场景下的map实现类,其大大优化了多线程下的性能。
Map接口中定义的常用方法:
-
V put(K key, V value):添加元素 -
V get(Object key):根据键获取值 -
V remove(Object key):根据键删除键值对 -
boolean containsKey(Object key):判断集合是否包含某个键 -
boolean containsValue(Object value):判断集合是否包含某个值 -
Set<K> keySet():获取所有键的不重复集合 -
Collection<V> values():获取所有值的集合 -
Set<Map.Entry<K, V>> entrySet():获取所有的键值对关系 -
int size():返回集合中的元素数量 -
boolean isEmpty():判断集合是否为空 -
boolean equals(Object o):比较集合是否相等 -
int hashCode():返回集合的哈希码
八、Spark和Hadoop的区别?
Hadoop主要是一个分布式系统基础架构,包括HDFS和MapReduce,用于存储和处理大规模数据集。Hadoop更适合离线批处理任务,因为它依赖于磁盘操作,每次迭代都需要将所有中间结果写入磁盘,处理速度相对较慢。
Spark则是一个通用的计算引擎,它可以在内存中处理数据,显著提高了处理速度。Spark支持Java, Scala, Python等多种语言,并提供了丰富的API和工具,Spark不仅支持批处理任务,还支持流处理和实时数据分析。
九、重载和重写的区别?
重载(Overloading)和重写(Overriding)是Java多态性的两种不同表现。
-
重载(Overloading):
重载是在一个类里面,同名的方法如果有不同的参数列表(参数类型,参数个数,参数顺序)则视为重载。
重载的好处是可以以同一个方法名调用不同的方法。
例如:
public class Test {
public void test(int a) {
System.out.println("int a");
}
public void test(String a) {
System.out.println("String a");
}
public void test(int a, String b) {
System.out.println("int a, String b");
}
public void test(String a, int b) {
System.out.println("String a, int b");
}
}
-
重写(Overriding):
重写是子类继承父类的时候,对父类中已有的同名方法进行重新定义。
重写的好处是可以让子类根据需要实现不同的方法。
重写的规则:
-
参数列表必须完全与被重写的方法相同。
-
返回类型必须完全与被重写的方法的返回类型相同。
-
访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。
-
重写的方法不能抛出比被重写方法声明的更多的异常。
例如:
class Parent {
public void test() {
System.out.println("Parent's test()");
}
}
class Child extends Parent {
@Override
public void test() {
System.out.println("Child's test()");
}
}
重载和重写的区别:
-
重载是在一个类中定义多个同名方法,但是它们的参数列表不同。
-
重写是在子类中定义与父类相同名称和参数的方法,用于实现多态。
-
重载关注的是单个类中方法的多态性,而重写关注的是继承类中方法的覆盖。
十、谈一谈你对Datax的理解?
【一】DataX概述
DataX 是阿里巴巴开源的一个异构数据源(多种不同数据源)离线同步工具,用于实现包括关系型数据库(MySQL、Oracle等)、HDFS、Hive、ODPS、HBase、FTP等各种异构数据源之间稳定高效的数据同步功能。
【二】DataX架构原理
【1】设计理念
为了解决异构数据源同步问题,DataX 把复杂的网状的同步链路变成了星型数据链路,DataX 作为中间传输载体负责连接各种数据源。当需要接入一个新的数据源的时候,只需要把此数据源对接到DataX,就能跟已有的数据源做到无缝数据同步。
【2】框架设计
DataX 本身作为离线数据同步框架,采用Framework+Plugin架构构建,把数据源读取和写入抽象成Reader/Writer插件,纳入到整个同步框架中。

(1)Reader:数据采集模块,负责采集数据源的数据,把数据发给Framework
(2)Writer:数据写入模块,负责不断地向Framework取数据,并把数据写入到目的端
(3)Framework:用于连接Reader和Writer,作为两者的数据传输通道,并处理缓存、流控、并发、数据转换等核心问题
【3】运行流程
(1)Job:单个作业同步的作业,称为一个job,一个job启动一个进程
(2)Task:根据不同数据源切分策略,一个job会切分为多个task,task是DataX作业的最小单元,每个Task负责一部分数据的同步工作。
(3)TaskGroup:Scheduler调度模块对Task进行分组,每个Task组叫做一个Task Group,每个Task Group负责以一定的并发度运行其分得得Task,单个Task Group的并发度为5。
(4)Reader->Channel->Writer:每个Task启动后,都会固定启动Reader->Channel->Writer的线程来完成同步工作。
我们很容易实现二次开发,当然主要是针对新插件的开发。DataX 已经实现了非常多的插件
|
类型 |
数据源 |
Reader(读) |
Writer(写) |
文档 |
|---|---|---|---|---|
|
RDBMS 关系型数据库 |
MySQL |
√ |
√ |
读 、写 |
|
Oracle |
√ |
√ |
读 、写 |
|
|
OceanBase |
√ |
√ |
读 、写 |
|
|
SQLServer |
√ |
√ |
读 、写 |
|
|
PostgreSQL |
√ |
√ |
读 、写 |
|
|
DRDS |
√ |
√ |
读 、写 |
|
|
Kingbase |
√ |
√ |
读 、写 |
|
|
通用RDBMS(支持所有关系型数据库) |
√ |
√ |
读 、写 |
|
|
阿里云数仓数据存储 |
ODPS |
√ |
√ |
读 、写 |
|
ADB |
√ |
写 |
||
|
ADS |
√ |
写 |
||
|
OSS |
√ |
√ |
读 、写 |
|
|
OCS |
√ |
写 |
||
|
Hologres |
√ |
写 |
||
|
AnalyticDB For PostgreSQL |
√ |
写 |
||
|
阿里云中间件 |
datahub |
√ |
√ |
读 、写 |
|
SLS |
√ |
√ |
读 、写 |
|
|
图数据库 |
阿里云 GDB |
√ |
√ |
读 、写 |
|
Neo4j |
√ |
写 |
||
|
NoSQL数据存储 |
OTS |
√ |
√ |
读 、写 |
|
Hbase0.94 |
√ |
√ |
读 、写 |
|
|
Hbase1.1 |
√ |
√ |
读 、写 |
|
|
Phoenix4.x |
√ |
√ |
读 、写 |
|
|
Phoenix5.x |
√ |
√ |
读 、写 |
|
|
MongoDB |
√ |
√ |
读 、写 |
|
|
Cassandra |
√ |
√ |
读 、写 |
|
|
数仓数据存储 |
StarRocks |
√ |
√ |
读 、写 |
|
ApacheDoris |
√ |
写 |
||
|
ClickHouse |
√ |
√ |
读 、写 |
|
|
Databend |
√ |
写 |
||
|
Hive |
√ |
√ |
读 、写 |
|
|
kudu |
√ |
写 |
||
|
selectdb |
√ |
写 |
||
|
无结构化数据存储 |
TxtFile |
√ |
√ |
读 、写 |
|
FTP |
√ |
√ |
读 、写 |
|
|
HDFS |
√ |
√ |
读 、写 |
|
|
Elasticsearch |
√ |
写 |
||
|
时间序列数据库 |
OpenTSDB |
√ |
读 |
|
|
TSDB |
√ |
√ |
读 、写 |
|
|
TDengine |
√ |
√ |
读 、写 |
囊括了绝大部分数据源,我们直接拿来用就行;如果如上数据源都未包括你们需要的数据源,你们也可以自实现插件。如果只是使用 DataX ,那下载 DataX 工具包,解压,配置好json文件,启动 DataX 进行同步
到 DataX 的 bin 目录下启动命令行窗口,然后执行如下命令
python datax.py ../job/mysql2Mysql.json
当我们看到如下输出,就说明同步成功了

十一、Hive内部表和外部表的区别?
Hive中的内部表和外部表是两种不同类型的表,它们之间的主要区别在于数据的管理方式。
内部表:Hive默认创建的表是内部表。hive完全管理表(元数据和数据)的生命周期。当删除内部表时,它会删除数据以及表的元数据。
外部表:外部表的数据不是Hive拥有或者管理的,只管理元数据的生命周期。要创建一个外部表,需要使用external关键字。删除外部表时,只会删除表的元数据,而不会删除实际数据。
十二、什么是数据倾斜?数据倾斜的原因和解决办法?
数据倾斜是指在分布式处理中,数据分布不均匀,导致部分数据比较集中,这会使处理过程中某些节点的处理效率过低,甚至造成内存溢出。
数据倾斜的原因主要包括业务本身造成的数据分布不均、key分布不均以及某些SQL操作容易造成数据倾斜。
解决数据倾斜的方法包括调整SQL语句、使用随机值赋值、过滤掉大量null值、使用Map Join将小表加载到内存中在map阶段完成join操作等。
-
原因:
- 业务本身造成的:某些业务场景下,数据本身就存在不均匀分布的情况。
- key分布不均:某些key值出现频率远高于其他值,导致在join操作时,部分reduce任务处理的数据量远大于其他任务。
- 某些SQL操作容易造成数据倾斜:如group by操作中,某些值特别大,导致分发到不同reduce的操作数据量不均;join操作中,如果大量数据为null或者某些key值特别多,也会导致数据倾斜。
-
解决办法:
- 调整SQL语句:例如,在join操作中,可以通过将key值加盐或者其他方式改变其分布,以达到负载均衡的效果。
- 使用随机值赋值:对于key为空或大量null值的情况,可以给其赋予一个随机值,以避免数据倾斜。
- 过滤无用信息:对于group by操作中造成的数据倾斜,可以通过对join中的一个表去重,以此结果过滤无用信息,减少reduce任务的数据量。
- 使用Map Join:当大小表join时,若大表数据分布不均匀,可以将小表加载到内存中,在map阶段完成join操作,避免reduce阶段的数据倾斜。
十三、MySQL查询性能优化有哪些?
避免使用SELECT *:只选择必要的列可以减少数据传输量和处理时间。
避免使用OR和NOT IN:使用OR和NOT IN会导致全表扫描,影响查询性能。
使用LIMIT分页:使用LIMIT分页可以避免一次性返回大量数据。
使用EXPLAIN查看执行计划:可以通过查看执行计划了解SQL的执行情况。
优化WHERE条件:使用where条件限定要查询的数据,避免返回多余的行。避免在索引字段上使用内置函数。避免使用!=、<>操作符。
使用JOIN语句代替子查询:JOIN语句比子查询更高效。
优化GROUP BY和ORDER BY子句:GROUP BY和ORDER BY子句可以使用索引优化器进行优化。
选择合适的数据类型:选择合适的数据类型可以减小索引的大小。
合理使用索引:创建合适的索引可以加快查询速度。
创建复合索引:根据查询条件创建复合索引可以提高查询速度。
避免创建过多的索引:创建过多的索引会影响写入性能。
使用覆盖索引:覆盖索引可以避免回表操作,提高查询速度。
使用临时表缓存中间结果
慎用distinct关键字
如果字段类型是字符串,where时要用引号引起来,否则索引失效
可以考虑在where和order by涉及的列上建立索引,避免全表扫描
浙公网安备 33010602011771号