Java面试题合集(持续更新)

1、redis缓存穿透,缓存击穿以及缓存雪崩问题和对应的解决方案

缓存穿透:当客户端访问一个不存在的数据,这个数据在缓存和数据库中都不能命中,如果有大量的这种穿过缓存直接访问数据库的请求,就会给数据库带来很大压力。

解决思路:

  • 缓存空对象:当发现数据库中也没有该数据时,我们把这个数据存在redis中,但值设置为空,这样下次访问时就会直接从redis中获取空对象。实现简单,维护方便,但内存消耗更大
  • 布隆过滤器:布隆过滤器实际上是一串很长的二进制数组,它通过哈希思想去判断key是否存在,如果不存在则拒绝请求,存在则放行右redis和数据库处理。内存占用少,但实现复杂,有误判可能(存在也可能被判为不存在)

实例解决方案(缓存空对象)

1、判断缓存中是否有数据,如果有直接返回,没有则去数据库查数据

2、判断从数据库查出的数据是否为空,如果为空,则将空对象存在redis中

 缓存击穿:当一个被高并发访问并且重建业务比较复杂的热点key失效时,会有大量的请求直接访问到数据库,给数据库带来巨大冲击。

解决思路:

  • 分布式互斥锁:当一个线程获得锁然后执行重建缓存的逻辑时,其他线程只能等待缓存重建完成后再去获取缓存(使用redis的setnx方法来表示获取锁 ,set if not exist)。有死锁和线程池阻塞的风险,降低吞吐量,但能有效保持数据一致
  • 设置永不过期:不直接设置key的过期时间,而是将过期时间放在value中,当有线程去查询key并发现已经到过期时间后,就异步地去更新缓存。解决了热点key的一系列危机,但在缓存更新时不能保证数据一致性

实例解决方案(分布式互斥锁)

篇幅限制下面就只能给大家展示小册部分内容了。这份面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处】即可免费获取

缓存雪崩:当缓存中大量的key同时失效或redis服务崩溃时,会有大量请求直接访问到数据库,带来巨大压力

解决思路:

  • 给不同的key的TTL增加随机值
  • 设置key永不过期,加分布式锁
  • 利用redis集群提高服务的可用性(比如使用redis哨兵)
  • 给缓存业务添加降级限流策略
  • 添加多级缓存

 实例解决方案(设置key永不过期)

与分布式互斥锁相比,其实就是在缓存命中之后加一层缓存是否过期的校验,过期后就执行获取锁更新缓存的操作,没过期就直接返回

值得注意的点:

  1. 该方案中可以封装一个实体类,里面存有逻辑缓存过期时间和真实value,redis中的value就引用该对象实例
  2. 判断缓存是否过期通过拿逻辑缓存过期时间与LocalDateTime做对比
  3. 更新缓存时,获得锁后,应该开启独立线程去更新缓存,然后直接返回旧的缓存数据,目的是减少响应时间,因为设置key永不过期本身就不能保证数据完全同步
  4. 开启独立线程使用的是Executor,一套线程池管理框架,可以处理多线程操作,具体实现如下
     
       
    1. private static final ExecutorService CACHE_REBUILD_EXECUTOR =

    2. Executors.newFixedThreadPool(10);

    3. if (isLock){

    4. CACHE_REBUILD_EXECUTOR.submit( ()->{

    5. try{

    6. //重建缓存

    7. this.saveShop2Redis(id,20L);

    8. }catch (Exception e){

    9. throw new RuntimeException(e);

    10. }finally {

    11. unlock(lockKey);

    12. }

    13. });

    14. }

 2、redis的五种数据类型

 篇幅限制下面就只能给大家展示小册部分内容了。这份面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处】即可免费获取

3.1、redis键值设计

①优雅的key结构 业务名称:数据名称:id 例:login:user:1

优点:可读性强,避免key冲突,方便管理,节省内存空间

3.2、bigkey和bigkey的危害

bigkey以key的成员数量和key的大小来综合评估,比如key本身数据量过大,key的成员数量过多

bigkey的危害有:

  • 网络阻塞:读取bigkey时,少量的QPS就可能宽带使用率拉满,造成网络阻塞,导致物理机和redis执行速度变慢
  • 数据倾斜:bigkey的内存使用率大,导致数据分片的内存分配不均衡
  • redis阻塞:对bigkey的数据进行一些运算操作时,耗时会比较久,就有可能导致redis进程阻塞
  • CPU压力:对bigkey进行序列化和反序列化将导致CPU的使用率飙升,给予CPU巨大压力

3.3 总结

对于key:

  • 固定格式:业务名称:数据名称:id
  • 足够简短:尽量不超过44字节,因为key是string类型,底层编码包含int、embstr和raw三种。embstr在小于44字节时使用,采用连续内存空间,内存占用更小
  • 不要包含特殊字符

对于value:

  • 合理地 拆分数据,拒绝bigkey
  • 选择合适的数据结构
  • hash的entry数量尽量不要大于500,因为hash的entry数量大于500时,会使用哈希表结构而不是ziplist,增大内存占用
  • 设置合理地过期时间

3.4 redis的更新策略

更新缓存还是删除缓存?

一般选择删除缓存,有以下好处:

 没有无效更新操作,线程安全问题小

先操作数据库还是先操作缓存

一般选择先操作数据库,理由如下:

在两个线程并发来访问时,假设线程1先来,他先把缓存删了,此时线程2过来,他查询缓存数据并不存在,此时他写入缓存,当他写入缓存后,线程1再执行更新动作时,实际上写入的就是旧的数据,新的数据被旧数据覆盖了。

而先操作数据库就不会有这种问题,安全系数更高

4、redis为什么快?

1、基于内存实现

Redis是基于内存存储的,读取数据时不需要进行磁盘IO,要比数据存储在磁盘的数据库快

2、高效的数据结构

redis中采用了许多高效的数据结构,比如

哈希表:可以保证查找操作空间复杂度是O(1)

跳表:保证查找/添加/插入/删除操作都能够在O(LogN)的复杂度内完成。

3、单线程操作,避免了不必要的线程上下文切换和竞争锁消耗,而且也不用去考虑线程安全问题,提高性能

4、高性能的多路复用IO模型

redis网络模型使用IO多路复用结合事件处理器

IO多路复用让redis在单线程的情况下也能处理多个连接请求,并且由于IO多路复用是非阻塞的,让redis能够高效网络通信

事件处理器通过事件监听和通知机制,避免了redis一直轮询关注事件进度,避免CPU浪费

并且在redis6.0中,为了提升性能,在命令回复和命令转换中使用了多线程

5、虚拟内存机制

Redis直接自己构建了VM机制 ,虚拟内存机制就是暂时把不经常访问的数据(冷数据)从内存交换到磁盘中,从而腾出宝贵的内存空间用于其它需要访问的数据(热数据)。

RedisIO多路复用模型:

IO多路复用模型通过调用select/poll/epoll等函数,开启一个监听线程(selector)去监听多个socket客户端,此时监听线程会阻塞,其他非监听线程仍可以继续工作,一旦某个socket客户端有IO操作就绪,selector会解除阻塞并通知redis,Redis就会建立连接,接收数据,然后selector继续阻塞等待客户端就绪。

一条命令在Redis是如何完成执行的?

Redis Server一旦和某一客户端建立连接后,就会在事件驱动框架中注册可读事件,对应客户端的命令请求。整个命令处理的过程可分为如下阶段:

  • 命令解析,对应processInputBufferAndReplicate

  • 命令执行,对应processCommand

  • 结果返回,对应addReply

5、Redis为什么是单线程的?

首先要知道,Redis的性能瓶颈在内存和网络IO,而不是CPU

使用多线程对Redis的性能提升并不大,而多线程本身也有线程安全和上下文切换问题,所以Redis开发者选择使用单线程。

但是在Redis6.0之后,在发送响应数据中引用了多线程操作,这主要是为了提升网络IO性能

6、Redis双写一致性如何保证

Redis跟数据库双写方案需要根据业务对数据一致性的需求来决定

1、需要强一致性

  采用Redission的读写锁,读数据采用读锁,写业务采用写锁

  比如在抢券业务中,券的剩余量是需要强一致性的

2、允许延迟一致

采用异步方案同步,即写操作先写入数据库,然后异步地去修改缓存

异步方案:MQ(基于消息队列),Canal(基于mysql binlog),

7、Redis持久化方式

Redis进行持久化时,在主进程中会fork一个子进程,子进程会共享主进程的内存,并且通过内存中的页表对物理内存进行读取,然后将数据写入磁盘文件中

Redis持久化有两种方式

1、RDB(Redis DataBase Backup file)

  RDB是Redis默认持久化方式,它按照配置好的时间周期策略将内存数据以快照的方式存入磁盘,生成rbg后缀文件,可以通过修改配置文件中的save参数来配置策略

2、AOF(Append only file)

  AOF会将每一个写命令通过wirte函数追加到aof文件最后,类似于Mysql的binlog,当redis重启后,会执行文件中保存的写命令恢复数据

  当两种方式都开启时,默认执行AOF

8、Redis过期删除策略

Redis采用惰性删除+定期删除策略

惰性删除:当访问key时判断是否过期,如果过期,就将key删除

定期删除:定期检查一定量key是否过期,如果过期就删除

惰性删除的问题是,如果key过期了,但却一直没有被访问,就无法删除,造成内存资源浪费

定期删除的问题是,会有很多key过期了但无法删除,不仅占用内存,访问时还会访问到过期数据

为什么不用到期自动删除?

因为这样就需要一个定时器去监视每个key是否过期,十分浪费CPU资源

9、Redis数据淘汰策略

Redis数据淘汰一共有8种策略

比较关键的是后面四种:

如果业务中数据访问频率差距大,有冷热数据之分,建议使用lru

如果要保留置顶数据,建议使用volatile,然后将置顶数据不设置过期时间

如果业务中有很多短时间内高频访问的数据,建议使用lfu,保留这些高频数据

场景题、数据库有1000w数据,但redis只能保存20w,如何让Redis存的数据更有价值?

首先建议采用lru算法,它能有效保存经常被访问的热点数据,然后如果有需要固定一些数据,则可以使用volatile-lru,然后不给固定数据设置TTL

10、在项目中Redis的使用场景

秒杀抢购逻辑:

在项目中,我使用了Redis保存了优惠券库存信息(string)和优惠券订单信息(set)

  • 用户抢购时,首先要从Redis中获取优惠券信息,是否库存大于0,是才能继续,否则直接返回

  • 接下来从Redis中获取优惠券订单信息,判断set集合中是否有用户id,如果是说明用户已经有订单了,就直接返回。否则将库存-1,然后在订单集合中增加该用户id。

以上均在lua脚本中完成

优惠券基本信息的缓存在添加优惠券时写入,TTL设置为优惠券过期时间减去当前时间。

判断有下单资格后,通过kafka消息队列异步生成订单,然后直接返回订单Id。

在项目中,我还使用了Redission分布式锁在生成订单操作前上锁,锁的key为order:user:{userId}

这里加锁主要是一个兜底,防止一人多单,实际上lua脚本中都判断过了

 篇幅限制下面就只能给大家展示小册部分内容了。这份面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处】即可免费获取

11、Redission分布式锁原理

Redission是Redis官方推荐的客户端,继承了juc中的lock接口,实现了可中断,可重入,超时释放等操作

底层结构:Redis中的hash结构,key存储锁名,field存储客户端id(uuid+ThreadId),value用于存储线程重入次数

加锁实现:

Redission加锁采用的是lua脚本来保证原子性,它首先会判断该key是否存在,如果不存在就创建一个,并设置线程重入次数为1,然后返回null

如果key存在就判断客户端id是否存在,如果存在就将重入次数+1,然后返回null

如果以上都不满足,就返回TTL

每次在加锁时都会判断,如果返回的TTL为null,说明加锁成功,否则失败,然后循环尝试获取锁

lease续约实现:

Redission中为了业务的安全性和可靠性引入watchdog机制,这个机制就是说在任务获取锁时,会同步开启一个守护线程,它会每隔leaseTime/3时间续约锁的超时时间,默认续约30s,可以配置,守护线程会一直续约直到锁释放,这样就保证了执行业务期间不会因超时释放锁

解锁实现:

首先判断锁是否是存在的,如果不存在直接返回nil; 如果该线程持有锁,则对当前的重入值-1,如果计算完后大于0,重新设置超时持有时间返回0; 如果算完不大于0,删除这个Hash,并且进行广播,通知watch dog停止进行刷新,并且 返回1.

RedissionRedLock红锁实现:

Redission锁没有解决主从节点切换导致的多个线程持有锁的问题,所以提供了,RedissionRedLock来实现,它要求对多个Redis实例都进行加锁,只有当超过一半的锁加锁成功时,才认为成功加锁。

12、Redis主从同步原理

单节点的Redis并发能力是有上限的,往往需要搭建Redis主从集群来提升性能,一般是一主多从,读写分离,主负责写,从负责读

主从同步的流程:

  1、从节点发送同步请求

  2、主节点判断是否是第一次请求,如果是,则进行全量同步,否则进行增量同步

全量同步:

  1、首先与从节点同步版本信息(replication id和offset)

  2、然后主节点执行一次bgsave,生成rgb文件,然后将rgb文件传给从节点去执行

  3、在bgsave期间,主节点会将这期间的命令写入到一个日志文件中(repl_back.log)

  4、在发送rgb文件之后,主节点会将日志文件发送给从节点同步

增量同步:

1、主节点获取从节点的offset信息

2、从命令日志文件中读取offset之后的数据,发送给从节点同步

13、Redis高可用,哨兵模式,脑裂问题

哨兵模式:哨兵模式可以实现主从集群的故障恢复

它主要实现了三个功能:

1、监控:每隔一秒钟向集群实例发送ping命令,检查集群实例是否在工作,如果有超过一定数量(可配置)的sentinel都认为某实例已经下线,则该实例被认为客观下线。

2、自动故障修复:当master节点被认为客观下线后,哨兵会将一个从节点提升为主节点,故障实例恢复后也以新的主节点为主

3、通知:Sentinel用来充当Redis的服务发现源,当Redis集群发生故障转移后,Sentinel会及时的将新的Redis节点信息推送到客户端。

脑裂问题:

发生脑裂问题一般是这种情况:

  1、主节点因某些原因出现暂时性失联,但仍在正常工作,Sentinel此时联系不到主节点,于是将一个从节点升级为主节点。

  2、然而Redis客户端会在Sentinel通知还未到时,继续向旧主节点写入数据

  3、当旧主节点恢复与哨兵的联系时,会清除自己的数据与新主节点同步,造成了一段时间的数据丢失。

脑裂解决办法:

1、设置Redis中的min-slave-to-write参数:这个参数确保了主节点要至少有n个从节点才能执行写命令

2、设置Redis中的min-slave-max-lag参数:这个参数确保在主从复制时,如果ACK没在规定时间内,就拒绝写命令

这样一来,即使主节点失联了,也会因为联系不上从节点而拒绝写命令,防止数据丢失。

14、Redis分片集群的作用和原理

说到Redis分片集群,我们可以从特点说起:

Redis分片集群有以下特点:

  • 有多个master节点,每个master节点中存储不同的数据——支持高并发写

  • 每个master节点都有多个slave节点——支持高并发读

  • 每个master节点会互相ping监测彼此健康状态——主节点监听

  • 每个请求最终都会被转发到正确的节点中——请求路由转发

 篇幅限制下面就只能给大家展示小册部分内容了。这份面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处】即可免费获取

Redis分片集群存储和读取原理

  • Redis分片集群引入了哈希槽的概念,哈希槽可以看做若干哈希值的集合,Redis中共有16384个哈希槽。

  • Redis将哈希槽分配在各个master实例上

  • 当读写请求到达集群时,会使用CRC16算法计算key的hash值,然后对16384取模来决定访问哪个节点

四种主流MQ的比较:

  • 17
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值