线程部分
1
、多线程的实现方式有哪些?
这个题目在一面的时候基本上都会碰到吧,继承
Thread
类、实现
Runnable
接口,最后调用 的是
start()
方法来启动线程。
这里还有个知识点是
start()
跟
run()
方法的区别和联系。
直接调用
start()
方法,此时线程处于一个就绪(可运行)的状态,但是并没有真正的运行。而是得到
CPU
的时间片后,开始执行
run()
方法,
run()
方法里面的是我们的线程体。
我们直接 运行
run()
方法,它其实就是一个普通的方法调用,在主线程中执行,
是不会开启多线程的
。
2
、描述一些线程死锁的情况?
这个问题在平常项目基本上没怎么接触到,但是我有过部分了解。回答的是: 两个线程在持有自己的锁的时候,还要去持有对方持有
的锁时,由于别人的锁已经被对方持有,造成彼此等待对方释放锁的情况。回答得比较片面,还有一些类型的死锁问题没有答出来,
后面直接交底了,面试官说没关系的。
建议大家在准备这个问题的时候能说出来产生死锁的条件、现象、解决办法等。然后配上一些实例说明,在面试过程中,面试官就提
到说根据我们平常遇到死锁问题的场景实例来说。
大家可以搜一下下面这两个死锁场景问题:
1
、三个人 三根筷子:每个人需要拿到身边的两根筷子才能开始吃饭
2
、银行转账问题:线程
A
从
X
账户向
Y
账户转账,线程
B
从账户
Y
向账户
X
转账,那么就会发生死锁。
3
、项目中有没有用过线程池 ?怎么用的 ?
回答了我们项目里面有些接口需要组装多个服务的数据进行封装,然后返回。这里面我们会使用多线程去并行拉取数据,减少接口响
应时间。
面试官说:
“ok
,那么你有没有看过线程池里面的源码呢 ?有哪几种线程池 ?
”
源码这里我迟疑了一下,我说不太熟,然后我说了几种类型的线程池
newSingleThreadExecutor
、
newFixedThreadPool
、
newCachedThreadPool
但是还漏了一种
newScheduledThreadPool
没想起来。
4
、线程池的原理是什么样子?底层方法的参数分别是什么意思?
回答这个问题的时候,当时我卡住了。我知道这几个底层都是对 调用的
ThreadPoolExecutor
,但是我死活没有想起来名字,这时候
面试官提醒了一下,然后说没关系的。
接着就问:
“
那你知道他的参数都有哪些吗 ?都分别代表什么意思吗 ?
”
我回答的是 有个 线程的个数 和 线程存活的时间,其他的没说上来。然后面试官说:
“
没关系的
”
。
补充一下:线程池底层都是通过
ThreadPoolExecutor
来实现的。
几个参数的意思分别为:
corePoolSize
: 线程池里最小线程数
maximumPoolSize
:线程池里最大线程数量,超过最大线程时候会使用
RejectedExecutionHandler
keepAliveTime
:线程最大的存活时间,超过这个时间就会被回收
unit
:线程最大的存活时间的单位
workQueue
:缓存需要执行的异步任务的队列
threadFactory
:新建线程工厂
handler
:拒绝策略,表示当
workQueue
已满,且池中的线程数达到
maximumPoolSize
时,线程池拒绝添加新任务时采取的策
略。
DiscardPolicy
:抛弃当前任务,
DiscardOldestPolicy
:扔掉最旧的,
CallerRunsPolicy
:由向线程池提交任务的线程来执行该
任务,
AbortPolicy
:抛出
RejectedExecutionException
异常。
数据库
先问的是,你平常使用得做多的是什么数据库,当然了,
mysql
。
8
、
mysql
锁机制 ?
面试官问的是,你了解
mysql
的锁机制么?我就只答出来一个行锁。然后其他的没想起,就认了,其他的忘记了。
建议你去了解了解还有表锁、页面锁 等等。
9
、排它锁
&
共享锁你了解吗 ?
这个地方我想了一会,说平时了解得不多。实时上,平常我们的小业务系统基本上没有用到这些,可能有用到的地方,也没有去在意
吧。
接着,面试官说了下面这个场景题,然后让出解决方案。
10
、场景问题:在
A
线程处理一条数据,比如扣款,或者是更新状态时候,其他的线程比如
B
需要对它进行阻塞,不能够再对这条数
据进行操作,包括查询也不行,得等
A
线程处理完成以后,
B
才能进行处理。
A
跟
B
是同样的业务代码产生的,非不同的业务。要使
用数据库的锁来实现,怎么实现 ?
问这个问题的时候,面试官很耐心的解释了这个场景,然后问我有没有想起点什么来?其实就是想考察上面的关于数据库锁的问题。
11
、
mysql
索引是怎么实现的?
回答的是
B+
树,接着面试官继续问:
“
能否大致描述一下
B+
树的大致结构 ?
”
。这块内容没怎么了解,直接认怂了。
缓存相关
这块内容是我项目上写得有使用了多级缓存的方案,然后面试官就这一块问了下面的这些关于使用缓存可能会遇到的问题。
12
、缓存击穿、缓存穿透 、缓存雪崩 ?
13
、热点数据失效怎么解决?
这两个问题,以前好好了解过,但是没整理成自己的东西,面试的时候也说得云里雾里。
14
、先删缓存还是先更新数据库,为什么?
这里我说的:
是先删缓存,然后再更新数据库
,
但这是错误的
,这里有非常大的问题。
想想这样一个场景:
如果一个线程
A
先把缓存删除了
,
然后去更新数据库,那么在它删了缓存还没有更新到数据库的这个中间时间,线程
B
进来了,发现缓
存没有,就去读库,这时候还是读取还是旧的数据,然后又更新到缓存去了。此时
A
才把新数据写到数据库。
此时就产生了一个典型的问题就是
“
双写不一致
”
。
kafka
15
、
kafka
的架构,包含了哪些角色?
这个问题我开始不知道怎么回答,就说了个
Broker
,然后面试官提醒了一下:
“
不是我们平常还有生产者,消费呀什么的吗 ?
”
额,我
说还有生产者、消费者、主题呀等等。
这过程中面试官还提到说平常我们在搭建的时候要配置写什么东西呀等等,按照官网的介绍说也行。
这里还有其他的比如
Partition
、消费者组、还有一个主要的 就是
zk
了。
这里建议大家好好的把
kafka
里面的这些概念、属于、架构图好好自己画一下。不然真是关键时候真说不出来,但是他一提你又明
白。这样子肯定是不行的,面试是你说,不是面试官说。
16
、
kafka
的最小工作单元?
这个问题问得也是蒙圈,其实就是说我们在写代码的时候,要用
kafka
的时候。我们需要使用那些最基础的组件,比如生产者、消费
者、主题、偏移量 等等。
这个问题如果你们遇到,最好向面试官问清楚。
17
、
kafka
消息重复消费的问题?幂等怎么做的?
刚开始面试官说,你知道
kafka
消息重复的问题吗?有没遇到。
我回答的是,会存在消息重复消费的问题。我们在消费数据这端做了幂等处理来解决。
然后面试官继续才问的是:幂等怎么来做的, 我说通过设置数据版本号,还有数据库唯一索引等等。
面试官:
“ok”
。
这个问题,如果你能告诉面试官产生重复消费的情况,比如说投递的时候重复了,消费的时候由于
offset
没处理好等等问题导致的
话,我想可能会更好。
18
、
kafka ack
机制?集群中的
ack
是怎么实现的?
这里我只回答上了
ack
机制是啥,但是实现原理没有回答上来。
Redis
19
、
Redis
中有哪些数据结构
平常使用得最多的是
String ,
还有
List
、
Hash
、
Set
、
ZSet
。
没有再问其他的内容。
但是像
Redis
为什么这么快这种问题,我认为你应该要去了解,其他小伙伴经常遇到。也就是多路复用是个什么玩意儿?
源码
20
、这里面试官问 你平常有没有看过一些源码?框架的也行?
JDK
的也行。
然后我说看了
HashMap
的源码
,
然后就巴拉巴拉的说了一哈大体的
put
、
get
流程 ,它的结构是什么样子的。
这过程中还问到了 怎么判断两个对象是否相等?也就是
==
和
equals
的知识点。
其他的就没再继续问了。到这里整个电面过程结束了,说
10
个工作日内会给我答复此次面试情况。整个过程大概持续了
40
分钟的样
子。
我知道,凉凉。
最后总结一下
上面的模块虽然顺序有变化,但是每个大块里面的问题都是按照顺序来的,基本上都是由浅入深、循序渐进的来问。
像数据库锁、线程池、缓存问题
这些内容几乎都是那种连环炮的形式,直到摸到你的底
。
通过这次面试,亲身体会到了差距。不过,更有方向了。
告诫大家一点东西:
平常多积累输出:输出或者教会别人是最好的学习方式,光看不练,几天就忘。
先深后广:深入学习,而不是只停留在使用
API
的层面,一块一块系统的深入了解,再去搞其他的。
建立知识体系:把自己学习的内容形成博客也好,什么导图也罢,记得把这些零散的内容,整理成自己的知识。
别抱有侥幸心理:别裸面,如果自己有整理的还是多看一下,多准备准备。大厂的面试会挖到你最深的部分,不要觉得只背一些
面试题就是
ok
的,题是背不完的。临时抱佛脚基本上过不了关。如果你是靠背面试题进去的,那么你厉害,佩服。
隔一段时间就去面试吧:不要学我,待一家公司三年多,中途都没有出去面过,出去面面才知道,哪些是需要去补充的。
有些问题可能答案这些不是太全面,需要你自己去动手。
希望这篇文章对你有帮助,哪怕只有一个点,都是值得的。如果其中有一些点你不了解,那么你是时候要去补充了。
别在自己的舒适区待太久,不然出不来。出来混,迟早是要还的!
电面一
1
、
2
分钟的自我介绍
大致讲了自己的姓名,毕业院校,哪年毕业,个人爱好以及平时空闲时间做点什么,这个如实回答就好。因为之前有面试过,所以准
备过。建议可以自己提前写下来,多说几遍,找点感觉。
2
、你自己认为自己最熟悉的技术是什么?
这个就因人而异了,每个人熟悉的东西都不一样,一定要说自己最擅长的东西,不要给自己挖坑。因为面试官下一步就会根据你的回
答进行提问。对于我来是,工作了几年学的东西多而杂,没有什么很深入的,但是总不能说没有吧,所以就说了
Java
开发比较多,
所以
Java
语言熟悉多一点。然后面试官就说:
“
好,那我就问你一点
Java
语言方面的。
”
3
、
HashMap
底层实现原理是什么?
这个作为一个面试必问的题目,所以我还是提前准备过的,看过源码。所以这个问题不是问题,答完,面试官说回答的对了。
HashMap
,
HashTable
,
ConcurrentHashMap
面试必备,针对
1.7
和
1.8
的不同实现加以说明。包括底层的数据结构,
Hash
碰撞生
成链表,
Java8
的链表转红黑树。
4
、
Java
的多线程有没有使用过
根据自身情况,用过就用过,没用过就没有用过。我回答有简单的使用过,但是使用的场景不多。面试官也就没追问了,说了没关
系,就继续。
5
、讲一下线程池,以及实现固定大小线程池底层是如何实现的?
讲了下四中线程池,单一线程池,固定大小线程池,缓存线程池,定时线程池。但是关于固定大小线程池底层是如何实现的,回答的
不好,面试官直接问底层的源码是不是没看过,就说是的。面试官说没关系。。。
追加:线程池底层都是通过
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue
workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
来实现的。
corePoolSize
:
表示需要设置的线程个数;
maximumPoolSize
:
线程池允许的最大线程个数;
keepAliveTime
:
空闲线程存活的
时间,超过这个时间就会被回收;
unit
:
存活时间的单位;
workQueue
:
需要执行的任务队列。
threadFactory
:
线程工厂,用
于创建线程,一般用默认的即可;
handler
:
拒绝策略,当任务太多来不及处理,如何拒绝任务; 拒绝策略: 直接丢弃
(
DiscardPolicy
) 丢弃队列中最老的任务
(DiscardOldestPolicy)
抛异常
(AbortPolicy)
将任务分给调用线程来执行
(CallerRunsPolicy)
6
、
Redis
为什么这么高效,使用的场景是什么?
回答了一下我们使用
redis
做缓存和登录
session
存在的场景,以及
redis
是单线程的。
1
、完全基于内存,大多数请求都是内存操作,非常快速;
2
、数据结构简单,操作简单;
3
、采用单线程,避免了不必要的上下文
切换和竞争条件,不存在多进程或者多线程的切换,不用考虑锁带来的性能消耗;
4
、使用多路
I/O
复用模型,非阻塞
IO
7
、分布式服务是否了解,
zookeeper
,
dubbo
是否使用过?
关于
zk
和
dubbo
这块用的不多,
zk
主要是在使用
kafka
的时候会用到,但是不涉及原理上面的研究。
dubbo
虽然项目中有用过,但
是并不是很深入,就没说用过,直接说没用过。
8
、幂等概念有没有了解过
幂等性是数学上的含义是对于参数
x
,
f(x)=f(f(x));
比如绝对值函数。 在分布式环境下表示的是对于同样的请求,在一次或者多次请
求的情况下对系统的使用资源是一样的。保证失败重试不会导致提交两次。 方法: 带版本号的方式; 采用数据库唯一索引方式;
9
、常用的数据库是什么?
我们常用的数据库是
MySQL
,所以就回答了
MySQL
。
10
、
MySQL
的事务特性有哪些?
•
首先事务是作为单个逻辑工作单元执行的一系列操作,这些操作作为一个整体一起向系统提交,要么都执行,要么都不执行。事务
是一个不可分割的逻辑单元。
•A
(原子性)事务的各步操作是不可分的,保证一系列的操作要么都完成,要么都不完成;
•C
(一致性)事务完成,数据必须处于一致的状态;
•I
(隔离性)对数据进行修改的所有并发事务彼此之间是相互隔离,这表明事务必须是独立的,不应以任何方式依赖或影响其他事
务;
•D
(持久性)表示事务对数据处理结束后,对数据更改必须持久化,不管是事务成功还是回滚。事务日志都能够保持事务的永久性。
11
、如果现在一台生产的数据库挂了怎么处理?
首先这题没有
get
的面试官想问的点是什么,所以就根据自己项目本身的情况做答了。我们项目生产上的数据库是有主备的,在主数
据库挂掉的情况下是会切换到备数据库,先保证业务的稳定性,然后在对崩溃现场进行保留,方便后续分析问题,找到原因。这里面
试官追问了一下,我们主备的切换是自动的还是手动的,这个由于是公司运维团队负责的,自己本身不是特别清楚,但是根据对公司
运维团队的了解,应该是自动的。所以就这样如实的回答了。
12
、数据库如何实现
rollback
的?
数据库在写入数据之前是先讲对数据的改动写入
redo log
和
undo log
,然后在操作数据,如果成功提交事务就会讲操作写入磁盘;如
果失败就会根据
redo log
和
undo log
逆向还原到事务操作之前的状态。
13
、工作这么久你遇到的最难的技术点是什么?
我这边根据具体的工具经理,回答的是
kafka
的初次使用,因为当时是公司内部第一个引入
kafka
,之前没有小组使用过,所以要采
很多坑。并且那个时候
kafka
还没有发布
1.0
版本,官网和网上提供的版本很杂乱不兼容。
14
、用过
Kafka
的话说下
Kafka
优缺点有哪些?
•Kafka
是一个高吞吐量的消息队列。基本的组件有生产者,消费者,
node
节点,生产者负责生产消息,将消息发送到指定的
topic
或者
partition
当中。
•
每个
partition
可以有多个分区副本,并且存放在不同的
broker
节点上,保证数据的安全。
partiton
的底层是根据
segment
段存放的
一系列日志文件,文件里面存放的具体的消息内容,每条消息都有一个唯一的
offset
偏移量,并且是按照磁盘顺序存放的。由于磁盘
是顺序读写,所以
kafka
可以有很高的吞吐量。磁盘的顺序读写比随机读写的性能高很多。
•
每个消费者都属于一个消费者组,可以消费指定
topic
下的数据。
15
、
TCP/IP
协议是如何保证数据可靠性的?
首先
TCP
是面向连接的传输协议。主要通过消息确认和重试机制来保证数据传输的可靠性。
电面二
流程
1
、先进行自我介绍,然后介绍自己做过的项目,从项目流程架构设计等方面介绍
根据个人经历说了自己所做的项目,以及流程和架构方面,因为是自己参与的项目,所以整个流程说的还是很流畅的。毕竟自己很熟
悉。这块尽量多从几个方面讲,流程,架构,设计等。
2
、
HashMap
的查询时间复杂度
理想情况下是
O(1)
的,但是实际中会出现
hash
碰撞,导致无法达到效果。
3
、
LinkedList
和
ArrayList
的区别
•LinkedList
底层是基于双向链表实现的,而
ArrayList
底层是基于动态数组实现的;
•
查询的时候
LinkedList
的效率要低于
ArrayList
,因为
LinkedList
需要遍历链表,而
ArrayList
底层数组根据下标直接获取数据。
•
插入删除数据的时候,
LinkedList
效率比
ArrayList
效率高,因为
ArrayList
在数据多的情况下会进行数组扩容或移动数组。
•
多进程与多线程在编程上面有什么需要注意的
首先进程是资源分配的最小单元,线程是任务调度的最小单元
5
、
ThreadLocal
的使用场景
ThreadLocal
适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的
场景。
6
、堆内存和栈内存有什么区别
•
堆内存是线程共享的,栈内存是线程私有的;
•
堆内存用来存放由
new
创建的对象和数组,栈内存中存放一些基本类型的变量和对象的引用变量;
7
、堆排序时间复杂度
8
、如果优化数据库的数据查询,另外应用层上还能如何优化?
1
)数据库层面上:
•
除了主键索引,唯一索引之外,对于常用的查询字段也要加索引。查询的时候尽量使用主键索引,因为
MySQL
的
InnoDB
的主键索引索引的是整行数据,而普通索引索引的是主键,会有回表操作。当然索引并不是越多越好,索引固然可以提高相应的 select
的效率,但同时也降低了 insert
及
update
的效率,需要酌情考虑。
2
、优化查询语句,尽量采用确认性查询语句,减少
or
,
in
,
not in
,%xxx%语法的使用。
2
)应用层面上:
•
采用缓存机制,将常用的数据进行缓存,增加访问速度;
•
分库分表,读写分离,将数据分开读写,提升性能
9
、强一致性,弱一致性,最终一致性
•
强一致性:对于更新后的数据,要求后续所有节点的任何操作都要获取最新值的情况;
•
弱一致性:对于更新后的数据,后续节点的数据操作可以是新值,也可以是旧值,通过一段时间后后续节点对数据的操作都是新值;
•
最终一致性:是弱一致性的特殊形式,存储系统保证在没有新的更新的条件下,最终所有的访问都是最后更新的值。
10
、有一个一百万行的文件,内部是购买的商品
ID
,如何获取到购买最多的前一百个商品。
•
思路:首先考察的肯定是大数据处理方案,这些数据肯定不能一次性读取到内存,那就需要拆分,将数据分隔处理。假设要分隔为
n个文件。
•
分隔:如果
ID
是整型的话,可以直接采用取模(
id % n
)的方式;如果
ID
是字符串可以先计算
hash
值然后再取模(
hash(x) % n
)
的方式,将相同
ID
的商品分到同一个文件中。
•
针对每个小文件进行
top100
的排序,返回购买最多的
100
个商品
ID
•
根据
n
个文件中的
100
个
ID
,在进行一次排序,即可得到需要的数据。
美团一面
1.
定义栈的数据结构,在该类型中实现一个能够得到栈最小元素的
min
函数。
这个问题我在知识星球里面也分享过了,回答的还可以,当时可能是在刷面试题的时候有印象,然后回答好之后面试官点了点头,说
大致思路是对的,具体的实现就不需要我写了,于是心惊胆战的过了这一道题。
2.
你在工作中遇到过棘手的什么问题么?可以随便列举。
当时听完这句话,瞬间想爆炸,我当时在想,我要说个空指针会不会被弄死,哈哈哈哈,开玩笑的,当时就回答了几个在使用框架的
时候遇到的一些版本不一致的坑,还有在
redis
中遇到的因为
GC
的问题导致
redis
数据异常的一些情况,然后说了我们当时是怎么处理
的,说完自我感觉良好,面试官当时思考了几分钟之后,应付的说了两句话,也没有发表任何的意见。我当时心里感觉慌了,完了,
第一波这是要凉凉呀,反转剧情出现,面试官开始和我聊人生,然后问了问年纪,工作了几年之类的话题,于是
30
分钟的电话面试就
结束了。
第二面确实是很全面的面试了,内容包含的挺全面的,从基础,到框架,到服务器上的一些
Linux
的一些命令,我给大家简单的列举
几个
1.
HashMap
是线程安全的吗?为什么?
当时听到这个问题,我就知道还有下文,肯定是不安全的,这还用说,为什么?我就开始长篇背诵,
HashMap
的实现里没有锁的机
制,然后巴拉巴拉一大堆,至于为什么不安全我相信观众肯定也都知道,
重点来了
,说完之后,接下来问,如果我要用线程安全的,
效率还稍微较高的,这时候来了
``ConcurrentHashMap`
就开始继续吹了,结果还行,这个问题回答的还可以。之后就是第二个问题了
1.
JVM
的垃圾回收机制你了解么?能简单说说么?
这个面试题之前我是有准备过的,然后就开始了我的老年代和新生代的各种垃圾回收机制和算法,具体文章
:
内存结构和垃圾回收算 法
这个问题回答完,时间才过去
20
分钟而已,柑橘好煎熬,怎么办?硬撑,这
2
个问题还算是可以的,然后就开始第三道题目了。
1.
你对设计模式是怎么理解的,工作中使用过么?都是怎么使用的,为什么会使用呢?
一连串的问题,让我有点蒙圈,不过我还是比较老实的回答了,设计模式,单例,工厂,策略,然后简单的说了一下他们在具体工作
中我都是怎么使用的,尤其是写代码的时候会注意到哪些内容。
当时我感觉第四个问题会不会深度更加的大的时候,然后面试官说了一句,你们工作中用的都是什么框架,你觉得他们都有哪些缺
陷?
这个问题抛出来的时候,我还很开心,结果事实证明,高兴过头是需要付出代价的,我把
Spring,SpringMVC,Mybatis,
都说了,顺带也
提了
Hibernate
和
Struts2
,结果因为我的话多了,面试官问到了那你们用的时候怎么选型的,为什么使用
Hibernate
而不适用
Mybatis
,
区别在哪里,优点和缺点在哪里?
这句话说完,内心感觉快凉透了呀,这你让我说
Spring
和
SpringMVC
还有
Mybatis
,那我还能扯淡一会,但是你让我说他们怎么选
型,考虑哪些方面,这个我确实是了解的不多呀,我就简单的说了一下,面试官问了一下,还有呢?我回答的是,我目前了解的只有
这么多了,结果可想而知,面试官的脸色已经开始阴云密布了。于是开始了不愉快的第四题。
1.
Linux
用的多么?你们之前公司都是怎么负责上线的?
由于之前的公司小的很,确实没什么可说的,于是我的经典回答,项目第一次部署不是我,我们更改了需求之后都是在测试环境下,
然后编译出相对应的
class
文件,然后做替换,然后我说了我上一家公司的名字,面试官可能百度了一下,确实是个小公司,可能没
有那么正规,于是这个问题回答的还算凑活,在我看来,可能算是勉勉强强过关了。
1.
你前端怎么样,你们之前公司前端使用的是什么?你写前端代码么?
说实话,这个问题有坑,你如果说你不写,那是不可能的,你如果说你写,那问题就多了,于是我偷了个巧,说了一句,前端还是写
的,我们大部分写的都是
JS
代码,
HTML
和
CSS
代码我会改,然后面试官问了几个常用的
JS
方法,还有常用的前端框架中的几个知识
点,这完美的第二次面试就过去了。
1.
Mysql
的存储引擎你都知道哪些,如何做优化等问题。
关于这个确实我不是很擅长,这也可能是我的一个非常薄弱的一个地方,然后我回答的总之不是特别的好,后来回来之后,把关于数
据库的知识,恶补了一下,也曾经写过一篇文章,专门来谈索引的优化,和数据库方面的,有兴趣的伙伴可以去看一下。
总体来说,二面结果还行,和我预料的虽然不太一样,但是效果还可以,出门的时候,如释重负,感觉空气都新鲜了好多。
第三面
有哪些问题。
1.
如何排查线上出现的
JVM
的问题。
2.
给你一个接口,你如何处理重复的请求。
3.
TCP/IP
协议,三次握手和四次挥手到底是怎么回事。
4.
分布式的
Session
如何处理。
总结
其实通过这次面试,我也总结出大厂对社招人员的一些要求:
1.
性能优化,数据结构和算法
(
非常重要
)
。
2.
高并发,怎么处理这类的事情。
3.
数据库的分库分表,底层实现,索引优化。
4.
基础
(HashMap,JVM,Mybatis,Http,TCP/IP)
。
![](https://i-blog.csdnimg.cn/blog_migrate/f4acc3f7379f5be1a622ffa89d86eff9.png)
SpringMVC
的组件:
1
、前端控制器
DispatcherServlet
作用:接收请求、响应结果 相当于转发器,有了
DispatcherServlet
就减少了其它组件之间的耦合度。
2
、处理器映射器
HandlerMapping
作用:根据请求的
URL
来查找
Handler
3
、处理器适配器
HandlerAdapter
注意:在编写
Handler
的时候要按照
HandlerAdapter
要求的规则去编写,这样适配器
HandlerAdapter
才可以正确的去执行
Handler
。
4
、处理器
Handler
5
、视图解析器
ViewResolver
作用:进行视图的解析 根据视图逻辑名解析成真正的视图(
view
)
6
、视图
View
View
是一个接口, 它的实现类支持不同的视图类型(
jsp
,
freemarker
,
pdf, json
等等)
![](https://i-blog.csdnimg.cn/blog_migrate/c8949a83bfdbf78cdd2349abe1130b37.png)
1.
通过构造方法实例化
Bean
对象。
2.
通过
setter
方法设置对象的属性。
3.
通过
Aware
,也就是他的子类
BeanNameAware
,调用
Bean
的
setBeanName()
方法传递
Bean
的
ID(XML
里面注册的
ID)
,
setBeanName
方法是在
bean
初始化时调用的,通过这个方法可以得到
BeanFactory
和
Bean
在
XML
里面注册的
ID
。
4.
如果说
Bean
实现了
BeanFactoryAware,
那么工厂调用
setBeanFactory(BeanFactory var1)
传入的参数也是自身。
5.
把
Bean
实例传递给
BeanPostProcessor
中的
postProcessBeforeInitialization
前置方法。
К.
完成
Bean
的初始化
7.
把
Bean
实例传递给
BeanPostProcessor
中的
postProcessAfterInitialization
后置方法。
М.
此时
Bean
已经能够正常时候,在最后的时候调用
DisposableBean
中的
destroy
方法进行销毁处理。
![](https://i-blog.csdnimg.cn/blog_migrate/dc2356a6f8775b896db0189d9e395f23.png)
![](https://i-blog.csdnimg.cn/blog_migrate/33ede60372e5416dda3601f31672ed58.png)
![](https://i-blog.csdnimg.cn/blog_migrate/228dbeb3a11ea8866f089c6d1d4b9940.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c6dcf1142d44806baedc2dc96216656f.png)
![](https://i-blog.csdnimg.cn/blog_migrate/e0bd5077329eae0d34dc7ff6455a0bf7.png)
![](https://i-blog.csdnimg.cn/blog_migrate/dded64ce08488d3ebd1471d116f9a258.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a31778d880d1dfe9ea4ba42aedfe8792.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d15493ea802152f1f43fce7c8b4e770f.png)
![](https://i-blog.csdnimg.cn/blog_migrate/23bcc760e17349708d854b37cbf8c9ee.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d1c909507567d1ac149ec0e0cd199e5b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/f4bd48fef44370e39e28a7cb89bb2d47.png)
注册中心主要作用如下:
1.
动态加入
,
服务提供者通过注册中心动态的把自己暴露给消费者,无需消费者逐个更新配置文件。
2.
动态发现服务,消费者可以动态发现新的服务,无需重启生效。
3.
统一配置,避免本地配置导致每个服务配置不一致。
4.
动态调整,注册中心支持参数动态调整,新参数自动更新到所有相关的服务节点。
5.
统一管理,依靠注册中心数据,可以统一管理配置服务节点。
![](https://i-blog.csdnimg.cn/blog_migrate/e385c30fa46f22291165706e22fd4c09.png)
主要工作流程可以分为如下几步:
1.
服务提供者启动之后,会将服务注册到注册中心。
2.
消费者启动之后主动订阅注册中心上提供者服务,从而获取到当前所有可用服务,同时留下一个回调函数。
3.
若服务提供者新增或下线,注册中心将通过第二步的注册的回调函数通知消费者。
4.
dubbo-admin(
服务治理中心
)
将会会订阅服务提供者以及消费者,从而可以在控制台管理所有服务提供者以及消费者。
Dubbo
之前版本主要可以使用
ZooKeeper
,
Redis
作为注册中心 ,而随着
Dubbo
版本不断更新,目前还支持
nacos
,
consul
,
etcd 等做为注册中心。
一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况,也就是说如果你的系统不是严格要求
“
缓存
+
数据库
”
必须保持一
致性的话,最好不要做这个方案,即:读请求和写请求串行化,串到一个内存队列里去。
串行化可以保证一定不会出现不一致的情况,但是它也会导致系统的吞吐量大幅度降低,用比正常情况下多几倍的机器去支撑线上请
求。
Cache Aside Pattern
最经典的缓存
+
数据库读写的模式,就是
Cache Aside Pattern
。
读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
更新的时候,先更新数据库,然后再删除缓存。
为什么是删除缓存,而不是更新缓存?
原因很简单,很多时候,在复杂点的缓存场景,缓存不单单是数据库中直接取出来的值。
比如可能更新了某个表的一个字段,然后其对应的缓存,是需要查询另外两个表的数据并进行运算,才能计算出缓存最新的值的。
另外更新缓存的代价有时候是很高的。是不是说,每次修改数据库的时候,都一定要将其对应的缓存更新一份?也许有的场景是这
样,但是对于比较复杂的缓存数据计算的场景,就不是这样了。如果你频繁修改一个缓存涉及的多个表,缓存也频繁更新。但是问题
在于,这个缓存到底会不会被频繁访问到?
举个栗子,一个缓存涉及的表的字段,在
1
分钟内就修改了
20
次,或者是
100
次,那么缓存更新
20
次、
100
次;但是这个缓存在
1
分钟内只被读取了
1
次,有大量的冷数据。实际上,如果你只是删除缓存的话,那么在
1
分钟内,这个缓存不过就重新计算一次而
已,开销大幅度降低,用到缓存才去算缓存。
其实删除缓存,而不是更新缓存,就是一个
lazy
计算的思想,不要每次都重新做复杂的计算,不管它会不会用到,而是让它到需要
被使用的时候再重新计算。像
mybatis
,
hibernate
,都有懒加载思想。查询一个部门,部门带了一个员工的
list
,没有必要说每次查
询部门,都里面的
1000
个员工的数据也同时查出来啊。
80%
的情况,查这个部门,就只是要访问这个部门的信息就可以了。先查部
门,同时要访问里面的员工,那么这个时候只有在你要访问里面的员工的时候,才会去数据库里面查询
1000
个员工。
最初级的缓存不一致问题及解决方案
问题:先修改数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。
解决思路:先删除缓存,再修改数据库。如果数据库修改失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因
为读的时候缓存没有,则读数据库中旧数据,然后更新到缓存中。
比较复杂的数据不一致问题分析
数据发生了变更,先删除了缓存,然后要去修改数据库,此时还没修改。一个请求过来,去读缓存,发现缓存空了,去查询数据库,
查到了修改前的旧数据,放到了缓存中。随后数据变更的程序完成了数据库的修改。
完了,数据库和缓存中的数据不一样了。。。
为什么上亿流量高并发场景下,缓存会出现这个问题?
只有在对一个数据在并发的进行读写的时候,才可能会出现这种问题。其实如果说你的并发量很低的话,特别是读并发很低,每天访
问量就
1
万次,那么很少的情况下,会出现刚才描述的那种不一致的场景。但是问题是,如果每天的是上亿的流量,每秒并发读是几
万,每秒只要有数据更新的请求,就可能会出现上述的数据库
+
缓存不一致的情况。
解决方案如下:
更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个
jvm
内部队列中。读取数据的时候,如果发现数据不在缓存
中,那么将重新读取数据
+
更新缓存的操作,根据唯一标识路由之后,也发送同一个
jvm
内部队列中。
一个队列对应一个工作线程,每个工作线程串行拿到对应的操作,然后一条一条的执行。这样的话,一个数据变更的操作,先删除缓
存,然后再去更新数据库,但是还没完成更新。此时如果一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队
列中,此时会在队列中积压,然后同步等待缓存更新完成。
这里有一个优化点,一个队列中,其实多个更新缓存请求串在一起是没意义的,因此可以做过滤,如果发现队列中已经有一个更新缓
存的请求了,那么就不用再放个更新请求操作进去了,直接等待前面的更新操作请求完成即可。
待那个队列对应的工作线程完成了上一个操作的数据库的修改之后,才会去执行下一个操作,也就是缓存更新的操作,此时会从数据
库中读取最新的值,然后写入缓存中。
如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回;如果请求等待的时间超过一定时长,那么这一次直接
从数据库中读取当前的旧值。
高并发的场景下,该解决方案要注意的问题:
1
、读请求长时阻塞
由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时时间范围内返回。
该解决方案,最大的风险点在于说,可能数据更新很频繁,导致队列中积压了大量更新操作在里面,然后读请求会发生大量的超时,
最后导致大量的请求直接走数据库。务必通过一些模拟真实的测试,看看更新数据的频率是怎样的。
另外一点,因为一个队列中,可能会积压针对多个数据项的更新操作,因此需要根据自己的业务情况进行测试,可能需要部署多个服
务,每个服务分摊一些数据的更新操作。如果一个内存队列里居然会挤压
100
个商品的库存修改操作,每隔库存修改操作要耗费
10ms
去完成,那么最后一个商品的读请求,可能等待
10 * 100 = 1000ms = 1s
后,才能得到数据,这个时候就导致读请求的长时阻
塞。
一定要做根据实际业务系统的运行情况,去进行一些压力测试,和模拟线上环境,去看看最繁忙的时候,内存队列可能会挤压多少更
新操作,可能会导致最后一个更新操作对应的读请求,会
hang
多少时间,如果读请求在
200ms
返回,如果你计算过后,哪怕是最
繁忙的时候,积压
10
个更新操作,最多等待
200ms
,那还可以的。
如果一个内存队列中可能积压的更新操作特别多,那么你就要加机器,让每个机器上部署的服务实例处理更少的数据,那么每个内存
队列中积压的更新操作就会越少。
其实根据之前的项目经验,一般来说,数据的写频率是很低的,因此实际上正常来说,在队列中积压的更新操作应该是很少的。像这
种针对读高并发、读缓存架构的项目,一般来说写请求是非常少的,每秒的
QPS
能到几百就不错了。
实际粗略测算一下
如果一秒有
500
的写操作,如果分成
5
个时间片,每
200ms
就
100
个写操作,放到
20
个内存队列中,每个内存队列,可能就积压
5
个写操作。每个写操作性能测试后,一般是在
20ms
左右就完成,那么针对每个内存队列的数据的读请求,也就最多
hang
一会
儿,
200ms
以内肯定能返回了。
经过刚才简单的测算,我们知道,单机支撑的写
QPS
在几百是没问题的,如果写
QPS
扩大了
10
倍,那么就扩容机器,扩容
10
倍的
机器,每个机器
20
个队列。
2
、读请求并发量过高
这里还必须做好压力测试,确保恰巧碰上上述情况的时候,还有一个风险,就是突然间大量读请求会在几十毫秒的延时
hang
在服务
上,看服务能不能扛的住,需要多少机器才能扛住最大的极限情况的峰值。
但是因为并不是所有的数据都在同一时间更新,缓存也不会同一时间失效,所以每次可能也就是少数数据的缓存失效了,然后那些数
据对应的读请求过来,并发量应该也不会特别大。
3
、多服务实例部署的请求路由
可能这个服务部署了多个实例,那么必须保证说,执行数据更新操作,以及执行缓存更新操作的请求,都通过
Nginx
服务器路由到相
同的服务实例上。
比如说,对同一个商品的读写请求,全部路由到同一台机器上。可以自己去做服务间的按照某个请求参数的
hash
路由,也可以用
Nginx
的
hash
路由功能等等。
4
、热点商品的路由问题,导致请求的倾斜
万一某个商品的读写请求特别高,全部打到相同的机器的相同的队列里面去了,可能会造成某台机器的压力过大。就是说,因为只有
在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以其实要根据业务系统去看,如果更新频率不是太高的话,这个问
题的影响并不是特别大,但是的确可能某些机器的负载会高一些。