8.1说说影响Redis性能的因素
Redis中有5大方面会影响Redis主线程的性能,分别是:
(1)Redis内部的阻塞式操作;
(2)CPU核和MUMA架构的影响;
(3)Redis关键系统配置;
(4)Redis内部碎片;
(5)Redis缓冲区;
【Redis实例与哪些对象进行交互?】
(1)客户端:网络IO,键值对增删改查操作,数据库操作;
(2)磁盘:生成RDB快照、记录AOF日志、AOF日志重写;
(3)主从节点:主库生成、传输RDB文件,从库接收RDB文件、清空数据库、加载RDB文件;
(4)切片集群实例:向其他实例传输哈希槽信息,数据迁移
【1】与客户端交互的阻塞点
- 网络IO:网络IO有时候会比较慢,但是Redis使用了IO多路复用机制,避免了主线程一直处在等待网络连接或请求到来的状态,所以网络IO不是导致Redis阻塞的因素;
- 键值对的增删改查操作是Redis与客户端交互的主要部分,也是Redis主线程执行的主要任务。所以,复杂度较高的增删改查操作肯定会阻塞Redis。如果操作的时间复杂度是O(N),这就有可能会阻塞Redis实例,在使用时需要重视起来。例如集合元素全量查询HgetAll、Smembers,以及集合的聚合统计操作,比如求交、并、差集。这些操作可以作为Redis的第一个阻塞点:集合全量查询和集合操作。
另外Redis的键值对删除操作也存在潜在的阻塞风险,因为这涉及到一个释放键值对所占用内存的过程。释放内存只是第一步,为了更加高效得管理内存空间,在应用程序释放内存时,操作系统需要把释放掉的内存块插入一个空闲内存块的链表,以便后续进行管理和再分配。这个过程本来需要一定的时间,而且会阻塞当前释放内存的应用程序。所以如果一下子释放掉了大量内存,空闲内存块链表操作时间就会增加,相应地会造成Redis主线程的阻塞。比如删除一个有100万元素的Hash集合类型,大概需要2s的耗时。所以BigKey就是Redis实例的第二个阻塞点。
涉及到与客户端交互的最后一个阻塞点,就是FlushDB和FlushAll等清空数据库的操作,因为它涉及到删除和释放所有的键值对。
【2】与磁盘交互时的阻塞点
磁盘IO一般都是比较费时费力的,Redis开发者早就认识到了磁盘IO会带来阻塞,所以就把Redis进一步设计为采用子进程的方式来生成RDB快照文件,以及执行AOF日志重写操作。这样一来,这两个耗时的操作就由子进程负责执行,慢速的磁盘IO就不会阻塞主线程了。但是Redis直接记录AOF日志时,会根据不同的写回策略对数据做落盘保存。一个同步写磁盘的耗时大约是1-2ms,如果有大量的写操作需要记录在AOF日志中,并同步写回的话,就会阻塞主线程了。
【3】主从节点交互时的阻塞点
在主从集群中,主库需要生成RDB文件,并传输给从库。主库在复制的过程中,创建和传输RDB文件都是由子进程来完成的,不会阻塞主线程。但是,对于从库来说,它在接收了RDB文件后,需要使用FlushDB命令来清空当前数据库。此外,从库在清空数据库后,还需要把RDB文件加载到内存,这个过程的快慢与RDB文件的大小有关,RDB文件越大,加载过程越慢。所以,加载 RDB 文件就成为了 Redis 的第五个阻塞点。
【4】切片集群实例交互时的阻塞点
当我们部署Redis切片集群时,每个Redis实例上分配的哈希槽信息需要在不同实例间进行传递,同时,当需要进行负载均衡或者有实例增删时,数据会在不同的实例间进行迁移。不过,哈希槽的信息量不大,并且数据迁移是渐进式执行的。不过如果使用的是Redis Cluster方案同步迁移,而且迁移的正好是BigKey的话,就会阻塞Redis主线程了。
【5】哪些阻塞点可以异步执行
如果一个操作能够被异步执行,就意味着它不会Redis主线程关键路径上的操作。关键路径上的操作是指:客户端把请求发给Redis,等待Redis返回数据结构的操作,客户端需要拿去处理。
对于Redis来说,读操作就是最典型的关键路径上的操作。所以Redis实例的集合全量查询以及聚合操作是不能异步执行的。
至于bigKey的删除操作以及清空数据库的操作,都是对数据做删除,并不在关键路径上。因此,我们可以使用后台子线程来异步执行删除操作。
对于AOF日志同步写操作,为了保证数据可靠性,Redis实例需要保证AOF日志中的操作记录已经落盘,这个操作虽然需要实例等待,但它不会返回具体的数据结果给实例。所以,我们也可以启动一个子线程来执行AOF日志的同步写,而不用让主线程等待AOF日志的写完成。
对于从库加载RDB文件这个阻塞点,从库要想对客户端提供服务,就必须把RDB文件加载完成。所以这个操作也是关键路径上的操作,我们必须要让从库的主线程来执行。
【6】异步的子线程机制
Redis的主线程启动后,会使用操作系统提供的pthread_create函数创建三个子线程,分别让它们负责:AOF日志落盘操作、键值对删除操作、文件关闭的异步执行。
主线程通过一个链表形式的任务队列和子线程进行交互。当收到键值对删除和清空数据库操作时,主线程会把这个操作封装成一个任务,放入到任务队列中,然后返回客户端一个完成信息,表明删除已经完成。但实际上,这个时候删除还没有执行,等到后台子线程从任务队列中读取任务后,才开始执行键值对删除操作,并释放相应的内存空间。
和惰性删除类似的,当AOF日志配置成everysec选项后,主线程会把AOF写日志操作封装成一个任务,放入到任务队列中。后台子线程读取任务后,开始执行写入AOF日志操作。