上篇说到epoll解决了轮询的方式,具体是怎么解决的?
在使用 select 或 poll 函数的时候,创建好文件描述符集合或 pollfd 数组后,就可以往数组中添加我们需要监听的文件描述符。
但是对于 epoll 机制来说,我们则需要先调用 epoll_create 函数,创建一个 epoll 实例。这个 epoll 实例内部维护了两个结构,分别是记录要监听的文件描述符和已经就绪的文件描述符,而对于已经就绪的文件描述符来说,它们会被返回给用户程序进行处理。
所以,我们在使用 epoll 机制时,就不用像使用 select 和 poll 一样,遍历查询哪些文件描述符已经就绪了。这样一来, epoll 的效率就比 select 和 poll 有了更高的提升。
因为 Redis 处理请求是「单线程」,当数据处理的时候,会有哪些阻塞点?
1,Redis 中涉及集合的操作复杂度通常为 O(N),我们要在使用时重视起来。例如集合元素全量查询操作 HGETALL、SMEMBERS,以及集合的聚合统计操作,例如求交、并和差集。这些操作可以作为 Redis 的第一个阻塞点:集合全量查询和聚合操作。
2,删除操作的本质是要释放键值对占用的内存空间,为了更加高效地管理内存空间,在应用程序释放内存时,操作系统需要把释放掉的内存块插入一个空闲内存块的链表,以便后续进行管理和再分配。bigkey 删除操作就是 Redis 的第二个阻塞点
3,清空数据库(例如 FLUSHDB 和 FLUSHALL 操作)必然也是一个潜在的阻塞风险,因为它涉及到删除和释放所有的键值对。所以,这就是 Redis 的第三个阻塞点:清空数据库。
4,Redis 直接记录 AOF 日志时,会根据不同的写回策略对数据做落盘保存。一个同步写磁盘的操作的耗时大约是 1~2ms,如果有大量的写操作需要记录在 AOF 日志中,并同步写回的话,就会阻塞主线程了。这就得到了 Redis 的第四个阻塞点了:AOF 日志同步写。
5,从库在清空当前数据库后,还需要把 RDB 文件加载到内存,这个过程的快慢和 RDB 文件的大小密切相关,RDB 文件越大,加载过程越慢,所以,加载 RDB 文件就成为了 Redis 的第五个阻塞点。
对于 Redis 的五大阻塞点来说,除了“集合全量查询和聚合操作”和“从库加载 RDB 文件”,其他三个阻塞点涉及的操作都不在关键路径上,所以,我们可以使用 Redis 的异步子线程机制来实现 bigkey 删除,清空数据库,以及 AOF 日志同步写。
Redis 主线程启动后,会使用操作系统提供的 pthread_create 函数创建 3 个子线程,分别由它们负责 AOF 日志写操作、键值对删除以及文件关闭的异步执行。
主线程通过一个链表形式的任务队列和子线程进行交互。当收到键值对删除和清空数据库的操作时,主线程会把这个操作封装成一个任务,放入到任务队列中,然后给客户端返回一个完成信息,表明删除已经完成。
但实际上,这个时候删除还没有执行,等到后台子线程从任务队列中读取任务后,才开始实际删除键值对,并释放相应的内存空间。因此,我们把这种异步删除也称为惰性删除(lazy free)。此时,删除或清空操作不会阻塞主线程,这就避免了对主线程的性能影响。
异步的键值对删除和数据库清空操作是 Redis 4.0 后提供的功能,Redis 也提供了新的命令来执行这两个操作。
键值对删除:当你的集合类型中有大量元素(例如有百万级别或千万级别元素)需要删除时,我建议你使用 UNLINK 命令。
清空数据库:可以在 FLUSHDB 和 FLUSHALL 命令后加上 ASYNC 选项,这样就可以让后台子线程异步地清空数据库
如果你使用的是 4.0 之前的版本,当你遇到 bigkey 删除时,我给你个小建议:先使用集合类型提供的 SCAN 命令读取数据,然后再进行删除。因为用 SCAN 命令可以每次只读取一部分数据并进行删除,这样可以避免一次性删除大量 key 给主线程带来的阻塞。
(成也萧何败也萧何,因为 Redis 处理请求是「单线程」,所以如果有任意请求在 Server 端发生耗时(例如操作 bigkey,或一次请求数据过多),就会导致后面的请求发生「排队」,业务端就会感知到延迟增大,性能下降)
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:主从全量同步,从库清空数据库时异步释放内存
e) lazyfree-lazy-user-del:Redis 6.0提供选项。打开这个选项后,使用DEL和UNLINK就没有区别了。
3、即使开启了lazy-free,如果直接使用DEL命令还是会同步删除key,只有使用UNLINK命令才会可能异步删除key。
4、这也是最关键的一点,上面提到开启lazy-free的场景,除了replica-lazy-flush之外,其他情况都只是可能去异步释放key的内存,并不是每次必定异步释放内存的。
极客时间:Redis 源码剖析与实战-09,Redis 核心技术与实战-16
网友评论