Redis之EventLoop分析

以下文章基于Redis3.0以上版本源码进行分析解读


摘要

  1. redis服务初始化分为三个阶段
    1.1. 服务配置
    1.2. 服务初始化
    1.3. 启动event loop
  1. redis服务端所有的操作都会被封装为event, 主要有两个类型的event
    2.1. IO Event 封装了在线的command请求
    2.2. Time Event 为1s执行一次的计划任务, 会处理以下逻辑
    -- 2.2.1. 过期key的清理
    -- 2.2.2. 内部的调用性能统计
    -- 2.2.3. DB对象的rehash扩容
    -- 2.2.4. RDB&AOF的数据持久化(如果有必要)
    -- 2.2.5. ...及其他一些检查操作
  1. event loop为单线程处理
    3.1. 所有event的处理因为是单线程顺序处理, 所以在操作DB等内存数据时是无锁的
    3.1. 在每个process循环中都尝试处理所有已加入队列的io event和time event
    3.2. io event和time event是在同一个loop processor中顺序执行
    3.3. event loop中process的时延直接决定了redis server的吞吐量

先分析一下Event Loop初始化过程中做了什么(代码为server.c#main的常驻进程启动逻辑)

  1. 创建event loop
   server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
   if (server.el == NULL) {
       serverLog(LL_WARNING,
           "Failed creating the event loop. Error message: '%s'",
           strerror(errno));
       exit(1);
   }
  1. 启动event loop
   // 每个event执行的前置函数
   aeSetBeforeSleepProc(server.el,beforeSleep);
   // 每个event执行完成后的后置函数
   aeSetAfterSleepProc(server.el,afterSleep);
   // 会真正启动event loop的处理, 一旦调用当前线程将block直至系统退出
   aeMain(server.el);
   // 系统退出前的关闭event loop
   aeDeleteEventLoop(server.el);
   return 0;

Event Loop是Redis Server调度的核心

EventLoop很简单, 不多说上代码

EventLoop很简单, 核心主要是event selector 和 event processor

  1. event loop中保留了全局待处理events链表(io event 和 time event)
typedef struct aeEventLoop {
   int maxfd;   /* highest file descriptor currently registered */
   int setsize; /* max number of file descriptors tracked */
   long long timeEventNextId;
   time_t lastTime;     /* Used to detect system clock skew */
   aeFileEvent *events; /* Registered events */
   aeFiredEvent *fired; /* Fired events */
   aeTimeEvent *timeEventHead;
   int stop;
   void *apidata; /* This is used for polling API specific data */
   aeBeforeSleepProc *beforesleep;
   aeBeforeSleepProc *aftersleep;
} aeEventLoop;
  1. event selector 其实就是简单的while true系循环, 执行顺序如下
    2.1 执行event loop前置的钩子函数 beforesleep
    2.2 调用event processor函数: aeProcessEvents执行所有队列中的io event 和 time event
void aeMain(aeEventLoop *eventLoop) {
   eventLoop->stop = 0;
   while (!eventLoop->stop) {
       if (eventLoop->beforesleep != NULL)
           eventLoop->beforesleep(eventLoop);
       aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
   }
}
  1. event processor 执行顺序如下
    3.1 从链表中获取待执行的io event
    3.2 在循环中顺序执行io event
    3.3 check是否有待执行的time event, 如果有会执行time event
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
        int processed = 0, numevents;
        ... 代码省略
       // poll所有带执行event
       /* Call the multiplexing API, will return only on timeout or when
       * some event fires. */
       numevents = aeApiPoll(eventLoop, tvp);

       /* After sleep callback. */
       if (eventLoop->aftersleep != NULL && flags & AE_CALL_AFTER_SLEEP)
           eventLoop->aftersleep(eventLoop);

       // 执行event... 此处代码很多直接忽略
       for (j = 0; j < numevents; j++) {       
          ... 代码省略
          processed++;
      }
      ... 代码省略
      // 在所有io event执行完后, 会check是否有需要执行的time event
      /* Check time events */
      if (flags & AE_TIME_EVENTS)
            processed += processTimeEvents(eventLoop);
}

EventLoop中主要是两个类型的event

  1. 网络io请求event, 即接收到的redis command处理
  2. 定时任务event, 只会在server的配置初始化阶段创建

service.c 中的main函数会注册一个1s执行一次的time event:

   /* Create the timer callback, this is our way to process many background
    * operations incrementally, like clients timeout, eviction of unaccessed
    * expired keys and so forth. */
   if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
       serverPanic("Can't create event loop timers.");
       exit(1);
   }

IO Event的注册及处理逻辑

  1. 服务初始化阶段#创建TCP端口侦听
    1.1 为io请求注册acceptTcpHandler(acceptUnixHandler)
    1.2 acceptTcpHandler(acceptUnixHandler) 将所有请求委派给acceptCommonHandler
  2. 服务运行时, acceptCommonHandler处理所有input io, 并将command交给函数processCommand处理
    重要的事情说三遍, 所有的在线命令处理入口就是processCommand函数
    重要的事情说三遍, 所有的在线命令处理入口就是processCommand函数
    重要的事情说三遍, 所有的在线命令处理入口就是processCommand函数

IO Event的在线处理逻辑分析

其中特别要注意的是对于key侦听的逻辑处理, 这里对性能的影响会在后面的文章中分析

/* If this function gets called we already read a whole
* command, arguments are in the client argv/argc fields.
* processCommand() execute the command or prepare the
* server for a bulk read from the client.
*
* If C_OK is returned the client is still alive and valid and
* other operations can be performed by the caller. Otherwise
* if C_ERR is returned the client was destroyed (i.e. after QUIT). */
int processCommand(client *c) {
   // ... 省略一些前置检查的代码,包括:认证状态;集群状态;内存使用...
   // command的处理的核心逻辑
   /* Exec the command */
   if (c->flags & CLIENT_MULTI &&
       c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
       c->cmd->proc != multiCommand && c->cmd->proc != watchCommand)
   {
       // pipeline command的处理
       queueMultiCommand(c);
       addReply(c,shared.queued);
   } else {
       // 执行command
       call(c,CMD_CALL_FULL);
       c->woff = server.master_repl_offset;

       // 会检查全局的key侦听链表, 并尝试通知到在这些key上侦听的client
       if (listLength(server.ready_keys))
           // 这里对性能的影响会在后面的文章中分析
           handleClientsBlockedOnLists();
   }
   return C_OK;
}

Time Event 的处理逻辑分析

  1. 从注释很清晰的知道, 在定时任务中处理了大量的对于内存和数据定期过期&持久化的工作
    1.1 Active expired keys collection (it is also performed in a lazy way >on
    1.2 lookup).
    1.3 Software watchdog.
    1.4 Update some statistic.
    1.5 Incremental rehashing of the DBs hash tables.
    1.6 Triggering BGSAVE / AOF rewrite, and handling of terminated children.
    1.7 Clients timeout of different kinds.
    1.8 Replication reconnection.
    1.9 Many more...
  2. 需要注意的是, time event的处理是和io event一样在event loop中串行被处理的, 这些批处理任务的逻辑会block在线请求的处理
/* This is our timer interrupt, called server.hz times per second.
* Here is where we do a number of things that need to be done asynchronously.
* For instance:
*
* - Active expired keys collection (it is also performed in a lazy way >on
*   lookup).
* - Software watchdog.
* - Update some statistic.
* - Incremental rehashing of the DBs hash tables.
* - Triggering BGSAVE / AOF rewrite, and handling of terminated children.
* - Clients timeout of different kinds.
* - Replication reconnection.
* - Many more...
*
* Everything directly called here will be called server.hz times per second,
* so in order to throttle execution of things we want to do less frequently
* a macro is used: run_with_period(milliseconds) { .... }
*/

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    // .... 代码省略
}

对于Event Loop的分析告一段落, 后面的文章会在Event Loop分析的基础上, 针对性的对Redis在线服务的性能场景进行进一步的讲解.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,600评论 18 139
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,693评论 2 59
  • 在做这个项目时,一开始并未考虑收集用户的崩溃日志,都是由用户直接反馈崩溃问题给客服或是产品经理,然后再交于开发进行...
    最晴天阅读 1,163评论 0 0
  • 一、探索“为教而学、先研后教”课改模式。 无锡市 蠡 园中学“六助”教学法 课堂改革的方向是:培养学生自主学习的能...
    稳稳的幸福_c9df阅读 166评论 0 0
  • 思路:1,引入jq的一个懒加载插件:jquery.lazyload.js2,不要给图片设置src属性,设置data...
    侯工阅读 2,576评论 0 1