面试知识点

"知识点总结"

Posted by ming on August 23, 2019

“The purpose of human life is to serve, and to show compassion and the will to help others.”

1.Hadoop知识总结

1.1 MR架构

JobClient、JobTracker、TaskTracker

![MR架构](https://upload-images.jianshu.io/upload_images/697231-069e09ede059d164?imageMogr2/auto-orient/strip imageView2/2/w/810/format/webp)
  1. JobClient向JobTracker请求一个新的JobId;
  2. 检查作业输出说明;
  3. 计算作业输出划分split
  4. 将运行作业所需的资源(作业的jar文件、配置文件、计算所得的输入划分)复制到一个以作业ID命名的目录JobTracker的文件系统;
  5. 通过调用JobClient的submitJob()方法,告诉JobTracker作业准备运行
  6. JobTracker接收到submitJob()方法调用后,把此调用放到一个内部队列中,交由作业调度器进行调度,并对其进行初始化;
  7. 创建任务运行列表,作业调度去首先从共享文件系统中获取JobClient已经计算好的输入划分信息(图中step6),然后为每个划分创建一个Map任务(一个split对应一个map,有多少split就有多少map)。
  8. TaskTracker执行一个简单的循环,定期发送心跳(heartbeat)调用JobTracker。

JobTracker 负责工作节点的资源管理,监控资源的使用情况,管理作业的生命周期。

TaskTracker 的职责是根据JobTracker 的命令启动/清除任务,并且周期性的向JobTracker 汇报任务的状态信息。

Yarn运行机制

YARN 的基本思想就是将JobTracker 的两大主要职能:资源管理、作业的调度监控分为两个独立的进程。一个是全局的ResourceManager,另一个是每一个应用对应的ApplicationMaster。

ResourceManager 是一个纯粹的调度器,它根据应用程序的资源请求严格限制系统的可用资源。在保证容量、公平性及服务器等级的前提下,优化集群资源利用率,即让所有的资源都能被充分利用。

ApplicationMaster 负责与ResourceManager 协商资源,并和NodeManager 进行协同工作来执行容器和监控容器的状态。

NodeManager 是YARN 节点上的工作进程,管理集群中独立的计算节点。其职责包括启动应用程序的容器,监控它们的资源使用情况,并且报告给ResourceManager。

![YARN](https://upload-images.jianshu.io/upload_images/697231-ea265a5516b96af5.png?imageMogr2/auto-orient/strip imageView2/2/w/657/format/webp)
  • ResourceManager 代替集群管理器
  • ApplicationMaster 代替一个专用且短暂的 JobTracker
  • NodeManager 代替 TaskTracker
  • 一个分布式应用程序代替一个 MapReduce 作业

一个全局 ResourceManager 以主要后台进程的形式运行,它通常在专用机器上运行,在各种竞争的应用程序之间仲裁可用的集群资源。 在用户提交一个应用程序时,一个称为 ApplicationMaster 的轻量型进程实例会启动来协调应用程序内的所有任务的执行。这包括监视任务,重新启动失败的任务,推测性地运行缓慢的任务,以及计算应用程序计数器值的总和。有趣的是,ApplicationMaster 可在容器内运行任何类型的任务。 NodeManager 是 TaskTracker 的一种更加普通和高效的版本。没有固定数量的 map 和 reduce slots,NodeManager 拥有许多动态创建的资源容器。

1.2 Spark

  1. spark有几种部署模式,每种模式特点?
  • 本地模式:Spark不一定非要跑在hadoop集群,可以在本地,起多个线程的方式来指定。方便调试,本地模式分三类。
  • standalone模式:分布式部署集群,自带完整的服务,资源管理和任务监控是Spark自己监控,这个模式也是其他模式的基础。
  • spark on yarn:分布式部署集群,资源和任务监控交给yarn管理;粗粒度资源分配方式,包含cluster和client运行模式(cluster 适合生产,driver运行在集群子节点,具有容错功能;client 适合调试,dirver运行在客户端)
  • spark on mesos
  1. spark技术栈有哪些组件,每个组件有什么功能,适用于什么应用场景?
  • Spark core:是其它组件的基础,spark的内核,其主要包括有向循环图、RDD、Lingage、Cache、broadcast等。
  • Spark Streaming:是一个对实时数据流进行高通量、容错处理的流式处理系统,将流式计算分解成一系列短小的批处理作业.
  • Spark SQL:能够统一处理关系表和RDD,使得开发人员可以轻松地使用SQL命令进行外部查询。
  • MLBase: 是Spark生态圈的一部分专注于机器学习,让机器学习的门槛更低.
  • GraphX: 是Spark中用于图和图并行计算.
  1. spark有哪些组件?
  • master:管理集群和节点,不参与计算;
  • worker: 计算节点,进程本身不参与计算,和master汇报。
  • Driver: 运行程序的main方法,创建spark context对象;
  • Spark Context:控制整个application的生命周期,包括dagsheduler和task scheduler等组件.
  • client: 用户提交程序的入口。
  1. spark工作机制
  • 用户在client端提交任务后,会由Driver运行main方法并创建spark context上下文
  • 执行add算子,形成dag图输入dagscheduler
  • 按照add之间的依赖关系划分stage输入task scheduler
  • task scheduler会将stage划分为taskset分发到各个节点的executor中执行
  1. RDD机制
  • 分布式弹性数据集,简单的理解成一种数据结构,是spark框架上的通用货币
  • 所有算子都是基于rdd来执行的
  • rdd执行过程中会形成dag图,然后形成lineage保证容错性等
  • 从物理的角度来看rdd存储的是block和node之间的映射

1.3 TODO

2. 面试问题积累

2.1 内存溢出和内存泄漏

在介绍这个问题之前,先来总结一下java中的内存是如何管理的:

  1. 在Java中,我们需要通过new关键字为每一个对象申请内存空间(基本数据类型除外),所有的对象都是在堆(Heap)中分配空间的。
  2. 在Java中,内存的分配是管理员决定的,但是内存的释放是由GC(Garbage Collection)完成的,这样收支两线的机制确实简化了程序员的工作量。
  3. 垃圾回收机制加重了JVM的工作,这也是Java程序执行速度比较慢的原因之一。GC为了能够正确、及时释放不再被引用的对象,GC必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。
  4. 在Java中,使用有向图的方式进行内存管理,精度高,但是效率较低,可以处理引用循环等问题。例如有三个对象互相引用,只要和根进程是不可到达的,就可以被GC回收。
  5. 另外一种常用的内存管理技术是使用计数器,例如COM模型采用计数器方式管理构件,它与有向图相比,精度低(很难处理循环引用的问题),但执行效率很高。
1.什么是内存泄漏?

如果具有满足以下两个条件的对象:

  • 对象是可达的。即在有向图中,存在通过达到该对象,GC不会回收。
  • 对象是无用的。即程序以后不会再使用这些对象。

那么这些对象是无用的,但是占用着内存空间,并且不会被GC回收这就是所谓的内存泄漏。

说直白点,就是程序在申请内存后,无法释放已经申请的内存空间。你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序,这就是内存泄漏。一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

内存泄漏一般可以分为以下四种类型:

  1. 常发性内存泄漏:发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
  2. 偶发性内存泄漏: 发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生.常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
  3. 一次性内存泄漏: 发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。
  4. 隐式内存泄漏: 程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。

与C++的比较

在C++中,内存泄漏的范围更大,因为C++不存在垃圾回收机制,因此对于那些不可达的对象,C++是永远都无法回收的。但是Java中不可达的对象是由GC负责的,因此程序员不用考虑这一部分对象,这在一定程度上减轻了开发难度。

内存泄漏对比

在Java中,对程序员来说,GC基本是透明的,虽然我们可以调用System.gc();通知垃圾回收机制进行回收,但是该函数不保证JVM一定会执行GC。因为不同的JVM实现者可能使用不同的算法管理GC。

通常,GC的线程的优先级别较低。JVM调用GC的策略也有很多种,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但通常来说,我们不需要关心这些。除非在一些特定的场合,GC的执行影响应用程序的性能,例如对于基于Web的实时系统,如网络游戏等,用户不希望GC突然中断应用程序执行而进行垃圾回收,那么我们需要调整GC的参数,让GC能够通过平缓的方式释放内存。

内存泄漏实例

1
2
3
4
5
6
7
Vector v=new Vector(10);
for (int i=1;i<100; i++)
{
    Object o=new Object();
    v.add(o);
    o=null; 
}

在这个例子中,我们循环申请对象o,并将o放入容器中,虽然我们释放了o,但是由于容器还引用这这个对象,所以GC仍然是不会回收的。我们需要通过释放容器才能被GC回收。

2. 什么是内存溢出?

如果内存泄漏非常严重的话,最终会导致内存溢出。下面给出常见的几种内存溢出的分类:

1. OutOfMemoryError: PermGen space

PermGen Space指的是内存的永久保存区,该块内存主要是被JVM用来存放class和mete信息的,当class被加载loader的时候就会被存储到该内存区中,与存放类的实例的heap区不同,java中的垃圾回收器GC不会在主程序运行期对PermGen space进行清理。因此,程序启动时如果需要加载的信息太多,超出这个空间的大小,则会发生溢出。

解决方案:增加空间分配——增加虚拟机中的XX:PermSize和XX:MaxPermSize参数的大小,其中XX:PermSize是初始永久保存区域大小,XX:MaxPermSize是最大永久保存区域大小。

2. OutOfMemoryError:Java heap space

heap是Java内存中的堆区,主要用来存放对象,当对象太多超出了空间大小,GC又来不及释放的时候,就会发生溢出错误。即内存泄漏越来越严重时,可能会发生内存溢出。

解决方案:

  1. 检查程序,减少大量重复创建对象的死循环,减少内存泄露。
  2. 增加虚拟机中Xms(初始堆大小)和Xmx(最大堆大小)参数的大小。

3. StackOverFlowError

stack是Java内存中的栈空间,主要用来存放方法中的变量,参数等临时性的数据的,发生溢出一般是因为分配空间太小,或是执行的方法递归层数太多创建了占用了太多栈帧导致溢出。

解决方案:修改配置参数-Xss参数增加线程栈大小之外,优化程序是尤其重要。

3. 内存溢出的常见原因及解决方案

引起内存溢出的原因有很多种,列出常见的几种如下所示:

  1. 内存中加载的数据量过于庞大,如一次性从数据库中取出了过多的数据。
  2. 集合类中有对对象的引用,使用后未清空,使得JVM不能回收;
  3. 代码中存在死循环或循环产生过多重复的对象实体;
  4. 使用的第三方软件中的BUG;
  5. 启动参数内存值设定的过小。

那么内存溢出常见的解决方案有哪些呢?

  • 第一步,修改JVM启动参数,直接增加内存。(-Xms, -Xmx参数一定不要忘记添加)
  • 第二步,检查错误日志,查看”OutOfMemory”错误前是否有其他异常或者错误。
  • 第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。其中,重点排查以下几点:
    1. 检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
    2. 检查代码中是否有死循环或者递归调用。
    3. 检查是否有大循环重复产生新的对象实体。
    4. 检查List,Map等集合对象是否有使用完毕后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。
  • 第四步,使用内存查看工具动态查看内存使用情况。
4. 总结

内存泄漏是堆中的存在无用但可达的对象,GC无法回收。

内存溢出是空间不足的溢出,主要分为PermGen space不足、堆不足、栈不足。

2.2 高并发的处理方式

1. 概念介绍

高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求。

高并发相关常用的一些指标有响应时间(Response Time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户数等。

响应时间:系统对请求做出响应的时间。例如系统处理一个HTTP请求需要200ms,这个200ms就是系统的响应时间。

吞吐量:单位时间内处理的请求数量。

QPS:每秒响应请求数。在互联网领域,这个指标和吞吐量区分的没有这么明显。

并发用户数:同时承载正常使用系统功能的用户数量。例如一个即时通讯系统,同时在线量一定程度上代表了系统的并发用户数。

2. 如何提高系统的并发能力

互联网分布式架构设计,提高系统并发能力的方式,方法论上主要有两种:垂直扩展和水平扩展。

垂直扩展提升单机处理能力。垂直扩展的方式又有两种:

  1. 增强单机硬件性能,例如:增加CPU核数如32核,升级更好的网卡如万兆,升级更好的硬盘如SSD,扩充硬盘容量如2T,扩充系统内存如128G;
  2. 提升单机架构性能,例如:使用Cache来减少IO次数,使用异步来增加单服务吞吐量,使用无锁数据结构来减少响应时间等。

在互联网业务发展非常迅猛的早期,如果预算不是问题,强烈建议使用“增强单机硬件性能”的方式提升系统并发能力,因为这个阶段,公司的战略往往是发展业务抢时间,而“增强单机硬件性能”往往是最快的方法。不管是提升单机硬件性能,还是提升单机架构性能,都有一个致命的不足:单机性能总是有极限的。所以互联网分布式架构设计高并发终极解决方案还是水平扩展。

水平扩展:只要增加服务器数量,就能线性扩充系统性能。水平扩展对系统架构设计是有要求的,如何在架构各层进行可水平扩展的设计,以及互联网公司架构各层常见的水平扩展实践,是本文重点讨论的内容。

3. 常见的互联网分层

互联网分层

常见互联网分布式架构如上,分为:

  1. 客户端层:典型调用方是浏览器browser或者手机应用APP
  2. 反向代理层:系统入口,反向代理
  3. 站点应用层:实现核心应用逻辑,返回html或者json
  4. 服务层:如果实现了服务化,就有这一层
  5. 数据-缓存层:缓存加速访问存储
  6. 数据-数据库层:数据库固化数据存储。
4. 分层水平扩展架构实战

反向代理层的水平扩展:

反向代理层的水平扩展

反向代理层的水平扩展,是通过“DNS轮询”实现的:dns-server对于一个域名配置了多个解析ip,每次DNS解析请求来访问dns-server,会轮询返回这些ip。当nginx成为瓶颈的时候,只要增加服务器数量,新增nginx服务的部署,增加一个外网ip,就能扩展反向代理层的性能,做到理论上的无限高并发。

站点层的水平扩展:

站点层的水平扩展,是通过“nginx”实现的。通过修改nginx.conf,可以设置多个web后端。当web后端成为瓶颈的时候,只要增加服务器数量,新增web服务的部署,在nginx配置中配置上新的web后端,就能扩展站点层的性能,做到理论上的无限高并发。

服务层的水平扩展:

服务层的水平扩展,是通过“服务连接池”实现的。站点层通过RPC-client调用下游的服务层RPC-server时,RPC-client中的连接池会建立与下游服务多个连接,当服务成为瓶颈的时候,只要增加服务器数量,新增服务部署,在RPC-client处建立新的下游服务连接,就能扩展服务层性能,做到理论上的无限高并发。如果需要优雅的进行服务层自动扩容,这里可能需要配置中心里服务自动发现功能的支持。

数据层的水平扩展:

在数据量很大的情况下,数据层(缓存,数据库)涉及数据的水平扩展,将原本存储在一台服务器上的数据(缓存,数据库)水平拆分到不同服务器上去,以达到扩充系统性能的目的。

互联网数据层常见的水平拆分方式有这么几种,以数据库为例:

  1. 按照范围水平拆分:每一个数据服务,存储一定范围的数据,user0库,存储uid范围1-1kw,user1库,存储uid范围1kw-2kw。这个方案的好处与缺点是:
    • 规则简单,service只需判断一下uid范围就能路由到对应的存储服务;
    • 数据均衡性较好;
    • 比较容易扩展,可以随时加一个uid[2kw,3kw]的数据服务;
    • 请求的负载不一定均衡,一般来说,新注册的用户会比老用户更活跃,大range的服务请求压力会更大。
  2. 按照哈希水平拆分:每一个数据库,存储某个key值hash后的部分数据,user0库,存储偶数uid数据,user1库,存储奇数uid数据。这个方案的优缺点是:
    • 规则简单,service只需对uid进行hash能路由到对应的存储服务;
    • 数据均衡性较好;
    • 请求均匀性较好;
    • 不容易扩展,扩展一个数据服务,hash方法改变时候,可能需要进行数据迁移。

这里需要注意的是,通过水平拆分来扩充系统性能,与主从同步读写分离来扩充数据库性能的方式有本质的不同。通过水平拆分扩展数据库性能:

  • 每个服务器上存储的数据量是总量的1/n,所以单机的性能也会有提升;
  • n个服务器上的数据没有交集,那个服务器上数据的并集是数据的全集;
  • 数据水平拆分到了n个服务器上,理论上读性能扩充了n倍,写性能也扩充了n倍(其实远不止n倍,因为单机的数据量变为了原来的1/n);

通过主从同步读写分离扩展数据库性能:

  • 每个服务器上存储的数据量是和总量相同;
  • n个服务器上的数据都一样,都是全集;
  • 理论上读性能扩充了n倍,写仍然是单点,写性能不变。

缓存层的水平拆分和数据库层的水平拆分类似,也是以范围拆分和哈希拆分的方式居多,就不再展开。

5. 总结

高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求。

提高系统并发能力的方式,方法论上主要有两种:垂直扩展(Scale Up)与水平扩展(Scale Out)。前者垂直扩展可以通过提升单机硬件性能,或者提升单机架构性能,来提高并发性,但单机性能总是有极限的,互联网分布式架构设计高并发终极解决方案还是后者:水平扩展。

互联网分层架构中,各层次水平扩展的实践又有所不同:

  1. 反向代理层可以通过“DNS轮询”的方式来进行水平扩展;
  2. 站点层可以通过nginx来进行水平扩展;
  3. 服务层可以通过服务连接池来进行水平扩展;
  4. 数据库可以按照数据范围,或者数据哈希的方式来进行水平扩展。

参考文章

什么是高并发 ,一些常见的处理方式

Java 高并发,什么方式解决?高并发和大流量解决方案

2.3 多路复用IO的底层实现原理

select、poll、epoll之间的区别(搜狗面试)