Redis面试题(一)

发布于 2025-09-25 14:49:46 浏览 85 次

1.Redis 主从复制的实现原理是什么?

Redis 主从复制通过以下步骤实现:
从库通过 SLAVEOF 命令指定主库地址,建立连接。
从库发送同步请求,主库执行 BGSAVE 生成 RDB 文件,同时记录此期间的写命令到缓冲区。
主库发送 RDB 文件给从库,从库加载 RDB 初始化数据。
主库发送缓冲区中的写命令,从库执行以保持数据一致。
后续主库的写操作会实时同步到从库(命令传播),通过心跳机制维护连接。
主从复制支持一主多从,从库可再作为其他从库的主库,形成级联结构。

2.Redis 集群的实现原理是什么?

Redis 集群(Redis Cluster)采用分片机制,将数据分布在多个节点,原理如下:
数据分片:通过哈希槽(共 16384 个)分配数据,键通过 CRC16(key) % 16384 计算所属槽位,每个节点负责部分槽位。
节点通信:节点间通过 Gossip 协议交换信息,维护集群状态(节点健康、槽位分配)。
高可用:每个主节点可配置从节点,主节点故障时,从节点通过选举晋升为主节点。
客户端路由:客户端连接任意节点,若键不在当前节点负责的槽位,节点返回重定向信息,指引客户端连接正确节点。

3.Redis 通常应用于哪些场景?

缓存:热点数据缓存(如商品信息、用户会话),减轻数据库压力。
会话存储:分布式系统中存储用户会话(如登录状态),实现跨服务共享。
计数器:利用 INCR 实现高并发计数器(如点赞数、访问量)。
分布式锁:通过 SET NX 实现分布式环境下的资源互斥访问。
消息队列:基于 List 的 LPUSH/RPOP 或 Pub/Sub 实现简单消息队列。
排行榜:使用 Sorted Set 的 ZADD/ZRANK 实现实时排行榜。
限流:结合过期时间和计数器实现接口限流(如令牌桶算法)。

4.Redis 为什么这么快?

内存存储:数据存于内存,避免磁盘 I/O 延迟,读写速度极快。
单线程模型:避免多线程上下文切换和锁竞争开销,简化设计。
高效数据结构:针对不同场景优化数据结构(如跳表、压缩列表),操作复杂度低。
I/O 多路复用:使用 epoll/kqueue 等机制,单线程处理多个 I/O 事件,提高并发能力。
精简代码:核心逻辑简洁,避免冗余操作,执行效率高。

5. 为什么 Redis 设计为单线程?6.0 版本为何引入多线程?

单线程设计原因:
避免多线程锁竞争和上下文切换开销,简化实现。
Redis 性能瓶颈在内存而非 CPU,单线程足够处理高并发(每秒可达 10 万 + 操作)。
内存操作本身是单线程安全的,无需复杂同步机制。
6.0 引入多线程:仅用于处理网络 I/O 操作(如接收请求、发送响应),核心数据操作仍为单线程。目的是解决网络 I/O 瓶颈(尤其是大键读写时的网络传输耗时),进一步提升吞吐量,同时保持单线程的数据安全性。

6.Redis 中常见的数据类型有哪些?

String(字符串):最基本类型,可存储文本、数字,支持增删改查、自增自减。
Hash(哈希):键值对集合,适合存储对象(如用户信息),支持单个字段操作。
List(列表):有序字符串集合,支持两端插入 / 删除,可作队列或栈。
Set(集合):无序且唯一的字符串集合,支持交集、并集、差集等操作。
Sorted Set(有序集合):类似 Set,但每个元素关联分数,按分数排序,适合排行榜。
其他类型:Bitmap(位图)、HyperLogLog(基数统计)、Geospatial(地理位置)等。

7.Redis 中跳表的实现原理是什么?

跳表是 Redis 中 Sorted Set 的底层数据结构之一,原理:
多层链表结构:底层是有序链表,上层是稀疏索引层,每层节点指向下方和同层后续节点。
随机层数:新节点插入时,随机生成层数(1-32),高层节点数量少,低层节点数量多。
查找过程:从最高层开始,沿索引快速跳过无关节点,逐层向下,最终在底层找到目标。
优势:插入、删除、查找复杂度均为 O (logN),且实现简单,比平衡树更适合 Redis 的内存场景。

8.Redis 的 hash 是什么?

Redis 的 Hash 是一种键值对集合,类似字典,适合存储对象(如用户信息、商品属性)。
结构:每个 Hash 包含多个字段(field)和对应的值(value),字段和值均为字符串类型。
操作:支持添加(HSET)、获取(HGET)、删除(HDEL)单个字段,以及获取所有字段(HGETALL)等操作。
底层实现:数据量小时用压缩列表(ziplist),节省空间;数据量大时转为哈希表(hashtable),提高操作效率。

9.Redis 和 Memcached 有哪些区别?

21f74f7942611112ed9bc786e964c71c.png

10.Redis 支持事务吗?如何实现?

Redis 支持简单事务,通过以下命令实现:
MULTI:开启事务,后续命令进入队列,不立即执行。
输入多个命令(如 SET、INCR),命令被缓存。
EXEC:执行队列中的所有命令,原子性执行(要么全执行,要么全不执行)。
DISCARD:取消事务,清空命令队列。
WATCH:监控键,若事务执行前键被修改,事务被打断(类似乐观锁)。
注意:Redis 事务不支持回滚,执行中若命令错误,已执行命令不会撤销。

11.Redis 数据过期后的删除策略是什么?

Redis 采用三种策略结合处理过期数据:
惰性删除:访问键时才检查是否过期,过期则删除,节省 CPU 但可能浪费内存。
定期删除:每隔一段时间(默认 100ms)随机检查部分过期键并删除,平衡 CPU 和内存。
内存淘汰:当内存达到 maxmemory 阈值,触发内存淘汰策略(如 LRU),删除部分键(包括未过期键)。
三种策略配合使用,既避免过期数据长期占用内存,又不过度消耗 CPU 资源。

12.Redis 中有哪些内存淘汰策略?

Redis 内存淘汰策略(maxmemory-policy)包括:
volatile-lru:从设置过期时间的键中,删除最近最少使用的键。
allkeys-lru:从所有键中,删除最近最少使用的键。
volatile-lfu:从设置过期时间的键中,删除最不经常使用的键。
allkeys-lfu:从所有键中,删除最不经常使用的键。
volatile-random:从设置过期时间的键中,随机删除。
allkeys-random:从所有键中,随机删除。
volatile-ttl:从设置过期时间的键中,删除剩余 TTL 最短的键。
noeviction:默认策略,不删除键,拒绝新写入请求并返回错误。

13.Redis 的 Lua 脚本功能是什么?如何使用?

Lua 脚本功能允许在 Redis 服务器端执行 Lua 脚本,实现复杂原子操作。
优势:脚本内的多个命令原子执行,减少网络往返,保证数据一致性。
使用方式:
通过 EVAL "脚本内容" 键数量 键名1 键名2 ... 参数1 参数2 ... 执行脚本。
示例:EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 name redis(设置键 name 的值为 redis)。
可通过 SCRIPT LOAD 预加载脚本,返回 SHA1 哈希,后续用 EVALSHA 执行,节省带宽。

14.Redis 的 Pipeline 功能是什么?

Pipeline 允许客户端一次性发送多个命令,服务器批量执行并返回结果,减少网络往返次数。
作用:解决单次命令往返的网络延迟问题,提升批量操作吞吐量(尤其高延迟网络环境)。
使用方式:客户端先将多个命令加入 Pipeline 队列,再一次性发送,服务器按顺序执行并返回所有结果。
注意:Pipeline 中的命令非原子执行,若中间命令失败,后续命令仍会执行;不适合依赖前序命令结果的场景。

15.Redis 中的 Big Key 问题是什么?如何解决?

Big Key 指占用内存大的键(如包含百万级元素的 List 或 Hash),问题:
占用大量内存,导致内存分布不均。
操作耗时久,阻塞单线程,影响其他请求。
序列化 / 反序列化慢,持久化和主从同步效率低。
解决方法:
拆分键:将 Big Key 拆分为多个小键(如将大 Hash 按字段范围拆分)。
避免批量操作:用 HSCAN 替代 HGETALL,分批获取数据。
监控预警:通过 MEMORY USAGE 命令检测 Big Key,及时处理。
选择合适数据结构:避免用 String 存储大对象,改用 Hash 分片存储。

16. 如何解决 Redis 中的热点 key 问题?

热点 key 指访问频率极高的键,可能导致单个 Redis 节点负载过高,解决方法:
数据分片:将热点 key 分散到多个节点(如在 key 后加随机后缀),均衡负载。
本地缓存:在应用层增加本地缓存(如 Caffeine),减少 Redis 访问次数。
读写分离:热点 key 读请求路由到从节点,主节点仅处理写请求。
限流削峰:对热点 key 访问设置限流(如每秒最大请求数),避免节点被打垮。
预计算:提前生成热点数据,避免实时计算导致的高频访问。

17.Redis 的持久化机制有哪些?

Redis 有两种持久化机制:
RDB(Redis Database):
原理:在指定时间间隔内,将内存中的数据快照写入磁盘(.rdb 文件)。
触发方式:手动执行 SAVE(阻塞)或 BGSAVE(后台异步),或通过配置自动触发(如 save 60 1000 表示 60 秒内 1000 次修改)。
优点:文件小,恢复速度快;缺点:可能丢失最近的数据(两次快照间的修改)。
AOF(Append Only File):
原理:记录所有写命令到日志文件(.aof 文件),恢复时重新执行命令。
配置:appendonly yes 开启,支持三种同步策略(appendfsync:always/everysec/no)。
优点:数据安全性高(最多丢失 1 秒数据);缺点:文件大,恢复速度慢。
可同时开启两种机制,恢复时 AOF 优先级高于 RDB。

18.Redis 在生成 RDB 文件时如何处理请求?

Redis 生成 RDB 文件时,通过 BGSAVE 命令触发:
主进程 fork 一个子进程,子进程拥有主进程数据的副本(写时复制,COW)。
子进程负责将数据写入 RDB 文件,主进程继续处理客户端请求。
若主进程修改数据,会复制被修改的内存页,不影响子进程的快照数据。
子进程完成 RDB 写入后,通知主进程,主进程替换旧 RDB 文件。
此过程中,主进程仅在 fork 阶段短暂阻塞,不影响后续请求处理,保证高可用性。

19.Redis 的哨兵机制是什么?

哨兵(Sentinel)是 Redis 主从架构的高可用解决方案,作用:
监控:持续检查主从节点是否正常运行。
自动故障转移:主节点故障时,选举一个从节点晋升为主节点,其他从节点切换到新主节点。
通知:通过 API 向客户端或管理员发送节点故障信息。
配置管理:客户端通过哨兵获取当前主节点地址,自动连接新主节点。
哨兵通常部署 3 个或以上节点(奇数),避免单点故障,通过投票机制决定故障转移。

20.Redis 集群会出现脑裂问题吗?

。Redis 集群脑裂指主节点与集群网络断开,但仍在处理写请求,而集群误认为主节点故障,选举新主节点,导致两个主节点同时存在,数据不一致。
解决措施:
配置 min-replicas-to-write N:主节点至少有 N 个从节点连接时才接受写请求。
配置 min-replicas-max-lag M:从节点与主节点的最大延迟不超过 M 秒。
当主节点无法满足上述条件时,拒绝写请求,避免脑裂时旧主节点写入新数据,保证数据一致性。

21.Redis 的订阅发布功能是什么?你了解吗?

Redis 的订阅发布(Pub/Sub)是一种消息通信模式,允许发送者(Publisher)向频道(channel)发送消息,多个接收者(subscriber)订阅频道接收消息。
核心命令:PUBLISH channel message(发送消息)、SUBSCRIBE channel(订阅频道)、UNSUBSCRIBE channel(取消订阅)、PSUBSCRIBE pattern(订阅匹配模式的频道)。
特点:消息实时传递,无持久化(消费者离线则消息丢失),适合实时通知场景(如聊天、系统告警),不适合需可靠投递的业务。

22.Redis 中如何实现分布式锁?

基于 Redis 实现分布式锁的核心思路:
加锁:使用 SET key value NX PX timeout 命令,NX 确保仅当键不存在时设置(互斥),PX 指定过期时间(避免死锁),value 用唯一标识(如 UUID)。
解锁:通过 Lua 脚本判断 value 匹配后删除键(if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end),保证原子性。
续期:若业务未完成,通过定时任务延长锁过期时间(如 Redisson 的 watchdog 机制)。
此方案确保锁的互斥性、安全性和防死锁。

23. 分布式锁在未完成逻辑前过期怎么办?

解决方法:
锁续期:业务执行中,启动定时任务(如每隔过期时间的 1/3 执行),检查锁是否仍持有,若持有则延长过期时间(需原子操作)。
合理设置过期时间:根据业务最大执行时间预估,预留缓冲时间(如实际需 5 秒,设 10 秒)。
优雅退出:检测到锁过期时,停止业务逻辑,记录日志,避免脏数据。
使用带续期的客户端:如 Redisson 内置自动续期机制,简化实现。

24.Redis 的 Red Lock 是什么?你了解吗?

Red Lock(红锁)是 Redis 官方提出的分布式锁实现方案,用于解决单节点 Redis 锁的可靠性问题(如主节点宕机)。
原理:在多个独立的 Redis 节点(通常 5 个)上获取锁,仅当在超过半数(≥3)节点上成功获取锁,且总耗时不超过锁过期时间的 1/3 时,认为锁获取成功。
优势:避免单节点故障导致的锁失效,适合对一致性要求极高的场景(如金融交易)。
缺点:实现复杂,性能开销大,需多个独立节点,实际中更多使用主从 + 哨兵的简化方案。

25.Redis 实现分布式锁时可能遇到的问题有哪些?

锁过期释放:业务未完成锁已过期,导致并发问题(需续期机制)。
主从同步延迟:主节点锁未同步到从节点时主节点宕机,从节点晋升后锁丢失。
锁误删:解锁时未验证 value,误删其他线程的锁(需 Lua 脚本原子验证)。
单节点故障:单节点 Redis 宕机导致锁服务不可用(需集群或 Red Lock)。
超时问题:获取锁时网络延迟,实际锁已过期但客户端认为成功。

26.Redis 中的缓存击穿、缓存穿透和缓存雪崩是什么?

缓存击穿:热点 key 过期瞬间,大量请求直达数据库,导致数据库压力骤增。解决:热点 key 永不过期,或加互斥锁限制并发。
缓存穿透:查询不存在的数据(如 ID=-1),缓存无法命中,请求全部打向数据库。解决:缓存空值,或用布隆过滤器提前拦截不存在的 key。
缓存雪崩:大量 key 同时过期,或 Redis 集群宕机,导致请求全部涌向数据库。解决:key 过期时间加随机值避免同时过期,Redis 集群高可用,数据库限流降级。

27.Redis 中如何保证缓存与数据库的数据一致性?

常用方案:
先更新数据库,再删除缓存(Cache Aside Pattern):更新 DB 后删除缓存,下次查询时从 DB 加载并更新缓存,避免脏读。
延迟双删:更新 DB → 删除缓存 → 延迟(如 500ms)→ 再次删除缓存,解决更新期间的并发问题。
读写锁:写操作加锁,确保更新 DB 和缓存的原子性;读操作无锁,适合读多写少场景。
最终一致性:通过消息队列异步更新缓存,允许短暂不一致,适合高并发场景。
需根据业务对一致性的要求选择方案,优先保证数据准确性,再权衡性能。

28.Redis String 类型的底层实现是什么?(SDS)

Redis String 类型底层基于简单动态字符串(Simple Dynamic String,SDS)实现,而非 C 语言原生字符串。
SDS 结构:包含 len(字符串长度)、alloc(已分配空间)、flags(类型标记)、buf(字节数组)。
优势:1. 获取长度 O (1)(无需遍历);2. 杜绝缓冲区溢出(修改时自动扩容);3. 减少内存重分配(预分配空间);4. 二进制安全(可存储图片等二进制数据);5. 兼容 C 字符串函数。
SDS 针对 Redis 场景优化,平衡了性能和安全性。

29. 如何使用 Redis 快速实现排行榜?

利用 Redis 的 Sorted Set(有序集合)实现,核心步骤:
添加数据:用 ZADD rankboard score member 插入用户(member)及分数(score),如 ZADD game_rank 1000 user1。
查询排名:ZRANK rankboard member(升序排名)或 ZREVRANK rankboard member(降序排名,从 0 开始)。
获取 Top N:ZREVRANGE rankboard 0 9(取分数最高的前 10 名),加 WITHSCORES 可返回分数。
分数更新:ZINCRBY rankboard increment member 增减分数,如 ZINCRBY game_rank 10 user1。
Sorted Set 内部用跳表实现,支持高效的插入、排序和范围查询,适合实时排行榜。

30. 如何使用 Redis 快速实现布隆过滤器?

Redis 可通过 Bitmap 或插件(如 RedisBloom)实现布隆过滤器,原理:
初始化:创建一个大的 Bitmap(如 2^32 位),并定义多个哈希函数。
添加元素:将元素通过多个哈希函数映射到 Bitmap 的多个位,设为 1。
检查元素:元素通过哈希函数映射后,若所有位均为 1,则可能存在;若有位为 0,则一定不存在。
示例(用 Bitmap):SETBIT bloom 123 1(设置位)、GETBIT bloom 123(检查位)。
用途:拦截不存在的 key(如缓存穿透),特点是空间效率高、查询快,但有一定误判率(可通过调整位数和哈希函数优化)。

0 条评论

发布
问题