当前位置: 首页 > 面试经验 >

美团大数据开发面试题库及答案

优质
小牛编辑
115浏览
2023-04-28

美团大数据开发面试题库及答案

大数据开发暑期实习总结 这篇文章总结了美团近30篇面经的题目,想着大家可能也需要答案,于是我根据自己的理解以及网上的一些答案进行了梳理,如果有不对的地方,大家可以评论区留言讨论哦(别喷我~~~)

Java

写一个多线程代码

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();
    }
}

写一个单例

定义:采取一定的方法让某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法

// 饿汉式-静态常量方式
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;
    }
}

LinkedBlockingQueue

定义:LinekedBlockingQueue是一个基于链表的阻塞队列,内部是由节点Node构成,每个被加入队列的元素都会被封装成Node节点,并且节点中有指针指向下一个元素。和队列一样,也符合先进先出的特点。

入队的方法包括put和offer,put方法分为两种情况,一种是队列满了之后,加入元素就会阻塞等待,还有就是队列没满的时候,创建一个node节点放入队列中,如果放完以后队列还有剩余空间,继续唤醒下一个添加线程进行添加。如果放之前队列中没有元素,放完以后要唤醒消费线程进行消费。offer方法不同的是 当队列满了之后,直接返回false,不会阻塞

出队的方法包括take和poll,take方法分为两种情况,一种是队列为空,删除元素就会阻塞等待,还有就是队列不为空的时候,从队首获取并移除一个元素,如果消费后还有元素在队列中,继续唤醒下一个消费线程进行元素移除。如果消费之前队列是满元素的情况,移除完后要唤醒生产线程进行添加元素。poll方法不同的是 队列为空时,直接返回false,不会阻塞

LinkedBlockingQueue实现的队列中的锁是分离的,其添加采用的是putLock,移除采用的则是takeLock,这样能大大提高队列的吞吐量,也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。

模板设计模式

定义:把抽象类整体就可以看做成一个模板,模板中不能决定的东西定义成抽象方法,让使用模板的类(继承抽象类的类)去重写抽象方法实现需求,模板已经定义了通用结构,使用者只需要关心自己需要实现的功能即可

public abstract class CompositionTemplate {
  public final void write(){
      System.out.println("<<我的爸爸>>");
      body();
      System.out.println("啊~ 这就是我的爸爸");
  }
  public abstract void body();
}
// Tom
public class Tom extends CompositionTemplate {
  @Override
  public void body() {
      System.out.println("那是一个秋天, 风儿那么缠绵,记忆中, " +
              "那天爸爸骑车接我放学回家,我的脚卡在了自行车链当中, 爸爸蹬不动,他就站起来蹬...");
  }
}
// Tony
public class Tony extends CompositionTemplate {
  @Override
  public void body() {

  }
}

如何设计一个 生产者-消费者队列

我:涉及到多线程安全的问题,所以需要给队列加锁。

面试官:如果加锁,生产操作会阻塞消费操作,有没有更好的方式?

我:我说利用两个锁,一个生产者锁,一个消费者锁。其实就是blockingqueue阻塞队列

ThreadLocal底层

  • 定义:ThreadLocal叫做线程变量,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,因此就不会有多线程安全问题。
  • 原理:我们从Thread类源码中可以看到有一个 threadLocals变量,它是 ThreadLocalMap 类型的变量, 而ThreadLocalMap 是ThreadLocal的静态内部类,当我们调用set或者get方法的时候,实际上是将变量存放到了当前线程的ThreadLocalMap

Synchronized 锁机制

  • synchronized的锁对象会关联一个monitor(监视器,它才是真正的锁对象),这个monitor不是我们主动创建的,是JVM的线程执行到这个同步代码块,发现锁对象没有monitor就会创建monitor,monitor内部有两个重要的成员变量owner:拥有这把锁的线程,recursions:记录线程拥有锁的次数,当一个线程拥有monitor后,其他线程只能等待。当执行到monitorexit时,recursions会减1,当计数器为0时,这个线程会释放锁

volatile使用场景和原理 ,与synchronized的区别

  • 适用场景:如果某个共享变量自始至终只是被赋值或者读取,而没有其他类似于自加这样的复杂操作,我们就可以使用volatile布尔标记位,因为boolean类型的标记位是会被直接赋值的,此时不会存在复合操作,并且volatile可以保证可见性,那么标记位一旦改变,其他线程立马可以得到最新的结果值
  • 区别:volatile只能修饰变量,synchronized可以用来代码块或者方法volatile不能保证数据的原子性,synchronized可以保证原子性volatile不会造成线程的阻塞,synchronized会造成线程的阻塞volatile主要用于解决变量在多个线程之间的可见性,synchronized主要用于解决多个线程之间访问资源的同步性

Java内存模型

JMM定义了Java 虚拟机在计算机内存中的工作方式。其规定所有的共享变量都存储在主内存中,每一个线程都有自己的工作内存,工作内存中只存储该线程对共享变量的副本,线程对变量的所有操作都必须在工作内存中完成,而不能直接读写主内存的变量。JMM的作用就是在多线程读写共享数据时,对共享数据的可见性、有序性和原子性的保证。

如何提高hashtable、hashmap性能

  • 应该避免HashMap多次进行了hash重构,扩容是一件很耗费性能的事,在默认中initialCapacity只有16,而loadFactor是 0.75,需要多大的容量,你最好能准确的估计你所需要的最佳大小,同样的Hashtable也是一样的道理。

redis如何使用布隆过滤器

  • 布隆过滤器本质上是一个二进制数组,元素的值不是1就是0。当我们存一个商品id为10的商品,假设我们经过三次哈希,存的数组下标为1,3,7,就将这三个下标的元素改为1。这样每次访问redis之前,先访问布隆过滤器。查询id为10的商品的时候,经过布隆过滤器的哈希算法,获取到该商品对应的下标是1,3,7。那么,如果这三个数组的下标对应的元素都为1,则表示存在该商品,放行这次请求。如果有一个为0,则不存在该商品。
  • 总结:布隆过滤器判断存在不一定真的存在,但是,判断不存在则一定不存在。

多线程的实现方式

  1. 继承Thread类,只需要创建一个类继承Thread类然后重写run方法,在main方法中调用该类实例对象的start方法
  2. 实现Runnable接口,只需要创建一个类实现Runnable接口然后重写run方法,在main方法中将该类的实例对象传给Thread类的构造方法,然后调用start方法
  3. 实现Callable接口,只需要创建一个类实现Callable接口然后重写call方法(有返回值),在main方法中将该类的实例对象传给 Future接口的实现类FutureTask的构造方法,然后再将返回的对象传给Thread类的构造方法,最后调用start方法
  4. 线程池,首先介绍它的好处,然后再说它可以通过ThreadPoolExecutor类的构造方法来进行创建。

线程池的作用、如何实现

  • 作用:降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗提高响应速度,当任务到达时,任务可以不需要等待线程就能立即执行提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅消耗资源而且降低系统的稳定性,使用线程池可以进行线程的统一分配,调优和监控
  • 实现方式:通过ThreadPoolExecutor构造函数实现(推荐)通过Executor框架的工具类Executors来实现我们可以创建三种类型的ThreadPoolExecutor:(不推荐,容量为Integer.MAX_VALUE,所以容易OOM)

大数据

hdfs的读写流程

  • 写流程:hadoop fs -put a.txt /user/sl/首先客户端会向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。【写的时候,是串行的写入 数据块】
  • 读流程:hadoop fs -get a.txt /opt/module/hadoop/data/首先客户端向namenode进行请求,然后namenode会检查文件是否存在,如果存在,就会返回该文件所在的datanode地址,这些返回的datanode地址会按照集群拓扑结构得出 datanode与客户端的距离,然后进行排序;然后客户端会选择排序靠前的datanode来读取block,客户端会以packet为单位进行接收,先在本地进行缓存,然后写入目标文件中。【读的时候,是并行的读取 数据块】

hdfs副本机制,为什么三副本,副本存放策略

副本机制:hdfs上的文件对应的Block会保存多个副本,这样如果副本丢失或节点宕机,就可以自动恢复,默认保存3个副本。

为什么3副本:算是一种权衡的数值,既可以保证数据的可靠性,也能改善读写性能。

副本存放策略:第一个副本会选择client所在的节点上,第二个副本会选择和第一个副本同机架的不同节点上,第三个副本会选择不同机架的任意节点上。

hdfs容错机制(secondarynamenode)

  • nammode高可用:配置多个namenode,一旦active挂了,standby就会起来
  • secondarynamenode机制:如果 NameNode 中的元数据丢失,是可以从 Secondary NameNode 恢复一部分元数据信息的,但不是全部,因为 NameNode 正在写的 edits 日志还没有拷贝到 Secondary NameNode,这部分恢复不了
  • 副本机制:每一个block都会有2-3个备份存在其他的设备上
  • 心跳机制:DataNode以固定周期 (3s) 向NameNode发送心跳,NameNode如果在一段时间内没有收到心跳,会把该节点判定为死亡

MapReduce执行流程

  • map阶段:首先通过InputFormat把输入目录下的文件进行逻辑切片,默认大小等于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按照分区进行快速排序【分区算法默认是HashPartitioner,分区号是根据key的hashcode对reduce task个数取模得到的。这时候有一个优化方法可选,combiner合并,就是预聚合的操作,将有相同Key 的Value 合并起来, 减少溢写到磁盘的数据量,只能用来累加、最大值使用,不能在求平均值的时候使用】;然后溢写到文件中,并且进行merge归并排序(多个溢写文件);reduce端shuffle:reduce会拉取copy同一分区的各个maptask的结果到内存中,如果放不下,就会溢写到磁盘上;然后对内存和磁盘上的数据进行merge归并排序(这样就可以满足将key相同的数据聚在一起); 【Merge有3种形式,分别是内存到内存,内存到磁盘,磁盘到磁盘。默认情况下第一种形式不启用,第二种Merge方式一直在运行(spill阶段)直到结束,然后启用第三种磁盘到磁盘的Merge方式生成最终的文件。】
  • reduce阶段:key相同的数据会调用一次reduce方法,每次调用产生一个键值对,最后将这些键值对写入到HDFS文件中。

spark和mr区别

shuffle都是需要落盘的,因为在宽依赖中需要将上一个阶段的所有分区数据都准备好,才能进入下一个阶段,那么如果一直将数据放在内存中,是耗费资源的

  • MapReduce需要将计算的中间结果写入磁盘,然后还要读取磁盘,从而导致了频繁的磁盘IO;而Spark不需要将计算的中间结果写入磁盘,这得益于Spark的RDD弹性分布式数据集和DAG有向无环图,中间结果能够以RDD的形式存放在内存中,这样大大减少了磁盘IO。(假设有多个转换操作,那么spark是不需要将第一个job的结果写入磁盘,然后再读入磁盘进行第二个job的,它是直接将结果缓存在内存中)
  • MapReduce在shuffle时需要花费大量时间排序,而spark在shuffle时如果选择基于hash的计算引擎,是不需要排序的,这样就会节省大量时间。
  • MapReduce是多进程模型,每个task会运行在一个独立的JVM进程中,每次启动都需要重新申请资源,消耗了大量的时间;而Spark是多线程模型,每个executor会单独运行在一个JVM进程中,每个task则是运行在executor中的一个线程。

求TopN 扩展:如果量过大不能完全写入内存怎么解决,MapReduce怎么实现

小数据量

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);
        }
        PriorityQueue<Map.Entry<Integer, Integer>> pq = new PriorityQueue<>((o1, o2) -> {
            return o1.getValue() - o2.getValue();
        }); // 小顶堆
        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            if (pq.size() < k)
                pq.offer(entry);
            else {
                if (pq.peek().getValue() < entry.getValue()) {
                    pq.poll();
                    pq.offer(entry);
                }
            }
        }
        int[] res = new int[k];
        for (int i = 0; i < k; i++) {
            res[i] = pq.poll().getKey();
        }
        return res;
    }
}

大数据量 {"movie":"1193","rate":"5","timeStamp":"978300760","uid":"1"}

public class TopN {
    public static class MapTask extends Mapper<LongWritable, Text, Text, MovieBean>{
        @Override
        protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, MovieBean>.Context context)
                throws IOException, InterruptedException {
            try {
                MovieBean movieBean = JSON.parseObject(value.toString(), MovieBean.class);
                String movie = movieBean.getMovie();
                context.write(new Text(movie), movieBean);
            } catch (Exception e) {
                
            }
        }
    }
    
    public static class ReduceTask extends Reducer<Text, MovieBean, MovieBean, NullWritable>{
        @Override
        protected void reduce(Text movieId, Iterable<MovieBean> movieBeans,
                Reducer<Text, MovieBean, MovieBean, NullWritable>.Context context)
                throws IOException, InterruptedException {
            List<MovieBean> list = new ArrayList<>();
            
            for (MovieBean movieBean : movieBeans) {
                MovieBean movieBean2 = new MovieBean();
                movieBean2.set(movieBean);
                list.add(movieBean2);
            }
            Collections.sort(list, new Comparator<MovieBean>() {
                @Override
                public int compare(MovieBean o1, MovieBean o2) {
                    return o2.getRate() - o1.getRate();
                }
            });
            for (int i = 0; i < Math.min(20, list.size()); i++) {
                context.write(list.get(i), NullWritable.get());
            }
        }
    }
    
    public static void main(String[] args) throws Exception{
        Configuration conf = new Configuration();
        
        Job job = Job.getInstance(conf, "avg");
        
        //设置map和reduce,以及提交的jar
        job.setMapperClass(MapTask.class);
        job.setReducerClass(ReduceTask.class);
        job.setJarByClass(TopN1.class);
        
        //设置输入输出类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(MovieBean.class);
        
        job.setOutputKeyClass(MovieBean.class);
        job.setOutputValueClass(NullWritable.class);
        
        //输入和输出目录
        FileInputFormat.addInputPath(job, new Path("E:/data/rating.json"));
        FileOutputFormat.setOutputPath(job, new Path("E:\\data\\out\\topN1"));
        
        //判断文件是否存在
        File file = new File("E:\\data\\out\\topN1");
        if(file.exists()){
            FileUtils.deleteDirectory(file);
        }
        
        //提交任务
        boolean completion = job.waitForCompletion(true);
        System.out.println(completion?"你很优秀!!!":"滚去调bug!!");
        
    }
}

spark有几种部署模式

主要有三种部署模式:

  1. Local:不需要其他任何节点资源就可以在本地执行Spark代码的环境
  2. Standalone:只使用Spark自身节点运行的集群模式
  3. Yarn:Spark作为计算框架,Yarn作为调度框架

spark的stage是怎么划分的

对于窄依赖,不会进行划分,也就是将这些转换操作尽量放在在同一个 stage中,可以实现流水线并行计算。 对于宽依赖,由于有 shuffle 的存在,只能在父 RDD 处理完成后,才能开始接下来的计算,也就是说需要要划分 stage(从后往前,遇到宽依赖就切割stage)

reduce join如何执行

  • 分为三个阶段:map阶段,shuffle阶段,reduce阶段map阶段:对来自不同表的数据打标签,然后用连接字段作为key,其余部分和标签作为value,最后进行输出shuffle阶段:根据key的值进行hash,这样就可以将key相同的送入一个reduce中reduce阶段:对来自不同表的数据进行join操作就可以了

大数据量如何优化join

  • 大表join小表:mapjoin
  • 大表join大表:加盐

hive优化手段

  • 建表优化:分区表:减少全表扫描,通常查询的时候先基于分区过滤,再查询分桶表:按照join字段进行分桶,join的时候就不会全局join,而是桶与桶之间进行join合适的文件格式:公司中默认采用的是ORC的存储格式,这样可以降低存储空间,内部有两个索引(行组索引和布隆过滤器索引)的东西,可以加快查询速度我知道的hive的文件存储格式有textFile,sequenceFile,ORC,Parquet;其中textFile为hive的默认存储格式,它和sequenceFile一样都是基于行存储的,ORC和Parquet都是基于列存储的。sequenceFile、ORC和Parque文件都是以二进制的方式存储的。合适的压缩格式:减少了IO读写和网络传输的数据量,比如常用的LZO(可切片)和snappy
  • 语法优化:单表查询优化:列裁剪和分区裁剪:如果 select * 或者不指定分区,全列扫描和全表扫描效率都很低(公司规定了必须指定分区,select *没有明确规定)group by优化:开启map端聚合开启负载均衡:这样生成的查询计划会有两个 MR Job,一个是局部聚合(加随机数),另外一个是全局聚合(删随机数)SQL写成多重模式:有多条SQL重复扫描一张表,那么我们可以写成 from 表 select... select...多表查询优化:CBO优化:选择代价最小的执行计划;自动优化 HQL 中多个 Join 的顺序,并选择合适的 Join 算法set hive.cbo.enable = true(默认开启)谓词下推:将 SQL 语句中的 where 谓词逻辑都尽可能提前执行,减少下游处理的数据量。hive.optimize.ppd = true(默认开启)MapJoin:将join双方比较小的表直接分发到各个Map进程的内存中,在Map进程中进行join操作,这样就不用进行Reduce,从而提高了速度 set hive.auto.convert.join=true(默认开启)set hive.mapjoin.smalltable.filesize=25000000(默认25M以下是小表)SMB Join:分桶join,大表转换为很多小表,然后分别进行join,最后union到一起
  • job优化:map优化复杂文件增加map数小文件合并map端聚合推测执行reduce优化合理设置reduce:为什么reduce的数量不是越多越好?过多的启动和初始化 reduce 也会消耗时间和资源;另外,有多少个 reduce,就会有多少个输出文件,如果生成了很多个小文件,那么如果这些小文件作为下一个任务的输入,则也会出现小文件过多的问题;推测执行任务整体优化:fetch抓取:Hive 中对某些情况的查询可以不必使用 MapReduce 计算【全局查找、字段查找、limit 查找】 hive.fetch.task.conversion=more小数据集启用本地模式 hive.exec.mode.local.auto=true多个阶段并行执行 set hive.exec.parallel=trueJVM重用:针对小文件过多的时候使用

HiveSQL怎么执行

  • 首先客户端提交SQL以后,Hive利用Antlr框架对HQL完成词法语法解析,将HQL转换成抽象语法树
  • 然后遍历AST,将其转换成queryblock 查询块,可以理解为最小的查询执行单元,比如where
  • 然后遍历查询块,将其转换为 操作树,也就是逻辑执行计划
  • 然后使用优化器对操作树进行逻辑优化,源码中会遍历所有的优化方式,比如mapjoin,谓词下推等,来达到减少MapReduce Job,减少shuffle数据量的目的
  • 最后通过执行器将逻辑执行计划转换为物理执行计划(MR到这就结束了)(Tez和Spark还需要 使用物理优化器对任务树进行物理优化),提交到hadoop集群运行

hive中内部表和外部表的区别

  • 我认为主要有两点的区别:内部表的数据由hive自身管理,外部表的数据由hdfs管理删除内部表的时候,元数据和原始数据都会被删除,而删除外部表的时候仅仅会删除元数据,原始数据不会被删除
  • 使用场景:通常都会建外部表,因为一个表通常要多个人使用,以免删除了,还可以找到数据,保证了数据安全

spark容错机制

  • 容错机制主要包括四个方面第一,stage输出失败的时候,上层调度器DAGScheduler会进行重试第二,计算过程中,某个task失败,底层调度器会进行重试第三,计算过程中,如果部分计算结果丢失,可以根据窄依赖和宽依赖的血统重新恢复计算第四,如果血统非常长,可以对中间结果做检查点,写入磁盘中,如果后续计算结果丢失,那么就从检查点的RDD开始重新计算。

flink里面保证容错性的技术是什么

  • checkpoint,这里需要进一步介绍checkpoint的原理(详细见V3.0笔记)

数据倾斜怎么处理

join数据倾斜优化

map倾斜
  • 原因:上游表文件大小特别不均匀,并且小文件特别多,导致map端读取的数据分布不均匀,引起长尾map端做聚合的时候(map join),由于某些map读取文件的某些值特别多,引起长尾
  • 解决:合并小文件,让文件大小尽量保持一致(调高单个map读取小文件的个数)对大表进行 distribute by rand() ,会将 Map 端分发后的数据重新按照随机值再进行一次分发
reduce倾斜
  • 原因:shuffle + key分布不均匀
  • 解决:最简单的解决办法是 增大reduce并行度join的某表比较小:采用map join: /*+ MAPJOIN(b) */ (Hint增强)并且可以适当提高小表大小的限制join的两张表都比较大,并且长尾是空值导致的:将空值处理成随机值,避免聚集 `select * from a join b ON coalesce(a.key,rand()*9999) = b.key`join的两张表都比较大,并且长尾是热点值导致的:先将热点值取出,对于主表数据用热点key切分成热点数据和非热点数据两部分分别和另外一张表进行join,最后进行union alljoin的两张表都比较大,并且长尾是由很多key导致的:将其中一张表的key加上随机前缀,另外一张表和随机前缀做笛卡尔积以确保可以和前面的表进行join,然后就是进行join操作,再去掉前缀

group by倾斜(单表数据倾斜优化)

  • 背景:如果某个key的数据量特别大,数据都集中到某一个reduce Task去进行相关数据的处理,这就导致了数据倾斜问题。
  • 解决方案:首先采用局部聚合,即给key加上100以内的随机前缀,进行一次局部聚合,然后对本次局部聚合后的结果进行去掉随机前缀,进行一次数据的全局聚合。
-- 原始SQL
select tkey, sum(tvalue) from a group by tkey;
-- 优化后的SQL
select split(tkey,'_')[1] as key, sum(tvalue) as total from 
(
    select concat_ws("_", ceiling(rand()*99), key) as tkey, sum(value) tvalue 
    from a 
    group by concat_ws("_", ceiling(rand()*99), key)
) temp 
group by split(tkey,'_')[1];

count distinct 优化

  • 问题:`select count(distinct account) from...where...`我发现不管reduce的个数设置为多少个,运行日志都是1,查找百度发现:Hive在处理count这种计算时,它会忽略用户指定的Reduce Task数,而强制使用1
  • 解决:转换为先group by,然后count

实时处理的了解吗(我说flink),反压了解吗。

  • flink是一个分布式的计算框架,主要用于对有界和无界数据流进行有状态计算
  • Flink的反压是通过TCP的反压来控制的TaskManager之间会启动一个TCP通道进行数据交互,TaskManager的所有Task通过多路复用使用同一个TCP通道。因此,当下游因一个Task处理能力不足造成反压时,就会导致整个TCP通道阻塞。即使其他Task还有空余的Buffer也无法接受数据。上游ResultPartition只能通过TCP通道的状态被动感知下游处理能力,不能提前调整数据发生评率,也不能根据ResultPartition当前数据挤压情况调节下游节点的数据处理能力。

两个窗口一个数据正常均匀,一个数据不平衡(比如前面数据特别多后面特别少),怎么处理

  • 可以调整窗口的起始位置和结束位置

计算机基础

OSI7层模型

  • 自上而下分别是应用层,表示层,会话层,传输层,网络层,数据链路层,物理层;应用层为各种各样的应用提供网络服务,因为网络应用非常的多,所以就要求应用层采用不同的应用协议来解决不同的应用请求,因此应用层是最复杂的一层。典型的协议有http 80,ftp 21,SMTP 25等;表示层主要处理在两个进程之间交换信息的方式;(一个系统的信息另一个系统看得懂)会话层主要负责两个进程之间的会话管理,包括建立,管理及终止进程间的会话;传输层主要负责两个进程之间数据的传输服务;网络层主要负责将传输层产生的TCP报文段或者UDP数据报封装成IP数据报;(选择合适的网间路由分发数据)数据链路层主要负责将网络层产生的IP数据报封装成帧;物理层主要就是实现比特流的透明传输;

http属于哪一层,tcp属于哪一层

http属于应用层,tcp属于传输层

http协议是哪一层的协议,讲一下对http的了解

  • HTTP协议又叫做超文本传输协议,它是一个应用层协议,规定了在浏览器和服务器之间的请求和响应的格式与规则。它有几个重要的特点HTTP是无状态的HTTP采用TCP作为传输层协议,保证了数据的可靠传输HTTP是无连接的,也就是说通信的时候不需要先建立HTTP连接

post和get请求的区别

  • 用途不同:get一般用于获取数据,而post一般用于提交数据
  • 安全性不同:get把请求的数据放在url上,而post把数据放在请求体中,更加安全
  • 数据长度不同:get对传输的数据长度有限制(与浏览器有关), 而post理论上没有限制,因为数据都是放在请求体中的
  • 是否自动缓存:get请求会被浏览器自动缓存,而post的缓存需要手动设置
  • 是否符合幂等性:GET是幂等的,而POST不是幂等的(对同一URL的多个请求应该返回同样的结果)get只是查询数据,不会影响到资源的变化,而post如果调用多次,都将产生新的资源

tcp三次握手

  • 第一次握手,就是客户端向服务端发送一个连接请求报文,在报文段中将SYN置为1;第二次握手,就是服务端向客户端发送一个确认连接报文,在报文段中将SYN和ACK都置为1;第三次握手,就是客户端向服务端发送一个确认的报文,表明自己收到了服务端的确认信息,在报文段中将ACK置为1;这样经过三次握手,TCP连接就建立了。

TCP与UDP

  • 第一,TCP面向连接,而UDP面向无连接;
  • 第二,TCP可靠,而UDP不可靠;
  • 第三,TCP面向字节流,而UDP面向报文;
  • 第四,TCP首部是20B,而UDP首部是8B;
  • 第五,TCP只支持一对一通信,而UDP支持广播通信;
  • 第六,UDP传输比TCP更快,所以更适合即时通信

http与https

  • HTTP的默认端口号是80,HTTPs的默认端口号是443
  • HTTP协议运行在TCP之上,所有传输的内容都是未加密的,而HTTPs协议是运行在SSL协议之上的HTTP协议,所有传输的内容都是加密的,所以HTTP安全性没有HTTPs高;
  • HTTPs协议需要到CA申请证书,所以需要耗费一定的费用

访问一个网址会经过哪些步骤

  • 首先浏览器会向DNS请求解析域名的ip地址,然后和该服务器建立TCP连接,浏览器会向服务器发出HTTP请求,服务器处理完请求后就会将HTTP响应结果发送给浏览器,然后关闭TCP连接,浏览器对响应结果进行解析并且渲染页面

访问HTTP接口慢,如何排查,可能出现问题的地方

排查思路:

  1. 确定是哪个接口存在性能问题
  2. 确定这个接口的内部逻辑是怎样的,做了哪些事情
  3. 分析接口存在性能问题的根本原因
  4. 寻找确立优化方案
  5. 回归验证方案效果

进程和线程

他们之间有3点区别:第一点,进程是资源分配的最小单位,而线程是程序执行的最小单位,一个线程只能属于一个进程,而一个进程可以多个线程,第二点,进程有自己独立的地址空间,而线程共享进程的地址空间,第三点,进程间不会相互影响,而一个进程内某个线程挂掉,将会导致整个多线程程序挂掉

linux操作系统命令

磁盘管理类:df、fdisk(磁盘分区工具)

系统管理类:free、iostat(I/O信息统计)

进程管理类:top、ps

文件传输类:scp、curl

文档查看编辑类:cat、head、tail、vi

文件管理类:cd、ls、mkdir、chmod

操作系统如何解决内存碎片的

  • 内存管理主要有块式管理,页式管理,段式管理和段页式管理;块式管理就是将内存分为一些固定大小的块,如果程序运行需要内存的话,操作系统就会分配给它一块。它有一个缺点,就是如果程序只需要很小的空间,那么分配的这一块内存很多没有被利用。页式管理就是 将内存分为一些固定大小的页,它比块要小,这样可以提高内存利用率。段式管理就是 将内存分为一些段,它比页要小,这样内存利用率更高,但是由于段很小,那么一个程序执行可能需要很多个段,这样会浪费很多时间在计算每一段的物理地址上面。段页式管理就是 结合了段式管理和页式管理,也就是 先将内存分为一些段,然后再把段分为一些页。

单核cpu同一时刻能处理多少个进程

一个

多线程和多进程的优缺点

多进程的优缺点

  • 优点多进程的优点是稳定性好,一个子进程崩溃了,不会影响主进程以及其余进程。基于这个特性,常常会用多进程来实现守护服务器的功能
  • 缺点多进程编程的缺点即创建进程的代价非常大,因为操作系统要给每个进程分配固定的资源,并且操作系统对进程的总数会有一定的限制,若进程过多,操作系统调度都会存在问题,会造成假死状态。

多线程的优缺点

  • 优点如果一个线程有很多缓存丢失,其他线程可以继续利用未使用的计算资源,这可能会导致整体进程更快的执行,因为如果只执行一个线程,这些资源就会闲置。此外,如果一个线程不能使用CPU的所有计算资源(因为指令依赖于彼此的运算结果),运行另一个线程可以防止这些资源变得空闲。
  • 缺点共享硬件资源(如缓存或转换查找缓冲器)时,多个线程可能会相互干扰。因此,由于适应线程切换的硬件需要较低频率或额外传输途径,甚至当只有一个线程正在执行时,单个线程的执行时间没有得到改善,甚至执行时间会降低。

什么是死锁,怎么解决死锁,用生活中的例子解释一下死锁

  • 定义:多个进程在执行的过程中,因为争夺资源造成了一种相互等待的状态
  • 解决死锁主要有四种方法:预防死锁,避免死锁,死锁检测与恢复,鸵鸟策略

常见的数据结构有哪些

主要分为线性数据结构和非线性数据结构

线性数据结构包括 数组、链表、栈和队列;非线性数据结构包括 哈希表、树、堆和图。

数组和链表的区别 什么时候用数组或者链表

  • 数组中的元素在内存中连续存放,而链表中的元素在内存中不连续
  • 数组可以通过下标快速访问任何元素,而链表则需要遍历整个链表才能找到对应的元素
  • 数组不适合插入或者删除元素,因为插入或者删除元素,就需要移动大量的元素,而链表插入或者删除元素,只用修改元素的指针就可以了
  • 总结:当需要随机访问数据时可以使用数组,当需要频繁进行插入和删除操作时可以使用链表。

队列跟栈的区别

  • 栈是先进后出,队列是先进先出
  • 栈只允许在表尾进行插入和删除,而队列只允许在表尾插入,在表头删除

树的遍历方式有哪些

  • 前序遍历
  • 中序遍历
  • 后序遍历
  • 层序遍历

排序算法,时间空间复杂度

  • 冒泡排序: 稳定 O(N^2)
  • 快速排序:不稳定 O(NlogN)
  • 简单插入排序:稳定 O(N^2)
  • 希尔排序:不稳定 O(NlogN) ~ O(N^2)
  • 简单选择排序:不稳定 O(N^2)
  • 堆排序:不稳定 初始化建堆的时间复杂度是O(N);排序重建堆的时间复杂度为 O(NlogN);最终的时间复杂度为O(NlogN)
  • 归并排序:稳定O(NlogN)
  • 桶排序:取决于桶内排序算法
  • 基数排序:稳定 O(N*M) 其中N为数据个数,M为数据位数

快排的过程

  • 先从数组中随机选择一个元素作为基准元素,然后将数组中比基准元素大的放到它的右边,比基准元素小的放到它的左边,然后再对这两边分别不断进行上述的操作,直到区间中只有一个元素

数据库三范式,第三范式举例说明

  • 第一范式:每一列属性都是不可再分的属性值 每一列的值都不能再进行划分
  • 第二范式:在1NF基础上消除非主键对主键的部分函数依赖,也就是需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关 消除了部分函数依赖
  • 第三范式:在2NF基础上,所有的非主键只依赖于主键,不依赖于其他的非主键(消除了传递函数依赖),也就是需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关 消除了传递函数依赖

什么是索引,有哪些索引,索引的缺点和优点

  • 索引是一种帮助mysql提高查询效率的数据结构,就像书的目录一样
  • 索引包括 普通索引 唯一索引 主键索引 复合索引 全文索引
  • 优点:索引可以加快数据查询速度索引帮助我们排序以避免使用临时表索引可以将随机的IO转换为顺序的IO(B+树的叶子结点是连接在一起的)
  • 缺点:创建索引和维护索引要耗费时间,并且时间随着数据量的增加而增加索引需要占用磁盘空间来进行存储

B树和B+树的区别

  • 他们都是一种平衡的多路查找树,但是有两点区别B+树非叶子节点只存储键值信息,而B树非叶子节点还存储数据记录,这样B+树会矮一些,那么查询的时候IO就少一些B+的数据记录存储在叶子节点中,并且所有叶子结点之间都有一个链指针,而B树数据记录存储在任意节点中,这样在进行区间查询的时候,需要中序遍历,效率会更低

mysql索引什么时候无作用,或者说什么情况不适合建索引

  • 查询语句中使用like关键字,如果匹配字符串的第一个字符为%,索引不会被使用
  • 查询语句中使用复合索引,如果查询字段不满足最左前缀原则,索引不会被使用
  • 查询语句中使用or关键字,如果or前后有一个列不是索引,索引就不会使用
  • 在索引列上进行计算时,会导致索引失效

索引的底层结构

  • mysql的默认存储引擎Innodb就是用B+树实现索引结构的
  • B+树就是在B树基础上的一种优化。B树中每个节点不仅包含数据的key值,还有data值,而每一页的存储空间是有限的(16KB),如果data数据较大时,将会导致一个页能存储的节点较少,这样会导致B树的深度较大,因此会增大查询的磁盘I/O次数,影响查询效率。在B+树中,所有数据记录都是按照键值大小顺序存放在叶子节点上,而非叶子节点只存储键值信息,这样可以加大每页存储的节点数量,降低B+树的高度,减少磁盘IO。

覆盖索引是什么

  • 只通过查询非主键索引的B+树就可以覆盖查询的要求,也就是不需要回表的过程

SQL查询语言分类

DQL:数据库查询语言 select

DML:数据库操作语言 update、delete、insert

DDL:数据库定义语言 create、drop、alter

DCL:数据库控制语言 grant

SQL的连接方式

  • inner join、left join、right join和full join

SQL的union 和 union all的区别

  • UNION:对两个结果集进行并集操作, 不包括重复行,同时会对获取的结果进行排序操作
  • UNION ALL:对两个结果集进行并集操作, 包括重复行,同时不会对获取的结果进行排序操作

事务定义

  • 一个事务是由一条或者多条sql语句组成的不可分割的单元,要么全部执行成功,要么全部执行失败。

mysql事务的特性

  • 原子性是说一个事务中的所有操作要么全部完成,要么全部不完成。
  • 一致性是说一个事务执行之前和执行之后都必须处于一致性状态。
  • 隔离性:同一时间,只允许一个事务请求同一数据,事务间互不干扰。
  • 持久性:一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的。

事务的隔离级别,mysql的隔离级别

mysql的默认事务隔离级别是可重复读

  • read uncommitted(读未提交):一个事务可以读取另外一个事务未提交的数据,隔离级别最低。
  • read committed(读提交):一个事务只能读取另外一个事务提交的数据。
  • repeatable read(可重复读):一个事务多次读取同一条记录,返回的结果是一致的(即使有其他事务对该条记录进行了修改)。
  • serializable(序列化):要求事务序列化执行,也就是只能一个接着一个地执行,隔离级别最高。

幻读是什么

  • 一个事务两次执行同一条查询语句,读取的数据量不一样,因为第二次读之前,其他事务删除了数据或者新增了数据

MVCC的作用及实现原理

  • MVCC多版本并发控制,就是同一条记录在系统中存在多个版本。其存在目的是在保证数据一致性的前提下提供一种高并发的访问性能。对数据读写在不加读写锁的情况下实现互不干扰,从而实现数据库的隔离性,在事务隔离级别为读提交和可重复读中使用到
  • MVCC通过维持一个数据的多个版本,在不加锁的情况下,使读写操作没有冲突。主要由三个隐式字段(事务ID,回滚指针,聚集索引ID)、undo log(存放了数据修改之前的快照)和read view(存放开始了还未提交的事务)去实现。在InnoDB中,事务在开始前会向事务系统申请一个事务ID,同时每行数据具有多个版本,我们每次更新数据都会生成新的数据版本,而不会直接覆盖旧的数据版本。每行数据中包含多个隐式字段,其中实现MVCC的主要涉及最近更改该行数据的事务ID和可以找到历史数据版本的指针。InnoDB在每个事务开启瞬间会为其构造一个记录当前已经开启但未提交的事务ID的readview视图数组。通过比较链表中的事务ID与该行数据的值对应的DB_TRX_ID,并通过回滚指针找到历史数据的值以及对应的DB_TRX_ID来决定当前版本的数据是否应该被当前事务所见。最终实现在不加锁的情况下保证数据的一致性。

Drop、truncate、delete的区别

  • delete,DML数据操纵语言,用来删除表的全部数据或者一部分数据,删除的数据可以回滚
  • truncate,DDL数据定义语言,用来删除表的所有数据,删除的数据不可以回滚
  • drop,DDL数据定义语言,用来删除表以及所有数据,删除的数据不可以回滚

算法

链表判断是否有环

二叉树的深度

NC92 最长公共子序列(二)

判断二叉树是否对称

滑动窗口最大值

最小栈

删除链表的倒数第 N 个结点

剑指 Offer 51. 数组中的逆序对

树的镜像

#数据人的面试交流地##你收到了团子的OC了吗##如何判断面试是否凉了##大数据开发#
 类似资料: