中级Java开发工程师面经(1)
Java相关
常⽤的线程池有哪些?使⽤场景分别是什么?你实际⽤过哪些?
newFixedThreadPool(固定数目的线程池)
newCachedThreadPool(可缓存线程的线程池)
newSingleThreadExecutor(单线程的线程池)
newScheduledThreadPool(定时及周期执行的线程池)
实际上使用的是线程池ThreadPoolExecutor的构造方法自定义线程池,避免默认提供的线程池会出现的队列超长、线程过多等场景可能引发的OOM问题
说⼀下延迟队列的实现都有哪些⽅式?
1、DelayQueue 延时队列:DelayQueue 是一个 BlockingQueue (无界阻塞)队列,它本质就是封装了一个 PriorityQueue(优先队列), PriorityQueue 内部使用完全二叉堆来实现队列元素排序,我们在向 DelayQueue 队列中添加元素时,会给元素一个 Delay (延迟时间)作为排序条件,队列中最小的元素会优先放在队首。队列中的元素只有到了Delay时间才允许从队列中取出。
2、Quartz 定时任务:存在处理业务不及时的问题(定时任务是周期性的处理)
3、Redis sorted set:Redis 的数据结构 Zset ,同样可以实现延迟队列的效果,主要利用它的 score 属性, redis 通过 score 来为集合中的成员进行从小到大的排序。通过 zadd 命令向队列 delayqueue 中添加元素,并设置 score 值表示元素过期的时间;while循环取最第一个元素 判断是否超时、超时((当前时间-元素过期时间<0))就移出
4、Redis 过期回调:监听key的过期事件-修改 redis.conf 文件开启 notify-keyspace-events Ex
5、kafka 、 netty都有基于时间轮算法实现延时队列,可以更高效的处理延时任务、减少系统消耗
能说⼀下DelayQueue的实现原理吗?
内部使用了一个优先级队列(完全二叉堆的数据结构),队列中最小的元素会优先放在队首。队列中的元素只有到了Delay时间才允许从队列中取出。元素必须实现Delay接口(set延迟时间作为排序条件)
Kafka中的时间轮和Netty的时间轮有什么区别?
netty是用时间间隔+轮数实现时间轮的,有空推进的问题,没到时间就等着。kafka是用多层次时间轮实现的,底层采用 DelayQueue 以槽为单位,用空间换时间解决空推进的问题
分别说⼀下,JVM的内存分配、执⾏原理、类加载过程?
对象优先在Eden分配、大对象直接进老年代、长期存活的对象将进入老年代、动态对象进行年龄判定再分代
某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
常⽤垃圾收集器有哪些?你们线上⽤的是什么收集器?有对CMS进⾏过优化吗?G1的⼯作原理是什么?
ParNew、CMS、G1
CMS:初始标记阶段-并发标记阶段-重新标记阶段-并发清除阶段,尽可能的减少停止用户线程的时间
G1:堆内存的布局是把连续的Java堆划分为多个大小相等的独立区域(Region),每个Region都可以扮演Eden空间、Survivor、老年代空间。对于用户线程暂停的时间可控可调,基于模型预测时间
如何才能让Metaspace区OOM?
Spring、 Hibernate对类进行增强时, 都会使用到CGLib这类字节码技术, 当增强的类越多, 就需要越大的方法区以保证动态生成的新类型可以载入内存 ,如果使用cglib过多导致生产的类没控制好过多后导致Metaspace区域满了,进而引发内存溢出。
了解nio的底层原理吗?操作系统epoll是怎样实现的?
None Blocking IO(同步非阻塞io)应用程序的线程需要不断的进行 I/O 系统调用,轮询数据是否已经准备好,如果没有准备好,继续轮询,直到完成系统调用为止。
轮训的过程是判断数据是否在内核空间内加载完成,如果加载完成则通知用户线程可以进行数据传输了(传输过程是阻塞的)
Java NIO(New IO) 不是IO模型中的NIO模型,而是另外的一种模型,叫做IO多路复用模型( IO multiplexing )。
select:底层使用数组存储socket连接信息,轮训哪些连接的数据在内核空间准备好了,单个进程能够监视的文件描述符的数量存在最大限制,通常是1024,内核 / 用户空间内存拷贝问题,select需要复制大量的句柄数据结构,产生巨大的开销;
poll:改用链表存储,解决了连接数的限制,但轮训还是cpu资源消耗大
epoll:改变了轮训这种cpu消耗大的方式,采用了事件通知模型
1、调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)
2、调用epoll_ctl向epoll对象中添加这100万个连接的套接字
3、调用epoll_wait收集发生的事件的连接
所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当相应的事件发生时会调用这个回调方法。这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中。
调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可。如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户。
nio使⽤的是边沿触发,还是⽔平触发?
水平触发(level-triggered,也被称为条件触发): 只要满足条件,就触发一个事件(只要有数据没有被获取,内核就不断通你)
边缘触发(edge-triggered): 每当状态变化时,触发一个事件。
nio使用的是条件触发
nio的epoll实现,是如何去唤醒或打断⼀个正在阻塞的seletor.select()⽅法?
什么场景下使⽤DirectBuffer?使⽤HeapBuffer去进⾏IO读写时,为什么会临时申请⼀块 DirectBuffer进⾏数据拷⻉?
nio的零拷⻉是怎么实现的?
mmap内存映射:DMA加载磁盘数据到内核缓冲区(kernel buffer)后,应用程序缓冲区(application buffers)和内核缓冲区(kernel buffer)进行映射,数据再应用缓冲区和内核缓存区的改变就能省略。mmap内存映射将会经历:3次拷贝: 1次cpu拷贝、2次DMA拷贝、4次上下文切换(用户态-内核态之间的切换)
sendfile:DMA将磁盘数据复制到内核缓冲区(kernel buffer),然后将内核缓冲区(kernel buffer)中数据的直接拷贝到socket缓冲区(socket buffer),代表数据转化的完成。socket buffer里的数据就能在网络传输了。sendfile会经历:3次拷贝:1次CPU拷贝、2次DMA拷贝、以及2次上下文切换
Sendfile With DMA Scatter/Gather Copy:
splice:从磁盘读取到内核buffer后,在内核空间直接与socket buffer建立pipe管道。splice会经历 2次拷贝: 0次cpu拷贝、2次DMA拷贝、2次上下文切换
Linux提供的领拷贝技术 Java并不是全支持,支持2种(内存映射mmap、sendfile);
单例模式Double check中volatile的作⽤是什么?
volatile关键字的一个作用是禁止指令重排,把instance声明为volatile之后,对它的写操作就会有一个内存屏障.这样,在它的赋值完成之前,就不用会调用读操作
讲⼀下synchronized的锁升级?
无锁
偏向锁:java对象头和栈帧中记录偏向的锁的threadID
轻量级锁:LockRecord是线程私有的(TLS),每个线程有自己的一份锁记录,在创建完锁记录的空间后,会将当前对象的MarkWord拷贝到锁记录中(DisplacedMark Word)。然后使用CAS把对象头中的内容替换为线程1存储的锁记录(DisplacedMarkWord)的地址,完成加锁。如果替换失败-加锁失败->自旋等待到一定次数后升级成为重量级锁
重量级锁:线程阻塞
并发编程时,为什么wait()函数都要放在while循环中,什么情况会造成假唤醒?
线程唤醒时需要重新判断下条件是否成立 才执行下面的业务逻辑、如果用if则不会判断,多线程情况下,会造成业务异常,假唤醒
HashMap如何解决hash冲突问题的?
数组+链表或红黑树
微服务
说⼀下Hystrix是如何限流的?
能说⼀下eureka多级缓存导致时效性差的原因吗?
eureka多级缓存分可读缓存、读写缓存
读写缓存的过期时间是180s、又下线、注册事件等直接过期缓存
可读缓存的同步时间为30s
再加上心跳过期时间为90s
各种时间加起来就导致时效性比较差
解决思路:调整各种缓存的时间
nacos和eureka有什么不同?
nacos默认是ap模式,当存在非临时实例时,则采用cp模式
eureka是ap模式
Sentinel是如何持久化降级规则的?
Sentinel的⼯作原理能说⼀下吗?它是否⽀持动态限流?Sentinel和Hystrix的区别是什么?
能说⼀下Feign的执⾏流程吗?为什么默认参数有问题?
通过feign注解解析到打了@feginClient的接口,在spring容器初始化的过程中,构造动态代理对象,最终通过代理对象请求解析注解方法、通过ribbon获取到负载均衡的请求地址、最后拼装请求发出
你熟悉的分布式事务解决⽅案都有哪些?能讲⼀下他们的区别和适⽤场景吗?
2pc:两阶段提交,第一阶段:投票阶段 和第二阶段:提交/执行阶段。一般是用在同一个项目中,多数据源的场景,存在单点问题,数据不一致问题
3pc:CanCommit(询问)-> PreCommit(预提交)-> DoCommit(提交)。相比2pc,进入了超时机制、但是3PC依然没有完全解决数据不一致的问题
补偿事务(TCC):Try阶段主要是对「业务系统做检测及资源预留」-> Confirm 阶段主要是对「业务系统做确认提交」->Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,「预留资源释放」。
解决了协调者单点,由主业务方发起并完成这个业务活动。业务活动管理器也变成多点,引入集群。
同步阻塞:引入超时,超时后进行补偿,并且不会锁定整个资源,将资源转换为业务逻辑形式,粒度变小。
数据一致性,有了补偿机制之后,由业务活动管理器控制一致性
缺点,代码量比较多,增加了业务复杂度
事务消息:多个服务解耦、业务比较复杂、rocketMQ事务消息实现或者写一个独立的可靠事务消息服务
最大努力通知:非核心业务、及时重试几次后不成功也不影响
可靠消息服务:rocketMQ事务消息的实现
讲⼀下分布式锁的⽅案有哪些和使⽤场景?
redis分布式锁
Redission框架
zookeeper分布式锁
Curator框架
Zookeeper分布式锁和Redis分布式锁的区别是什么?
Zookeeper采用临时节点和事件监听机制可以实现分布式锁
Redis主要是通过setnx命令实现分布式锁。
Redis需要不断的去尝试获取锁,比较消耗性能,Zookeeper是可以通过对锁的监听,自动获取到锁,所以性能开销较小。
另外如果获取锁的jvm出现bug或者挂了,那么只能redis过期删除key或者超时删除key,Zookeeper则不存在这种情况,连接断开节点则会自动删除,这样会即时释放锁。
另外如果获取锁的jvm出现bug或者挂了,那么只能redis过期删除key或者超时删除key,Zookeeper则不存在这种情况,连接断开节点则会自动删除,这样会即时释放锁。
Redis如何去保证锁过期后不互斥,或者主从集群锁丢失问题的?
MySQL
有没有做分库分表?数据库的主键⽣成策略是什么?
数据库自增 id:单库、性能有瓶颈
UUID:UUID 太长了、占用空间大、作为主键性能太差
获取系统当前时间:并发很高的时候,主键容易重复
snowflake 算法
41 bit 作为毫秒数 - 41位的长度可以使用69年
10 bit 作为机器编号 (5个bit是数据中心,5个bit的机器ID) - 10位的长度最多支持部署1024个点
12 bit 作为毫秒内序列号 - 12位的计数顺序号支持每个节点每毫秒产生4096个ID序号
服务器实例时钟回拨的问题,可能会造成主键重复
可以操作10位机器编号,workerId做多可以部署1024台机器,可以对workerId进行操作,划分为n份(比如4份)那么看可以部署256台机器(0-255),(256-511)(512-767)(768-1023)为备用worderId,如果发生时钟回拨,则采用备用workerId,可以抗住回拨三次,如果回拨三次抛异常 ,例如当前workerId = 0 则对应的备用id为256-512-768.
MySQL读写分离⼯作原理有了解吗?什么业务做得读写分离?为啥要读写分离,是遇到了什么瓶颈吗?
主库将变更写入 binlog 日志,然后从库连接到主库之后,从库有一个 IO 线程,将主库的 binlog 日志拷贝到自己本地,写入一个 relay 中继日志中。接着从库中有一个 SQL 线程会从中继日志读取 binlog,然后执行 binlog 日志中的内容,也就是在自己本地再次执行一遍 SQL,这样就可以保证自己跟主库的数据是一样的。
半同步复制(至少一个半从库发送ack)、并行复制(从库 io线程、sql线程并发执行) 来对主从数据延迟的问题优化
MySQL的⽇志有哪些?分别有什么⽤?让你去做⼀个MySQL的告警系统应该如何去设计(⽐如索引失 效导致慢sql,⼤事务,死锁等场景)?
undolog-回滚日志用来实现事务的原子性
redolog-重做日志用来实现事务的持久性
基于undo log多版本链条+ReadView机制实现事务的隔离性
事务一致性以上三个特性保证
binglog-mysql全局日志
消息队列
如何保证消息不丢失和不重复消费?
生产者端:
kafka:开启ack = all保证成功发送到中间件端
rabbitMQ:事务消息(吞吐量下降)、confirm机制(异步回调通知消息已被收到、需要在内存维护一个消息id列表)
rocketMQ:事务消息(吞吐量下降)
MQ端:
kafka:至少设置两个副本、设置重试、开启ack = all(必须是写入所有 replica 之后,才认为是写成功了)
rabbitMQ:开启消息持久化、与生产者配合必须持久化消息到磁盘中才算发送成功
rocketMQ:消息保存机制修改为同步刷盘方式,即消息存储磁盘成功。集群部署同步复制
消费者端:
手动ack
消息队列使⽤场景有哪些?你们是怎样进⾏技术选型的?
服务解耦、流量削峰
吞吐量、社区维护人数、是否满足功能
分布式缓存
常⽤的Redis存储结构有哪些?能说⼀下zset的数据结构是什么样的吗?
string、hash、list、zset、set
跳跃表-层级关系-往上提取索引、以时间换空间的做法提升查询、插入效率
Redis Cluster hash槽的概念?
Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。
Redis Cluster节点之间的通信机制?
Gossip协议的主要职责就是信息交换,信息交换的载体就是节点之间彼此发送的Gossip消息,常用的Gossip消息有ping消息、pong消息、meet消息、fail消息
ping:每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元数据。
pong: 返回ping和meet,包含自己的状态和其他信息,也可以用于信息广播和更新。
fail: 某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了。
meet:某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信,不需要发送形成网络的所需的所有CLUSTER MEET命令。
Redis Cluster 从节点选举流程是怎样的
slave发现自己的master不可用
slave将记录集群的currentEpoch(选举周期)加1,并广播FAILOVER_AUTH_REQUEST 信息进行选举
其他节点收到FAILOVER_AUTH_REQUEST信息后,只有其他的master可以进行响应,master收到消息后返回FAILOVER_AUTH_ACK信息,对于同一个Epoch,只能响应一次ack
slave收集maste返回的ack消息
slave判断收到的ack消息个数是否大于半数的master个数,若是,则变成新的master
广播Pong消息通知其他集群节点,自己已经成为新的master。
Redis 默认的回收算法是什么?怎么理解它?
volatile-ttl:在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。【删除即将过期的key】
volatile-random:就像它的名称一样,在设置了过期时间的键值对中,进行随机删除。【随机删除即将过期的key】、
volatile-lru:会使用 LRU 算法筛选设置了过期时间的键值对删除。【使用LRU算法删除即将过期的key】
volatile-lfu:会使用 LFU 算法筛选设置了过期时间的键值对删除。【使用LFU算法删除即将过期的key】
allkeys-random:从所有键值对中随机选择并删除数据。【所有的数据集中随机删除数据,非常不靠谱的方式】
allkeys-lru:使用 LRU 算法在所有数据中进行筛选删除。【所有的数据集中筛选部分使用LRU算法删除key】
allkeys-lfu:使用 LFU 算法在所有数据中进行筛选删除。【所有的数据集中筛选部分使用LFU算法删除key】
noeviction:(默认使用该策略)不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作
LRU 算法(Least Recently Used,最近最少使用)淘汰很久没被访问过的数据,以最近一次访问时间作为参考。
LFU 算法(Least Frequently Used,最不经常使用)淘汰最近一段时间被访问次数最少的数据,以次数作为参考。
LFU 算法(Least Frequently Used,最不经常使用)淘汰最近一段时间被访问次数最少的数据,以次数作为参考。
Redis 缓存⼀致性⾥Cache Aside Pattern模式的意义是什么?
失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中
命中:应用程序从cache中取数据,取到后返回
更新:先把数据存到数据库中,成功后,再让缓存失效
Redis ⾼并发情况下解决缓存⼀致性问题的⽅案是什么?
延时双删
同步biglog异步删除缓存
先删除缓存->监听binlog->发送消息队列->删除缓存
Redis ⾼并发的场景下,异步串⾏化解决缓存⼀致性⽅案要注意的问题是什么?
MySQL主从这种情况,如果发生同步延迟,那么如何保证一致性
监听binlog从库
为什么会存在bigkey的情况?如何去定义⼀个key是bigkey?
bigkey 业务倾斜、会影响redis的性能
1、社交类:粉丝列表,如果某些明显或大V,一定是bigkey
2、统计类:如果按天存储某项功能或网站的用户集合,除非没几个人用,否则必定是bigkey
3、缓存类:作为数据库数据的冗余存储,这种是redis的最常用场景,但有2点要注意:
1)是不是有必要把所有数据都缓存
2)有没有相关关联的数据
2、统计类:如果按天存储某项功能或网站的用户集合,除非没几个人用,否则必定是bigkey
3、缓存类:作为数据库数据的冗余存储,这种是redis的最常用场景,但有2点要注意:
1)是不是有必要把所有数据都缓存
2)有没有相关关联的数据
多层缓存架构是怎样设计的?多层缓存架构需要注意什么?
地址:https://www.processon.com/view/link/6264f71c0e3e745194df994f

浙公网安备 33010602011771号