Redis面试整理

Redis

redis使用场景
  • 缓存
    • 穿透、击穿、雪崩
    • 双写一致、持久化
    • 数据过期、淘汰策略
  • 分布式锁
    • setnx、redisson
  • 计数器
  • 保存token
  • 消息队列
  • 延迟队列
Redis的数据持久化策略有哪些?

RDB(Redis Database Backup file)redis数据快照

把内存中的所有数据都记录到磁盘中,当Redis实例故障重启后,从磁盘读取快照文件,恢复数据

怎么备份?

设置redis的conf配置文件来触发备份条件,900秒内,有1个key被修改,则执行bgsave

RDB执行原理

bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入RDB文件。

linux进程是不能直接访问物理内存的,通过页表来映射虚拟地址与物理内存

在这里插入图片描述

AOF Append Only File (追加文件)

Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件 默认是关闭的,要在配置文件开启

AOF命令记录频率

AOF重写功能

AOF是记录命令的,相对RDB较大,AOF会记录对同一个key的多次写操作,但只有最后一次的操作才有意义

执行bgrewriteaof命令,可以让AOF文件执行重写功能

配置文件配置重写阈值 超过上次文件百分比 超过固定体积大小 触发重写

RDB 和 AOF 对比

什么是缓存穿透,怎么解决?

缓存穿透:查询一个不存在的数据,MySql查询不到数据也不会写入缓存,就会导致每次请求都查询数据库

两种解决方案:

  • 缓存空数据,查询返回的数据为空,仍把这个空结果进行缓存
    • 优点:简单
    • 缺点:消耗内存,可能会发生不一致的问题
  • 布隆过滤器:(缓存预热时,先预热布隆过滤器)先查询布隆过滤器,过滤器中存在则查redis,不存在就返回
    • 优点:占用内存少,没有多余key
    • 缺点:实现复杂,存在误判
什么是布隆过滤器?

bitmap(位图):相当于是一个以(bit)位为单位的数组,数组中每个单元只能存储二进制数0或1

布隆过滤器依赖bitmap来检索一个元素是否存在一个集合中。

  • 存储数据:id为1的数据,根据多个hash函数获取hash值,根据hash计算对应位置改为1
  • 查询数据: 使用相同hash函数获取hash值,判断对应位置是否都为1

误判率:数组越小误判率就越大,数组越大误判率就越小,数组越大内存消耗更多。

实现方案:

  • Redisson
  • Guava
什么是缓存击穿,怎么解决?

给某一个key设置了过期时间,当key过期的时候,恰好这个时间点对这个key有大量的并发请求过来,这些并发的请求可能会瞬间把DB压垮

DB查询到的数据不是应该存Redis嘛,为什么还会把Db击垮?

存储数据到Redis可能消耗时间较长,Redis还没有构建好索引,DB已经被击垮了

怎么办呢?

  • 互斥锁(分布式锁)

    查询缓存(未命中)->获取互斥锁(其他线程获取失败 休眠一会重试)->查询数据库重建缓存数据->写入缓存->释放锁

    • 强一致
    • 性能差
  • 逻辑过期(有个逻辑过期时间字段)

    查询缓存,逻辑时间过期->获取互斥锁(其他线程获取失败)->异步开启一个线程(DO: 1、查询数据库重建缓存数据 2、写入缓存重逻辑过期时间 3、释放锁)->返回过期数据(不需要等待上一步线程执行完)

    • 高可用
    • 性能优
什么是缓存雪崩,怎么解决?

缓存雪崩是指同一时间大量key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力

  • 针对大量key同时失效,可以给不同的Key的TTL添加随机值(过期时间随机)
  • 利用Redis集群提高服务的可用性(哨兵模式、集群模式)
  • 给缓存业务添加降级限流策略(nginx或SpringCloudGateway)降级可作为系统的保底策略,适用于穿透、击穿、雪崩
  • 给业务添加多级缓存(Guava或Caffeine)

<<缓存三兄弟>>

穿透无中生有key,布隆过滤null隔离

缓存击穿过期key,锁与非期解难题

雪崩大量过期key,过期时间要随机

面试必考三兄弟,可用限流来保底

redis双写问题

双写一致性:当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保持一致

  • 读操作:缓存命中,直接返回;缓存未命中查询数据库,写入缓存,设定超时时间

  • 写操作:延迟双删

    • 先修改数据库还是先删除缓存呢
    • 为什么要延迟呢?

    引入问题

    先删除缓存 再修改数据库

    ​ 脏读问题(删除缓存后还没有来的及修改数据库被另一个线程读取到旧数据,缓存和数据库数据从而不一致)

    先修改数据库,再删除缓存

    ​ 脏读问题(查询缓存后未命中,查询数据库并保存到Redis,还没有存到redis缓存时,被修改数据库数据并且将redis缓存删除之后才保存之前的缓存数据)

为什么要删除两次缓存?

避免缓存中存在之前的旧数据,达到数据的一致性

为什么要延时删除呢?

因为一般情况下数据库都是读写分离,主从复制的,需要等待从库的数据的更新,极大地控制了脏数据的风险,但是因为延迟时间不好确定,还是有脏数据的风险的

双写一致的实现(Redis缓存的数据都是读多写少)

  • 分布式锁强一致性,性能差 redisson读写锁
    • 共享锁:读锁ReadLock,加锁之后,其他线程可以共享读操作
    • 排他锁:也叫独占锁writeLock,加锁之后,阻塞其他线程读写操作
    • 读数据的时候使用共享锁,写数据的时候使用排他锁
  • 异步通知
    • 消息中间件保证数据最终一致性MQ需要可靠
      • 写入数据库
      • 发布消息到队列
      • 相关服务监听到信息
      • 相关服务更新缓存 保证最后的数据一致性
    • Canal监控数据库的binlog来保证数据一致性基于mysql的主从同步实现
      • 1、写入数据库
      • 2、canal监听mysql的binlog
      • 3、canal通知数据变更情况
      • 4、相关服务更新缓存
Redis分布式锁如何实现

结合业务场景进行回答

使用场景:集群下的定时任务、抢单、幂等性场景

Redis实现分布式锁主要利用Redis的setnx命令。setnx是SET if not exists(如果不存在,则SET)的简写

获取锁

一条命令保证原子性

# 添加锁 ,NX是互斥、EX 是设置超时时间
SET lock value NX EX 10

释放锁

DEL key

在这里插入图片描述

  • 续期来控制锁的有效时长
    • 控制分布式锁的有效时长
    • 合理
  • while循环来获取锁
    • 性能好
    • 高并发
  • 加锁、设置过期时间都是基于lua脚本完成
    • 原子性

redisson实现的分布式锁-可重入

判断是否为同一个线程,如果是同一个线程则可重入

使用hash结构来存储线程信息和重入的次数

redisson实现的分布式锁-主从一致性

RedLock(红锁):不能只在一个redis实例上创建锁,应该是在多个redis实例上创建锁(n/2+1),避免在一个redis实例上加锁

  • 红锁实现(redis AP思想)
    • 不建议(实现复杂、性能差、运维繁琐)
  • zookeeper(cp思想)

为什么要用分布式锁?

分布式锁最主要的服务之间可以共享锁,synchronized只能在每个服务内部加锁,即java程序内部加锁,而分布式锁可以加锁在每个服务。

为什么要设置超时时间呢? 怎么合理控制Redis分布式锁的有效时长?

如果不设置超时时间,当一个服务获取锁成功,但是这个服务突然宕机了,那么这个锁就不会被释放,其他服务也无法拿到这个锁

Redis分布式锁如何合理的控制锁的有效时长

为什么要控制锁的有效时长呢?

超时时间设置的太短了如果业务还没有执行完就会影响数据,如果设置太长了影响性能。

  • 判断锁的时长
  • 续期
    • redisson分布式锁中,提供了WatchDog来给持有锁的线程续期(默认每隔10秒续期一次)
Redis的数据过期策略有哪些?

Redis对数据设置数据的有效时间,数据过期以后,就需要将数据从内存中删除掉。可以按照不同的规则进行删除,这种删除规则就被称之为数据的删除策略(数据过期策略)

  • 惰性删除

    设置该key过期时间后,不去管它,当需要该key时,再检查是否过期,如果过期,就删掉它,反之则返回该key

    优点:对CPU友好,只会在使用该key时才会进行过期检查,对于很多用不到的key不用浪费时间进行过期检查

    缺点:对内存不友好,如果一个key已经过期,但是一直不用,则该key会一直在内存中,内存永远不会释放

  • 定期删除

    每隔一段时间,就对一些key进行检查,删除里面过期的key(从一定数量的数据库中取出一定数量的随机key进行检查,并删除对应的key)

    • SLOW模式(定时任务,默认执行频率10hz,每次不超过25ms,可以修改配置文件的hz选项来调整次数)
    • FAST模式 执行频率不固定,但两次间隔不低于2ms,每次耗时不超过1ms

    优点:通过限制删除操作执行的时长和频率来减少删除操作对CPU的影响。定期删除,有效的释放过期键占用的内存

    缺点:难以确定删除操作执行的时长和频率

Redis的过期删除策略:惰性删除+定期删除两种策略进行配合使用

Redis的数据淘汰策略有哪些?

当Redis中的内存不够用时,此时向Redis中添加新的Key,那么Redis就会按照某一种规则将内存中的数据删除掉,这种数据的删除规则被称之为内存的淘汰策略

  • noeviction: 不淘汰任何key,但是内存满时不允许写入新数据,默认策略
  • volatile-ttl: 对设置了TTL(过期时间)的key,比较key剩余的TTL值,TTL越小越先被淘汰
  • allkeys-random: 对全体key,随机进行淘汰
  • volatile-random:对设置了TTL的key,随机进行淘汰。
  • allkeys-lru: 对全体key,基于LRU算法进行淘汰
    • LRU(Least Recently Used)最近最少使用。用当前时间减去最后一次访问时间,这个值越大,淘汰优先级越高
  • volatile-lru: 对设置了TTL的key,基于LRU算法进行淘汰
  • allkeys-lfu:对全体key,基于LFU算法进行淘汰
    • LFU(Least Frequently Used)最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高
  • volatile-lfu: 对设置了TTl的key,基于LFU算法进行淘汰

使用建议

  1. 优先使用allkeys-lru 策略。充分利用LRU算法的优势,把最近常访问的数据留在缓存中。如果业务有明显的冷热数据区分,建议使用
  2. 如果业务中数据访问频率差别不大,没有明显冷热数据区分,建议使用allkeys-random,随机选择淘汰
  3. 如果业务中有置顶的需求,可以使用volatile-lru策略,同时置顶数据不设置过期时间,这些数据就一直不被删除,会淘汰其他设置过期时间的数据。
  4. 如果业务中有短时高频访问的数据,可以使用allkeys-lfu或volatile-lfu策略。频率

关于数据淘汰策略其他的面试问题

  1. 数据库有1000万数据,Redis中只能缓存20W数据,如何保证Redis中的数据都是热点数据

    使用allkeys-lru(挑选最近最少使用的数据淘汰)淘汰策略,留下来的都是经常访问的热点数据

  2. Redis的内存用完了会发生什么

    看数据淘汰策略是什么,如果是默认的配置(noeviction),会直接报错

开发中用的较多的是allkeys-lru

其他面试题
  • 集群
    • 主从
    • 哨兵
    • 集群
  • 事务
  • Redis为什么快?
Redis集群有哪些方案,知道嘛?
  • 主从复制
  • 哨兵模式
  • 分片集群
什么是Redis主从同步

主从复制

单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建Redis集群,实现读写分离。

主从数据同步原理

全量同步

执行流程

  1. 从节点发送数据请求同步(replid,offset)
  2. 主节点根据replid判断是否为第一次请求,第一次则返回数据版本信息
  3. 主节点执行bgsave命令,生成RDB文件并发送到子节点
  4. 主节点在生成RDB时会记录期间的命令到缓冲区里面的repl_backlog文件
  5. 把生成之后的命令日志文件发送给从节点进行同步

思考两个问题:

  1. 是怎么判断是否为第一次同步呢?

    子节点发送replid到主节点,如果主节点的replid与子节点的replid不一致,则视为第一次同步

  2. 是怎么判断子节点缺少多少信息要发送多少信息呢?

    通过获取子节点的偏移量,子节点的偏移量到 主节点的偏移量的信息 就是要发送的信息。

主从增量同步

执行流程

  1. 判断是否是第一次同步
  2. 不是第一次同步则从日志文件拿出offset后的数据进行同步
你们使用Redis是单点还是集群?哪种集群?
redis哨兵(Sentinel)是什么

Redis 提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。

作用

  • 监控
    • 不断检查主节点和从节点是否按预期工作
  • 自动故障恢复
    • 如果主节点故障,Sentinel会将一个子节点提升为Master。当实例故障恢复后也会以新的master为主
  • 通知
    • 充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新消息推送给Redis的客户端
    • 就是说主节点故障后,告诉客户端让它联系新的主节点

服务状态监控

sentinel基于心跳机制检测服务状态,每隔1秒向集群的每个实例发送ping命令

  • 主观下线:如果某个sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线
  • 客观下线:若超出指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。

哨兵选主规则

  • 首先判断主与从节点断开时间长短,如超过指定值就排除该从节点
  • 然后判断从节点slave-priority值,越小优先级越高
  • 如果slave-prority一样,判断slave节点的offset值,越大优先级越高
  • 最后判断slave节点的运行id大小,越小优先级越高
Redis分片集群中数据是怎么存储和读取的?

分片集群结构

  • 集群中有多个master,每个master保存不同数据
  • 每个master都可以有多个slave节点
  • master之间通过ping监测彼此健康状态
  • 客户端请求可以访问集群任意节点,最终都会被转发到正确节点

数据读写

Redis 分片集群引入了哈希槽的概念,Redis 集群有 16384 个哈希槽,每个 key通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。

redis集群脑裂

集群脑裂是由于主节点从节点sentinel处于不同的网络分区,使得sentinel没有能够心跳感知到主节点,所以通过选举的方式提升了一个从节点为主,这样就存在了两个主节点,就像大脑分裂了一样,会导致客户端还在老的主节点那里写入数据,新节点无法同步数据,当网络恢复后,sentinel会将老的主节点降为从节点,这时再从新master同步数据,就会导致数据丢失

修改配置

min-replicas-towrite 1 表示最少的salve节点为1个

min-replicas-max-lag 5 表示数据复制和同步的延迟不能超过5s

怎么保证redis的高并发高可用

哨兵模式:实现主从集群的自动故障恢复(监控、自动故障恢复、通知)

你们用过Redis的事务吗?事务的命令有哪些?
Redis是单线程的,但是为什么还那么快?
  • Redis是纯内存操作,执行速度非常快
  • 采用单线程,避免不必要的上下文切换可竞争条件,多线程还要考虑线程安全问题
  • 使用I/O多路复用模型,非阻塞IO
能解释下I/O多路复用模型吗?

Redis是纯内存操作,执行速度非常快,它的性能瓶颈是网络延迟而不是执行速度, I/O多路复用模型主要就是实现了高效的网络请求

用户空间和内核空间

​ Linux系统中一个进程使用的内存情况划分两部分:内核空间、用户空间

  • 内核空间
    • 可以执行特权命令(Ring0),调用一切系统资源
  • 用户空间
    • 只能执行受限的命令(Ring3),而且不能直接调用系统资源,必须通过内核提供的接口来访问

Linux系统为了提高IO效率,会在用户空间和内核空间都加入缓冲区

  • 写数据时,要把用户缓冲数据拷贝到内核缓冲区,然后写入设备
  • 读数据时,要从设备读取数据到内核缓冲区,然后拷贝到用户缓冲区

阻塞IO

两个阶段都必须阻塞等待

阶段一

1 用户进程尝试读取数据(比如网卡数据)
2 此时数据尚未到达,内核需要等待数据
3 此时用户进程也处于阻塞状态

阶段二

  1. 数据到达并拷贝到内核缓冲区,代表已就绪
  2. 将内核数据拷贝到用户缓冲区
  3. 拷贝过程中,用户进程依然阻塞等待
  4. 拷贝完成,用户进程解除阻塞,处理数据

非阻塞IO

非阻塞IO的recvfrom操作会立即返回结果而不是阻塞用户进程。

阶段一

  1. 用户进程尝试读取数据(比如网卡数据)
  2. 此时数据尚未到达,内核需要等待数据
  3. 返回异常给用户进程
  4. 用户进程拿到error后,再次尝试读取
  5. 循环往复,直到数据就绪

阶段二

  1. 将内核数据拷贝到用户缓冲区
  2. 拷贝过程中,用户进程依然阻塞等待
  3. 拷贝完成,用户进程解除阻塞,处理数据

可以看到,非阻塞IO模型中,用户进程在第一个阶段是非阻塞,第二个阶段是阻塞状态。虽然是非阻塞,但性能并没有得到提高。而且忙等机制会导致CPU空转,CPU使用率暴增。

IO多路复用

是利用单个线程来同时监听多个Socket ,并在某个Socket可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。

​ 不过监听Socket的方式、通知的方式又有多种实现,常见的有

  • select
  • poll
  • epoll

差异:

  • select和poll只会通知用户进程有Socket就绪,但不确定具体是哪个Socket ,需要用户进程逐个遍历Socket来确认
  • epoll则会在通知用户进程Socket就绪的同时,把已就绪的Socket写入用户空间

阶段一

  1. 用户进程调用select,指定要监听的Socket集合
  2. 内核监听对应的多个socket
  3. 任意一个或多个socket数据就绪则返回readable
  4. 此过程中用户进程阻塞

阶段二

  1. 用户进程找到就绪的socket
  2. 依次调用recvfrom读取数据
  3. 内核将数据拷贝到用户空间
  4. 用户进程处理数据

Redis网络模型

Redis通过IO多路复用来提高网络性能,并且支持各种不同的多路复用实现,并且将这些实现进行封装, 提供了统一的高性能事件库

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值