Redis 面经
1.什么是Redis?为什么你要用它?
- 基于内存的键值对NoSql的数据库
- 单线程模型处理请求,避免线程切换和锁竞争
- IO多路复用(redis内部核心是基于epoll,工作方式:事件通知)
- 提供多种高效数据结构
对比
- 对比Hashmap,Redis突出的优点:
- hashmap只在JVM进程可用,不同进程不能互通;Redis独立在内存中,可以跨多个进程服务器访问;
- Hashmap在程序运行期间存在,程序结束丢失;Redis可以用RDB和AOF持久化和恢复;
- Hashmap只支持基本的数据类型做键;Redis支持多种字符串、哈希、列表、集合(有序)等;
- 对比MySQL,Redis突出优点:
- Redis基于内存,访问速度快;MySQL关系数据库,数据主要存储在硬盘,且适合复杂的事务场景;
- 可以用来做的场景:
- 排行榜
- 缓存
- 分布式锁
2.Redis里面的键又有什么?并且分别存储的是什么?
- 键五种基本数据类型:
-
string(字符串)
- 存储内容:数字、字符串、二进制(图,视频,音频,不可超512mb)
- 使用场景:缓存、计数、共享session、限速
- 使用指令:
SET key value
:设置键 key 的值为 value。GET key
:获取键 key 的值。DEL key
:删除键 key。INCR key
:将键 key 存储的数值增一。DECR key
:将键 key 存储的数值减一。
-
hash(哈希)
- 存储内容:一个map的集合(容易将对象序列化)
- 使用场景:缓存用户信息、缓存对象
- 使用指令:
HSET key field value
:向键为 key 的哈希表中设置字段 field 的值为 value。HGET key field
:获取键为 key 的哈希表中字段 field 的值。HGETALL key
:获取键为 key 的哈希表中所有的字段和值。HDEL key field
:删除键为 key 的哈希表中的一个或多个字段。
-
list(列表)
- 存储内容:存储一个字符串的列表
- 使用场景:消息队列、文章列表
- 使用指令:
LPUSH key value
:将一个值插入到列表 key 的头部。RPUSH key value
:将一个值插入到列表 key 的尾部。LPOP key
:移除并返回列表 key 的头元素。RPOP key
:移除并返回列表 key 的尾元素。LRANGE key start stop
:获取列表 key 中指定范围内的元素。
-
set(集合)
- 存储内容:无序的集合,元素唯一
- 使用指令:
SADD key member
:向集合 key 添加一个元素。SREM key member
:从集合 key 中移除一个元素。SMEMBERS key
:返回集合 key 中的所有元素。
-
zset(有序集合)
- 比set有序,但比set多一个排序属性score
- 使用场景:排行榜
- 使用指令:
HSET key field value
:向键为 key 的哈希表中设置字段 field 的值为 value。HGET key field
:获取键为 key 的哈希表中字段 field 的值。HGETALL key
:获取键为 key 的哈希表中所有的字段和值。HDEL key field
:删除键为 key 的哈希表中的一个或多个字段
-
3.Redis6.0使用多线程是什么回事?
- Redis 6.0 引入了多线程网络 I/O 以提升高并发场景下的性能。多个客户端请求通过 epoll 被接收后,由 I/O 线程并行进行网络读操作和协议解析,但解析后的命令仍由主线程单线程顺序执行(保持原子性)。执行完成后,I/O 线程再负责将响应并行写回客户端。这种设计在保持 Redis 单线程命令处理优势的同时,利用多核 CPU 加速网络 I/O 处理。"
4.Redis的持久化机制是什么?
- 共两个,分别是RDB(保存数据)、AOF(保存操作):
- RDB:通过创建数据集的快照来工作,在指定的时间间隔内将 Redis 在某一时刻的数据状态保存到磁盘的一个 RDB 文件中
- save 命令:会同步地将 Redis 的所有数据保存到磁盘上的一个 RDB 文件中。这个操作会阻塞所有客户端请求直到 RDB 文件被完全写入磁盘
- 当数据集较大,使用save导致redis服务器停止响应客户端
- bgsave 命令:会在后台异步地创建 Redis 的数据快照,并将快照保存到磁盘上的 RDB 文件中。这个命令会立即返回,Redis 服务器可以继续处理客户端请求。
- 快照的创建过程是由一个子进程完成的,主进程不会被阻塞。是在生产环境中执行 RDB 持久化的推荐方式。
- save 命令:会同步地将 Redis 的所有数据保存到磁盘上的一个 RDB 文件中。这个操作会阻塞所有客户端请求直到 RDB 文件被完全写入磁盘
触发场景:
- 在配置文件redis.config 设置指令触发条件,如多少时间间隔变换或者多少变换次数变换;
- 通过 shutdown 关闭触发持久化;
- 复制过程,从节点会与主节点建立连接,可能会根据配置接受主节点的RDB来初始化,这个过程主节点会自动触发RDB持久化并发给从节点
- AOF:记录每个写操作命令并将其追加到 AOF 文件中来工作,恢复时通过重新执行这些命令来重建数据集。
- 工作步骤:
- 命令写入:redis服务器将命令书写到AOF缓冲区末尾
- 文件同步:将缓存刷到AOF文件(三种策略)
- always:每次命令都同步
- everysec:每秒一次
- no:发生宕机,那么丢失的数据量由操作系统内核的缓存冲洗策略决定
- 文件重写
- 如果AOF文件过大后,会进行重写处理文件,但重写会只保留最终的状态,通过fork一个子进程进行重写,避免主进程被阻塞。
- 重启加载:服务器重启后,会读取AOF命令并重新执行恢复状态.
- 工作步骤:
重写过程,指令可能既写旧的AOF,又写新的缓冲区,这是为了保证在重写尚未完成时,redis崩溃,即使重写失败,我的旧的AOF还是完整,但是重写之后,会通过原子操作将旧的AOF进行替换
5.RDB和AOF的有无各自的优缺点?
-
RDB 是一个非常紧凑的单文件(二进制文件 dump.rdb),代表了 Redis 在某个时间点上的数据快照。非常适合用于备份数据,比如在夜间进行备份,然后将 RDB 文件复制到远程服务器。但可能会丢失最后一次持久化后的数据。
- 最优使用场景:重启后数据恢复快
-
AOF 的最大优点是灵活,实时性好,可以设置不同的 fsync 策略,如每秒同步一次,每次写入命令就同步,或者完全由操作系统来决定何时同步。但 AOF 文件往往比较大,恢复速度慢,因为它记录了每个写操作。
- 最优使用场景:要尽可能减少数据丢失
生成环境可以组合使用,当同时使用时:
- AOF 开启且存在 AOF 文件时,优先加载 AOF 文件。
- AOF 关闭或者 AOF 文件不存在时,加载 RDB 文件。
6.Redis 4.0 的混合持久化了解吗?
-
在 Redis 4.0 版本中,混合持久化模式会在 AOF 重写的时候同时生成一份 RDB 快照,然后将这份快照作为 AOF 文件的一部分,最后再附加新的写入命令。
-
这样,当需要恢复数据时,Redis 先加载 RDB 文件来恢复到快照时刻的状态,然后应用 RDB 之后记录的 AOF 命令来恢复之后的数据更改,既快又可靠。
-
如何设置持久化模式:
- 通过指令动态修改;
- 在配置文件 redis.conf设置快照规则以及AOF的写入频率,大小等配置
7.Redis的高可用有什么?(单节点故障靠什么?)
-
主从复制:允许一个主节点将数据复制到从节点,并且支持主节点写数据,从节点读数据,达到读写分离。(数据的复制是单向的)
- 作用:
- 用于数据热备份
- 当主节点挂,从节点升级
- 负载均衡,主写从读
- 哨兵和集群的基础
- 出现问题:(高可用,分布式问题)
- 主从复制异步,主崩溃数据丢失;
- 写操作集中在主,从只负责读,写入压力大;
- 脑裂问题(主从模式或集群模式下,由于网络分区或节点故障,可能导致系统中出现多个主节点,从而引发数据不一致、数据丢失等问题————通过Sentinel或Cluster模式的投票或强制下线)
- 作用:
-
哨兵模式:Sentinel 监控主从节点状态,实现自动化故障转移
- 出现问题(解决高可用,剩下分布式问题)
-
集群模式:分片存储数据,每个节点存储数据一部分,用户请求可以并行处理(Redist Cluster 支持自动分区,故障转移)
- 出现问题:(j解决了高可用和分布式问题)
8.Redis的拓扑结构有多少种?
- 一主一从
- 一主多从 (主从复制基本结构)
- 树状主从结构(从节点不仅可以复制主节点,还可以向下层节点传输)
9.主从复制的原理是什么?(工作流程)
- 保存主节点(保存IP和port)
- 主从建立连接(从发现新节点,尝试与主建立网络连接)
- 发生ping命令(连接建立成功后,从节点发ping进行首次通信,主要检测套接字能否用,主节点能否接受处理指令)
- 权限校验(可选,如果主有设置密码校验,从要用密码进行验证)
- 同步数据集(建立成功后主将数据集发给从)
- 命令持续复制(主持续把写命令发给从,保持数据一致性)
10.主从数据同步的方式:
- 全量复制:一般用于初次复制场景,Redis 早期支持的复制功能只有全量复制,它会把主节点全部数据一次性发送给从节点,当数据量较大时,会对主从节点和网络造成很大的开销。
- 部分复制:对全量的优化,使用 psync{runId}{offset}命令实现。当从节点(slave)正在复制主节点 (master)时,如果出现网络闪断或者命令丢失等异常情况时,从节点会向 主节点要求补发丢失的命令数据,如果主节点的复制积压缓冲区内存在这部分数据则直接发送给从节点,这样就可以保持主从节点复制的一致性
11.主从复制是异步操作,当执行写操作后立刻返回给客户端而不是等从节点同步完毕,如何确保数据一致性?
- 保障主从节点网络连接良好,处于同一个局域网;
- redis含有命令可以监控主从节点的复制进度,及时发现和处理复制延迟
12.Redis 哨兵实现原理知道吗?(工作流程)
- 定时监控(Sentinel 定期发ping向主从发心跳包)
- 主观下线和客观下线(一个节点没有响应PING,则为主观下线,多个Sentinel认为则为客观下线)
- 领导者 Sentinel 节点选举(通过类似Raft选举算法进行选举,选举后转为新的主结点)
- 故障转移(选举后的进行故障转移)
三个定时任务:主从信息获取、哨兵信息发布、节点心跳检测
Raft选举算法:当主节点失效时,任意 Sentinel 节点可以发起选举,通过向其他节点请求投票。收到请求的节点会根据其日志和任期号判断是否支持候选者,候选者需获得超过半数节点的票数才能成为新的主节点。如果没有节点获得足够票数,选举将失败,所有候选者会重新发起选举,避免无限制的选举失败采用随机超时时间。
13.Redis集群(cluster)是什么?其原理又是什么?
-
Redis 集群是一种分布式架构,通过将数据分片存储在多个节点上,实现高可用性和扩展性。在集群中,每个节点可以存储部分数据,并且支持自动故障转移,确保系统在某个节点失效时仍能正常运行。(有多少分片就将数据进行拆分多少快)
-
Redis 集群通过哈希槽机制(CRC16哈希算法) 将键映射到特定节点,每个节点负责一定范围的哈希槽。客户端每次请求通过哈希算法计算键对应的槽,从而确定请求的目标节点中的数据。
- 同时,集群支持主从复制,主节点处理写请求,从节点提供读取服务,提高了并发处理能力和数据安全性。(共有16384个哈希槽)
- 收缩原理:集群移除某一个节点,其他节点重新计算,将该节点的数据迁移到剩余的节点。这个过程是自动化的,不影响系统的正常运行。
14.Cluster中数据是如何分区?
- 节点取余分区:类似hashmap一样取余操作,将数据项分发到不同的节点上,但扩容得重新分配迁移
- 一致性哈希分区:将哈希值空间组织成一个环,数据项和节点都映射到这个环上。数据项由其哈希值直接映射到环上,然后顺时针分配到遇到的第一个节点,但节点分布不均匀,造成部分缓存结点压力大
- 虚拟槽分区 :每个键通过哈希算法(比如 CRC16)映射到这些槽上,每个集群节点负责管理一定范围内的槽
15.布隆过滤器是什么?
-
布隆过滤器是一个空间效率很高的概率型数据结构,用于快速检测元素是否存在一个集合中,常用于穿透的业务情景。
-
当然该过滤器可能会存在误判的情况,例如哈希碰撞,数组大小,元素个数等情况,且布隆过滤器不支持删除元素,因为多个元素可能映射到同一个位置,如果删除后对其他元素有影响。在布隆过滤器中,一个元素会被映射到多个位置。
-
与哈希表的区别:布隆过滤器适用资源有限,数据量大且容许误判的场景,哈希表的内存开销大,不适合大规模数据存储。
16.Redis里面的预热key是什么?在处理大key的时候又可以干嘛?
- 预热key指的是在同一个时间内有多个请求访问该key;
- 如何找:通过分析使用的频率
- 解决方案:
- 使用集群分布式部署redis;
- 互斥锁,
- 加入桶令牌进行限流;
- 加入消息队列;
- 在已知爆火时间段进行提前预热,加永不过期;
- 使用本地缓存;
- 将热key加载到JVM,后续直接取JVM。
- 大key指的是当前key的value值在内存中占用很大的比例,每次进行调用后会产生客户端的请求延时,IO性能,集群数据倾斜;
- 如何找:通过指令查看大小,通过工具进行分析
- 解决方案:
- 不可删除:压缩拆分key;
- 可删除:定期删除,dxingsc删除大key
17.Redis里面出现了各种缓存问题,如击穿,穿透,雪崩,你能讲一下吗?
- 击穿:
- 高并发环境下,某一个热key失效,大多数请求穿透打到数据库,导致数据库性能下降;
解决:设置热key永不过期,预加热key;加锁排队(在代码中使用synchronized),并且在从数据库中查找到对应的数据重新保存在redis中,每次加锁进来就重新判断获取一下缓存;
- 雪崩:
- 高并发环境下,大部分的key在同一时刻同时失效,大多数不同请求都打到数据库,数据库承受不住宕机,进而导致整个程序崩溃,产生类似雪崩的效应。
解决:加锁排队(在代码中使用synchronized),并且在从数据库中查找到对应的数据重新保存在redis中(当然可以设置一下随机加锁时间),每次加锁进来就重新判断获取一下缓存;redis集群部署,降低容错性; 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 穿透:
- 数据库不存在的数据,缓存中也不存在,这时每次请求都打到数据库,如果请求过多,也会直接导致数据库宕机。
解决:参数校验(可能有人绕过前端故意发请求,对传入的参数进行拦截判断);缓存空对象,在数据库无论有没有查到,都进行缓存;设置布隆过滤器
18.Redis里面缓存的数据是热点数据还是冷数据?还是说都有,那你是怎么区分的?
- 设计时间窗口,评估一定时间内数据被访问的次数,当超过某个阀值时数据就为热点数据
19. 假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?
- 使用keys指令可以扫出指定模式的key列表。
对方接着追问:
- 如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
这个时候你要回答redis关键的一个特性:
redis是单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。
20.怎么设计用户连续登录情况?
- 这里从设计出发,不可能设计一个表,这样得多维护一个表,如果用户数量一多,每天进行一登录就注册入表,等到时数据量大时,要计算就很慢;
- 可以采用Redis中的一个bitmap结构来进行记录(这个结构是二进制,可以用来只有2值的统计,如签到,点赞)
- 但里面设计这个持久化问题,得定期持久化
- 如果用户过多得考虑分片处理或者设置多个bitmap
// 用户登录方法
public void userLogin(String userId) { // 获取当前日期作为位图的索引 long todayIndex = getTodayIndex(); // 使用 Redis Bitmap 设置用户登录状态 redisClient.setBit("user_login_bitmap:" + userId, todayIndex, 1); // 记录最后登录时间(可选)redisClient.set("last_login:" + userId, getCurrentTimestamp());
} // 获取用户连续登录天数
public int getConsecutiveLoginDays(String userId) { long todayIndex = getTodayIndex();int consecutiveDays = 0; // 从今天开始向前检查 for (int i = 0; i < MAX_DAYS; i++) { if (redisClient.getBit("user_login_bitmap:" + userId, todayIndex - i) == 1) { consecutiveDays++; } else {break; // 一旦发现某天未登录,停止检查 } } return consecutiveDays;
}
21.怎么保证缓存和数据库的数据的一致性?
对初始时有一定的容错性:
- 先写MySQL,再删除redis数据缓存
- 第一次,B查询为10,A更为11,第二次就重新查为11,并加入缓存(相反就可能缓存中一直是10)
- 为什么不更新缓存而是删除:删除比更新快(更新得先计算库存再进行更新缓存)
一致性要求高:
- 消息队列保持缓存被删除
- 数据库订阅+消息队列保持缓存被删除
- 延时双删防止脏数据(在第一次删除缓存之后,过一段时间之后,再次删除缓存)
- 设置缓存过期时间兜底(给缓存设置一个合理的过期时间,即使发生了缓存和数据库的数据不一致问题,也不会永远不一致下去,缓存过期后,自然就一致了)
22.怎么保证本地缓存与Redis缓存的一致性?
-
设置本地缓存的过期时间,这是最简单也是最直接的方法,当本地缓存过期时,就从 Redis 缓存中去同步。
-
使用 Redis 的 Pub/Sub 机制,当 Redis 缓存发生变化时,发布一个消息,本地缓存订阅这个消息,然后删除对应的本地缓存。
-
Redis 缓存发生变化时,引入消息队列,比如 RocketMQ、RabbitMQ 去更新本地缓存。
23.Redis内存不足怎么处理?
- 修改配置文件 redis.conf 的 maxmemory 参数,增加 Redis 可用内存
- 也可以通过命令 set maxmemory 动态设置内存上限
- 修改内存淘汰策略,及时释放内存空间
- noeviction:默认策略,不进行任何数据淘汰,直接返回错误信息。
- allkeys-lru:从所有键中,使用 LRU(Least Recently Used) 算法淘汰最近最少使用的键。
- allkeys-lfu:从所有键中,使用 LFU (Least Frequently Used)算法淘汰最少使用的键。
- volatile-lru:从设置了过期时间的键中淘汰最近最少使用的键。
- volatile-ttl:从设置了过期时间的键中淘汰即将过期的键。
- 使用 Redis 集群模式,进行横向扩容
- 过期淘汰策略定期回收key
- 惰性删除
- 定期删除
24.Redis阻塞?怎么查找解决。
- Redis阻塞通常来讲指的是客户端在等待某个指令执行进入了等待状态,可以通过showlog查看慢查询,找出执行时间长的命令;或者通过client list查看活跃连接数,看看是否有大量的客户端请求;或可以调整大key,因为大key在传输过程中,也会造成传输慢,延迟高的情况。
- 如果开启了持久化的Redis节点等待,需要进行排查,因为AOF进行刷盘,频繁的进行IO操作,会对后台程序的运行进行阻塞;而且RDB执行save命令时也会阻塞客户端的请求。
25.Redis看门狗机制是什么?
Redisson的看门狗机制,该机制用于自动续期分布式锁,防止锁因超时释放导致的问题。
线程去获取锁,获取成功则执行lua脚本,保存数据到redis数据库。如果获取失败: 一直通过while循环尝试获取锁(可自定义等待时间,超时后返回失败),获取成功后,执行lua脚本,保存数据到redis数据库。Redisson提供的分布式锁是支持锁自动续期的,也就是说,如果线程仍旧没有执行完,那么redisson会自动给redis中的目标key延长超时时间,这在Redisson中称之为 Watch Dog 机制
26.Redisson是什么?
在Redis的基础上进行封装,提供多种方法调用redis方法。
27.Lua脚本是什么?用来干嘛的?
Lua脚本为Redis中使用的脚本语言,主要是可以将多个命令合并为一个脚本,避免多次往返请求,实现了原子操作
28.Redis支持事务吗?
- 是的,Redis 支持事务。通过
MULTI
、EXEC
、DISCARD
和WATCH
等命令,可以实现事务操作。事务中的命令会被排队,直到执行EXEC
时一起执行,从而保证原子性。
29.Redis如何做异步队列,延时队列。
-
异步队列: 可以使用列表(如
LPUSH
和BRPOP
)实现,生产者将任务放入列表,消费者从列表中取出任务。 -
延时队列: 可以使用 Sorted Set,将任务的执行时间作为分数。通过定期扫描(如使用定时任务)或使用
ZRANGEBYSCORE
获取到期任务并处理。
30.Redis如何做分布式锁。
Redis 分布式锁的实现通常使用 SETNX
命令。具体步骤:
- 使用
SETNX
创建锁,设置一个过期时间。 - 如果成功获取锁,执行任务。
- 任务完成后,释放锁(使用
DEL
)。 - 如果超时未完成,可以使用
GET
检查锁。
31.Redis的管道Pipeline是什么?
Redis 的管道(Pipeline)是一种批量处理命令的方式,允许客户端在不等待响应的情况下发送多个命令。通过 Pipeline,可以减少网络延迟,提高性能。客户端一次性发送多个请求,服务器在处理完后一次性返回所有结果。
32.Redis常见的性能优化方案有什么?
- 使用管道:批量发送命令,减少网络往返时间;
- 为了主从复制的速度和连接的稳定性,Slave 和 Master 最好在同一个局域网内。
- 尽量避免在压力较大的主库上增加从库;
- 在集群过程中进行数据分片,负载均衡;
- 避免大数据,最好将器拆分为多个小键;
- 对不需要的数据进行设置过期时间,减少内存占用;