33redis场景题
redis应用场景是什么?
Redis 是一种基于内存的数据库,对数据的读写操作都是在内存中完成,因此读写速度非常快,常用于缓存,消息队列、分布式锁等场景。
- 缓存: Redis最常见的用途就是作为缓存系统。通过将热门数据存储在内存中,可以极大地提高访问速度,减轻数据库负载,这对于需要快速响应时间的应用程序非常重要。
- 排行榜: Redis的有序集合结构非常适合用于实现排行榜和排名系统,可以方便地进行数据排序和排名。
- 分布式锁: Redis的特性可以用来实现分布式锁,确保多个进程或服务之间的数据操作的原子性和一致性。
- 计数器 由于Redis的原子操作和高性能,它非常适合用于实现计数器和统计数据的存储,如网站访问量统计、点赞数统计等。
- 消息队列: Redis的发布订阅功能使其成为一个轻量级的消息队列,它可以用来实现发布和订阅模式,以便实时处理消息。
| 误区 | 正确理解 | 为什么 |
|---|---|---|
| “Redis 是多线程” | ❌ 错误! 命令执行是单线程 | Redis 官方文档明确: ‘Redis 是单线程的’ |
| “多线程 I/O 会乱序” | ❌ 错误! 顺序执行 | I/O 线程只处理 I/O,命令执行仍在主线程 |
| “单线程无法高并发” | ❌ 错误! 实测 10 万 QPS | 事件循环 + 非阻塞 I/O 使单线程高效 |
| “Redis 6.0 用了多线程命令” | ❌ 错误! 仅 I/O 多线程 | 命令执行仍是单线程(redis-cli 测试可验证) |
Redis的大Key问题是什么?
Redis大key问题指的是某个key对应的value值所占的内存空间比较大(数据量过多),导致Redis的性能下降、内存不足、数据不均衡以及主从同步延迟等问题。
到底多大的数据量才算是大key?
没有固定的判别标准,通常认为字符串类型的key对应的value值占用空间大于1M,或者集合类型的k元素数量超过1万个,就算是大key。
Redis大key问题的定义及评判准则并非一成不变,而应根据Redis的实际运用以及业务需求来综合评估。
例如,在高并发且低延迟的场景中,仅10kb可能就已构成大key;然而在低并发、高容量的环境下,大key的界限可能在100kb。因此,在设计与运用Redis时,要依据业务需求与性能指标来确立合理的大key阈值。
大Key问题的缺点?
- 内存占用过高。大Key占用过多的内存空间,可能导致可用内存不足,从而触发内存淘汰策略。在极端情况下,可能导致内存耗尽,Redis实例崩溃,影响系统的稳定性。
- 性能下降。大Key会占用大量内存空间,导致内存碎片增加,进而影响Redis的性能。对于大Key的操作,如读取、写入、删除等,都会消耗更多的CPU时间和内存资源,进一步降低系统性能。
- 阻塞其他操作。某些对大Key的操作可能会导致Redis实例阻塞。例如,使用DEL命令删除一个大Key时,可能会导致Redis实例在一段时间内无法响应其他客户端请求,从而影响系统的响应时间和吞吐量。
- 网络拥塞。每次获取大key产生的网络流量较大,可能造成机器或局域网的带宽被打满,同时波及其他服务。例如:一个大key占用空间是1MB,每秒访问1000次,就有1000MB的流量。
- 主从同步延迟。当Redis实例配置了主从同步时,大Key可能导致主从同步延迟。由于大Key占用较多内存,同步过程中需要传输大量数据,这会导致主从之间的网络传输延迟增加,进而影响数据一致性。
- 数据倾斜。在Redis集群模式中,某个数据分片的内存使用率远超其他数据分片,无法使数据分片的内存资源达到均衡。
[如何发现 bigkey?]
1、使用 Redis 自带的 --bigkeys 参数来查找。
2、使用 Redis 自带的 SCAN 命令
3、借助开源工具分析 RDB 文件。
Redis大key如何解决?
- 对大Key进行拆分。例如将含有数万成员的一个HASH Key拆分为多个HASH Key,并确保每个Key的成员数量在合理范围。
- 对大Key进行清理。将不适用Redis能力的数据存至其它存储,并在Redis中删除此类数据。注意,要使用异步删除。
- 监控Redis的内存水位。可以通过监控系统设置合理的Redis内存报警阈值进行提醒,
- 对过期数据进行定期清。堆积大量过期数据会造成大Key的产生,例如在HASH数据类型中以增量的形式不断写入大量数据而忽略了数据的时效性。可以通过定时任务的方式对失效数据进行清理。
什么是热key?
通常以其接收到的Key被请求频率来判定,例如:
- QPS集中在特定的Key:Redis实例的总QPS(每秒查询率)为10,000,而其中一个Key的每秒访问量达到了7,000。
- 带宽使用率集中在特定的Key:对一个拥有上千个成员且总大小为1 MB的HASH Key每秒发送大量的HGETALL操作请求。
- CPU使用时间占比集中在特定的Key:对一个拥有数万个成员的Key(ZSET类型)每秒发送大量的ZRANGE操作请求。
如何解决热key问题?
- 在Redis集群架构中对热Key进行复制。在Redis集群架构中,由于热Key的迁移粒度问题,无法将请求分散至其他数据分片,导致单个数据分片的压力无法下降。此时,可以将对应热Key进行复制并迁移至其他数据分片,例如将热Key foo复制出3个内容完全一样的Key并名为foo2、foo3、foo4,将这三个Key迁移到其他数据分片来解决单个数据分片的热Key压力。
- 使用读写分离架构。如果热Key的产生来自于读请求,您可以将实例改造成读写分离架构来降低每个数据分片的读请求压力,甚至可以不断地增加从节点。但是读写分离架构在增加业务代码复杂度的同时,也会增加Redis集群架构复杂度。不仅要为多个从节点提供转发层(如Proxy,LVS等)来实现负载均衡,还要考虑从节点数量显著增加后带来故障率增加的问题。Redis集群架构变更会为监控、运维、故障处理带来了更大的挑战。
如何保证 redis 和 mysql 数据缓存一致性问题?
对于读数据,我会选择旁路缓存策略,如果 cache 不命中,会从 db 加载数据到 cache。对于写数据,我会选择更新 db 后,再删除缓存。
布隆过滤器原理介绍一下
布隆过滤器由「初始值都为 0 的位图数组」和「 N 个哈希函数」两部分组成。当我们在写入数据库数据时,在布隆过滤器里做个标记,这样下次查询数据是否在数据库时,只需要查询布隆过滤器,如果查询到数据没有被标记,说明不在数据库中。
布隆过滤器会通过 3 个操作完成标记:
- 第一步,使用 N 个哈希函数分别对数据做哈希计算,得到 N 个哈希值;
- 第二步,将第一步得到的 N 个哈希值对位图数组的长度取模,得到每个哈希值在位图数组的对应位置。
- 第三步,将每个哈希值在位图数组的对应位置的值设置为 1;
举个例子,假设有一个位图数组长度为 8,哈希函数 3 个的布隆过滤器。

当应用要查询数据 x 是否数据库时,通过布隆过滤器只要查到位图数组的第 1、4、6 位置的值是否全为 1,只要有一个为 0,就认为数据 x 不在数据库中。
布隆过滤器由于是基于哈希函数实现查找的,高效查找的同时存在哈希冲突的可能性,比如数据 x 和数据 y 可能都落在第 1、4、6 位置,而事实上,可能数据库中并不存在数据 y,存在误判的情况。
所以,查询布隆过滤器说数据存在,并不一定证明数据库中存在这个数据,但是查询到数据不存在,数据库中一定就不存在这个数据。
(2)优缺点空间小且查询高效:相比于传统的数据结构,布隆过滤器在存储大量元素时,占用的空间小得多,且查询极快(O(k))
k为哈希函数的个数存在误判:底层是哈希函数,由于不同元素也有一定概率哈希映射到相同位置,因此会存在误判
无法删除元素:因为布隆过滤器的位数组中某个位置被置为1,很难判断这个1是由哪个或哪些元素的哈希映射导致的,所以在经典的布隆过滤器中,是不支持删除元素操作的.
(3)扩容与误判率降低布隆过滤器误判率,需要哈希函数个数与位数组长度协同调整
单增哈希函数,空间剩余值超过阈值误判率会反弹.
单扩容数组,虽能降误判,但空间利用率大幅下降,内存浪费严重
唯有k≈ln2*(m/n)才能在控制误判和维持空间效率实现平衡
慢查询日志
在 redis.conf 文件中,我们可以使用 slowlog-log-slower-than 参数设置耗时命令的阈值,并使用 slowlog-max-len 参数设置耗时命令的最大记录条数。
获取慢查询日志的内容很简单,直接使用 SLOWLOG GET 命令即可。
慢查询日志中的每个条目都由以下六个值组成:
- 唯一 ID: 日志条目的唯一标识符。
- 时间戳 (Timestamp): 命令执行完成时的 Unix 时间戳。
- 耗时 (Duration): 命令执行所花费的时间,单位是微秒。
- 命令及参数 (Command): 执行的具体命令及其参数数组。
- 客户端信息 (Client IP:Port): 执行命令的客户端地址和端口。
- 客户端名称 (Client Name): 如果客户端设置了名称 (CLIENT SETNAME)。
对Redis中的缓存雪崩,缓存击穿,缓存穿透有了解吗,怎么解决

[缓存穿透和缓存击穿有什么区别?]
缓存穿透中,请求的 key 既不存在于缓存中,也不存在于数据库中。
缓存击穿中,请求的 key 对应的是 热点数据 ,该数据 存在于数据库中,但不存在于缓存中(通常是因为缓存中的那份数据已经过期) 。
redis的lua脚本
Redis对Lua脚本的处理由命令执行器与Lua解释器协同完成
1)Lua解释器:Redis内置,由Lua解释器进行语法校验,通过调用redis.call()接口将Lua函数调用映射为Redis内部命令
2)命令执行器:处理原生Redis命令
3)一个Lua脚本可视为命令执行器的原子执行单元
(1)脚本加载与语法解析Redis将Lua脚本交给Lua解释器Lua解释器先做适语法校验,确保脚本无语法错误
(2)命令映射与执行(协同)Lua解释器逐行执行脚本,当执行到redis.call()时Lua解释器暂停脚本执行,将Lua函数映射为Redis命令转发给Redis命令执行器,并将结果返回给Lua解释器Lua解释器恢复执行,用返回值进行==判断(
3)原子性保障Redis执行Lua脚本时,会进入单线程原子模式无论脚本包含多少redis.call调用多少次命令执行器,Redis会阻塞所有其它客户端命令, 直到脚本完全执行完毕,保证了原子性
缺陷:
- 如果 Lua 脚本运行时出错并中途结束,之后的操作不会进行,但是之前已经发生的写操作不会撤销,所以即使使用了 Lua 脚本,也不能实现类似数据库回滚的原子性。
- Redis Cluster 下 Lua 脚本的原子操作也无法保证了,原因同样是无法保证所有的 key 都在同一个 hash slot(哈希槽) 上。
redis阻塞
1.默认情况下,Redis 默认配置会使用 bgsave 命令。如果手动使用 save 命令生成 RDB 快照文件的话,就会阻塞主线程。
ZRANGE/ZREVRANGE:返回指定 Sorted Set 中指定排名范围内的所有元素。时间复杂度为 O(log(n)+m),n 为所有元素的数量, m 为返回的元素数量,当 m 和 n 相当大时,O(n) 的时间复杂度更小。ZREMRANGEBYRANK/ZREMRANGEBYSCORE:移除 Sorted Set 中指定排名范围/指定 score 范围内的所有元素。时间复杂度为 O(log(n)+m),n 为所有元素的数量, m 被删除元素的数量,当 m 和 n 相当大时,O(n) 的时间复杂度更小。
3.aof为什么是在执行完命令之后记录日志呢?
- 避免额外的检查开销,AOF 记录日志不会对命令进行语法检查;
- 在命令执行完之后再记录,不会阻塞当前的命令执行\
当后台线程( aof_fsync 线程)调用 fsync 函数同步 AOF 文件时,需要等待,直到写入完成。当磁盘压力太大的时候,会导致 fsync 操作发生阻塞,主线程调用 write 函数时也会被阻塞。fsync 完成后,主线程执行 write 才能成功返回。
4.阻塞就是出现在
当子线程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新的 AOF 文件保存的数据库状态与现有的数据库状态一致。
的过程中,将缓冲区中新数据写到新文件的过程中会产生阻塞。
5.删除操作的本质是要释放键值对占用的内存空间。
释放内存只是第一步,为了更加高效地管理内存空间,在应用程序释放内存时,操作系统需要把释放掉的内存块插入一个空闲内存块的链表,以便后续进行管理和再分配。这个过程本身需要一定时间,而且会阻塞当前释放内存的应用程序。
所以,如果一下子释放了大量内存,空闲内存块链表操作时间就会增加,相应地就会造成 Redis 主线程的阻塞,如果主线程发生了阻塞,其他所有请求可能都会超时,超时越来越多,会造成 Redis 连接耗尽,产生各种异常。
删除大 key 时建议采用分批次删除和异步删除的方式进行
6.Redis 集群可以进行节点的动态扩容缩容,这一过程目前还处于半自动状态,需要人工介入。
在扩缩容的时候,需要进行数据迁移。而 Redis 为了保证迁移的一致性,迁移所有操作都是同步操作。
执行迁移时,两端的 Redis 均会进入时长不等的阻塞状态,对于小 Key,该时间可以忽略不计,但如果一旦 Key 的内存使用过大,严重的时候会触发集群内的故障转移,造成不必要的切换。
7.清空数据库和上面 bigkey 删除也是同样道理,flushdb、flushall 也涉及到删除和释放所有的键值对,也是 Redis 的阻塞点
8.连接拒绝、网络延迟,网卡软中断等网络问题也可能会导致 Redis 阻塞。
9。CPU竞争等硬件问题
ZSCAN
Cursor分页 vs ZRANGE:5大核心优势
| 维度 | ZRANGE(深分页) | ZSCAN(Cursor) | 为什么Cursor更优 |
|---|---|---|---|
| 1. 性能 | offset=100万时,需扫描100万行 | 始终O(1)时间 | Cursor不依赖offset,每次只返回固定数量数据 |
| 2. 内存占用 | 一次性返回所有数据,内存占用高 | 增量返回,内存占用低 | 适合大数据量(如1000万+记录) |
| 3. 数据一致性 | 无法处理遍历过程中的数据变化 | 正确处理变化(新增/删除) | 遍历过程中,新增数据不会被遗漏 |
| 4. 适用场景 | 仅适合小offset分页 | 完美适合深分页 | Cursor是深分页的’救命稻草’ |
| 5. 代码复杂度 | 简单:ZRANGE key offset size |
稍复杂:需维护游标 | 优化后,代码更简洁 |
Q:为什么ZSCAN比ZRANGE更适合深分页?
A:
“ZSCAN通过增量迭代方式遍历数据,不依赖offset,因此查询时间与offset无关。
而ZRANGE使用LIMIT offset, size,当offset很大时,MySQL会扫描offset行,导致性能随offset指数级下降。
—— ZSCAN是深分页的’救命稻草’,ZRANGE是’定时炸弹’!” 💣
Q:ZSCAN在遍历过程中,如果数据变化会怎样?
A:
“ZSCAN能正确处理数据变化:
- 新增数据:如果新增数据的score在当前游标之后,会被包含在后续遍历中
- 删除数据:如果删除的数据在已遍历范围内,会被跳过
- 数据变化:ZSCAN不会重复返回或遗漏数据
—— 这是ZSCAN的核心优势,也是它适合高并发场景的原因!” ✅
脑裂
脑裂问题的原因与影响
脑裂通常由网络分区、哨兵误判或主从切换异常引起。其影响包括:
- 数据不一致:不同客户端连接到不同主节点,写入的数据可能冲突。
- 数据丢失:旧主节点恢复后,其数据可能被覆盖。
- 请求异常:部分客户端连接旧主节点,导致查询失败。
解决脑裂的配置与优化
Sentinel 模式下的优化
- 配置 quorum 和 down-after-milliseconds:
sentinel monitor mymaster 192.168.1.100 6379 2
sentinel down-after-milliseconds mymaster 5000
- 设置 min-replicas-to-write 和 min-replicas-max-lag:
min-replicas-to-write 2
min-replicas-max-lag 10
- 启用 protected-mode:
protected-mode yes
解决方案
redis的配置文件中,存在两个参数
1 | |
第一个参数表示连接到master的最少slave数量
第二个参数表示slave连接到master的最大延迟时间
按照上面的配置,要求至少3个slave节点,且数据复制和同步的延迟不能超过10秒,否则的话master就会拒绝写请求,配置了这两个参数之后,如果发生集群脑裂,原先的master节点接收到客户端的写入请求会拒绝,就可以减少数据同步之后的数据丢失。
注意:较新版本的redis.conf文件中的参数变成了
1 | |
redis中的异步复制情况下的数据丢失问题也能使用这两个参数
end