史上最全的中高级JAVA工程师-面试题汇总
文章目录
缓存
memcache的分布式原理
memcache的内存分配机制
如何存放数据到memcached缓存中?(memcache内存分配机制)
memcache的惰性失效机制
memcache缓存的无底洞现象
一致性Hash算法的实现原理
Hash环
一致性Hash算法
Hash环的倾斜
虚拟节点解决Hash环倾斜
hash算法平衡性
memcached与redis的区别
Redis的主从复制
Redis的部分复制过程
Redis的主从复制阻塞模式
Redis的数据持久化方式
Redis的高可用部署方式
哨兵模式
Redis哨兵主要功能
Redis哨兵的高可用
哨兵如何判断redis主从节点是否正常?
集群模式
Redis可以在线扩容吗?zk呢
Redis高并发和快速的原因
浏览器本地缓存的了解和使用
缓存雪崩
缓存穿透
HashMap
HashMap的Hash碰撞
HashMap的get和put原理
HashMap的rehash
HashMap的线程不安全问题
HashMap和Hashtable的区别
为什么collection没有实现clonable接口
为什map没有实现collection接口
Map接口的实现有哪些,区别是什么
线程池
Executors框架的四种线程池及拒绝策略
四种线程池
JDK拒绝策略
Reactor模式
Reactor单线程模型
Reactor多线程模型
主从Reactor模型
JVM
Object的内存布局
方法区卸载Class的条件
可以作为GC Roots的对象包括哪些
JVM运行时内存模型
Netty的ByteBuffer的引用计数器机制
判断对象是否存活的两种方法
Java对象的初始化过程
类加载双亲委派模型
从上到下分三个类加载器:
双亲委派模型:
Zookeeper
Zookeeper的常用应用场景有哪些
Zookeeper的分布式数据一致性算法
Zk启动过程的Leader选举分析及数据同步
Zookeeper数据同步的简单描述
ZK集群最少需要几台机器?
Zookeeper和Eureka的区别
Mysql
InnoDB和MyISAM存储引擎的区别
Btree索引和Hash索引的区别
数据库的ACID特性
Mysql数据库的隔离级别
Select For Update使用场景
分布式事务模型之XA和TCC的区别和联系?
XA-DTP模型
TCC模型
Mysql-binlog日志复制方式
mysql主从复制原理
基于日志点的复制和GTID的复制有何区别?
Mysql性能诊断和优化
聚簇索引和非聚簇索引的区别
消息队列
消费者宕机:怎么保证消息队列消息不丢失?
MQ集群宕机:怎么保证消息不丢失?
Spring源码系列
springmvc如何解决循环依赖的问题
spring事务的传播行为和隔离级别
spring事务七个事务传播行为
Spring事务的五种隔离级别
设计模式
单例模式
策略模式
JDK源码
ThreadLocal的实现原理
AQS实现公平锁和非公平锁
RPC
RPC的序列化方式有哪些
服务熔断与服务降级概念
服务熔断:
服务降级:
其他整理
ThreadLocalMap的线性探测法、HashMap的拉链法。两种解决hash碰撞的方式有何不同?
Netty的RPC如何实现
Netty中源码inbound和outbound有啥区别?
怎么分库分表可以做到多维度查找
Fork/Join框架
JAVA线程执行中怎么kill掉
HA主备怎么预防脑裂
性别字段是否需要加索引
Https的SSL握手过程
select和epoll的区别
Epoll导致的selector空轮询
正排索引和倒排索引
正排索引
倒排索引
可以说这一篇(宝典)说实话,熟知本文80%以上内容,找个开发工作问题不大。对3-5年经验的朋友,也是快速温习的利器。
学习更多JAVA知识与技巧,关注与私信博主(666)
最后给大家分享Spring系列的学习笔记和面试题,包含spring面试题、spring cloud面试题、spring boot面试题、spring教程笔记、spring boot教程笔记、
最新阿里巴巴开发手册(63页PDF总结)、2022年Java面试手册。一共整理了1184页PDF文档。私信博主(666)领取,祝大家更上一层楼!!!
高级Java工程师面试必考题目
我目前是一名大数据开发工程师,曾经是一名高级Java工程师,我总结我在面试过程中的一些知识和经验,还有遇到的问题,希望对正在面试求职的你有一些帮助。这篇文章有题目有答案,没有完整答案的题目也会提供给你思路。
**顺序写磁盘**
在我们的生产环境中,为了节约成本,大部分服务器仍然使用机械磁盘,而非固态硬盘。我们知道,机械磁盘读写数据,首先需要寻道,将磁头调整到对应的磁道上,紧接着,会进行旋转,将磁头旋转到对应的位置上,最后才是读写数据,寻道跟旋转,占用了磁盘读写的大量时间,所以,Apache Kafka在写磁盘的时候,大量的使用了追加写的模式,减少了磁盘寻道与旋转地时间,从而达到更高的磁盘利用率。
**大量使用内存页**
我们知道,即便是顺序写磁盘,磁盘的读写速度仍然比内存慢慢的多得多,好在操作系统已经帮我们解决这个问题了,在Linux操作系统中,Linux会将磁盘中的一些数据读取到内存当中,我们称之为内存页。当需要读写硬盘的时候,都优先在内存页中进行处理。读的话大家比较好理解,就是缓存嘛,写的话如果只写在缓存没有落盘不是会形成脏数据么?的确如此,只有当网页到达一定比例之后,Linux操作系统才会把数据刷到磁盘当中。在机器发生掉电的时候,的确会出现脏数据。但是,我们更应该使用分布式写到多台机器上来从根本上解决这个问题。
并且,写在内存页的另外一个好处是减少应用内存的使用,我们都知道Apache Kafka是用Java语言编写的,不得不避免的便是Java的GC问题,随着应用内存的增多,垃圾回收的时间会更多,带来整体性能的下降。
**零拷贝技术的使用**
作为一个消息系统,不可避免的便是消息的拷贝,常规的操作,一条消息,需要从创建者的socket到应用,再到操作系统内核,然后才能落盘。同样,一条消息发送给消费者也要从磁盘到内核到应用再到接受者的socket,中间经过了多次不是很有必要的拷贝。
在Linux操作系统中,同样为我们提供了零拷贝技术,零拷贝技术并非真的不用拷贝,而是大大地减少了拷贝次数。举个例子,原本从磁盘到接收者Socket中,需要见过内核态,又要经过Apache Kafka应用,那么有没有可能直接越过这两层状态,达到更少的拷贝次数呢?Java 的NIO库,帮我们实现了这一切,借用JavaNIO库封装的API,我们可以直接使用Linux操作系统的零拷贝技术,让系统更快。
**分区分段**
partition和segment
慢开始算法##
当主机开始发送数据时,如果立即将大量数据字节注入到网络,那么就有可能引起网络拥塞,因为现在并不清楚网络的负荷情况。因此,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是说,由小到大逐渐增大拥塞窗口数值。通常在刚刚开始发送报文段时,先把拥塞窗口 cwnd 设置为一个最大报文段MSS的数值。而在每收到一个对新的报文段的确认后,把拥塞窗口增加至多一个MSS的数值。用这样的方法逐步增大发送方的拥塞窗口 cwnd ,可以使分组注入到网络的速率更加合理。
慢开始的“慢”并不是指cwnd的增长速率慢,而是指在TCP开始发送报文段时先设置cwnd=1,使得发送方在开始时只发送一个报文段(目的是试探一下网络的拥塞情况),然后再指数增大cwnd。
拥塞避免算法
让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍。这样拥塞窗口cwnd按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。
快速重传和快速恢复
重复确认: 发送端有一个方法可以快速确认发送端是不是出错了,当丢失的数据包的后续数据包到达接收端时候,他们触发给发送端返回确认。这些确认号都是一样的,叫做重复确认。
快速重传 当发生三次重复确认,即使没有超时,也会重传,叫做快速重传。重传完毕,阀值减半,就像发生超时一样,慢启动开始,到了阀值,再线性增长。
快速恢复 快速重传后,拥塞窗口被设置到慢启动的阀值,开始线性增长。
不生效
InnoDB行锁是通过给索引上的索引项加锁来实现的,只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁
InnoDB是通过搜索或者扫描表中索引来完成加锁操作,InnoDB会为他遇到的每一个索引数据加上共享锁或排他锁。所以我们可以称行级锁(row-level locks)为索引记录锁(index-record locks),因为行级锁是添加到行对应的索引上的。
InnoDB的行级锁定同样分为两种类型,共享锁和排他锁
当一个事务需要给自己需要的某个资源加锁的时候,如果遇到一个共享锁正锁定着自己需要的资源的时候,自己可以再加一个共享锁,不过不能加排他锁。但是,如果遇到自己需要锁定的资源已经被一个排他锁占有之后,则只能等待该锁定释放资源之后自己才能获取锁定资源并添加自己的锁定。而意向锁的作用就是当一个事务在需要获取资源锁定的时候,如果遇到自己需要的资源已经被排他锁占用的时候,该事务可以需要锁定行的表上面添加一个合适的意向锁。如果自己需要一个共享锁,那么就在表上面添加一个意向共享锁。
可以采用“快慢指针”的方法。就是有两个指针fast和slow,开始的时候两个指针都指向链表头head,然后在每一步
操作中slow向前走一步即:slow = slow->next,而fast每一步向前两步即:fast = fast->next->next。
问题六:redis
对于“Redis挂掉了,请求全部走数据库”这种情况,我们可以有以下的思路:
- 事发前:实现Redis的高可用(主从架构+Sentinel 或者Redis Cluster),尽量避免Redis挂掉这种情况发生。
- 事发中:万一Redis真的挂了,我们可以设置本地缓存(ehcache)+限流(hystrix),尽量避免我们的数据库被干掉(起码能保证我们的服务还是能正常工作的)
- 事发后:redis持久化,重启后自动从磁盘上加载数据,快速恢复缓存数据。
- 解决方法:在缓存的时候给过期时间加上一个随机值,这样就会大幅度地减少缓存在同一时间过期。
缓存穿透:
解决缓存穿透也有两种方案:
– 由于请求的参数是不合法的(每次都请求不存在的参数),于是我们可以使用布隆过滤器(BloomFilter)或者压缩filter**提前拦截**,不合法就不让这个请求到数据库层!
**布隆过滤器会隔一段时间扫描一次缓存和数据库,会将缓存和数据库共有的数据添加到布隆过滤器,当由请求过来时,会先进入布隆过滤器中进行查找,有的话返回数据,没有的话会到数据中查找**
– 当我们从数据库找不到的时候,我们也将这个
空对象设置到缓存里边去
。下次再请求的时候,就可以从缓存里边获取了。
– 这种情况我们一般会将空对象设置一个**较短的过期时间**。
**缓存击穿:意思是缓存当中没有,而数据库有的数据,如果有98次请求的话,就会查询98次数据库,出现的了是并发问题。
原因:缓存中没有这个数据或者是这个数据刚刚达到过期时间失效了。
解决方法:添加分布式锁** 如果我们有98次请求去查找这个key,当我们redisLock.lock(key),开启分布式锁时,98次请求就会只进去一次请求,只有一个线程执行,查询缓存,如果缓存没有的话去数据库查找,然后给缓存添加数据,最后解锁,然后就会再进去一次请求,还是只有一个线程执行,此时缓存中已经有数据了,这个线程就会到缓存中去查找,后续的都一样,所以就会执行一次查询数据的方法,这样就解决了缓存穿透这个问题。
#### 什么是缓存与数据库双写一致问题?
从理论上说,只要我们设置了**键的过期时间**,我们就能保证缓存和数据库的数据**最终是一致**的。因为只要缓存数据过期了,就会被删除。随后读的时候,因为缓存里没有,就可以查数据库的数据,然后将数据库查出来的数据写入到缓存中。
除了设置过期时间,我们还需要做更多的措施来**尽量避免**数据库与缓存处于不一致的情况发生。
**分布式事务**的问 题。
### 3.3.1操作缓存
操作缓存也有两种方案:
– 更新缓存
– 删除缓存
一般我们都是采取**删除缓存**缓存策略的,原因如下:
1. 高并发环境下,无论是先操作数据库还是后操作数据库而言,如果加上更新缓存,那就**更加容易**导致数据库与缓存数据不一致问题。(删除缓存**直接和简单**很多)
2. 如果每次更新了数据库,都要更新缓存【这里指的是频繁更新的场景,这会耗费一定的性能】,倒不如直接删除掉。等再次读取时,缓存里没有,那我到数据库找,在数据库找到再写到缓存里边(体现**懒加载**)
基于这两点,对于缓存在更新时而言,都是建议执行**删除**操作!
unsafe是java提供的获得对对象内存地址访问的类,注释已经清楚地写出了,它的作用就是在更新操作时提供“比较并替换”的作用 。
如何保证原子性:自旋 + CAS(乐观锁)。在这个过程中,通过compareAndSwapInt比较更新value值,如果更新失败,重新获取旧值,然后更新 。
通过CAS乐观锁保证原子性,通过自旋保证当次修改的最终修改成功,通过降低锁粒度(多段锁)增加并发性能。
varchar Varchar往往用来保存可变长度的字符串。简单的说,我们只是给其固定了一个最大值,然后系统会根据实际存储的数据量来分配合适的存储空间 .
CHAR数据类型与VARCHAR数据类型不同,其采用的是固定长度的存储方式。简单的说,就是系统总为其分配最大的存储空间 显然,这种存储方式会造成磁盘空间的浪费 .
经常需要修改而容易形成碎片 , 十分频繁改变的column。因为varchar每次存储都要有额外的计算,得到长度等工作,如果一个非常频繁改变的,那就要有很多的精力用于计算,而这些对于char来说是不需要的
1.通过年龄计数器判断一个对象是否需要转移。 1.通过年龄计数器判断一个对象是否需要转移。对象每经过一个GC仍然存活,年龄计数器加一。当年龄超过设定的值,则将其通过担保机制转移到老年代。
2.或者动态绑定,当Suvivor中年龄相同的对象数量超过一半,则年龄大于等于该年龄的的对象转移到老年代,无需等待设置的最大年龄值。
3.大对象直接进入老年代。
Spring使用ThreadLocal解决线程安全问题
我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程享了。 ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
步骤一:主库db的更新事件(update、insert、delete)被写到binlog 步骤二:从库发起连接,连接到主库 步骤三:此时主库创建一个binlog dump thread线程,把binlog的内容发送到从库 步骤四:从库启动之后,创建一个I/O线程,读取主库传过来的binlog内容并写入到relay log. 步骤五:还会创建一个SQL线程,从relay log里面读取内容,从Exec_Master_Log_Pos位置开始执行读取到的更新事件,将更新内容写入到slave的db
Sqoop在import时,需要制定split-by参数。Sqoop根据不同的split-by参数值来进行切分,然后将切分出来的区域分配到不同map中。
每个map中在处理数据库中获取的一行一行的值,写入到HDFS中。同时split-by根据不同的参数类型有不同的切分方法,如比较简单的int型,
Sqoop会取最大和最小split-by字段值,然后根据传入的num-mappers来确定划分几个区域。 比如select max(split_by),min(split-by) from
得到的max(split-by)和min(split-by)分别为1000和1,而num-mappers为2的话,则会分成两个区域(1,500)和(501-100),
同时也会分成2个sql给2个map去进行导入操作,分别为select XXX from table where split-by>=1 and split-by<500和
select XXX from table where split-by>=501 and split-by<=1000。最后每个map各自获取各自SQL中的数据进行导入工作。
本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com
文章为作者独立观点不代本网立场,未经允许不得转载。