Redis常见阻塞原因总结

O(n) 命令

Redis 中的大部分命令都是 O(1)时间复杂度,但也有少部分 O(n) 时间复杂度的命令,例如:

  • KEYS *:会返回所有符合规则的 key。
  • HGETALL:会返回一个 Hash 中所有的键值对。
  • LRANGE:会返回 List 中指定范围内的元素。
  • SMEMBERS:返回 Set 中的所有元素。
  • SINTER/SUNION/SDIFF:计算多个 Set 的交集/并集/差集。

由于这些命令时间复杂度是 O(n),有时候也会全表扫描,随着 n 的增大,执行耗时也会越长,从而导致客户端阻塞。不过, 这些命令并不是一定不能使用,但是需要明确 N 的值。另外,有遍历的需求可以使用 HSCANSSCANZSCAN 代替。

除了这些 O(n)时间复杂度的命令可能会导致阻塞之外, 还有一些时间复杂度可能在 O(N) 以上的命令,例如:

  • 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) 的时间复杂度更小。

SAVE 创建 RDB 快照

Redis 提供了两个命令来生成 RDB 快照文件:

  • save : 同步保存操作,会阻塞 Redis 主线程;
  • bgsave : fork 出一个子进程,子进程执行,不会阻塞 Redis 主线程,默认选项。

默认情况下,Redis 默认配置会使用 bgsave 命令。如果手动使用 save 命令生成 RDB 快照文件的话,就会阻塞主线程。

AOF

AOF 日志记录阻塞

Redis AOF 持久化机制是在执行完命令之后再记录日志,这和关系型数据库(如 MySQL)通常都是执行命令之前记录日志(方便故障恢复)不同。

        

为什么是在执行完命令之后记录日志呢?

  • 避免额外的检查开销,AOF 记录日志不会对命令进行语法检查;
  • 在命令执行完之后再记录,不会阻塞当前的命令执行。

这样也带来了风险(我在前面介绍 AOF 持久化的时候也提到过):

  • 如果刚执行完命令 Redis 就宕机会导致对应的修改丢失;
  • 可能会阻塞后续其他命令的执行(AOF 记录日志是在 Redis 主线程中进行的)

AOF 刷盘阻塞

开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到 AOF 缓冲区 server.aof_buf 中,然后再根据 appendfsync 配置来决定何时将其同步到硬盘中的 AOF 文件。

在 Redis 的配置文件中存在三种不同的 AOF 持久化方式( fsync策略),它们分别是:

  1. appendfsync always:主线程调用 write 执行写操作后,后台线程( aof_fsync 线程)立即会调用 fsync 函数同步 AOF 文件(刷盘),fsync 完成后线程返回,这样会严重降低 Redis 的性能(write + fsync)。
  1. appendfsync everysec:主线程调用 write 执行写操作后立即返回,由后台线程( aof_fsync 线程)每秒钟调用 fsync 函数(系统调用)同步一次 AOF 文件(write+fsyncfsync间隔为 1 秒)
  2. appendfsync no:主线程调用 write 执行写操作后立即返回,让操作系统决定何时进行同步,Linux 下一般为 30 秒一次(write但不fsyncfsync 的时机由操作系统决定)。

当后台线程( aof_fsync 线程)调用 fsync 函数同步 AOF 文件时,需要等待,直到写入完成。当磁盘压力太大的时候,会导致 fsync 操作发生阻塞,主线程调用 write 函数时也会被阻塞。fsync 完成后,主线程执行 write 才能成功返回。

AOF 重写阻塞

  1. fork 出一条子线程来将文件重写,在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子线程创建新 AOF 文件期间,记录服务器执行的所有写命令。
  2. 当子线程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新的 AOF 文件保存的数据库状态与现有的数据库状态一致。
  3. 最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作。

阻塞就是出现在第 2 步的过程中,将缓冲区中新数据写到新文件的过程中会产生阻塞

大 Key

如果一个 key 对应的 value 所占用的内存比较大,那这个 key 就可以看作是 bigkey。具体多大才算大呢?有一个不是特别精确的参考标准:

  • string 类型的 value 超过 1MB
  • 复合类型(列表、哈希、集合、有序集合等)的 value 包含的元素超过 5000 个(对于复合类型的 value 来说,不一定包含的元素越多,占用的内存就越多)。

大 key 造成的阻塞问题如下:

  • 客户端超时阻塞:由于 Redis 执行命令是单线程处理,然后在操作大 key 时会比较耗时,那么就会阻塞 Redis,从客户端这一视角看,就是很久很久都没有响应。
  • 引发网络阻塞:每次获取大 key 产生的网络流量较大,如果一个 key 的大小是 1 MB,每秒访问量为 1000,那么每秒会产生 1000MB 的流量,这对于普通千兆网卡的服务器来说是灾难性的。
  • 阻塞工作线程:如果使用 del 删除大 key 时,会阻塞工作线程,这样就没办法处理后续的命令。

查找大 key

当我们在使用 Redis 自带的 --bigkeys 参数查找大 key 时,最好选择在从节点上执行该命令,因为主节点上执行时,会阻塞主节点。

  • 我们还可以使用 SCAN 命令来查找大 key;

  • 通过分析 RDB 文件来找出 big key,这种方案的前提是 Redis 采用的是 RDB 持久化。网上有现成的工具:

    • redis-rdb-tools:Python 语言写的用来分析 Redis 的 RDB 快照文件用的工具
    • rdb_bigkeys:Go 语言写的用来分析 Redis 的 RDB 快照文件用的工具,性能更好。

删除大 key

删除操作的本质是要释放键值对占用的内存空间。

释放内存只是第一步,为了更加高效地管理内存空间,在应用程序释放内存时,操作系统需要把释放掉的内存块插入一个空闲内存块的链表,以便后续进行管理和再分配。这个过程本身需要一定时间,而且会阻塞当前释放内存的应用程序。

所以,如果一下子释放了大量内存,空闲内存块链表操作时间就会增加,相应地就会造成 Redis 主线程的阻塞,如果主线程发生了阻塞,其他所有请求可能都会超时,超时越来越多,会造成 Redis 连接耗尽,产生各种异常。

删除大 key 时建议采用分批次删除和异步删除的方式进行。

清空数据库

清空数据库和上面 bigkey 删除也是同样道理,flushdbflushall 也涉及到删除和释放所有的键值对,也是 Redis 的阻塞点。

集群扩容

Redis 集群可以进行节点的动态扩容缩容,这一过程目前还处于半自动状态,需要人工介入。

在扩缩容的时候,需要进行数据迁移。而 Redis 为了保证迁移的一致性,迁移所有操作都是同步操作。

执行迁移时,两端的 Redis 均会进入时长不等的阻塞状态,对于小 Key,该时间可以忽略不计,但如果一旦 Key 的内存使用过大,严重的时候会触发集群内的故障转移,造成不必要的切换。

Swap(内存交换)

什么是 Swap? Swap 直译过来是交换的意思,Linux 中的 Swap 常被称为内存交换或者交换分区。类似于 Windows 中的虚拟内存,就是当内存不足的时候,把一部分硬盘空间虚拟成内存使用,从而解决内存容量不足的情况。因此,Swap 分区的作用就是牺牲硬盘,增加内存,解决 VPS 内存不够用或者爆满的问题。

Swap 对于 Redis 来说是非常致命的,Redis 保证高性能的一个重要前提是所有的数据在内存中。如果操作系统把 Redis 使用的部分内存换出硬盘,由于内存与硬盘的读写速度差几个数量级,会导致发生交换后的 Redis 性能急剧下降。

识别 Redis 发生 Swap 的检查方法如下:

1、查询 Redis 进程号

redis-cli -p 6383 info server | grep process_id
process_id: 4476

2、根据进程号查询内存交换信息

cat /proc/4476/smaps | grep Swap
Swap: 0kB
Swap: 0kB
Swap: 4kB
Swap: 0kB
Swap: 0kB
.....

如果交换量都是 0KB 或者个别的是 4KB,则正常。

预防内存交换的方法:

  • 保证机器充足的可用内存
  • 确保所有 Redis 实例设置最大可用内存(maxmemory),防止极端情况 Redis 内存不可控的增长
  • 降低系统使用 swap 优先级,如echo 10 > /proc/sys/vm/swappiness

CPU 竞争

Redis 是典型的 CPU 密集型应用,不建议和其他多核 CPU 密集型服务部署在一起。当其他进程过度消耗 CPU 时,将严重影响 Redis 的吞吐量。

可以通过redis-cli --stat获取当前 Redis 使用情况。通过top命令获取进程对 CPU 的利用率等信息 通过info commandstats统计信息分析出命令不合理开销时间,查看是否是因为高算法复杂度或者过度的内存优化问题。

网络问题

连接拒绝、网络延迟,网卡软中断等网络问题也可能会导致 Redis 阻塞。

<think>好的,我现在需要整理一下关于Redis常见高频面试题,帮助用户准备面试。首先,我得回忆一下自己学过的Redis知识,以及之前看过的面试题。可能的问题包括基本概念、数据结构、持久化、高可用、应用场景等等。 首先,Redis的基本概念是什么?它是一个内存数据库,支持多种数据结构,比如字符串、哈希、列表、集合、有序集合。还有,Redis是单线程的,但为什么性能还这么高呢?可能是因为它基于内存操作,并且采用了非阻塞I/O多路复用机制。 接下来是数据结构部分。常见的数据结构需要详细说明,比如String、Hash、List、Set、Zset,以及它们的使用场景。比如String适合缓存,Hash适合存储对象,List可以做队列,Zset可以做排行榜。 然后是持久化机制,RDB和AOF的区别。RDB是快照,适合备份,恢复快,但可能丢失数据。AOF记录每次写操作,更安全,但文件大,恢复慢。混合持久化可能是在Redis 4.0之后引入的,结合了两者的优点。 高可用方面,主从复制、哨兵、集群这三种模式需要解释清楚。主从复制实现数据同步,哨兵负责监控和故障转移,集群则提供数据分片和高可用。 缓存相关的问题,比如缓存穿透、击穿、雪崩的区别和解决方案。穿透是指查询不存在的数据,可以用布隆过滤器或者缓存空值。击穿是热点key过期导致大量请求到数据库,可以用互斥锁或者永不过期。雪崩是大量key同时过期,可以随机设置过期时间或者集群部署。 事务和管道。Redis的事务是通过MULTI/EXEC命令实现的,但不支持回滚。管道则是将多个命令打包发送,减少网络开销,提升性能。 内存淘汰策略,比如LRU、LFU、随机淘汰等,需要列举不同的策略及其应用场景。 还有可能问到Redis的线程模型,单线程如何处理并发连接,为什么单线程效率高。此外,Redis6.0引入了多线程IO,但执行命令还是单线程的,这点需要说明。 应用场景方面,除了缓存,还有分布式锁、消息队列(使用List或者Stream)、会话存储、计数器、排行榜等。 其他可能的问题包括Redis的过期策略(惰性删除和定期删除)、与Memcached的区别、如何实现分布式锁、Redis的集群数据分片方式(哈希槽)等。 需要确保每个问题都有简明扼要的回答,并且涵盖高频考点。可能还需要注意一些细节,比如持久化的配置参数,哨兵选举过程,集群的节点通信等。不过用户可能需要的是常见问题,所以不需要太深入,但重点要突出。 可能还需要检查是否有遗漏的重要问题,比如Redis的事务ACID属性,是否支持原子性,或者Redis的并发竞争问题如何解决。此外,大key和热key的问题,如何处理,也是常见的生产环境问题。 总结下来,应该分几个大类来组织问题,每个大类下列举几个具体的问题和简要答案。这样用户看起来清晰,容易复习。</think>以下是 Redis 常见高频面试题及简要解析,助你快速掌握核心知识点: --- ### **一、基础概念** 1. **Redis 是什么?** Redis 是基于内存的键值数据库,支持多种数据结构(String、Hash、List、Set、ZSet等),常用于缓存、消息队列、分布式锁等场景。 2. **Redis 为什么快?** - 内存操作,读写无磁盘 I/O 瓶颈。 - 单线程模型避免上下文切换,配合 I/O 多路复用处理高并发。 - 高效数据结构(如跳表、哈希表)。 --- ### **二、数据结构与应用** 1. **Redis 支持哪些数据结构?应用场景?** - **String**:缓存、计数器(如 `INCR`)。 - **Hash**:存储对象(如用户信息)。 - **List**:消息队列、栈(`LPUSH`/`RPOP`)。 - **Set**:去重、交集(共同好友)。 - **ZSet**:排行榜(按分数排序)。 - **BitMap**:位统计(日活用户)。 - **HyperLogLog**:基数统计(UV)。 - **Stream**:消息流(类似 Kafka)。 2. **ZSet 底层实现?** 使用**跳表(SkipList)** + **哈希表**,跳表支持范围查询,哈希表保证 O(1) 复杂度单值查询。 --- ### **三、持久化** 1. **RDB 和 AOF 的区别?** - **RDB**:定时生成内存快照,文件小、恢复快,但可能丢失最后一次快照后的数据。 - **AOF**:记录每次写命令,数据更安全,但文件大、恢复慢。 - **混合持久化(Redis 4.0+)**:结合两者,AOF 文件包含 RDB 头 + 增量 AOF 命令。 2. **AOF 重写的作用?** 压缩 AOF 文件体积,移除冗余命令(如多次修改同一 key),通过读取内存数据生成新 AOF。 --- ### **四、高可用与集群** 1. **主从复制原理?** 从节点连接主节点后发送 `SYNC` 命令,主节点执行 `BGSAVE` 生成 RDB 文件并发送给从节点,后续通过传播写命令保持同步。 2. **哨兵(Sentinel)机制的作用?** 监控主从节点状态,自动故障转移:当主节点宕机时,选举新主节点并通知客户端。 3. **Redis Cluster 如何分片?** 采用**哈希槽(16384 个槽)**,每个节点负责部分槽。数据按 `CRC16(key) % 16384` 分配到对应槽。 --- ### **五、缓存问题** 1. **缓存穿透、击穿、雪崩的区别及解决方案?** - **穿透**:查询不存在的数据。 → 布隆过滤器拦截非法 Key,缓存空值。 - **击穿**:热点 Key 过期后高并发请求压垮数据库。 → 互斥锁(如 `SETNX`)、永不过期 + 异步更新。 - **雪崩**:大量 Key 同时过期。 → 随机过期时间、集群部署、永不过期。 2. **如何保证缓存数据库一致性?** - 旁路缓存模式(Cache-Aside):先更新库,再删缓存(可能短暂不一致)。 - 延时双删、设置缓存过期时间兜底。 - 最终一致性:通过消息队列异步同步。 --- ### **六、高级特性** 1. **Redis 事务(MULTI/EXEC)的原子性?** 事务中的命令按顺序执行,但非原子(单条命令失败不影响后续执行),不支持回滚。 2. **如何实现分布式锁?** - 使用 `SET key value NX EX` 设置锁(避免死锁)。 - Redlock 算法(多节点部署防止单点故障)。 - 推荐 Redisson 客户端(自动续期、可重入)。 3. **内存淘汰策略有哪些?** - `volatile-lru`/`allkeys-lru`:淘汰最近最少使用的 Key。 - `volatile-lfu`/`allkeys-lfu`:淘汰最不经常使用的 Key。 - `volatile-ttl`:淘汰即将过期的 Key。 - `noeviction`:默认策略,不淘汰,写操作返回错误。 --- ### **七、其他高频问题** 1. **Redis 单线程为什么高效?** 纯内存操作 + 非阻塞 I/O 多路复用(epoll)处理高并发,避免线程切换开销。 2. **Pipeline 的作用?** 批量发送命令,减少网络往返次数,提升批量操作性能(但非原子性)。 3. **大 Key 和热 Key 问题如何解决?** - **大 Key**:拆分(如 Hash 分多个 Key)、异步删除(`UNLINK`)。 - **热 Key**:多级缓存、本地缓存、Key 打散分布。 --- 掌握以上问题后,可进一步结合实际场景和源码深入理解,面试时结合项目经验回答更佳!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值