我们知道在Redis6.0 之前Redis处理网络IO,数据读写都是使用同一个线程来执行,我们将这个线程也叫做是主线程,那么,当某些操作阻塞了主线程时,就会影响redis对外提供正常的数据读取。本文就一起来看下都可能有哪些操作会阻塞主线程,即看下阻塞点都有哪些,这样我们才能更好的使用Redis,避免出现性能问题。接下来我们就一起来看下吧!
为了能够更加系统的分析,我们以一个redis实例为中心,看下都有哪些元素可能会和其发生交互,然后以这些可能和redis实例发生交互的元素为起点分析下,与之相关的操作哪些可能是阻塞点,与Redis实例可能发生交互的元素如下图:
说明如下:
客户端:如redis-cli,redission等
磁盘:数据持久化
主从:主从架构
集群:分片集群架构
接下来我们从这4部分来展开分析。
对于客户端操作来说可能产生阻塞的点有两个,首先是网络IO,其次是对redis实例的增删改查操作,对于网络IO,因为Redis使用了IO多路复用模型 ,所以不会产生阻塞,那么对于增删改查,首先是增,不会产生阻塞操作,删,当删除的元素中包含了大量的元素,即bigkey删除时,因为被清空的内存会被插入一个空闲内存块的链表,以便后快速分配使用,这个插入空闲内存块链表的过程可能会产生阻塞。经过测试,对于一百万数据量hash的key删除,会产生将近2秒的阻塞,这已经是一个很长时间的阻塞了,如下是网上对于这种情况的一个测试结果对比:
既然删除单个的bigkey会产生主线程阻塞,那么flushdb和flushall清空库的操作就更会产生主线程阻塞了。
接下来是改,该操作不会引起主线程阻塞,最后是查,对于集合的全量查询,比如hgetall,smembers,sunionstore聚合操作等,其操作的时间复杂度都是O(n),因此对于有大量数据的集合的全量查询操作一定要格外注意,避免阻塞主线程,出现性能问题。
总结以上可能主线程阻塞的点如下:
1:bigkey删除
2:清空库操作
3:全量查询和聚合操作
redis和磁盘相关的主要是两个文件,AOF和RBD ,我们先来看AOF,如果是我们的磁盘写回策略配置的是appendfsync=Always
的话,因为要同步的等数据写到磁盘才返回给客户端,当有大量的写操作都需要同步写入磁盘的话,就会存在阻塞主线程的问题了。接着来看RDB,RDB文件虽然是也写到磁盘中,但是是通过fork子进程方式异步写入的,因此不会阻塞主线程,所以对于磁盘可能的阻塞点如下:
1:AOF日志的同步写入
主从 执行salve of host port
首次建立和主节点连接时,从节点会发送psync从主节点获取RDB文件,通过前面的分析我们知道,主节点生成RBD文件的过程不会产生主线程阻塞,接着主节点会将生成的RBD文件发送到从节点来同步数据,从节点收到主节点的RBD文件后,首先会执行flushdb,清空数据库,这个可能阻塞主线程的点我们在前面已经分析过了,接着就会加载RDB文件到内存中,如果是RDB文件特别大的话,这个过程也会比较长,此时客户端的查询请求就会阻塞等待,也就会阻塞主线程。
总结以上可能主线程阻塞的点如下:
1:加载RBD文件
即Redis的分片集群 ,Redis分片集群主要是集群之间同步分片信息,因为一共才16384
个哈希槽,所以这些信息很小,不会产生阻塞主线程的问题,另外就是,当增删节点时的数据迁移,因为Redis采用的是渐进式的数据迁移,所以也不会阻塞主线程。
到这里我们可以看到,集群目前没有阻塞主线程的点。
到这里简单总结可能阻塞主线程的点有如下的5个:
1:bigkey删除
2:清空库操作
3:全量查询和聚合操作
4:AOF日志的同步写入
5:加载RBD文件
以上的操作都是因为放在主线程中执行才导致主线程阻塞,那么是不是将其放到子线程中异步执行就可以解决阻塞主线程的问题了,当然是可以的,但是还需要考虑,是否可以被异步执行,而判断是否可以被异步执行的标准只有一个,就是给客户端返回的消息是否必须依赖该操作的结果,如果是不依赖则就可以异步执行,如果是依赖则就不能异步执行,针对这点我们一个一个来看下:
1:bigkey删除
内部只是要对空闲的内存分配空闲内存块的链表,不影响返回给客户端结果,可以异步执行。
2:清空库操作
同1,可以异步执行。
3:全量查询和聚合操作
需要操作的结果,执行不完成,无法返回给客户端结果,因此不能异步执行。
4:AOF日志的同步写入
只是写磁盘,不影响返回给客户端结果,可以异步执行。
5:加载RBD文件
RDB文件不加载完毕,无法查询数据,无法返回客户端要查询的结果,因此不能异步执行。
可以看到1,2,4
是可以通过异步执行来解决阻塞主线程问题,那么应该如何实施呢?在redis4.0版本中,Redis在启动时会通过pthread_create函数直接创建3个子线程,分别用来处理AOF写入,数据删除,文件关闭,具体的过程是将这些操作封装为一个任务,放到任务队列中,由子线程来异步执行,比如key删除,返回给用户成功后,其实此时数据还没有真正的被删除,而是要等到对应的队列中删除key的任务被子线程执行后才会真正的删除数据,也就是惰性删除
,与惰性删除类似,对于AOF写入,如果将appenfsync
设置为everysec
,也会将AOF写入操作封装为任务放到任务队列中,等待子线程读取并执行任务,这个过程可参考下图:
注意这是在redis4之后才自动提供的功能,如果是redis4之前的版本怎么办呢?对于删除操作,我们一般使用的是del,可以改用unlink(异步版本的del)
,这个命令内部会异步删除,如下:
redis> SET key1 "Hello"
"OK"
redis> SET key2 "World"
"OK"
redis> UNLINK key1 key2 key3
(integer) 2
redis>
对于清库操作可以加上async
关键字指定异步删除,如下:
FLUSHDB ASYNC
FLUSHALL AYSNC
参考文章列表: