Java八股文详细文档.2(基于黑马、ChatGPT、DeepSeek)
通过B站黑马程序员的八股文教学,自己也二刷了,结合ChatGpt、deepSeek总结了一下,Java八股文详细文档.2(Redis篇和消息中间件篇,还没有写完,这只是一部分)
Java八股文详细文档.1(包含JVM篇、数据库篇和常见集合篇):
https://blog.csdn.net/weixin_73144915/article/details/145535602?spm=1001.2014.3001.5502
Java八股文详细文档.3(包含框架篇和并发编程篇):
https://blog.csdn.net/weixin_73144915/article/details/145535602?spm=1001.2014.3001.5502
Java八股文面试重点完整版:
https://blog.csdn.net/weixin_73144915/article/details/145657974?spm=1001.2014.3001.5501
四、Reids篇
1.什么是缓存穿透,怎么处理?
缓存穿透是指请求数据时,缓存中没有该数据,并且数据库中也没有改数据,导致每次请求都绕过了缓存直接访问数据库,造成缓存失效,数据库压力增大;
(1)使用布隆过滤器:一种概率性数据结构,能够高效判断一个元素是否在一个集合中,但也存在误判(跟内存成反比)
- 在访问缓存之前使用布隆过滤器判断某个请求的数据是否存在,如果不存在则直接返回空或错误信息,避免查询数据库;
- 如果布隆过滤器认为数据存在,才查询缓存,如果缓存也没有再查询数据库并将结果放入缓存中;
(2)缓存空对象:对于缓存和数据库都查询不到的对象,我们可以将该对象设置为空缓存起来,需要设置合适的过期时间,防止空对象长时间存在缓存中。
2.什么是缓存击穿,怎么处理?
缓存击穿是指某个热点数据过期,导致在同一时间内大量请求直接访问数据库,导致数据库压力过大甚至崩溃;
(1)使用互斥锁,强一致性但性能差;
(2)使用逻辑过期,高可用性和性能好,但不能保证数据强一致性;
3.什么是缓存雪崩,怎么处理?
缓存雪崩是指在同一时间内有大量的缓存key同时失效或者Redis服务宕机,导致大量请求访问数据库,给数据库带来巨大压力。
(1)随机设置过期时间,避免同一时间内大量key过期;
(2)使用分布式锁来实现互斥锁,确保同一时刻只有一个请求可以访问;
(3)缓存预热,通过定时任务或根据访问频率加载热点数据,减少缓存未命中的概率
(4)降级处理,缓存未命中返回默认值或备用缓存;
4.reids作为缓存,怎么确保数据库和redis的数据进行同步?(结合黑马点评)(重点)
(1)项目中把文章的热点数据先存入到缓存中,虽然是热点数据但实时要求没有这么高,所以采用了异步通知的方案,就是使用MQ中间件,更新数据之后,通知缓存删除数据。也可以使用canal中间件,不需要修改业务代码,伪装成MySQL的一个从节点,canal通过读取binlog数据更新缓存;
(2)项目中将抢卷存入缓存中,这个需要实时的进行数据同步,为了确保数据的强一致性,采用的是redission提供的读写锁来保证数据的同步。一旦加锁后,就会阻塞其他线程进行读写操作。
5.Redis作为缓存,数据的持久化是怎么实现的?(重点)
Redis中提供了两种数据持久化的方式,一种是RDB,另一种是AOF。RDB是一个快照文件,将redis存储的数据写到磁盘中,当redis实例宕机恢复数据时,可以从RDB的快照文件中恢复数据;而AOF是追加文件,当redis操作写命令时,都会存储到这个文件中,当redis实例宕机恢复数据时,就可以从这个文件再次执行一遍命令来恢复数据;
6.Redis的数据过期策略有哪些?
(1)定期删除:Redis会定期扫描数据库中的键,检查这些键的过期时间,如果键的过期时间到了,就会立即删除,通常是每100毫秒扫描一次;
(2)惰性删除:当你访问一个键时,Reids会检查这个键首付过期,如果过期立即删除,也就是说过去数据只有被访问时才会被删除;
(3)内存不足时删除:当Reids的内存使用达到限制时,会根据淘汰策略删除一些过期或不常用的键。
因此,Redis的过期删除策略时定期删除和惰性删除配合使用。
7.Redis的数据淘汰策略有哪些?(重点)
(1)不淘汰任何的key,当内存满时写入新数据就会报错(默认)
(2)对于设置了TTL的数据,比较key的TTL剩余值,越小越优先淘汰
(3)对全体/设置了TTL的key,随即进行淘汰
(4)对全体/设置了TTL的key,基于LRU(最近最少使用)算法进行淘汰
(5)对全体/设置了TTL的key,基于LFU(最少频率使用)算法进行淘汰
综上,建议根据业务需求设置数据淘汰策略如下:
(1)如果业务中有明显的冷热数据区分,使用allkeys-lru 策略,将最近最常访问的数据留在缓存中;
(2)如果业务中数据访问频率差别不大,建议使用allkeys-random,随机淘汰
(3)如果业务有置顶的需求,可以使用volatile-lru策略,确保没有设置过期时间的数据不被删除
(4)如果业务中有短时高频访问的数据,可以使用allkeys-lfu或volatile-lfu策略
8.redis分布式锁是怎么实现的?(重点)
项目中我是使用redission实现的分布式锁,底层是setnx和lua脚本(保证原子性),在redission的分布式锁中,提供了一个看门狗机制,一个线程获取锁成功之后,看门狗会次序给持有锁的线程续期(默认是每隔10秒续期一次);另外,redission是可以重入的,根据线程ID判断持有锁的是否当前线程,利用hash结构存储线程信息和重入的次数,当重入的次数为0时就释放锁;但这个不能解决redis主从数据一致的问题,可以使用redission提供的红锁来解决(因性能低不推荐使用),如果非要保证数据的强一致性,建议使用zookeepr实现分布式锁;
9.能介绍一下redis主从数据同步吗
因为单节点Redis的并发能力是有上限的,如果想要进一步提高reids的并发能力,可以搭建主从集群,实现读写分离。一般默认是一主多从,主节点负责写操作,从节点负责读操作。主节点写入数据之后,需要把数据同步到从节点中。
主节点会先判断是否第一次请求,如果是第一次请求就会跟从节点同步版本信息;主节点执行bssave命令,生成RDB文件后发送给从节点去执行,在RDB生成执行期间,主节点会以命令的方式记录到缓冲区(日志文件),把生成的命令日志文件发送到从节点进行同步。
10.redis是单线程的,为啥还这么快?(重点)
(1)Redis 是基于内存的数据库,访问速度远远高于磁盘存储系统;
(2)正因为采用的是单线程,确保了每个操作的执行顺序和一致性,避免了复杂的并发控制和锁机制;
(3)使用IO多路复用模型,并非阻塞IO;
(4)redis的存储形式是简单的键值对和一些字符串、哈希等数据结构,并且每个命令也不复杂;
五、消息中间件篇
1.RabbitMQ 如何保证消息不丢失?
(1)开启生产者确认机制,确保生产者的消息能达到队列;
(2)开启持久化功能,确保消息未消费前在队列中不会丢失;
(3)开启消费者确认机制为auto,由spring确认消息处理成功后完成ack;
(4)开启消费者失败重试机制,多次重试失败后将消息投递到异常交换机,交由人工处理;
2.RabbitMQ消息的重复消费问题怎么解决?
(1)启用消息确认(ack),确保每次消费者成功处理完消息后,发送一个信号给RabbitMQ, 如果消费者处理失败,重新排队未确认的消息;
(2)幂等性:无论消息是否被重复消费,最终结果应该是一样的,如果使用数据库,可以检查消息是否已经被处理过(或者使用rediss分布式锁)
(3)给每个消息设置唯一ID(UUID或雪花算法),判断该消息是否被消费过
3.延迟队列了解过吗?(重点)
延迟队列是通过死信交换机+TTL实现的。我们可以先把消息的TTL设置为需要的延迟时间,消息在TTL过期之后会被转发到死信队列,接着消费者到死信队列中消费消息,从而达到延迟消费的作用。一般需要延迟队列的场景有超时订单、限时优惠、定时发布等。
4.消息队列有很多消息累积怎么解决?
这个问题就是消费速度比不上生产速度,重点是怎么提高消费速度。第一,可以增加更多的消费者,提高消费速度。第二、在消费者内开启线程池可以加快消息处理速度。
另外,可以扩大队列容积,提高堆积上线,比如采用惰性队列,它是基于磁盘存储的,性能比较稳定,但是受限于磁盘IO,时效性会降低。
重平衡(Rebalance) 是 Kafka 中消费者组(Consumer Group)在消费过程中经常遇到的一个情况,尤其是在消费者的数量发生变化(加入或离开)或消费者与 Kafka 集群之间的负载不均衡时。简单来说,重平衡是指 Kafka 会重新分配消费者组中各个消费者负责的分区。
5.RabbitMQ的高可用机制有了解过吗?
在生产环境下,可以采用镜像集群模式来搭建集群,共有三个节点;镜像集群结构是一主多从(从节点就是镜像),所有的操作都在主节完成,接着同步给镜像节点;如果主节点宕机,镜像节点会替代成为新的主节点(如果主从同步完成前,刚好主节点宕机,可能会导致数据丢失)
如果数据丢失,我们可以采用仲裁队列(声明队列时候指定为仲裁队列即可),跟镜像队列一样都是主从模式,支持主从数据同步,它们的主从同步是基于Raft协议的,具有强一致性
6.Kafka是如何保证消息不丢失的?
Kafka的大致流程可以概括为:生产者向Kafka集群发送消息,消息被存储到某个主题的某个分区,消费者从对应的分区中消费消息,需要从三个层面解决这个问题:
(1)生产者发送消息到Brocker丢失:可以设置异步回调发送,如果发送失败,我们可以通过回调获取失败后的消息信息,考虑重试或记录日志;还可以设置消息重试机制,避免因网络抖动导致发送不成功的问题
(2)消息在Broker中存储丢失:可以设置发送确认acks(默认为1),acks=1 表示只需要等待 Leader 分区确认,acks=all 表示等待所有follwer分区确认。可以选择acks=all,让所用副本都确认保存数据。
(3)消费者从Broker接收消息丢失:Kafka消费信息都是按照offset进行标记消费的,可以关闭默认的自动提交偏移量(每5秒提交一次),改为手动提交,当消费成功后在报告给broker,这样可以避免消息丢失和重复消费。
7.Kafka中的消息重复消费怎么解决?
(1)Kafka消费消息都是按照偏移量来进行标记消费的,消费者默认是自动提交已经消费的偏移量(默认每隔5秒发送一次),如果出现重平衡的情况,可能会重复消费或丢失数据。我们可以设置为手动提交偏移量,当消费成功后在报告给broker消费的位置。
(2)幂等方案(设置分布式锁,数据库的乐观锁和悲观锁)
8.KafKa是如何保证消费的顺序性?
一个主题的数据可能会存储到不同的分区中,每个分区都是按照消息写入来存储的,如果消费者关联了多个分区就不能保证顺序性。解决方案如下:
(1)发送消息时指定分区号;
(2)发送消息时按照相同的业务设置相同的key;
9. RabbitMQ的高可用机制有了解过吗?
这主要是两个方面,一个是集群,另一个是复制机制:
(1)Kafka集群指的是它有多个broker实例组成,即使某一台实例宕机了,也不耽误其他broker继续对外提供服务;
(2)一个主题有多个分区,每个分区有多个fllower和一个leader,如果leader发生故障,会自动将其中一个follower提升为leader,保证了系统的高可用性;
10.Kafka中是怎么实现高性能的设计?
(1)消息分区:不受单台服务器的限制,不受限地处理更多的数据;
(2)顺序读写:磁盘顺序读写,提升读写效率;
(3)页缓存:把磁盘中的数据缓存到内存中,把对磁盘的访问改为对内存的访问;
(4)零拷贝:减少上下文切换以及数据拷贝;
(5)消息压缩:较少磁盘IO和网络IO;
(6)分批发送:将消息打包批量发送,减少网络开销;