课程地址:新版Java面试专题视频教程,java八股文面试全套真题+深度详解(含大厂高频面试真题)_哔哩哔哩_bilibili
课程名称:新版Java面试专题视频教程,java八股文面试全套真题+深度详解(含大厂高频面试真题)
一、面试准备
技术栈细化;
二、Redis篇
Redis的面试占很大篇幅,很重要。
1. 布隆过滤器:
1. 自己写
- 布隆过滤器_百度百科 (这里有自己写的java代码实现布隆过滤器)
2. Google 开发著名的 Guava 库(单机、内存)
- 【项目实践03】【布隆过滤器】-CSDN博客
- https://juejin.cn/post/6844904007790673933?searchId=202404272300043574EFFB0E7CD94BEB09
- 布隆过滤器介绍及实战应用(防止缓存穿透)_布隆过滤器实战-CSDN博客
3. Redisson实现(适用分布式)
- 最牛一篇布隆过滤器详解-腾讯云开发者社区-腾讯云
- Redis详解(十三)------ Redis布隆过滤器-腾讯云开发者社区-腾讯云
- https://juejin.cn/post/7355011549827350555?searchId=202404272300043574EFFB0E7CD94BEB09
- 布隆过滤器-腾讯云开发者社区-腾讯云
- 【Redisson】Redisson--布隆(Bloom Filter)过滤器_redisson布隆过滤器-CSDN博客
2. 缓存击穿概念&解决方案
常见的解决方案有两种:
互斥锁:给重建缓存逻辑加锁,避免多线程同时指向
逻辑过期:热点key不要设置过期时间,在活动结束后手动删除。
缓存击穿实现代码:
- Redis 缓存击穿问题及解决方案_Redis_脚本之家
- 缓存穿透、雪崩、击穿实践-腾讯云开发者社区-腾讯云
3. 双写一致
总结:
- 第一种方案:更新数据之后删除缓存,并为缓存设置过期时间。(延迟双删也有不一致风险,反而增加了业务复杂性:黑马微服务课程2-CSDN博客);
- 第二种方案:采用Redisson提供的读写锁,强一致,性能低,只有要求强一致的场景采用;
- 第三种方案:最终一致方案,使用MQ中间件。
- 第四种方案:canal中间件,最终一致方案。
4. 持久化
1)RDB的执行原理?
bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。 fork采用的是copy-on-write技术: 当主进程执行读操作时,访问共享内存; 当主进程执行写操作时,则会拷贝一份数据,执行写操作。
2)AOF vs RDB
RDB全称Redis Database Backup file(Redis数据备份文件),也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。
AOF全称为Append Only File(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。
AOF默认是关闭的。AOF的命令记录的频率也可以通过redis.conf文件来配。
因为是记录命令,AOF文件会比RDB文件大的多,通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,进行文件压缩。Redis也会在触发阈值时自动去重写AOF文件。阈值也可以在redis.conf中配置:
5. 数据过期策略
假如redis的key过期之后,会立即删除吗?
- 惰性删除:访问key的时候判断是否过期,如果过期,则删除;
- 定期删除:定期检查一定量的key是否过期( SLOW模式+ FAST模式)
- Redis的过期删除策略:惰性删除 + 定期删除两种策略进行配合使用
6. 数据淘汰策略
假如缓存过多,内存是有限的,内存被占满了怎么办?
- Redis提供了8种不同的数据淘汰策略,默认是noeviction不删除任何数据,内存不足直接报错
- LRU:最少最近使用。用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高
- LFU:最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高
- 平时开发过程中用的比较多的就是allkeys-lru(结合自己的业务场景)
数据淘汰策略-使用建议
- 优先使用 allkeys-lru 策略。充分利用 LRU 算法的优势,把最近最常访问的数据留在缓存中。如果业务有明显的冷热数据区分,建议使用。
- 如果业务中数据访问频率差别不大,没有明显冷热数据区分,建议使用 allkeys-random,随机选择淘汰。
- 如果业务中有置顶的需求,可以使用 volatile-lru 策略,同时置顶数据不设置过期时间,这些数据就一直不被删除,会淘汰其他设置过期时间的数据。
- 如果业务中有短时高频访问的数据,可以使用 allkeys-lfu 或 volatile-lfu 策略。
关于数据淘汰策略其他的面试问题
1)数据库有1000万数据 ,Redis只能缓存20w数据, 如何保证Redis中的数据都是热点数据 ?
- 使用allkeys-lru(挑选最近最少使用的数据淘汰)淘汰策略,留下来的都是经常访问的热点数据。
2)Redis的内存用完了会发生什么?
- 主要看数据淘汰策略是什么?如果是默认的配置( noeviction ),会直接报错。
7. 分布式锁
redis分布式锁,是如何实现的?
需要结合项目中的业务进行回答,通常情况下,分布式锁使用的场景: 集群情况下的定时任务、抢单、幂等性场景。
1)抢券:加synchronized可以吗?
如果是单体项目,并且只启动了一台服务,synchronized锁是没问题的。但如果集群部署,也就是将同一份代码部署在多台服务器上,synchronized就不行了。synchronized属于本地锁,JVM锁,只能解决同一JVM下线程的互斥,无法解决多个JVM下线程的互斥,所以在集群下,就不能使用本地锁了,只能使用外部锁解决(分布式锁)。
2)Redis分布式锁setnx
- 死锁问题 -> 设置失效时间;
- 锁的失效时长不好控制 -> 自动续期(看门狗线程);
- 释放别人的锁 -> 设置锁key对应的UUID的value值;
3)Redisson分布式锁
redisson实现的分布式锁-执行流程
- 每隔releaseTime时间做一次续期(releaseTime表示锁的过期时间,默认10秒);
- 其他线程非阻塞方式获取锁(while循环,不断尝试获取锁,有一定阈值);
- 加锁、设置过期时间都是基于lua脚本完成。
redisson实现的分布式锁:
- 可重入;(可以重入,多个锁重入需要判断是否是当前线程,在redis中进行存储的时候使用的hash结构,来存储线程信息和重入的次数);
- 主从不一致(解决方案是RedLock红锁,但实现复杂、性能差,实际很少用,还是使用Redisson,因为Master宕机导致主从不一致概率低);
- redis本身就是支持高可用的,做到强一致性,就非常影响性能。所以如果有强一致性要求高的业务,建议使用zookeeper实现的分布式锁。
8. Redis集群
1)Redis集群有哪些方案?★★★
- 主从复制
- 哨兵模式
- 分片集群
2)主从集群
问题一:redis主从数据同步的流程是什么?
单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离。 一般都是一主多从,主节点负责写数据,从节点负责读数据。
全量同步:
- 从节点请求主节点同步数据(replication id、 offset )
- 主节点判断是否是第一次请求,是第一次就与从节点同步版本信息(replication id和offset)
- 主节点执行bgsave,生成rdb文件后,发送给从节点去执行
- 在rdb生成执行期间,主节点会以命令的方式记录到缓冲区(一个日志文件)
- 把生成之后的命令日志文件发送给从节点进行同步
增量同步:
- 从节点请求主节点同步数据,主节点判断不是第一次请求,不是第一次就获取从节点的offset值
- 主节点从命令日志中获取offset值之后的数据,发送给从节点进行数据同步
3)哨兵模式
Redis集群哨兵模式也会产生脑裂的问题,导致数据丢失。
问题一:Redis集群脑裂,该怎么解决呢?
集群脑裂是由于主节点和从节点和sentinel处于不同的网络分区,使得sentinel没有能够心跳感知到主节点,所以通过选举的方式提升了一个从节点为主,这样就存在了两个master,就像大脑分裂了一样,这样会导致客户端还在老的主节点那里写入数据,新节点无法同步数据,当网络恢复后,sentinel会将老的主节点降为从节点,这时再从新master同步数据,就会导致数据丢失
解决:我们可以修改redis的配置,可以设置最少的从节点数量以及缩短主从数据同步的延迟时间,达不到要求就拒绝请求,就可以避免大量的数据丢失。
redis中有两个配置参数:
- min-replicas-to-write 1 表示最少的salve节点为1个
- min-replicas-max-lag 5 表示数据复制和同步的延迟不能超过5秒
问题二:怎么保证redis的高并发高可用?
首先可以搭建主从集群,再加上使用redis中的哨兵模式,哨兵模式可以实现主从集群的自动故障恢复,里面就包含了对主从服务的监控、自动故障恢复、通知;如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主;同时Sentinel也充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端,所以一般项目都会采用哨兵的模式来保证redis的高并发高可用。
问题三:你们使用redis是单点还是集群,哪种集群?
我们当时使用的是主从(1主1从)加哨兵。一般单节点不超过10G内存,如果Redis内存不足则可以给不同服务分配独立的Redis主从节点。尽量不做分片集群。因为集群维护起来比较麻烦,并且集群之间的心跳检测和数据通信会消耗大量的网络带宽,也没有办法使用lua脚本和事务。
4)分片集群
1. 分片集群结构
主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:
- 海量数据存储问题
- 高并发写的问题
使用分片集群可以解决上述问题,分片集群特征:
- 集群中有多个master,每个master保存不同数据
- 每个master都可以有多个slave节点
- master之间通过ping监测彼此健康状态
- 客户端请求可以访问集群任意节点,最终都会被转发到正确节点
分片集群:不再需要Sentinel哨兵了,已经具有了哨兵所有的功能。
2. redis的分片集群有什么作用
- 集群中有多个master,每个master保存不同数据
- 每个master都可以有多个slave节点
- master之间通过ping监测彼此健康状态
- 客户端请求可以访问集群任意节点,最终都会被转发到正确节点
3. Redis分片集群中数据是怎么存储和读取的?
- Redis 分片集群引入了哈希槽的概念,Redis 集群有 16384 个哈希槽
- 将16384个插槽分配到不同的实例
- 读写数据:根据key的有效部分计算哈希值,对16384取余(有效部分,如果key前面有大括号,大括号的内容就是有效部分,如果没有,则以key本身做为有效部分)余数做为插槽,寻找插槽所在的实例。
9. Redis其他面试题
1)Redis是单线程的,但是为什么还那么快?
2)能解释一下I/O多路复用模型?
Redis是纯内存操作,执行速度非常快,它的性能瓶颈是网络延迟而不是执行速度, I/O多路复用模型主要就是实现了高效的网络请求。
能解释一下I/O多路复用模型?
I/O多路复用
是指利用单个线程来同时监听多个Socket ,并在某个Socket可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。目前的I/O多路复用都是采用的epoll模式实现,它会在通知用户进程Socket就绪的同时,把已就绪的Socket写入用户空间,不需要挨个遍历Socket来判断是否就绪,提升了性能。
Redis网络模型
就是使用I/O多路复用结合事件的处理器来应对多个Socket请求
- 连接应答处理器
- 命令回复处理器,在Redis6.0之后,为了提升更好的性能,使用了多线程来处理回复事件;
- 命令请求处理器,在Redis6.0之后,将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程。
三、MySQL篇
3.1 如何定位慢查询?
方案一:使用开源工具,如:调试工具Arthas, 运维工具Prometheus和Skywalking。
方案二:开启MySQL自带慢日志功能,slow_query_log设置为1表示开启,long_query_time通常设置为2秒,具体如下图所示。开启之后,对于执行时间超过2秒的sql,就会被写入慢sql的日志文件。不过,通常用于调试阶段,生产上开启这个功能会损耗一定的性能。
3.2 SQL语句执行很慢, 如何分析呢?
聚合查询 -> 可以新增临时表
多表查询 -> 可以试着优化sql语句的结构
表数据量过大查询 -> 可以添加索引
上面这三种,都可以SQL执行计划(找到慢的原因);须掌握possible_key、key、key_len、Extra、type这几个字段。
可以采用MySQL自带的分析工具 EXPLAIN
- 通过key和key_len检查是否命中了索引(索引本身存在是否有失效的情况)
- 通过type字段查看sql是否有进一步的优化空间,是否存在全索引扫描或全盘扫描
- 通过extra建议判断,是否出现了回表的情况,如果出现了,可以尝试添加索引或修改返回字段来修复
深度分页查询(后面分析)
3.3 MYSQL支持的存储引擎
存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式 。存储引擎是基于表的,而不是基于库的,所以存储引擎也可被称为表类型。
3.4 索引
1)索引的底层数据结构了解过吗?
MySQL的InnoDB引擎采用的B+树的数据结构来存储索引
- 阶数更多,路径更短
- 磁盘读写代价B+树更低,非叶子节点只存储指针,叶子阶段存储数据
- B+树便于扫库和区间查询,叶子节点是一个双向链表
2)B树与B+树对比:
①:磁盘读写代价B+树更低;②:查询效率B+树更加稳定;③:B+树便于扫库和区间查询
3)什么是聚集索引,什么是非聚集索引?
- 聚簇索引(聚集索引):数据与索引放到一块,B+树的叶子节点保存了整行数据,有且只有一个
- 非聚簇索引(二级索引):数据与索引分开存储,B+树的叶子节点保存对应的主键,可以有多个
4)什么是回表查询?
先通过二级索引去找到对应的主键值,再通过主键值到聚集索引中找到整行的(或select后面的除主键以外的)数据。
5)什么叫覆盖索引?
覆盖索引是指查询使用了索引,返回的列,必须在索引中全部能够找到
- 使用id查询,直接走聚集索引查询,一次索引扫描,直接返回数据,性能高。
- 如果返回的列中没有创建索引,有可能会触发回表查询,尽量避免使用select *
6)MYSQL超大分页怎么处理 ?
7)索引创建原则有哪些?
- 针对于数据量较大,且查询比较频繁的表建立索引。
- 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引。
- 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
- 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
- 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。
- 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。
- 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询。
8)什么情况下索引会失效 ?
- 违反最左前缀法则;
- 范围查询右边的列,不能使用索引;(不包含自身)
- 不要在索引列上进行运算操作, 索引将失效;
- 字符串不加单引号,造成索引失效;(类型转换)
- 以%开头的Like模糊查询,索引失效。
3.5 SQL优化经验
- 表的设计优化(参考阿里开发手册《嵩山版》)
①设置合适的数值(tinyint int bigint),要根据实际情况选择
②设置合适的字符串类型(char和varchar)char定长效率高,varchar可变长度,效率稍低
- SQL语句优化
①SELECT语句务必指明字段名称(避免直接使用select * )
②SQL语句要避免造成索引失效的写法
③尽量用union all代替union,union会多一次过滤,效率低
④避免在where子句中对字段进行表达式操作
⑤Join优化 能用innerjoin 就不用left join right join,如必须使用 一定要以小表为驱动,
内连接会对两个表进行优化,优先把小表放到外边,把大表放到里边。left join 或 right join,不会重新调整顺序
- 主从复制、读写分离
①如果数据库的使用场景读的操作比较多的时候,为了避免写的操作所造成的性能影响 可以采用读写分离的架构。 读写分离解决的是,数据库的写入,影响了查询的效率。
3.6 事务相关★★★
- 事务特性(ACID);
- 隔离级别;(MySQL有四种隔离级别)
- MVCC
并发事务带来哪些问题?怎么解决这些问题呢?MySQL的默认隔离级别是?
- 脏读 -> 读已提交来解决;
- 不可重复读 -> 可重复读来解决;
- 幻读 -> 串行化来解决。
- MySQL默认的隔离级别是可重复读。
1)undo log和redo log的区别
总结:undo log和redo log的区别
- redo log: 记录的是数据页的物理变化,服务宕机可用来同步数据
- undo log :记录的是逻辑日志,当事务回滚时,通过逆操作恢复原来的数据
- redo log保证了事务的持久性,undo log保证了事务的原子性和一致性
2)事务中的隔离性是如何保证的呢?
- 锁:排他锁(如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁。平时使用的update、delete、insert自动会获取排他锁。)
- mvcc : 多版本并发控制
3)解释一下MVCC(讲的非常好)★★★★★
MVCC,全称 Multi-Version Concurrency Control,多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突。
MVCC的具体实现,主要依赖于数据库记录中的隐式字段、undo log日志、readView。
MVCC总结:
3.7 MySQL主从同步原理
3.8 项目用过分库分表吗?
1)什么时候需要分库分表
2)四种拆分策略
垂直拆分(垂直分库、垂直分表)
水平拆分(水平分库、水平分表)
水平分库:将一个库的数据拆分到多个库中;
水平分表:将一个表的数据拆分到多个库(或者同一个库)的表中。
3)分库分表中间件
分库之后会面临一些问题:
- 分布式事务一致性问题
- 跨节点关联查询
- 跨节点分页、排序函数
- 主键避重
于是,引入了分库分表中间件,可以很好的解决这些问题。常用的有:
- sharding-sphere
- mycat
四、框架篇
4.1 Spring部分
1. Spring框架中的单例bean是线程安全的吗?
不是线程安全的
Spring框架中有一个@Scope注解,默认的值就是singleton,单例的。
因为一般在spring的bean的中都是注入无状态的对象,没有线程安全问题,如果在bean中定义了可修改的成员变量,是要考虑线程安全问题的,可以使用多例或者加锁来解决。
2. 什么是AOP,你们项目中有没有使用到AOP?