大数据笔记 August 20, 2018

面试笔记

Words count 12k Reading time 11 mins. Read count 0

大数据相关的面试题 :

1. zookeeper概念,以及为什么是奇数个?

概念

ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

为什么是奇数个?

Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。

所以Zookeeper在选举leader的时候是采用选举制的,意思就是投票数超过半数一上即可被选举为leader。

举个栗子:

当你有三台服务器,其中两台服务器投票成功,那么即可选出一个leader

但当你有四台服务器,其中有两台投票成功,这时候达不到选举制中的1/2以上的要求,所以无法选出leader,那集群就没法正常运行了。

参考文章:http://blog.csdn.net/liuj2511981/article/details/42460069

2.SecondaryNameNode的作用?

在了解Secondary NameNode作用之前,先了解NameNode的作用。

NameNode

NameNode主要是用来保存HDFS的元数据信息,比如命名空间信息,块信息等。当它运行的时候,这些信息是存在内存中的。但是这些信息也可以持久化到磁盘上。

上面的这张图片展示了NameNode怎么把元数据保存到磁盘上的。这里有两个不同的文件:

fsimage - 它是在NameNode启动时对整个文件系统的快照
edit logs - 它是在NameNode启动后,对文件系统的改动序列

只有在NameNode重启时,edit logs才会合并到fsimage文件中,从而得到一个文件系统的最新快照。但是在产品集群中NameNode是很少重启的,这也意味着当NameNode运行了很长时间后,edit logs文件会变得很大。在这种情况下就会出现下面一些问题:

edit logs文件会变的很大,怎么去管理这个文件是一个挑战。
NameNode的重启会花费很长时间,因为有很多改动要合并到fsimage文件上。
如果NameNode挂掉了,那我们就丢失了很多改动因为此时的fsimage文件非常旧。

因此为了克服这个问题,我们需要一个易于管理的机制来帮助我们减小edit logs文件的大小和得到一个最新的fsimage文件,这样也会减小在NameNode上的压力。这跟Windows的恢复点是非常像的,Windows的恢复点机制允许我们对OS进行快照,这样当系统发生问题时,我们能够回滚到最新的一次恢复点上。

Secondary NameNode

SecondaryNameNode就是来帮助解决上述问题的,它的职责是合并NameNode的edit logs到fsimage文件中。

首先,它定时到NameNode去获取edit logs,并更新到fsimage上。
一旦它有了新的fsimage文件,它将其拷贝回NameNode中。
NameNode在下次重启时会使用这个新的fsimage文件,从而减少重启的时间。

Secondary NameNode的整个目的是在HDFS中提供一个检查点。它只是NameNode的一个助手节点。这也是它在社区内被认为是检查点节点的原因。

JAVA相关

常见的排序方法

@Test
/**
 * 1.冒泡法排序
 * 比较相邻元素 如果第一个比第二个大,那么把他们交换位置
 *
 */
public void bubbleSort() {
    int temp = 0;
    int size = numbers.length;
    for (int i = 0; i < size - 1; i++) {
        for (int j = 0; j < size - 1 - i; j++) {
            if (numbers[j] > numbers[j + 1]) {
                temp = numbers[j];
                numbers[j] = numbers[j + 1];
                numbers[j + 1] = temp;
            }
        }
    }
    logger.info("排序后={}", numbers);
}

/**
 * 2.快速排序
 * 将数组第一元素看做是中轴记录,然后通过while循环,将小于中轴记录的放到左侧,将大于中轴记录的放到右侧,得到中轴位置
 * 然后经分别对中轴左侧和右侧进行递归调用进行排序
 */
@Test
public void quickSort() {

    quick(numbers, 0, numbers.length - 1);
    logger.info("{}",numbers);
}

public void quick(int[] numbers, int low, int high) {
    if (low < high) {
        int middle = getMiddle(numbers, low, high); //获取中轴的位置
        quick(numbers, low, middle - 1); //对比中轴小的字段进行递归排序
        quick(numbers, middle + 1, high);// 对比中轴大的字段进行递归排序
    }
}

public int getMiddle(int[] numbers, int low, int high) {
    int temp = numbers[low]; //将数组的第一个作为中轴
    while (low < high) {
        while (low < high && numbers[high] > temp) {
            high--;
        }
        numbers[low] = numbers[high];//比中轴小的记录移到低端
        while (low < high && numbers[low] < temp) {
            low++;
        }
        numbers[high] = numbers[low];//比中轴大的记录移动到高端

    }
    numbers[low] = temp; //中轴记录
    return low; //返回中轴的位置
}

/**
 * 3.选择排序
 * 在排序的一组数中,选出最小的一个与第一个位置中的数交换,然后再在
 * 剩下的数中再找与第二个位置的数交换。
 */

@Test
public void selectSort() {
    int size = numbers.length;
    int temp = 0; //中间变量

    for (int i = 0; i < size; i++) {
        int k = i; //待确定位置
        for (int j = size - 1; j > i; j--) {
            if (numbers[j] < numbers[k]) {
                k = j;
            }
        }
        //交换两个数
        temp = numbers[i];
        numbers[i] = numbers[k];
        numbers[k] = temp;
    }

    logger.info("{}", numbers);

}

/**
 * 4.插入排序
 * 从第一个元素开始,该元素可以认为已经被排序
 * 取出下一个元素,在已经排序的元素序列中从后向前扫描
 * 如果该元素(已排序)大于新元素,将该元素移到下一位置
 * 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
 * 将新元素插入到该位置中
 * 重复步骤2
 */
@Test
public void insertSort() {
    int size = numbers.length;
    int temp = 0;
    int j = 0;
    for (int i = 0; i < size; i++) {
        temp = numbers[i];
        for (j = i; j > 0 && temp < numbers[j - 1]; j--) {
            numbers[j] = numbers[j-1];
        }
        numbers[j] = temp;
    }
    logger.info("{}", numbers);
}

/**
 * 5.希尔排序法
 *
 */
@Test
public void shellSort(){
    int j = 0;
    int temp = 0;
    //将每次步长缩短为原来的一半
    for (int increment = numbers.length/2;increment > 0 ; increment /= 2){
        for (int i = increment; i < numbers.length; i++){
            temp = numbers[i];
            for (j=i;j>=increment;j -= increment){
                if (temp > numbers[j - increment])
                {
                    numbers[j] = numbers[j - increment];
                }else {
                    break;
                }
            }
            numbers[j] = temp;
        }
    }
    logger.info("{}", numbers);
}

java多线程

java多线程主要体现在Thread这个类和Runnable这个接口上。他们里面都有一个共同的方法run()

Thread常用方法

创建线程的两种方法

第一个是继承Thread类

public class Actor extends Thread{
    @Override
    public void run() {
    //TO-DO 线程要执行的方法
    }
    public static void  main(String[] args){
        Thread actor = new Actor();
        actor.setName("Mr.Thread");
        actor.start();
        //也可以直接调用run()方法
        //new Actor().run();
    }
}

第二个是Runnable接口

public class Actress implements Runnable{
    public void run() {
     //TO-DO 线程要执行的方法
    }
    
    public static void  main(String[] args){
        Thread actress = new Thread(new Actress(),"MS.Runnable");
        actress.start();
        //也可以直接调用run()方法
        //new Actress().run();
    }
}

stop()弃用

不用interrupt停止线程

java IO流

http://davidisok.iteye.com/blog/2106489

String、Stringbuilder、StringBuffer

String 不可变

StringBuilder、StringBuffer可变

StringBuilder(线程不安全,但是单线程场景应用广泛,因为速度比StringBuffer快)

StringBuffer(线程安全)

用法 StringBuilder sb = new StringBuilder();

插入数据

sb.append(“这里是要插入的数据,将会被插入到末端”)

sb.insert(0,”在指定0的位置插入我这条数据”)

ArrayList、Vector、LinkedList

https://www.cnblogs.com/mgod/archive/2007/08/05/844011.html

抽象类和接口

http://blog.csdn.net/chenssy/article/details/12858267

常见异常

http://blog.csdn.net/liu_jian140126/article/details/50517001

spring原理

IOC 控制反转 AOP 面向切面

http://blog.csdn.net/guolin6315/article/details/6732993

spring 与Springmvc区别

aop的实现

JS相关

js线程问题

利用Worker实现js代码后台运行

var date = new Date();
console.log(date.getTime());
var worker = new Worker("workjob.js");
worker.postMessage("startwork");
worker.onmessage = function (p1) {
    console.log("工作线程完成的回调函数"+p1)
};
worker.onerror = function (error) {
    console.log(error)
};
var aa = [];
console.log("我们不需要等待workjob.js里的任务完成在执行我们这个任务,让他们在后台执行吧,我们来执行我们的任务")
for (var i = 0 ; i <1000;i++){
    aa.push("正常任务"+i)
}
console.log(date.getTime()+aa)

workjob.js

onmessage = function (event) {
    console.log(event.data+"后台执行我们的方法");
    var aa  = [];
    for(var i=0;i<1000;i++){
        aa.push("单独开辟的任务"+i)
    }
    console.log(aa);
    //这是返回信息
    postMessage("done!")
};

利用异步回调实现

var date = new Date();
f1();
//利用回调实现异步
    function f1(f2) {
        setTimeout(function () {
            console.log("f1执行了");
            f2();
        },100)
    }
    //不等上一个任务,执行完成,我们先执行
    console.log(date.getTime()+"不等上一个任务,执行完成,我们先执行")
    function f2() {
        console.log("f2执行了")
    }
0%