Redis学习(十三):性能风险之内部阻塞风险

阻塞风险

1.Redis实例的阻塞点

redis实例运行过程中发生的交互

  • 客户端:网络IO、键值的读写、数据库操作
  • 磁盘:生成RDB快照、AOF文件读写、AOF重写
  • 主从节点:主库生成、传输RDB文件、从库接收RDB文件、清空数据库、加载RDB文件
  • 切片集群实例:向其他实例同步哈希槽信息、数据迁移

img

那么上述操作哪些可能产生阻塞风险呢?

2.和客户端交互的阻塞

2.1.网咯IO

由于采取了多路复用机制,针对监听套接字和已连接套接字已无阻塞风险

2.2.数据的增删改查

(1)集合全量查询和聚合操作

复杂度高的命令肯定会阻塞redis,一般复杂度为O(N),redis中集合的操作复杂度通常为O(N)。例如集合的全量操作,和集合的交集、并集等

(2)bigkey的删除操作

删除操作需要释放键值对所占用的内存空间,而操作系统在释放空间之后会插入一个空的内存块的链表,便于后续的管理和再分配,因此再,这个过程会阻塞应用程序,特别是对于释放大量的数据的时候

针对bigkey的删除操作的耗时:

img

(1)集合元素越大删除时间越长

(2)百万级别的数据删除时间甚至可以达到2s

(3)清空数据库

因为清空数据库也涉及了大量内存空间的释放操作,因此也会阻塞

3.和磁盘交互的阻塞

(1)AOF同步写

redis中AOF和RDB与磁盘的交互都是通过fork子进程的方式实现,因此不会阻塞主进程。

但是在AOF的写入策略如果采取同步写的话就会阻塞了

4.主从节点的交互的阻塞

(1)清空数据库

主从集群同步中,从节点接收到RDB文件后会清空数据库,因此会产生阻塞

(2)RDB加载入内存

清空数据库后还需要加载RDB快照入内存,如果RDB越大,那么阻塞时间就越长

5.切片集群实例交互

使用Redis Cluster方案,同时迁移的是Bigkey,就会造成主线程的阻塞。因为Redis Cluster采取的是同步迁移

6.哪些阻塞点可以异步执行

总结阻塞点如下:

  • 集合全量查询和聚合操作
  • bigkey删除
  • 清空数据库
  • AOF日志同步写
  • 从库加载RDB文件

6.1.关键路径操作

看是否能够异步操作,主要是看该操作是否是关键路径上的操作,而关键路径主要就是指的存在耦合依赖的操作

上述的五个阻塞点,除了读场景和从库加载RDB文件是关键路径上面的操作,其余操作都可以使用redis的异步子线程机制来实现bigkey的删除、清空数据库、AOF日志同步写

针对读场景和从库加载RDB提供下面的建议:

  • 集合全量查询和聚合操作:可以使用**SCAN命令**,分批读取数据,再在客户端进行聚合计算
  • 从库加载RDB文件:把主库的数据量大小控制在2~4GB左右,以保证RDB文件能以较快的速度加载。

6.2.异步子线程机制

redis启动后,会使用操作系统提供的pthread_create创建三个子线程,分别由它们负责AOF日志写、键值对删除和文件关闭等异步操作(异步的键值对删除和数据库清空操作是Redis 4.0后提供的功能)

主线程通过一个任务队列链表和子线程进行交互,将删除键值对、清空数据库封装成一个任务丢到队列中给子线程执行。针对这种异步删除,我们称做懒删除(lazy-free),AOF设置为everysec之后也是将AOF的写任务放到队列中

img

redis4.0 也提供了命令支持键值对的异步删除和数据的清空操作

  • 键值对删除:当你的集合类型中有大量元素(例如有百万级别或千万级别元素)需要删除时,我建议你使用UNLINK命令。
  • 清空数据库:可以在FLUSHDBFLUSHALL命令后加上ASYNC选项,这样就可以让后台子线程异步地清空数据库,如下所示:
FLUSHDB ASYNC
FLUSHALL AYSNC

关于lazy-free的补充

1、lazy-free是4.0新增的功能,但是默认是关闭的,需要手动开启

2、手动开启lazy-free时,有4个选项可以控制,分别对应不同场景下,要不要开启异步释放内存机制:
a) lazyfree-lazy-expire:key在过期删除时尝试异步释放内存
b) lazyfree-lazy-eviction:内存达到maxmemory并设置了淘汰策略时尝试异步释放内存
c) lazyfree-lazy-server-del:执行RENAME/MOVE等命令或需要覆盖一个key时,删除旧key尝试异步释放内存
d) replica-lazy-flush:主从全量同步,从库清空数据库时异步释放内存

3、即使开启了lazy-free,如果直接使用DEL命令还是会同步删除key,只有使用UNLINK命令才会可能异步删除key。

4、这也是最关键的一点,上面提到开启lazy-free的场景,除了replica-lazy-flush之外,其他情况都只是可能去异步释放key的内存,并不是每次必定异步释放内存的。

开启lazy-free后,Redis在释放一个key的内存时,首先会评估代价,如果释放内存的代价很小,那么就直接在主线程中操作了,没必要放到异步线程中执行(不同线程传递数据也会有性能消耗)。

什么情况才会真正异步释放内存?这和key的类型、编码方式、元素数量都有关系(详细可参考源码中的lazyfreeGetFreeEffort函数):

a) 当Hash/Set底层采用哈希表存储(非ziplist/int编码存储)时,并且元素数量超过64个
b) 当ZSet底层采用跳表存储(非ziplist编码存储)时,并且元素数量超过64个
c) 当List链表节点数量超过64个(注意,不是元素数量,而是链表节点的数量,List的实现是在每个节点包含了若干个元素的数据,这些元素采用ziplist存储)

只有以上这些情况,在删除key释放内存时,才会真正放到异步线程中执行,其他情况一律还是在主线程操作

也就是说String(不管内存占用多大)、List(少量元素)、Set(int编码存储)、Hash/ZSet(ziplist编码存储)这些情况下的key在释放内存时,依旧在主线程中操作。

可见,即使开启了lazy-free,String类型的bigkey,在删除时依旧有阻塞主线程的风险。所以,即便Redis提供了lazy-free,我建议还是尽量不要在Redis中存储bigkey

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×