大数据相关的面试题 :
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执行了")
}