一.影响redis的5大因素
Redis性能 的5大方面潜在因素
- Redis内部的阻塞式操作
- CPU核和NUMA架构的影响
- Redis关键系统配置
- Redis内存碎片
- Redis缓冲区
Redis 内部的阻塞式操作以及应对的方法
Redis实例有哪些阻塞点
- 客户端:网络IO,键值对增删改查操作,数据库操作
- 磁盘:生成RDB快照,记录AOF日志,AOF日志重写
- 主从节点:主库生成,传输RDB文件,从库接收RDB文件,清空数据库,加载RDB文件
- 切片集群实例:向其他实例传输哈希槽信息,数据迁移
1.客户端交互时阻塞点
- 网络io:Redis采用IO多路复用机制,避免主线程一直处在等待网络连接或者请求带来状态,所以网络IO不是Redis阻塞的因素。
- 键值对的增删改查操作:是Redis和客户端交互的主要部分,也是Redis主线程执行的主要任务。复杂度高的增删改查操作肯定会阻塞Redis。查看操作的 复杂度是否为O(N)。
- 集合元素的全量操作HGETALL,SMEMBERS,以及集合的聚合统计操作:求交集并集和差集。这些操作可以作为Redis的第一个阻塞点:集合全量查询和聚合操作。
- 集合的自身删除:删除操作的本质就是要释放键值对占用的内存空间,不要小瞧内存的释放过程:释放内存只是第一步,为了更高效的管理内存空间,应用程序释放内存时,操作系统需要把释放掉的内存插入一个空闲的内存块链表,以便后续进行内存管理和再分配。
- 这个过程需要一定时间,而且会阻塞当前释放内存的,所以如果一下子释放大量内存,空闲内存块链表操作时间就会大大增加,相应的造成Redis主线程的阻塞。
- 删除大量键值对数据的时候,最典型的就是删除包含大量元素的集合,也成为了bigkey删除。
集合类型 | 10w(8字节) | 100w(8字节) | 10w(128字节) | 100w(128字节) |
hash | 50ms | 962ms | 91ms | 1980ms |
list | 25ms | 133ms | 29ms | 283ms |
set | 42ms | 821ms | 75ms | 1347ms |
sorted set | 53ms | 809ms | 61ms | 991ms |
- 当元素数量从10w到100w的时候,4大集合类型的删除时间增长幅度从5倍上升到了20倍
- 集合元素越大,删除花费时间越长
- 当删除有100万个元素集合时,最大的删除时间绝对值达到了1.98s(hash类型)。Redis响应时间一般在毫秒级别,所以一个操作达到了2s,就不可避免的阻塞主线程。
BigKey删除操作信息就是Redis第二个阻塞点
删除操作对Redis实例性能的负面影响很大,而且实例业务开发中容易忽略。
既然频繁删除键值对都是潜在的阻塞点,那么Redis清空数据库级别操作
(FLUSHDB和FLUSHALL)必然也是潜在的阻塞风险,它涉及到删除和释放所有键值对。
Redis第三个阻塞点:清空数据库。
2.磁盘交互时的阻塞点
Redis与磁盘交互的单独列为一类
- 磁盘IO费时费力
- Redis开发者早已认识到磁盘IO会带来阻塞,将Redis设计为采用子进程的方式生成RDB快照,以及AOF日志重写操作。
- 这两个操作由子进程负责执行,慢速的磁盘IO就不会阻塞主线程。但是,Redis直接记录AOF
- 日志时,会根据不同的写回策略对数据做落盘保存。
- 一个同步写磁盘的操作耗时1-2ms,如果有大量写操作需要记录在AOF中,并同步写回就会阻塞主线程
Redistribution第四个阻塞点:AOF 日志同步写
3.主从节点交互时的阻塞点
- 在主从集群中,主库需要生成RDB文件并传输给从库
- 主从复制中,创建和传输RDB文件都需要子进程完成,不需要阻塞主线程。
- 但是对于从库来说,他在接收RDB文件后,需要使用FLUSHDB命令清空当前数据库
- 从库在清空数据库后,还需要把 RDB文件加载到内存,文件越大加载过程越慢。
加载RDB文件成为Redis第五个阻塞点。
4.切片集群实例交互时的阻塞点
- 当我们部署Redis切片集群时,每个Redis实例分配的哈希从信息都需要在不同实例间进行传递。
- 当需要进行负载均衡或者实例增删时,数据回在不同实例间进行迁移。
- 哈希槽信息量不大,数据迁移是渐进式的,所以一般来讲这两类操作对Redis主线程的阻塞风险不大。
- Redis Cluster 方案,同时正好迁移的是bigkey就会造成主线程的阻塞。
5.Redis的五个阻塞点:
- 集合全量查询和聚合操作
- bigkey删除
- 清空数据库
- AOF日志同步写
- 从库加载RDB文件
6.Redis异步线程机制
原因:主线程中执行这些操作必然会导致主线程长时间无法提供其他请求服务。
异步线程机制:Redis会启动一些子线程,然后把一些任务交给这些子线程,让他们在后台完成,而不是由主线程来执行这些任务。
结果:避免阻塞主线程。
7.哪些阻塞点可以异步执行
异步对操作的要求
- 一个操作能被异步执行,意味着他并不是Redis主线程的关键路径上的操作
- 关键路径上的操作:客户端可以把请求发送给Redis后,等着Redis返回数据结果
对于Redis来说,读操作是典型的关键就操作,因为客户端需要读取数据返回,以便会序数据处理。而 Redis 的第一个阻塞点“集合全量查询和聚合操作”都涉及到了读操作,所以,它们是不能进行异步操作了。
删除操作并不需要给客户端返回具体的数据结果,所以不算是关键路径操作。而我们刚才总结的第二个阻塞点"bigkey删除"和第三个阻塞点"清空数据库"都是对数据做删除,并不是关键路径上。因此我们可以使用后台来异步执行删除操作。
"AOF日志同步写",为保证数据可靠性,Redis实例需要保证AOF日志中操作记录已经落盘,这个操作虽然需要实例等待,但是并不会返回具体的数据结果给实例。我们可以启动子线程来执行AOF日志的同步写操作,不让主线程等待AOF日志的写完成。
"从库加载RDB文件"这个阻塞点。从库想要对客户端提供数据存取服务,就必须把RDB加载完成,所以,这个操作也属于关键路径的操作,我们必须让从库的主线程来执行。
Redis 五大阻塞点
Redis 的异步子线程 | bigkey 删除,清空数据库,AOF日志同步写 |
Redis关键路径动作 | 集合全量查询和聚合操作 从库加载RDB文件 |
异步的子线程机制
- Redis主线程启动后,会使用操作系统提供pthread_create函数创建3个子线程,分别由他们负责 AOF日志写操作,键值对删除以及文件关闭的异步执行。
- 主线程通过一个链表的任务队列和子线程进行交互。当收到键值对删除和清空数据库的操作时,主线程会把这个操作封装成一个任务,放入 任务队列中,然后给客户端返回一个完成信息,表示删除已经完成。
- 其实删除还没有执行,等到后台子线程从任务队列中读取任务后,才开始实际删除键值对,并释放相应的内存空间。因此我们把这种异步删除也成为惰性删除,此时删除或清空操作并不会阻塞主线程,这就避免对主线程的性能影响。
- 当 AOF 日志配置成 everysec 选项后,主线程会把 AOF 写日志操作封装成一个任务,也放到任务队列中。后台子线程读取任务后,开始自行写入 AOF 日志,这样主线程就不用一直等待 AOF 日志写完了。
异步的键值对删除和数据库清空操作是Redis 4.0后提供的功能,Redis提供了新的命令来执行
- 键值对删除:当你的集合类型中有大量元素(例如有百万级别或千万级别元素)需要删除时,我建议你使用 UNLINK 命令。
- 清空数据库:可以在 FLUSHDB 和 FLUSHALL 命令后加上 ASYNC 选项,这样就可以让后台子线程异步地清空数据库,如下所示:
FLUSHDB ASYNC
FLUSHALL ASYNC