「面试」拿到B站的意向书

这次B站服务端开发面试之旅可谓惊险,不过经过对大部分面试题套路的掌握,不出意外仍是拿下了,下面咱们来看看这些骚题是否是常见的不能再常见的了。这些面试题看了就能面上?固然不是,只是经过这些题让本身知道所欠缺的是什么,以及能够去看看哪些资料。java

1 操做系统相关

  • 自旋锁和通常锁的区别是什么?为何要使用自旋锁?

当一个线程在获取锁的时候,若是这个锁已经被其余线程获取,那么这个线程不会破门而入,而是循环等待,可是嗷嗷待哺,须要不断地嗷嗷叫判断锁是否被成功获取,直到获取到锁才会退出循环。mysql

自旋锁一般会出现哪些问题?web

若是某个线程拿着锁死不放手,其余线程无法拿到这把锁,只好等待获取锁的线程进入循环等待的状态,等待不是睡觉,仍是会消耗CPU,等待久了就会致使CPU的使用率过高。面试

那么自旋锁和其余锁到底有啥不一样?redis

从线程状态来看,自旋锁的状态是运行-运行-运行。而非自旋锁的状态是运行—阻塞—运行,因此自旋锁会更高效。sql

无论是什么锁,都是为了实现保护共享资源而提出的一种锁机制,都是为了对某项资源的互斥使用。对于互斥锁而言,若是资源已经被占用,那么资源的申请者只会进入睡眠的状态。而自旋锁不会引发调用者睡眠,而是一直循环在那里查看该自旋锁的保持着是否已经释放了锁。数据库

那么在Java中如何去实现一个自旋锁后端

public class SpinLock { 
 
  
    private AtomicReference<Thread> cas = new AtomicReference<Thread>();
    public void lock() { 
 
  
        Thread current = Thread.currentThread();
        // 利用CAS
        while (!cas.compareAndSet(null, current)) { 
 
  
            // DO 
        }
    }
    public void unlock() { 
 
  
        Thread current = Thread.currentThread();
        cas.compareAndSet(current, null);
    }
}

上段代码中,方法lock利用的CAS,当线程A获取锁的时候,成功获取不会进入while循环。若是此时线程A没有释放锁,当线程B来获取锁的时候,因为不知足CAS,就会进入whilei循环,不断判断是否知足CAS,直到线程A调用unlock释放。数组

自旋锁有哪些优势?缓存

  1. 由于运行在用户态,没有上下文的线程状态切换,线程一直处于active,减小了没必要要的上下文切换,从而执行速度较快
  2. 由于非自旋锁在没有获取锁的状况下会进入阻塞状态,从而进入内核态,此时就须要线程的上下文切换,由于阻塞后进入内核调度状态,会致使用户态和内核态之间的切换,影响锁的性能。
  • 了解哪些I/O模型?select是阻塞IO吗?

首先将IO模型给安排一遍,而后把本身很熟悉的IO模型详细说一波并介绍出应用场景,这个装的X就算比较完美,具体的很是详细的在下一篇文章,这里简要说一波。这一部分在上一篇详细阐述过

阻塞IO

咱们知道在调用某个函数的时候无非就是两种状况,要么立刻返回,而后根据返回值进行接下来的业务处理。当在使用阻塞IO的时候,应用程序会被无情的挂起,等待内核完成操做,由于此时的内核可能将CPU时间切换到了其余须要的进程中,在咱们的应用程序看来感受被卡主(阻塞)了。

阻塞IO

非阻塞IO

当使用非阻塞函数的时候,和阻塞IO类比,内核会当即返回,返回后得到足够的CPU时间继续作其余的事情。

非阻塞

IO复用模型

当使用fgets等待标准输入的时候,若是此时套接字有数据但不能读出。IO多路复用意味着能够将标准输入、套接字等都当作IO的一路,任何一路IO有事件发生,都将通知相应的应用程序去处理相应的IO事件,在咱们看来就反复同时能够处理多个事情。这就是IO复用

IO复用

信号驱动IO

在信号驱动式 I/O 模型中,应用程序使用套接口进行信号驱动 I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个 SIGIO 信号,能够在信号处理函数中调用 I/O 操做函数处理数据。

信号驱动

异步IO

用程序告知内核启动某个操做,并让内核在整个操做(包括将数据从内核拷贝到应用程序的缓冲区)完成后通知应用程序。那么和信号驱动有啥不同?

异步IO

  • 讲讲select和epoll的区别?

这里同样的套路,先说出二者的用途,而后二者的优缺点。

select的缺点

  • select返回的是含有整个句柄的数组,应用程序须要遍历整个数组才能发现哪些句柄发生了事件
  • select的触发方式是水平触发,应用程序若是没有完成对一个已经就绪的文件描述符进行IO操做,那么以后每次select调用仍是会将这些文件描述符通知进程
  • 内核 / 用户空间内存拷贝问题,select每次都会改变内核中的句柄数据结构集,于是每次select调用时都须要从用户空间向内核空间复制全部的句柄数据结构,产生巨大的开销
  • 单个进程可以监视的文件描述符的数量存在最大限制,一般是1024,固然能够更改数量

epoll实现

epoll在内核中会维护一个红黑树和一个双向链表,红黑树存放经过epoll_ctl方法向epoll对象中添加进来的事件,因此不须要每次调用epoll_wait都全量复制全部的事件结构。双向链表存放就绪的事件,全部添加到epoll中的事件都会与设备(网卡)驱动程序创建回调关系,也就是说,当相应的事件发生时会调用这个回调方法,这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中。调用epoll_wait就会直接返回链表中的就绪事件,效率高。

  • select适合少许活跃链接,通常几千。

  • epoll适合大量不太活跃的链接。

  • 乐观锁和悲观锁了解吗?

这个问题延伸的问题会不少,好比线程安全,CAS原理,优缺点等。

啥是悲观和乐观,咋们面试的时候不得乐观一些。想给面试来一波官方解释,而后大白话解释一波就差很少了。

官方:悲观锁是老是假设最坏的状况,每次那数据都认为别人会修改它,因此每次去那数据都要上锁,这样别人去拿这个数据就会阻塞。乐观锁就不同了,老是以为一切都是最好的安排,每次拿数据都认为别人不会修改,因此也就不上锁,可是在更新的时候会判断这个期间别人有没有更新这个数据。

  • 什么是缓存穿透?如何避免?什么是缓存雪崩?何如避免?

缓存穿透

通常来讲,缓存系统会经过key去缓存查询,若是不存在对应的value,就应该去后端系统查找(好比DB)。这个时候若是一些恶意的请求到来,就会故意查询不存在的key,当某一时刻的请求量很大,就会对后端系统形成很大的压力。这就叫作缓存穿透。

如何避免?

对查询结果为空的状况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了以后清理缓存。对必定不存在的key进行过滤。能够把全部的可能存在的key放到一个大的Bitmap中,查询时经过该bitmap过滤。

缓存雪崩

当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。致使系统崩溃。

如何避免?

在缓存失效后,经过加锁或者队列来控制读数据库写缓存的线程数量。好比对某个key只容许一个线程查询数据和写缓存,其余线程等待。

作二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,能够访问A2,A1缓存失效时间设置为短时间,A2设置为长期。

不一样的key,设置不一样的过时时间,让缓存失效的时间点尽可能均匀。

2 redis相关

若是是后端/服务端面试的同窗,怎么说都的去找一本redis书来看看,其出现的几率只有那么大了,切记切记。看看B站问了哪几个问题。

  • redis的淘汰删除策略了解吗?

能说不了解吗,就算是没有据说过,咋们也能够来一句:“很差意思面试官,这一块还不怎么深刻,可是从字面意思来理解巴拉巴拉”,不至于一脸懵逼。下面咱们看看redis的缓存策略

Redis中经过maxmemory参数来设定内存的使用上限,若是Redis所使用内存超过设定的最大值,那么会根据配置文件中的策略选取要删除的key来删除,从而留出新的键值空间。主要的六种淘汰key策略

  1. volatile-lru

在键空间中设置过时时间,移除哪些最近最少使用的key,占着茅坑不拉屎的key

  1. allkeys-lru

移除最近最少使用的key

  1. volatile-random

在键空间中设置过时时间,随机移除一个key

  1. allkeys-random

随机移除一个key

  1. noeviction

当内存使用达到阀值的时候,全部引发申请内存的命令会报错;

ok,如今知道了须要淘汰哪些key,那咱们如何去淘汰这些key

  1. 定时删除

很简单,设置一个闹钟,闹钟响了就删除便可。这种方式对于内存来讲仍是比较友好,内存不须要啥额外的操做,直接经过定时器就可保证尽快的删除。对于CPU来讲就有点麻烦了,若是过时键比较多,那么定时器也就多,这删除操做就会占用太多的CPU资源

  1. 惰性删除

每次从键空间获取键的时候检查键的过时时间,若是过时了,删除完事。

  1. 按期删除

每隔一段时间就去数据库检查,删除过时的键

这种方案是定时删除和惰性删除的中和方法,既经过限制删除操做执行的时长来减小对CPU时间的影响,也能减小内存的浪费。可是难点在于间隔时长须要根据业务状况而定。

3 mysql

  • mysql中使用的锁有哪些?何时使用行锁,何时会使用表锁?

InnoDB中的行锁是经过索引上的索引项实现,主要特色是,只有经过索引条件检索数据,InnoDB才会使用行级锁,不然InnoDB将使用表锁。

这里注意,在Mysql中,行级锁不是锁记录而是锁索引。索引又分为主键索引和非主键索引两种。若是在一条语句中操做了非主键索引,Mysql会锁定该非主键索引,再锁定相关的主键索引。

  • 了解过间隙锁吗?间隙锁的加锁范围是怎么肯定的?
  • 了解B+树吗?B+树何时会出现结点分裂?

这个回答在上一篇的B+树已经详细说了。这里简述一下

  1. 将已满结点进行分裂,将已满节点后M/2节点生成一个新节点,将新节点的第一个元素指向父节点。
  2. 父节点出现已满,将父节点继续分裂。
  3. 一直分裂,若是根节点已满,则须要分类根节点,此时树的高度增长。
  • 事务还没执行完数据库挂了,重启的时候会发生什么?
  • undo日志和redo日志分别是干吗的?

redo log重作日志是InnDB存储引擎层的,用来保证事务安全。在事务提交以前,每一个修改操做都会记录变动后的数据,保存的是物理日志-数据,防止发生故障的时间点,有脏页未写入磁盘,在重启mysql的时候,根据redo log进行重作从而达到事务的持久性

undo log回滚日志保存了事务发生以前的数据的一个版本,能够用于回滚,同时也提供多版本并发控制下的读。

  • 简单讲讲数据库的MVCC的实现原理?

细说太多了,几个大写字母表明啥,这几个大写字母又是如何关联起来完事。细问再深究

  • mysql的binlog日志何时会使用?

首先应该知道binlog是一个二进制文件,记录全部增删改操做,节点之间的复制都会依靠binlog来完成。从底层原理来讲,binlog有三个模式

  1. 模式1–row模式

每一行的数据被修改就会记录在日志中,而后在slave段对相同的数据进行修改。好比说"update xx where id in(1,2,3,4,5)",使用此模式就会记录5条记录

  1. 模式2–statement模式

修改数据的sql会记录到master的binlog中。slave在复制的时候sql thread会解析成和原来maseter端执行过的相同的sql在此执行

  1. 模式3–mixed模式

mixed模式即混合模式,Mysql会根据执行的每一条具体sql区分对待记录的日志形式。那么binlog的主从同步流程究竟是咋样的

binlog同步

流程简述:

Master执行完增删改操做后都会记录binlog日志,当须要同步的时候会主动通知slave节点,slave收到通知后使用IO THREAD主动去master读取binlog写入relay日志(中转日志),而后使 SQL THREAD完成对relay日志的解析而后入库操做,完成同步。

4 基本数据结构

  • 使用LRU时,若是短期内会出现大量只会使用一次的数据,可能致使以前大量高频使用的缓存被删除,请问有什么解决办法?
  • 了解过循环链表吗?他的长度怎么计算?

他的主要特色是链表中的最后一个节点的指针域指向头结点,整个链表造成一个环。***这里*循环链表判断链表结束的标志是,判断尾节点是否是指向头结点

  • 哪一种数据结构能够支持快速插入,删除,查找等操做?

思考这个问题的时候,咱们不凡复习下不错的二分查找,它依赖数组随机访问的特性,其查找时间复杂度为O(log n)。若是咱们将元素放入链表中,二分查找还好使吗?这就是今天和你们分享的跳表

理解跳表

假设使用单链表存储n个元素,其中元素有序以下图所示

一级索引

从链表中查找一个元素,天然从头开始遍历找到须要查找的元素,此时的时间复杂度为O(n)。那采用什么方法能够提升查询的效率呢?问就是加索引,如何加,咱们从这部分数据中抽取几个元素出来做为单独的一个链表,以下图所示]

假设此时咋们查找元素16,首先一级索引处寻找,当找到元素14的时候,下一个节点的值为18,意味着咱们寻找的数在这两个数的中间。此时直接从14节点指针下移到下面的原始链表中,继续遍历,正好下一个元素就是咱们寻找的16。好了,咱们小结一下,若是从原始链表中寻找元素16,须要遍历比较8次,若是经过索引链表寻找咱们只须要5次便可。

在这里插入图片描述

咱们继续查找元素16,此时比较次数变为4次。这样看来,加一层索引查找的次数就变少,若是有n个元素到底有多少索引?

假设咱们按照每两个结点就抽出一个结点做为上一层的索引节点,第一层因此节点个数n/2,第二层为n/4,第x级索引的结点个数是第x-1级索引的结点个数的1/2,那第x级索引结点的个数就是n/(2x)。假设索引有y级,咱们能够获得n/(2y)=2,从而求得y=log2n-1。

这么多索引是否是就很浪费内存嘞?

假设原始链表大小为n,那第一级索引大约有 n/2 个结点,第二级索引大约有 n/4 个结点,以此类推,每上升一级就减小一半,直到剩下 2 个结点。若是咱们把每层索引的结点数写出来,就是一个等比数列。这几级索引的结点总和就是 n/2+n/4+n/8…+8+4+2=n-2 。因此,跳表的空间复杂度是 O(n) 。那还能不能下降一些呢。机智的你应该就考虑到假设每三个结点抽取一个节点做为索引链表的节点。

跳表与二叉查找树

二者其查找的时间复杂度均为O(logn) ,那跳表还有哪些优点?

先看二叉查找树,

特殊二叉查找树

这种结构会致使二叉查找树的查找效率变为 O(n),。

跳表与红黑树

说实话,红黑树确实比较复杂,面试的时候让你写红黑树,你就给他大嘴巴子?

红黑树须要经过左右旋的方式去维持树大小平衡。而跳表是经过随机函数来维护前面提到的 “ 平衡性 ” 。当咱们往跳表中插入数据的时候,咱们能够选择同时将这个数据插入到部分索引层中。如何选择加入哪些索引层呢?
咱们经过一个随机函数,来决定将这个结点插入到哪几级索引中,好比随机函数生成了值 K ,那咱们就将这个结点添加到第一级到第 K 级这 K 级索引中。当咱们往跳表中插入数据的时候,咱们能够选择同时将这个数据插入到部分索引层中。

小结

Redis中的有序集合采用了跳表的方式来实现,其实还采用了散列表等数据结构进行融合。它在插入,删除等都有比较快的速度,虽然红黑树也能够作到,可是红黑树对于按照区间查找数据这个操做,跳表能够作到 O(logn) 的时间复杂度定位区间的起点,而后在原始链表中顺序日后遍历就能够了

  • 平时爱看技术博客吗?分享一篇最近的技术博客?平时上B站吗?

看的技术博客多了,这就是唠嗑。好比说,看看小贱一每天BB的文章,哈哈哈哈哈

面试官:我擦,尼玛说的这个我都关注了,难怪我问啥你都能说个一二三。

5 总结

请记下如下几点:

  • 公司招你去是干活了,不会由于你怎么怎么的而下降对你的要求标准。
  • 工具上面写代码和手撕代码彻底不同。
  • 珍惜每一次面试机会并学会复盘。
  • 对于应届生主要考察的仍是计算机基础知识的掌握,项目要求没有那么高,是本身作的就使劲抠细节,作测试,只有这样,才知道会遇到什么问题,遇到什么难点,如何解决的。从而能够侃侃而谈了。
  • 非科班也不要怕,怕了你就输了!必定要多尝试。

img


我是小蓝,一个专为你们分享面试经验的蓝人。若是以为文章不错或者对你有点帮助,感谢分享给你的朋友,也可在下方给小蓝赞,这对小蓝很是重要,谢谢大家,下期再会。