Redis 实例有哪些阻塞点?
Redis 实例在运行时,要和许多对象进行交互,这些不同的交互就会涉及不同的操作。以下就是常见的操作:
- 客户端操作:网络IO、键值对的增删改查操作、数据库操作;
- 磁盘:RDB文件生产、AOF日志、AOF日志的重写;
- 主从节点:生成RDB、传输RDB、接收和加载RDB;
- 切片集群: 哈希曹传输、数据迁移
客户端交互的阻塞点
网络 IO 有时候会比较慢,但是 Redis 使用了 IO 多路复用机制,避免了主线程一直处在等 待网络连接或请求到来的状态,所以,网络 IO 不是导致 Redis 阻塞的因素(而且redis 6.0后使用了多线程来处理网络io的读写)。
键值对的增删改查操作是 Redis 和客户端交互的主要部分,也是 Redis 主线程执行的主要 任务。所以,复杂度高的增删改查操作肯定会阻塞 Redis。
那么,怎么判断操作复杂度是不是高呢?这里有一个最基本的标准,就是看操作的复杂度 是否为 O(N)。
Redis 中涉及集合的操作复杂度通常为 O(N),我们要在使用时重视起来。例如集合元素全 量查询操作 HGETALL、SMEMBERS,以及集合的聚合统计操作,例如求交、并和差集。 这些操作可以作为 Redis 的第一个阻塞点:集合全量查询和聚合操作。
除此之外,集合自身的删除操作同样也有潜在的阻塞风险。为什么呢?因为删除操作的本质是释放键值对的内存空间,为了更加高效地管理内存空间,在应用程序释放内存时,操作系统需要把释放掉的内存块插入一个空闲内存块的链表,以便后续进行管理和再分配。如果一下子释放了大量内存,空闲内存块链表操作时间就会增加,相应地就会造成 Redis 主线程的阻塞。
那么,什么时候会释放大量内存呢?其实就是在删除大量键值对数据的时候,最典型的就是删除包含了大量元素的集合,也称为 bigkey 删除。也就是大量的内存释放操作是阻塞点。
磁盘交互时的同步写
Redis 开发者早已认识到磁盘 IO 会带来阻塞,所以就把 Redis 进一步设计为采用子进程的方式生成 RDB 快照文件,以及执行 AOF 日志重写操作。这样一来,这两个操作由子进程负责执行,慢速的磁盘 IO 就不会阻塞主线程了。
但是,Redis 直接记录 AOF 日志时,会根据不同的写回策略对数据做落盘保存。一个同步写磁盘的操作的耗时大约是 1~2ms,如果有大量的写操作需要记录在 AOF 日志中,并同步写回的话,就会阻塞主线程了。这就得到了 Redis 的第四个阻塞点了:AOF 日志同步写。
主从节点阻塞点
在主从集群中,主库需要生成 RDB 文件,并传输给从库。主库在复制的过程中,创建和传输 RDB 文件都是由子进程来完成的,不会阻塞主线程。但是,对于从库来说,它在接收了RDB 文件后,需要使用 FLUSHDB 命令清空当前数据库,这就是阻塞点。
此外,从库在清空当前数据库后,还需要把 RDB 文件加载到内存,这个过程的快慢和RDB 文件的大小密切相关,RDB 文件越大,加载过程越慢,所以,加载 RDB 文件就成为了 Redis 的阻塞点。
题目:如何删除大key?
- 分批次删除。 先使用集合类型提供的 SCAN 命令读取数据,然后再进行删除。因为用 SCAN 命令可以每次只读取一部分数据并进行删除,这样可以避免一次性删除大量 key 给主线程带来的阻塞.
- 异步删除(redis 4.0之后提供),用 unlink 命令代替 del 来删除
题目:如何找到大key?
- 方法1: 可以通过 redis-cli --bigkeys 命令查找大 key,注意需要在从节点执行,因为该命令会阻塞主节点,或者业务低峰期执行。缺点:这个方法只能返回每种类型中最大的那个 bigkey,无法得到大小排在前 N 位的 bigkey;
- 方法2: 使用RdbTools工具直接分析RDB文件,找到其中的大key,比如找到大于10k的key;