曹工说Redis源码(6)-- redis server 主循环大体流程解析

Redis源码系列的初衷,是帮助我们更好地理解Redis,更懂Redis,而怎么才能懂,光看是不够的,建议跟着下面的这一篇,把环境搭建起来,后续可以自己阅读源码,或者跟着我这边一起阅读。由于我用c也是好几年以前了,些许错误在所难免,希望读者能不吝指出。

曹工说Redis源码(1)-- redis debug环境搭建,使用clion,达到和调试java一样的效果

曹工说Redis源码(2)-- redis server 启动过程解析及简单c语言基础知识补充

曹工说Redis源码(3)-- redis server 启动过程完整解析(中)

曹工说Redis源码(4)-- 通过redis server源码来理解 listen 函数中的 backlog 参数

曹工说Redis源码(5)-- redis server 启动过程解析,以及EventLoop每次处理事件前的前置工作解析(下)

本讲主题

先给大家复习下前面一讲的功课,大家知道,redis 基本是单线程,也就是说,假设我们启动main方法的,是线程A,那么,最终,去处理客户端socket连接、读取客户端请求、以及向客户端socket写数据,也还是线程A。

同时,大家想必也知道,redis 里还是有一些后台任务要做的,比如:

字典的rehash(rehash的意思是,redis 里,字典结构,其实是包含了两个hashtable,一般使用第一个;当需要扩充其size的时候,hashtable[1] 就会扩充内存到扩充后的size,然后,就需要把hashtable[0]里面的数据,全部迁移到 hashtable[1] 来,这个过程,即所谓的rehash),rehash的过程,还是比较耗时的;

redis 里的键,如果设了过期时间,到了过期时间后,这个key,是不是就在redis里不存在了呢?不一定,但是你去访问的时候,肯定是看不到了。但这个怎么做到的呢?难道每次来一个这种key,就设置一个timer,在指定过期时间后执行清除任务吗?这个想来,开销太大了;

所以,其实分了两种策略:

一是redis 给自己开了个周期性的定时任务,就是那种,每隔30s执行一次之类的,在这个任务中,就会去主动检查:设置了过期时间的key的集合,如果发现某个key过期了,直接删除;但是,redis由于其单线程特性,如果遇到过期key特别多的话,就要一直忙着清理过期key了,正事就没法干了(比如处理客户端请求),所以,每次redis执行这种任务的时候,基本就是敷衍了事,得过且过,随机选几个键,删了就算完事。

二是,redis在你真正去get 这个key的时候,才去检查是否过期,如果发现过期了,再删除。这是什么策略?就是懒。所以叫惰性删除。

检查当前的客户端集合,看看哪些是一直空闲,且超过了一定时间的,这部分客户端,被筛选出来,直接干掉,关掉与该客户端之间的长连接。

还有其他一些任务,下边再说。

所以,从上面可知,redis 主要要干两类活,一种是客户端要它干的,比如,我执行个get/set命令,这个优先级比较高;另一类就是例行工作,每隔多久就得干一次。

前面一讲,我们已经讲到了下面这个代码:

void aeMain(aeEventLoop *eventLoop) { eventLoop->stop = 0; while (!eventLoop->stop) { // 如果有需要在事件处理前执行的函数,那么运行它 if (eventLoop->beforesleep != NULL) // 1 eventLoop->beforesleep(eventLoop); // 2 开始处理事件 aeProcessEvents(eventLoop, AE_ALL_EVENTS); } }

1处,我们已经讲完了;本讲,主要讲2处,这个主循环。ok,扯了一堆,let's go!

循环大体流程 获取:当前还有多长时间,到达周期任务的时间点

获取有没有周期任务要执行,如果有,则计算一下,要过多久,才到周期任务的执行时间;把过多久这个时间,算出来后,定义为 timeLeftToScheduledJobTime;如果没有周期任务,这个时间可以定义为null;

如果发现时间已经到了,则表示现在就可以执行这个周期任务了,把timeLeftToScheduledJobTime 设为0

这部分代码,如下所示:

if (eventLoop->maxfd != -1 || ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) { int j; aeTimeEvent *shortest = NULL; // 1 struct timeval tv, *tvp; // 获取最近的时间事件 if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT)) // 2 shortest = aeSearchNearestTimer(eventLoop); if (shortest) { // 如果时间事件存在的话 // 那么根据最近可执行时间事件和现在时间的时间差来决定文件事件的阻塞时间 long now_sec, now_ms; // 计算距今最近的时间事件还要多久才能达到 // 并将该时间距保存在 tv 结构中 /** * 3 获取当前时间,这里把两个long 局部变量的地址传进去了,在里面会去修改它 */ aeGetTime(&now_sec, &now_ms); tvp = &tv; // 4 tvp->tv_sec = shortest->when_sec - now_sec; if (shortest->when_ms < now_ms) { tvp->tv_usec = ((shortest->when_ms + 1000) - now_ms) * 1000; tvp->tv_sec--; } else { tvp->tv_usec = (shortest->when_ms - now_ms) * 1000; } // 5 时间差小于 0 ,说明事件已经可以执行了,将秒和毫秒设为 0 (不阻塞) if (tvp->tv_sec < 0) tvp->tv_sec = 0; if (tvp->tv_usec < 0) tvp->tv_usec = 0; } else { // 执行到这一步,说明没有时间事件 if (flags & AE_DONT_WAIT) { // 6 tv.tv_sec = tv.tv_usec = 0; tvp = &tv; } else { /* Otherwise we can block */ // 7 tvp = NULL; /* wait forever */ } }

1处,定义了一个变量tvp,基本用来存储前面我们说的那个timeLeftToScheduledJobTime

2处,会获取最近的周期任务的时间

3处,获取当前时间,保存到 long now_sec, now_ms

4处,最近的周期任务的时间,减去当前时间,差值保存到tvp->tv_sec

5处,如果最终算出来,时间差为负数,则设为0,表示,这个周期任务现在就可以运行

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zydfpf.html