目录
Redis 作为高性能的键值对存储系统,在实际项目中得到了广泛的应用。但是在使用中我们必须重视有可能影响 Redis 性能的各种因素,本篇文章就带你了解可能会引起 Redis 阻塞的各种因素及解决方案。
一、Redis 有哪些阻塞点
Redis 在实际运行中,要和许多对象进行交互,这些不同的交互会涉及不同的操作,下面是 Redis 实例交互的对象,以及交互过程中会发生什么操作。
- 客户端:为客户端提供服务是最重要的功能,Redis 与客户端之间存在网络 I/O,通过网络 I/O 来实现数据的增删改查操作;
- 磁盘:Redis 在运行过程中要保证数据的可靠性,就需要将内存中的数据持久化到磁盘。因此,Redis 会记录 AOF 日志、AOF 日志重写,以及 RDB 快早生成;
- 主从节点:为了防止 Redis 实例挂掉后无法提供服务,通常会有若干个从节点进行数据冗余,以保证可用性。在同步的过程中,主库会生成、传输 RDB 文件,从库会接受到 RDB 文件,清空数据库,加载 RDB 文件等操作;
- 切片集群:为了保存大量的数据,对 Redis 进行切分,每个切片上保存一部分数据,这样就可以保存大量的数据。这个过程中会涉及向其他实例传输哈希槽信息以及数据迁移等。
下面来分析一下这些操作中哪些可能会引起阻塞。
二、客户端交互
2.1 网络I/O
网络 I/O 有时会成为性能瓶颈,但 Redis 使用了 I/O 多路复用机制,避免了主线程一直处于网络连接或等待状态,所以在 Redis 中,网络 I/O 不是做成阻塞的因素。
2.2 键值对操作
键值对的增删改查是 Redis 和客户端交互的主要部分,也是 Redis 主线程的主要任务。所以复杂度高的增删改查操作肯定会阻塞主线程,进而阻塞 Redis。
判断操作复杂度的高低有一个基本的标准,就是看操作的复杂度是否为O(N),Redis 中涉及到集合操作时,操作的复杂度为 O(N),例如集合元素的全量查询 HGETALL、SMEMBERS,以及集合的聚合统计,如求交集、并集。
关于操作命令的复杂度,Redis 官方提供了复杂度查询方式:Commands | Docs
所以 Redis 的第一个阻塞点为:集合的全量查询和聚合操作。
此外,客户端的某些命令也是有阻塞的,比如 BLPOP、BRPOP 等,所以要尽量避免使用阻塞的命令。
除此之外,集合自身的删除操作同样存在潜在的阻塞风险点,为什么删除可能会阻塞呢?其实,删除操作的本质是要释放键值对占用的内存空间。内存释放过程中释放内存只是第一步,为了更加高效的管理内存空间,在应用程序释放内存时,操作系统需要把释放掉的内存块插入一个空闲内存块链表中,以便后续进行管理和分配。这个过程本身需要一定时间,而且会阻塞当前释放内存的应用程序,所以一下释放大量内存,空闲内存块链表操作时间会增加,相应的就造成 Redis 阻塞。
什么时候会存在释放大量内存呢?其实就是删除大量键值对数据的时候,最典型的就是删除包含大量元素的集合,或者 bigkey。
如果大量的 key 在同意时间过期,也会出现大量 key 删除的情况,可能出现阻塞。
所以,第二个阻塞点为:删除大量数据时。
既然删除数据会造成阻塞,那清空整个库也会有同样的问题,所以,第三个阻塞点位:清空数据库。
三、和磁盘交互
磁盘 I/O 一般是很耗时的操作,Redis 采用子进程的方式生成RDB快照文件,以及执行 AOF 日志重写操作。这样一来,这两个操作由子进程负责执行,磁盘 I/O 就不会阻塞主线程。但 Redis 直接记录 AOF 日志时,会根据不同的写回策略对数据罗盘保存。如果采用的 always 写回策略,Redis 主线程会在每次执行写命令后立即写入 AOF 文件,这意味着 Redis 主线程直接参与 AOF 文件的写入操作,主线程会阻塞。
所以,第四个阻塞点位:AOF 同步写。
四、主从节点交互
在主从集群中,主库需要生成 RDB 文件,并传输给从库。主库在复制过程中,创建和传输 RDB 都是由子进程完成的,不会阻塞主线程。但是,对于从库来说,在收到 RDB 文件后,首先要做的事就是要清空数据库,以免旧数据有影响,这就正好撞上了第三个阻塞点。
此外,从库在清空当前数据库后,还需要把RDB文件加载到内存,这个过程的快慢和RDB文件的大小密切相关,RDB文件越大,加载过程越慢。
所以,Redis 第五个阻塞点为:加载 RDB 文件。
五、切片集群实例交互时阻塞点
当我们部署 Redis 切片集群时,每个 Redis 实例上分配的哈希槽信息需要在不同实例间传递,同时,当需要进行负载均衡或者实例增删时,数据会在不同实例间进行迁移。不过,哈希槽的信息量不大,而迁移数据是渐进式执行的,所以这两类操作对 Redis 主线程的阻塞风险不大。
如果使用了Redis Cluster方案,而且同时正好迁移 bigkey 的话,就会造成主线程阻塞,因为 Redis Cluster 使用了同步迁移。
六、如何避免阻塞
6.1 客户端
在客户端操作时要避免使用全量查询的命令,如 keys *、全量查询集合或对集合进行聚合统计等操作;避免使用阻塞的命令,如 BLPOP、BRPOP 等。
比如需要返回 set 中所有的成员,不需要使用 SMEMBERS 命令,而使用 SSCAN 多次迭代返回,避免一次返回大量数据,造成阻塞。当你需要执行交集、并集、排序操作时,可以在客户端进行这些操作,以免拖慢 Redis。
在删除数据时,要避免大量的数据在同一时间过期,可以有小时间间隔的设置过期时间。
对于 bigkey 的大集合删除,可以采用渐进式的删除方式,这种方式允许你分批删除 bigkey 中的元素,而不是一次性的删除整个 bigkey。比如要删除 hash 表的 bigkey 可以采用一下的命令:
HSCAN key cursor [MATCH pattern] [COUNT count]
HDEL key field [field ...]
6.2 AOF
在使用 AOF 进行持久化时,避免使用同步写回机制,以免影响主线程阻塞,影响客户端使用。
AOF 日志提供了三种写回策略:no、everysec、always。这三种写回策略依赖文件系统的两个系统调用来完成,也就是 write 和 fsync。
write 只是把数据写到了内核缓冲区,就可以返回了,并不需要等待日志时机写入磁盘。fsync 需要把日志记录写会磁盘才能返回,时间较长。
当写回策略设置为 everysec 或 always 时,Redis 需要调用 fsync 把日志写会磁盘。但这两种写回策略的具体情况不同。
在使用 everysec 写回策略时,Redis 允许丢失 1s 的操作记录,所以 Redis 主线程不需要确保每个操作记录日志都写回磁盘。如果在 Redis 主线程执行 fsync 就容易阻塞。所以在使用 everysec 时,Redis 会使用后台子线程异步的完成 fsync 的操作。
而对于 always 写回策略来说,Redis 需要确保每个操作记录都写回磁盘,如果用后台子线程异步完成,主线程就无法及时的知道每个操作是否完成,所以 always 不使用后台子线程来执行。
所以,在选在写回策略时要进行取舍,根据业务场景选择合适的写回策略,避免 Redis 阻塞。
6.3 RDB
当 Redis 实例从 RDB 快照文件恢复数据时,如果 RDB 文件非常大,加载过程可能会非常慢,这会导致 Redis 在启动期间无法接受客户端请求,从而造成阻塞。
可以调整 RDB 文件生成的频率,两次 RDB 之间可以使用 AOF 进行持久化,以此来减小 RDB 文件的大小,要在业务低峰期进行处理等。
总之,通过正确的方式来进行 Redis 操作,可以有效的避免 Redis 的阻塞,从而提升整体的性能。
往期经典推荐
Logback 日志打印导致程序崩溃的实战分析_logback 打日志导致卡死-CSDN博客
从理论到实践:零拷贝技术的全面解读_零拷贝详解-CSDN博客
决胜高并发战场:Redis并发访问控制与实战解析_redis并发控制-CSDN博客