Mushroom Notes Mushroom Notes
🍄首页
  • JavaSE

    • 基础篇
    • 数据结构
    • IO流
    • Stream流
    • 函数式接口
    • JUC
    • 反射
    • 网络编程
    • 设计模式
  • JavaEE

    • Servlet
    • JDBC
    • 会话技术
    • 过滤器监听器
    • 三层架构
  • JDK

    • 总览
  • JVM

    • 总览
  • 常用mate
  • CSS
  • JavaScript
  • rds 数据库

    • MySQL
    • MySQL 进阶
    • MySQL 库表规范
  • nosql 数据库

    • Redis
    • Redis 进阶
    • Redis 底层
    • MongoDB
  • Spring生态

    • Spring
    • Spring MVC
    • Spring boot
    • Spring Validation
  • Spring Cloud生态

    • Spring Cloud
    • 服务治理
    • 远程调用
    • 网关路由
    • 服务保护
    • 分布式事务
    • 消息中间件
  • 数据库

    • Mybatis
    • Mybatis Plus
    • Elasticsearch
    • Redisson
  • 通信

    • Netty
📚技术
  • 方案专题
  • 算法专题
  • BUG专题
  • 安装专题
  • 网安专题
  • 面试专题
  • 常用网站
  • 后端常用
  • 前端常用
  • 分类
  • 标签
  • 归档

kinoko

一位兴趣使然的热心码农
🍄首页
  • JavaSE

    • 基础篇
    • 数据结构
    • IO流
    • Stream流
    • 函数式接口
    • JUC
    • 反射
    • 网络编程
    • 设计模式
  • JavaEE

    • Servlet
    • JDBC
    • 会话技术
    • 过滤器监听器
    • 三层架构
  • JDK

    • 总览
  • JVM

    • 总览
  • 常用mate
  • CSS
  • JavaScript
  • rds 数据库

    • MySQL
    • MySQL 进阶
    • MySQL 库表规范
  • nosql 数据库

    • Redis
    • Redis 进阶
    • Redis 底层
    • MongoDB
  • Spring生态

    • Spring
    • Spring MVC
    • Spring boot
    • Spring Validation
  • Spring Cloud生态

    • Spring Cloud
    • 服务治理
    • 远程调用
    • 网关路由
    • 服务保护
    • 分布式事务
    • 消息中间件
  • 数据库

    • Mybatis
    • Mybatis Plus
    • Elasticsearch
    • Redisson
  • 通信

    • Netty
📚技术
  • 方案专题
  • 算法专题
  • BUG专题
  • 安装专题
  • 网安专题
  • 面试专题
  • 常用网站
  • 后端常用
  • 前端常用
  • 分类
  • 标签
  • 归档
  • 方案专题

  • 算法专题

  • BUG专题

  • 安装专题

  • 网安专题

  • 面试专题

    • Java 基础篇
    • JUC篇
    • JVM篇
    • SSM篇
    • Springboot篇
    • SpringCloud篇
    • MQ篇
    • MySQL篇
    • Redis篇
      • Redis和memcached有什么区别
      • Redis的线程模型?为什么单线程还能有很高的效率?
      • redis为什么效率这么高
      • Redis的持久化策略有哪些?有什么区别吗?项目中一般用哪种?
      • 什么是缓存穿透?如何避免?
      • 什么是缓存雪崩?如何避免?
      • 什么是缓存击穿?如何避免?
      • Redis的集群方式有哪些?
      • Redis是如何知道一个key是否过期的呢?是不是TTL到期立即删除呢?
      • 内存淘汰策略
    • 设计模式篇
    • Elasticsearch篇
  • 专题
  • 面试专题
kinoko
2023-12-26
目录

Redis篇面试题

# Redis和memcached有什么区别


一、从数据类型上比较
Redis相比Memcached来说,拥有更多的数据结构并支持更丰富的数据操作,支持更多复杂的场景,通常在Memcached中,我们需要将数据拿到客户端来进行类似数据类型转换的修改再set回去。这大大增加了网络IO的次数和数据体积。在Redis中,这些复杂的操作通常和一般的getset一样高效。所以数据存储上Redis有绝对的优势。

二、集群模式
memcached没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据,但是Redis从3.0版本以后是原生支持cluster模式(哨兵模式/集群)的,这点比memcached要更好。

由于memcached基本上已经被淘汰,所以这个问题应该不会问,可以当做了解,问到就说这两个点就行了

# Redis的线程模型?为什么单线程还能有很高的效率?


线程模型

redis 内部使用文件事件处理器 file event handler,它是单线程的,所以redis才叫做单线程模型。它采用IO多路复用机制同时监听多个 socket(即NIO),将产生事件的 socket 压入内存队列中,事件分派器根据 socket 上的事件类型来选择对应的事件处理器进行处理。

文件事件处理器的结构:

  • 多个 socket: 由连接应答处理器创建的与 server socket 接收的请求客户端对应的socket
  • IO 多路复用程序: 监听 socket 中产生的事件,并将产生该事件的 socket 压入队列中
  • 文件事件分派器: 根据 socket 中携带的事件分配不同的事件处理器
  • 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器):根据不同的事件进行不同的处理

多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO多路复用程序会监听多个 socket,会将产生事件的 socket 放入队列中排队,事件分派器每次从队列中取出一个 socket,根据 socket 的事件类型交给对应的事件处理器进行处理。

一次客户端与redis的完整通信过程
image.png
一、建立连接

  1. 首先,redis 服务端进程初始化的时候,会将 server socket 的 AE_READABLE 事件与连接应答处理器关联。
  2. 客户端 socket01 向 redis 进程的 server socket 请求建立连接,此时 server socket 会产生一个 AE_READABLE 事件,IO 多路复用程序监听到 server socket 产生的事件后,将该 socket 压入队列中。
  3. 文件事件分派器从队列中获取 socket,交给连接应答处理器。
  4. 连接应答处理器会创建一个能与客户端通信的 socket01,并将该 socket01 的 AE_READABLE 事件与命令请求处理器关联,等待客户端发送命令请求。

二、执行一个set请求

  1. 客户端发送了一个 set key value 请求,此时 redis 中的 socket01 会产生 AE_READABLE 事件,IO 多路复用程序将 socket01 压入队列,
  2. 此时事件分派器从队列中获取到 socket01 产生的 AE_READABLE 事件,由于前面 socket01 的 AE_READABLE 事件已经与命令请求处理器关联,
  3. 因此事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取 socket01 的 key value 并在自己内存中完成 key value 的设置。
  4. 操作完成后,它会将 socket01 的 AE_WRITABLE 事件与命令回复处理器关联。
  5. 如果此时客户端准备好接收返回结果了,那么 redis 中的 socket01 会产生一个 AE_WRITABLE 事件,同样压入队列中,
  6. 事件分派器找到相关联的命令回复处理器,由命令回复处理器对 socket01 输入本次操作的一个结果,比如 ok,之后解除 socket01 的 AE_WRITABLE 事件与命令回复处理器的关联。

# redis为什么效率这么高

  1. 纯内存操作。

由于文件事件处理器中的每个结构的分工都明确并且简单,不需要经过什么复杂的计算处理,所以在每一个结构上消耗的时间都非常的少,并且是在内存中的操作,执行速度在毫秒级,所以效率高

  1. 核心是基于非阻塞的 IO 多路复用机制,这是Redis效率高最关键的一点。

原因在于文件事件处理器中的IO 多路复用程序,相当于NIO中的Selector,他是非阻塞的,他不需要处理请求,只负责管理多个连接,接收请求,若是BIO的话就需要接收请求后进行一系列处理,处理结束后才接收第二个请求,而IO 多路复用程序接收到请求直接将请求压到队列中就行了,只管压,不处理,所以效率高

  1. C 语言实现,语言更接近操作系统,执行速度相对会更快。
  2. 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。

上下文切换: cpu通过时间片循环执行线程任务,当线程的cpu时间片用完后会保存当前任务状态,方便下次获取到cpu时间片的时候能继续执行,当下次分配时间片后执行到该线程时,会重新加载该线程的任务状态,而这个从保存任务状态到重新加载的过程就叫上下文切换。

存在的问题: 每次上下文切换也都要消耗cpu资源的,就好比看英文书一样,你边英文看边去看翻译,在你看翻译之前你必须要记住当前英文书看到了第几页,这样的切换是会影响读书的效率,同样上下文切换也会影响多线程的执行速度。

**结论:**文件事件处理器是单线程,就不存在时间片耗尽切换线程的情况,所以不会造成cpu资源浪费。

面试话术 Redis单线程又高效率的很大一个原因是在于他的线程模型,内部是有一个叫文件事件处理器的东西,这个东西是单线程的,利用了NIO多路复用的机制,我记得这个文件事件处理器大致的结构是这样的,他会有多个socket用于保持跟多个客户端的连接,就只要是客户端跟Redis连接上了就会有一个socket嘛,就是这个socket对象,然后有一个NIO的多路复用程序,Redis快的核心原因就在这里了,这个程序是非阻塞的,他不需要处理请求,只做接收,就相当于NIO的那个selector,接收请求后就socket就会生成事件,然后这个多路复用程序就会将事件压栈到文件事件分派器,由分派器来分派事件类型,到对应的事件处理器进行处理。大致的一个流程就是这样子,简单说就是每一个部分做的事情都很少很简单,所以执行的也很快,而且由于是单线程就不需要加锁,不存在加锁释放锁的问题,也不存在多线程才有的多线程上下文切换问题,而且这些都是完全基于内存的操作,源码也是用的C编写,更接近操作系统,结合这些就是Redis单线程任然能达到高并发效果的原因了。

# Redis的持久化策略有哪些?有什么区别吗?项目中一般用哪种?


面试话术
持久化策略有RDB和AOF,RDB就是对整个内存数据做快照,持久化到硬盘,特点是文件体积比较小,但是每次RDB的时间间隔会比较长,容易出现数据丢失,RDB是可以主动执行的,有两个指令,一个是save一个是bgsave,一个同步一个异步,Redis停机的时候也会触发RDB。因为我们一般都是会选择异步持久化嘛,所以RDB的时候会开启一个子进程去进行持久化,然后持久化的时候,主进程其实会做一个页表的fork给子进程,页表就是内存数据的一个映射关系,因为fork的过程是阻塞的嘛,如果fork内存中的数据的话,会非常耗时间,所以Redis采用的是fork页表,这样效率就高很多,然后子进程拿到页表后就通过这个页表去对内存中的数据进行持久化,这个过程中主进程还在一直接收指令的嘛,就可能会接收到命令去修改内存中正在RDB的数据,读写就会有冲突,这里话fork的底层是有用到copyonwrite技术来解决这个问题,拷贝一份要修改的数据的副本,对副本进行修改,然后将页表的数据地址指向新的数据,这样就能保证不会读写冲突,等子进程RDB结束后,再fork一个新的页表给子进程来进行下一次的持久化,但是这边又有一个极端的情况,就是在这个过程中,用户把内存里的所有数据都修改了一遍,等于我得拷贝一个完整的内存副本,直接让数据翻倍了,所有我们在使用Redis的时候服务内存不会说有多少就给多少,而是预留一半给Redis来防止内存溢出。
而AOF就相当于是一个日志快照,记录每一条指令,同样也会持久化到硬盘,但是指令的话体积就会比较大了,不过AOF快照日志的时间很快,默认1s一次,所以数据完整性会比较高,默认一秒一次就是先将一秒内的指令存到缓冲区,然后每隔一秒刷一次到AOF文件中,如果在这一秒中内Redis宕机了,那缓冲区的数据也会丢失,也就是最多丢失一秒的数据,这个频率是可以配置的,像如果想做到数据绝对不丢失的话可以设置为always,每执行一条就写到AOF文件中,这个是主进程执行的,对性能影响就比较大,还有一种是由操作系统来判断什么时候要刷到文件中,这种就可能会丢失大量的数据,相对的性能也最好。关于那个AOF文件体积过大的问题,Redis自己其实也有做一个优化,就是bgrewriteof命令,可以做指令的重写,比如我们重复的去修改一个key,实际上我们需要的只是最后一次修改的指令,所以呢,重写的话就可以折叠这些重复的指令到一行,保留有效指令,从而做到一定程度上压缩AOF文件的大小, 这个也是可以配置的,比如文件大小达到多少开启重写,指令增长了多少比例执行一次之类的。
在服务重启的时候RDB的数据还原会比较快,等于是直接读取数据到内存,而AOF需要一条条指令去执行,效率会比较慢。服务刚启动的时候默认是使用AOF进行数据还原,因为AOF的数据完整性高。

# 什么是缓存穿透?如何避免?


首先先回顾一下一般缓存的使用逻辑,用户发送请求,先查缓存,如果缓存中存在数据,则直接返回。如果缓存中不存在,则再查数据库,如果数据库中存在,则将数据放入缓存,然后返回。如果数据库中也不存在,则直接返回失败。

缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。
image.png
**场景:**比如恶意用户伪造不存在的id发起请求,发现返回数据为空,他就创建非常多的线程,并发的向不存在的数据发送请求,这样所有的请求都会打到数据库,导致缓存根本没有发挥作用,数据库压力增大,甚至崩溃。

对策:

  • 缓存空对象
  • 布隆过滤
  • 增强id的复杂度,避免被猜测id规律,如:使用雪花算法生成id
  • 做好数据的基础格式校验
  • 加强用户权限校验,过滤能够发送这类请求的用户
  • 做好热点参数的限流

第一第二个都是较为被动的对策,也是主要分析的对策,其余的都是主动的对策

缓存空对象

即使数据库没有查询到数据,也将空对象,若是string类型的键值对,则是将空字符串作为value缓存起来,下次再有用户访问这个不存在的数据,依然可以走缓存。
image.png

**优点:**简单粗暴,维护方便
缺点:

  • 额外的内存消耗

由于需要存放大量的空对象到缓存中,必然会有额外的内存消耗,当然这里也有解决方法,可以设置TTL缓存过期时间,来保证这些空对象在一定时间后会过期,一定程度上可以减缓内存的消耗

  • 可能造成短期的数据不一致

当请求了不存在的数据,并将代表该数据的key以及空对象存放入缓存后,此时数据库添加了能与该请求对应的数据,但再次访问时却访问的是缓存中的空对象,就会造成数据不一致,这里有两个解决方案,一是通过设置短期的TTL来加快缓存中数据的更新,二是直接在数据库新增数据后,主动将这个数据插入缓存中来覆盖空对象

布隆过滤

通过布隆过滤器来过滤请求,若通过算法计算发现该请求的数据存在则放行,否则拒绝访问。
image.png
布隆过滤器是基于一种算法来实现的,大致可以理解成:

布隆过滤器底层使用bit数组存储数据即二进制储存,该数组中的元素默认值是0。

布隆过滤器第一次初始化的时候,会把数据库中所有已存在的key,经过一些列的hash算法(比如:三次hash算法)计算,每个key都会计算出多个位置,然后把这些位置上的元素值设置成1。
image.png
之后,有用户key请求过来的时候,再用相同的hash算法计算位置。

如果多个位置中的元素值都是1,则说明该key可能存在数据库中。这时允许继续往后面操作。如果有1个以上的位置上的元素值是0,则说明该key在数据库中不存在。这时则拒绝该请求,直接返回。

为什么说1的时候是可能存在呢?因为是通过hash算法来计算位置,所以避免不了会出现hash冲突,所以不同的key可能会计算出相同的hash值。存储的数据越多,布隆过滤器的hash冲突就越明显。

比如某个用户发起请求,本来的话请求的这个数据对应的hash计算出来的位置应该为0,但是被其他的key置为了1,这时就出现了误判,所以才说是可能存在。

补充:Redis提供了一个布隆过滤器的实现叫bitmap

**优点:**内存占用较少,没有多余key

不需要像缓存空对象一样存储空对象到缓存中,并且是二进制存储,

缺点:

  • 实现复杂

由我们自己去实现这个布隆算法其实是比较麻烦的,好在Redis有提供bitmap来实现布隆过滤,简化开发

  • 存在误判的可能

**结论:**在开发过程中一般会选择缓存空对象来解决缓存穿透的问题。

面试话术
缓存穿透就是说存在恶意用户持续访问不存在的key导致请求穿过redis直接打到mysql,有两个对策,一个是缓存空对象一个使用布隆过滤器,缓存空对象就是比较简单粗暴的方法,就是我mysql查不到这个key但是也把空对象存到redis,这样就能让他一直访问redis了,但是这种的话也有问题,首先就是会有额外的内存消耗,毕竟存了很多脏数据进redis嘛,然后就是会导致短期的数据不一致,比如假设恶意用户访问一个key不存在,但是我后面又在数据库添加了这个key,就得等redis的key过期,才能数据同步。而布隆过滤器则是通过算法来判断请求中的key是否存在,存在则放行这个请求,不存在则拒绝访问。布隆过滤器大致的一个原理是通过字节数组存储表示数据库中的数据,就是会把数据库中所有已存在的key,经过一些列的hash算法(比如:三次hash算法)计算,每个key都会计算出多个位置,然后把这些位置上的元素值设置成1。访问的key经过同样的计算判断索引位存储的是不是1,当所有位置都是1则放行,只要有0则拒绝。但是布隆过滤器是存在误判的,因为是通过hash来计算索引下标,所以避免不了会有hash冲突,比如我查询了一个不存在的key,正常的话hash计算出的下标应该会对应是0,又因为hash冲突,导致存在key的hash结果使这个位置变为了1,这样出现了误判。所以说只要数据量越大,hash冲突的概率的越高,误判的概率也就越高。而且布隆过滤器的实现据说比较复杂,好像redis是有提供一个bitmap来实现布隆过滤器,但我没有实际用过,所以不太清楚。
缓存空对象和布隆过滤器都是出现缓存穿透的对策,我们也可以增强id的复杂度,做好数据的基础格式校验以及加强用户权限校验,添加白名单之类的,过滤能够发送这类请求的用户,以及做好热点参数的限流。

# 什么是缓存雪崩?如何避免?


缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
image.png
**场景:**比如在做缓存预热的使用提前将数据库中的数据导入缓存中,设置统一的TTL值,导致大量的数据在缓存中同时过期造成缓存雪崩。

对策:

  • 给不同的key的TTL添加一定范围的随机值
  • 利用Redis集群提高服务的可用性

Redis集群建立主从关系,利用Redis哨兵模式,当主库宕机了就可以从从库中选出一个来替代

  • 给缓存业务添加降级限流策略

比如直接让请求失败,拒绝服务,而不是让请求压到数据库中,牺牲部分部分来保护数据库

  • 给业务添加多级缓存

比如在多个层面建立缓存,比如在Nginx反向代理建立一次缓存,在Redis建立缓存,再在JVM建立本地缓存

面试话术
缓存雪崩就是说大量的key在同时过期,或者说redis宕机,导致大量的请求直接透过redis,直接打到数据库上造成瞬时的巨大压力,因为原因是在于同时过期嘛,这种情况有可能是出现在进行缓存预热的时候设置了同样的TTL,所以我们只需要给TTL设置一个范围的随机值,让他不要在同一时间过期。然后防止redis宕机可以搭建主从集群,从而提高服务的可用性,以及可以给业务添加服务降级策略,直接拒绝对数据库的访问,然后的话还可以添加多级缓存,什么JVM缓存啊,用Lua脚本给Nginx也添加缓存之类的。

# 什么是缓存击穿?如何避免?


缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会瞬间给数据库带来巨大的冲击。

**场景:**热点Key,比如电商网站搞活动,某个热销商品,这个就是热点Key,缓存重建业务比较复杂指的是这个key在缓存中过期了,需要重写从数据库中查询,再插入到缓存中,但是这个时候业务场景并不一定是查询数据库然后插入缓存这么简单,可能还会需要从多个表中查询数据,然后进行一系列的表关联运算,最终组装成的结果才插入缓存中,这样一来业务的耗时就会比较长,可能会达到几十毫秒甚至几百毫秒,要知道在亿级别的并发环境中,这个时间算是非常久的了,在这段时间内的所有请求都会直接打到数据库上。
image.png

常见对策有两种:

  • 互斥锁
  • 逻辑过期

互斥锁

很简单的思路,因为缓存在重建的过程中,会导致多个线程请求数据失败,需要查询数据库写入缓存中,也就是会有多个线程进行重建,那么给写入操作加上互斥锁就能保证,进行重建的只有一个线程,也就是访问数据库的只会有一个线程,其他线程呢,尝试获取锁失败,则让他休眠一会再重新查询缓存,查看是否重建完毕,若没有才让他再次尝试去获取锁。
image.png
优点:

  • 没有额外的内存消耗
  • 保证一致性
  • 实现简单

缺点:

  • 会导致大量线程等待,影响性能
  • 可能有死锁风险

当需要获取的互斥锁在其他业务里的时候就可能会出现互相等待死锁的情况

互斥锁实现示例:

// 这里的key不建议直接使用请求数据的key,应该使用独立的,又能识别为该业务的key
private boolean tryLock(String key){
    // 锁需要设置过期时间,防止程序出现异常时锁无法释放的情况
    Boolean flag = stringRedisTemplate.opsForValue()
                  .setIfAbsent(key,"1",10,TimeUnit.SECONDS);
    return flag;
}

private void unLock(){
    stringRedisTemplate.delete(key);
}
1
2
3
4
5
6
7
8
9
10
11

逻辑过期

归根结底导致缓存击穿出现的原因就是热点key过期,那么干脆就不设置过期时间,key不过期就永远不会出现穿透的情况,取而代之的是采用逻辑过期的方式来更新或者删除key。
image.png
做法就是在存入key的时候多存一个表示过期时间的字段,以当前时间为起始向后延顺一定时间作为过期时间。
image.png
每次请求获取热点key时都检查这个过期时间字段,即逻辑时间是否等于当前时间或小于当前时间,也就是是否过期,若过期则通过互斥锁进行一个逻辑时间的更新,并且是获取锁后开启新线程,由新线程去查询数据库进行缓存重建,而原来的线程则直接返回过期数据就行了,也不会影响服务性能。

**优点:**线程无需等待,性能较好
缺点:

  • 不保证一致性
  • 有额外内存消耗

要在原有的数据的基础上还要多维护一个过期时间的字段

  • 实现复杂

**结论:**两种对策的思路都是针对缓存重建的这段时间进行优化,互斥锁则是保证线程在这段时间内是串行执行,需要等待,保证了数据的一致性但是牺牲了服务的可用性,而逻辑过期则是维护了一个过期时间的字段来表示热点key的过期时间,由此一来热点key就不会过期,也就不会出现缓存击穿的情况,缓存重建也由独立的线程去完成,不会影响当前服务的性能,保证了服务的可用性但是牺牲了数据的一致性,两者没有优劣,只是根据业务场景选择最合适的对策罢了。

面试话术
缓存击穿就是指某个高并发且重建业务复杂的热点key过期,导致这个key在重建的过程中,大量的请求持续打到数据库上造成巨大压力,因为重建业务复杂,所以耗时很长,导致每个线程进来都进行缓存的重建,就会涉及到各种重复的查表,这同样也会给数据库带来压力,所以其中一种解决方案就是加上互斥锁,当第一个线程进来查询缓存未命中,开始重建缓存的时候就获取互斥锁,直到缓存成功重建才释放锁,而其他线程则是尝试获取锁,获取失败了就让他休眠几秒,然后再去查缓存,如果查不到就再尝试获取锁,这样就能保证只有一个线程在进行缓存重建。这种方法就好在保证了数据的一致性,而且没有额外的内存消耗,实现也简单,但是问题也有,就是会导致大量的线程阻塞,影响性能,而且因为用到了锁,所以有可能会有死锁的风险。而另外一种方法也是我个人比较推荐的,就是使用逻辑过期,从根本上看,缓存击穿就是因为热点key过期, 那只要让这个key永不过期就可以保证不会出现击穿的问题,做法也很简单,就是给key添加一个逻辑过期的字段,每次获取key的时候都判断一下逻辑过期时间是否到了,是否需要重新设置,如果需要重新设置则开启一个异步线程去进行缓存重建,在重建的过程中是不影响服务的,这种方案的好处就是不会影响性能,服务高可用,但是会出现短期的数据不一致,因为多加了一个字段嘛,虽然我觉得这个字段的内容也没多少,而且热点key本身也不是很多,所以是会有额外的内存消耗,但是影响应该不大,这两种方法有点像CAP定理的感觉,互斥锁的方案就是CP模式,强调高一致,逻辑过期的方案则是AP模式,强调高可用,看具体业务场景怎么取舍了。

# Redis的集群方式有哪些?


面试话术
redis的集群可以分为主从集群和分片集群吧,主从集群就是读写分离嘛,主库写,从库读,然后结合哨兵来做到主库宕机时的选主,简单说主从集群就是保证服务高可用吧,然后分片的话就是提高redis的存储能力,用多个redis节点组成集群,然后将好像是16384个插槽均分到不同的节点上,数据存储对key做hash运算来确定存储的位置,因为因为存储数据面向的是插槽而非节点本身,因此可以做到集群动态伸缩。

# Redis是如何知道一个key是否过期的呢?是不是TTL到期立即删除呢?


面试话术
redis的keyspace里面有两个字典,一个存储key-value一个存储key-ttl,就通过查key-ttl,来判断是否过期。过期删除策略有惰性删除策略以及周期删除策略,惰性删除就是到期了不是立即删除,而是当去访问这个key的时候就,检查该key的存活时间,判断如果过期才执行删除。周期删除就是周期性的抽样部分过期的key好像是一次20个key,然后执行删除,执行周期有两种,一种SLOW一种FAST,慢周期Redis会设置一个定时任务serverCron(),按照server.hz(默认为1秒十次)的频率来执行过期key清理,每次执行周期为100ms,然后还会判断总的一个过期key比例是否达到10%,达到了才会去抽样清理,快周期跟慢周期的最大区别就是字面意思,执行的周期不同,快周期是2ms一次而且处理时间不超过1ms,Redis每个事件循环前会调用beforeSleep()函数,执行过期key清理,模式为FAST

# 内存淘汰策略


Redis支持8种不同策略来选择要删除的key:

  • **no-enviction:**不淘汰任何key,但是内存满时不允许写入新数据,是Redis默认的内存淘汰策略。
  • **volatile-ttl:**对设置了TTL的key,比较key的剩余TTL,TTL越小越先被淘汰。
  • **allkeys-random:**对全体的key,随机进行淘汰。也就是直接从db->dict中随机挑选。
  • **volatile-random:**对设置了TTL的key,随机进行淘汰。也就是从db->expires中随机挑选。
  • **allkeys-lru:**对全体的key,基于LRU算法进行淘汰
  • **volatile-lru:**对设置了TTL的key,基于LRU算法进行淘汰
  • **allkeys-lfu:**对全体key,基于LFU算法进行淘汰
  • **volatile-lfu:**对设置了TTL的key,基于LFI算法进行淘汰

LRU算法(Least Recently Used),最少最近使用。用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高,简单说就是越久没用了就越先淘汰。
LFU算法(Least Frequently Used),最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高。

面试话术
redis有8中内存淘汰策略,主要分为对全局的key和对设置了TTL的key,首先默认的内存淘汰策略是no-envication,就是不淘汰,内存满的时候不允许写入新的数据,直接报oom,然后是针对设置了TTL的,TTL越小的越优先淘汰,然后就按算法分全局key和设置TTL的key了,首先是random随机抽key来删除,然后是LRU算法,就是越久没使用的key越先淘汰,最后就是LFU,就是统计key的使用频率,用的越少的越优先删除。

#redis#面试
上次更新: 2023/12/29 11:32:56
MySQL篇
设计模式篇

← MySQL篇 设计模式篇→

最近更新
01
JVM 底层
09-13
02
JVM 理论
09-13
03
JVM 应用
09-13
更多文章>
Theme by Vdoing | Copyright © 2022-2024 kinoko | MIT License | 粤ICP备2024165634号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式