首页 > 编程语言 >Java面试题合集(持续更新)

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

时间:2024-08-01 21:25:32浏览次数:16  
标签:面试题 缓存 Java redis Redis 线程 key 合集 节点

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的比较:

标签:面试题,缓存,Java,redis,Redis,线程,key,合集,节点
From: https://blog.csdn.net/2301_78976656/article/details/140857732

相关文章

  • 全网最强Java面试题 很详细了!!!
    一、缓存面试官:什么是缓存穿透 ?怎么解决?候选人:(穿透无中生有Key,布隆过滤NULL隔离)嗯~~,我想一下缓存穿透是指查询一个一定不存在的数据,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到DB去查询,可能导致DB挂掉。这种情况大概率是遭到了攻......
  • Java/SpringCloud/RabbitMq/无感实现消息携带用户信息 --- 逻辑讲解、代码实现、图形
    一、需求:依据黑马商城hmall为例,用户下单创建订单后,交易服务trade-service向交换机topic发送消息,交换机topic路由到队列,购物车服务cart-service监听此队列,实现自动清空购物车。改造下单功能,将基于OpenFeign的清理购物车同步调用,改为基于RabbitMQ的异步通知:定义t......
  • Java基础知识分享(二)相关练习题
    写在前面大家前面的方法和数组学的怎么样了,快来看看这些题你能不能快速地说出答案,数组和方法在Java学习中还是非常重要的,快来检测你的薄弱点在哪,及时查漏补缺!填空题1.数组会在内存中开辟一块连续固定大小的空间,每个空间相当于之前的一个变量,称为数组的元素。数组的长度一经确定......
  • Socket网络编程:Java中的实现与应用
    Socket网络编程:Java中的实现与应用大家好,我是微赚淘客系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!Socket网络编程是实现网络通信的基础,通过它可以在不同的计算机之间传输数据。Java的java.net包提供了强大的网络编程功能,支持各种网络协议。本文将深入探讨Java中Socket编......
  • Java多线程编程详解:从基础到高级
    Java多线程编程详解:从基础到高级大家好,我是微赚淘客系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!Java的多线程编程允许程序同时执行多个任务,提高了应用的性能和响应能力。本文将从基础到高级,全面介绍Java中的多线程编程,包括线程的创建、线程池、同步机制及并发工具的使用......
  • Java串口编程:与硬件通信的实现
    Java串口编程:与硬件通信的实现大家好,我是微赚淘客系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!串口编程在与硬件设备通信时扮演着重要角色,尤其是在工业自动化、嵌入式系统和其他硬件设备的控制中。Java虽然在串口编程上并不直接支持,但通过第三方库(如JavaSerialPortAPI......
  • Java堆栈详解:内存管理与优化
    Java堆栈详解:内存管理与优化大家好,我是微赚淘客系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!Java的内存管理系统由堆(Heap)和栈(Stack)两部分组成,这些部分负责管理Java程序运行时的数据。理解Java堆栈的内存管理以及如何优化这些资源对于开发高效的Java应用至关重要。本文将......
  • Calendar类在Java中的使用与技巧
    Calendar类在Java中的使用与技巧大家好,我是微赚淘客系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!Calendar类是Java中处理日期和时间的重要工具。虽然Java8引入了更现代的java.time包,但Calendar类依然在许多现有项目中广泛使用。本文将介绍Calendar类的基本用法、常见技......
  • javascript学习 - 函数介绍
    函数简介编程时,可能会定义许多相同或者功能相似的代码,此时我们每需要使用一次,就需要重写编写一次。虽然利用循环结构也能够实现一些简单的重复操作,但是功能较为局限。此时,我们就需要使用到JavaScript中的函数。所谓函数,就是通过将一段可以重复调用的代码块进行封装,从而......
  • javascript学习 - 面向对象
    什么是对象之前学习的数据类型在存储一些复杂的信息时,十分不方便,而且也难以区分。为此,为了更加详细方便的描述某一个事物,因而提出面向对象的概念。那什么是对象呢?所谓对象,也是JavaScript中的一种数据类型,可以看做是一系列无序数据的集合。有了对象,就可以用来描述某一......