最近有很多公司都开始了秋招提前批 像字节、阿里、百度、京东、拼多多等 我的一位朋友最近就去字节跳动试了试 经过3轮面试 4 天就拿下了 offer! 这种好事我怎么会忘了大家 在我"严刑逼供"下 我和他详细的复盘了整个面试过程 以及面试题和答案 赶快收藏 | 分享吧! 1.一面 主要聚集点:Mysql、Redis、操作系统、计算机网络等 1.1自我介绍 1.2项目经历 1.3算法题 1.4进程和线程的区别? 进程:资源分配的最小单位。 线程:独立调度的最小单位。 1.5你了解哪些锁? 1.6 死锁的四个必要条件? 1.7 volatie与sychronized的对比? 分享一下,你的朋友面试也许需要它 1.8 volatie的应用场景? 单例模式(懒汉式) public class Singleton { private volatile static Singleton instance = null; private Singleton(){} public static Singleton getInstance(){ if(instance == null){ synchronized (Singleton.class) { if(instance == null){ instance = new Singleton(); } } } return instance; } } 1.9 虚拟内存了解吗?与物理内存的区别? 虚拟内存就是一块虚拟的空间可以给用户存放数据,如果让用户直接去操作物理内存的话,不同用户彼此不知道他使用了哪块物理内存空间,就会造成冲突。 有了虚拟内存之后用户就可以将数据直接在虚拟内存操作,而不需要关心这些内存最终会映射到操作系统哪些物理内存上。 这个映射的操作是操作系统完成的,用户不感知。 那下面就顺便讲下操作系统是如何完成虚拟地址和物理地址之间的映射的。 这里面涉及到一个叫做 " 页表 " 的概念。进程访问虚拟内存,CPU 执行时通过分页机制转换成物理内存访问,虚拟地址到物理内存的转换表称为页表,这个转换是个查表的过程。 同一程序运行起来的两个进程,虚拟地址空间相同,但对应的物理空间是不相同的。OS 需要给每个进程设置一份页表,在进程调度过程中,上下文切换阶段会做页表的切换。 小总结:页表就是一个用于将虚拟地址转换为物理地址的工具。 此时又来了一个新的家伙,叫做 " 快表 ",**那快表是干嘛用的呢?**快表是为了加快虚拟地址到物理地址这个转换过程而存在的。快表一般存放在 CPU 内部的高速缓冲存储器 Cache 中。 快表与页表功能很类似,就是将一部分页表存到 CPU 内部的 Cache 中。CPU 寻址时先根据虚拟地址中的页号,到快表查询相应的页表项对应的物理地址,如果查询不到,则到物理内存中去找,找到了就将页表项调入快表。 但是如果快表的存储空间,即 Cache 满了,就按照一定的淘汰策略将页表换出。因为高速缓冲存储器的访问速度要比内存的访问速度快很多,因此使用快表可以大大加快虚拟地址转换成物理地址。 1.10 Java分配内存两种方式?(指针碰撞、空闲列表) Java在堆上分配内存有两种方式,分别是 "指针碰撞" 和 "空闲列表"。 (1)指针碰撞 假设JVM堆中内存是规整的,所有用过的内存放在一边,没用过的内存放在另一边,中间放着一个指针作为分界点的指示器,那么分配内存的过程就仅仅是把那个指针向空闲空间的方向挪动一段与对象大小相等的距离,这种分配方式叫做指针碰撞。 (2)空闲列表 如果JVM堆中内存不规整,使用过的内存和未使用过的内存相互交错,就无法用指针碰撞分配对象了,虚拟机就必须维护一个列表,记录哪些内存块是可用的,分配的时候在列表中找到一段足够大的内存空间分配给对象实例,并更新列表中的记录。 1.11 OSI七层协议讲一讲? 1.12 应用层协议有哪些? 1.13 TCP与UDP区别? 简单来说 (1) TCP 传输控制协议TCP(Transmission Control Protocol)是面向连接的,提供可靠交付,有流量控制,拥塞控制,提供全双工通信,面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块),每一条TCP连接只能是点对点的(一对一)。 (2) UDP 用户数据报协议UDP(User Datagram Protocol)是无连接的,尽最大可能交付,没有拥塞控制,面向报文(对于应用程序传下来的报文不合并也不拆分,只是添加UDP首部),支持一对一、一对多、多对一、多对多的交互通信。 (3) 应用场景 如果我们是发送消息、浏览网页之类的场景,因为要确保用户消息不丢失,所以要使用TCP协议。 如果是在视频聊天或看直播,那可以用UDP协议,因为即使几个画面丢失,对用户来说也影响不大。 1.14 聚簇索引与非聚簇索引对比? Innodb 中索引的组织形式是B+树,非叶子结点存key,数据data都保存在叶子结点,叶子结点之间用指针链接。 聚簇索引:数据都记录在叶子结点。 非聚簇索引:叶子结点存放的是主键的值,得到主键后还需要在聚集索引上再查询一次。 在效率方面最好使用聚集索引,并给表设定唯一主键。在数据索引的存储有序的情况下,可以大大提高效率。 1.15 Redis的内存淘汰机制说一说? 1.16 Redis的key的过期删除策略? 1.17 Redis两种持久化方式讲一下? RDB(默认持久化方式) (1)概述:在指定时间间隔内,将内存中的数据集快照写入磁盘(恢复就是恢复这个快照) (2)过程:Redis会创建一个子进程来进行持久化操作,子进程会先将数据写入临时文件(快照),再用这个临时文件替换上次持久化好的文件。整个过程,父进程不进行任何IO操作,性能极高。 (3)存在的问题:最后一次数据可能丢失,因为最后的那部分还没持久化。 (4)触发方式:save / flushall命令 / 退出Redis AOF (1)概述:将所有命令都记录下来,恢复时就把这个文件全部再执行一遍(读操作不记录) (2)存在的问题:可能丢失最后1s的数据 (3)触发方式:默认每秒执行一次同步 2. 二面 主要聚集点:项目经历、Redis、计算机网络 2.1 自我介绍 2.2 项目经历 2.3 算法题 2.4 Redis 五种数据结构底层实现都了解吗? 2.5 ConcurrentHashMap 了解吗? 2.6 HTTPS的加密方式? HTTP直接运行在TCP上; HTTPS运行在SSL/TLS之上,SSL/TLS运行在TCP之上,所有传输的内容都经过加密的。 HTTP的通信没有加密; HTTPS采用非对称加密+对称加密的形式。(对称加密速度快,非对称加密安全,所以折衷方案,结合两种模式使用) 1.某网站(服务器)拥有用于非对称加密的公钥A、私钥A’。 2.浏览器向网站服务器请求,服务器把公钥A明文给传输浏览器。 3.浏览器随机生成一个用于对称加密的密钥X,用公钥A加密后传给服务器。 4.服务器拿到后用私钥A’解密得到密钥X。5.这样双方就都拥有密钥X了,且别人无法知道它。之后双方所有数据都通过密钥X加密解密即可。(对称加密) HTTPS这样使用 对称加密+非对称加密 就真的安全吗? 不安全 。 这个过程是使用非对称加密进行链接,使用对称加密进行通信。 在建立连接之初,服务器将公钥(A) 明文 传输给浏览器,在这个过程中公钥可能会被劫持。 黑客将这个公钥(A)替换成他自己的公钥(B),当然黑客自己也有私钥(B),然后将公钥B传送给浏览器。 浏览器生成一个用于对称加密的密钥X,用公钥B加密传给服务器。 中间人劫持后用私钥B解密得到密钥X,再用公钥A加密后传给服务器。 服务器拿到后用私钥A解密得到密钥X。 经过了这个过程,在浏览器看来就是已经与服务器建立连接了,所以接下来浏览器会用这个密钥X与服务器进行对称加密传输,但是此时黑客已经是知道了这个密钥X,所以接下来浏览器与服务器的一切数据传输操作都会暴露给黑客。非常不安全。 那如何解决这个问题呢,如何证明浏览器收到的公钥一定是该网站的的公钥? 找CA机构,颁发数字证书。以后使用https通信时,服务器把证书传输给浏览器,浏览器从证书里获取公钥就行了。(这样就不会发生公钥被劫持的问题了) 那怎么防止数字证书被篡改呢? 利用防伪技术——" 数字签名 "。把证书原来的内容生成一份"签名"(即 数字签名),浏览器比对证书内容和签名是否一致就能判别是否被篡改。(浏览器使用CA机构的公钥S解密,得到S’,然后对证书的明文T用指明的hash算法计算得到T’,如果S’==T’,则证明证书可信) 2.7 数据库索引简单说一说?B+树的结构了解吗? 可以讲一下B+树的前世今生,也就是它的演进过程。 AVL树 -> B树 -> B+树 AVL树: 对二叉查找树BST,做了一些限制,限制必须满足任何节点的两个子树的最大差为1。(AVL树是一种自平衡二叉查找树)。当树插入/更新/删除时就会破坏树的平衡,此时就需要树进行左旋和右旋来调整平衡。AVL树的查找效率为 O(logn) 。 而且索引文件(数据)是存储在磁盘的。文件系统在磁盘读取数据时,一般以页为单位进行读取,假设一个页内的数据过少,那么操作系统就需要读取更多的页,设计磁盘随机IO访问的次数就更多。(将数据从磁盘读入内存涉及随机I/O的访问,成本很高) 因为AVL树的树高会随数据量增多急剧增加,每次更新数据又需要通过左旋和右旋维护平衡,所以不太适合用于存储在磁盘上的索引文件(因为涉及的IO操作过多,效率低)。 B树: 将二叉树变成m叉树,这个m的大小可以根据单个页的大小做对应调整,从而使得一个页可以存储更多的数据,从磁盘中读取一个页可以读到更多的数据,随机IO次数变少,大大提升效率。缺点:B树只能通过中序遍历查询全表,当进行范围查询时,可能需要中序回溯。 B+树:(针对B树进行改进) 改进1:在叶子结点增加了指针进行连接,即叶子结点间形成了链表(双向链表)。改进2: 非叶子结点只存关键字key,不再存储数据(数据只在叶子结点存储) 优点 :因为叶子结点的双向链表,可以更方便进行范围查询; 非叶子结点只存储索引key,相当于单个索引大小变小,所以同一个页可以存储更多的关键字,这样可以减少随机I/O的读写次数,查询效率大大提升了。 2.8 LRU Cache了解吗? LRU = Least Recently Used 最近最少使用 它是一种缓存的淘汰策略。LRU 算法是假设最近最少使用的那些信息,将来被使用的概率也不大,所以在容量有限的情况下,就可以把这些不常用的信息踢出去,腾地方给别人。 3. 三面 主要聚集点:项目、Mysql、操作系统 3.1 自我介绍 3.2 项目经历 3.3 算法题 3.4 Redis单线程、多线程问题 点击直达我之前写的一篇文章,里面详细描述了Redis的这个特性。 面试官:Redis 是单线程还是多线程?(你为何怎么说都不对?) 3.5 Mysql 事务的特性? (1)原子性(Atomicity) 原子性是指一个事务是一个不可分割的工作单位,其中的操作要么都做,要么都不做; 如果事务中一个sql语句执行失败,则已执行的语句也必须回滚,数据库退回到事务前的状态。 实现原理: 实现原子性的关键,是当事务回滚时能够撤销所有已经成功执行的sql语句。 In-noDB实现回滚,靠的是undo log:当事务对数据库进行修改时,InnoDB会生成对应的undo log。 如果事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。 介绍一下MySQL的事务日志:MySQL的日志有很多种,如二进制日志、错误日志、查询日志、慢查询日志等。 此外InnoDB存储引擎还提供了两种事务日志:redo log(重做日志)和undo log(回滚日志)。其中redo log用于保证事务持久性;undo log则是事务原子性和隔离性实现的基础。 undo log属于逻辑日志,它记录的是sql执行相关的信息。当发生回滚时,InnoDB会根据undo log的内容做与之前相反的工作:对于每个insert,回滚时会执行delete;对于每个delete,回滚时会执行insert;对于每个update,回滚时会执行一个相反的update,把数据改回去。 以update操作为例:当事务执行update时,其生成的undo log中会包含被修改行的主键(以便知道修改了哪些行)、修改了哪些列、这些列在修改前后的值等信息,回滚时便可以使用这些信息将数据还原到update之前的状态。 (2)一致性(Consistent) 一致性是指事务执行结束后,数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态。数据库的完整性约束包括但不限于:实体完整性(如行的主键存在且唯一)、列完整性(如字段的类型、大小、长度要符合要求)、外键约束、用户自定义完整性(如转账前后,两个账户余额的和应该不变)。 实现 可以说,一致性是事务追求的最终目标:前面提到的原子性、持久性和隔离性,都是为了保证数据库状态的一致性。此外,除了数据库层面的保障,一致性的实现也需要应用层面进行保障。 实现一致性的措施包括: 1.保证原子性、持久性和隔离性,如果这些特性无法保证,事务的一致性也无法保证 2.数据库本身提供保障,例如不允许向整形列插入字符串值、字符串长度不能超过列的限制等 3.应用层面进行保障,例如如果转账操作只扣除转账者的余额,而没有增加接收者的余额,无论数据库实现的多么完美,也无法保证状态的一致 (3) 隔离性(Isolation) 与原子性、持久性侧重于研究事务本身不同,隔离性研究的是不同事务之间的相互影响。隔离性是指,事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。 严格的隔离性,对应了事务隔离级别中的Serializable (可串行化),但实际应用中出于性能方面的考虑很少会使用可串行化。隔离性追求的是并发情形下事务之间互不干扰。SQL标准定义了4类隔离级别:Read Uncommitted(读取未提交内容)、Read Committed(读取提交内容)、Repeatable Read(可重读)、Serializable(可串行化)。 简单起见,我们仅考虑最简单的读操作和写操作(暂时不考虑带锁读等特殊操作),那么隔离性的探讨,主要可以分为两个方面:(一个事务)写操作对(另一个事务)写操作的影响:锁机制保证隔离性 (一个事务)写操作对(另一个事务)读操作的影响:MVCC保证隔离性。 (4) 持久性(Durable) 持久性是指事务一旦提交,它对数据库的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。 实现原理: redo log和undo log都属于InnoDB的事务日志,下面先聊一下redo log存在的背景。 InnoDB作为MySQL的存储引擎,数据是存放在磁盘中的,但如果每次读写数据都需要磁盘IO,效率会很低。为此,InnoDB提供了缓存(Buffer Pool),Buffer Pool中包含了磁盘中部分数据页的映射,作为访问数据库的缓冲:当从数据库读取数据时,会首先从Buffer Pool中读取,如果Buffer Pool中没有,则从磁盘读取后放入Buffer Pool;当向数据库写入数据时,会首先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)。 Buffer Pool的使用大大提高了读写数据的效率,但是也带了新的问题:如果MySQL宕机,而此时Buffer Pool中修改的数据还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。 于是,redo log被引入来解决这个问题:当数据修改时,除了修改Buffer Pool中的数据,还会在redo log记录这次操作;当事务提交时,会调用fsync接口对redo log进行刷盘。如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复。redo log采用的是WAL(Write-ahead logging,预写式日志),所有修改先写入日志,再更新到Buffer Pool,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求。 既然redo log也需要在事务提交时将日志写入磁盘,为什么它比直接将Buffer Pool中修改的数据写入磁盘(即刷脏)要快呢?主要有以下两方面的原因: (1)刷脏是随机IO,因为每次修改的数据位置随机,但写redo log是追加操作,属于顺序IO。 (2)刷脏是以数据页(Page)为单位的,MySQL默认页大小是16KB,一个Page上一个小修改都要整页写入;而redo log中只包含真正需要写入的部分,无效IO大大减少。 如果这篇文章有帮助到你,别忘了关注 点赞 在看一键三连哦! 我是浩说 关注我的公众号,不断为你分享各种干货 我的经历经验,或许可以给你带来意想不到的帮助!