Redis代码阅读3--Redis网络监听(3)

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 是介绍Redis网络监听的最后一篇文章,着重分析定时时间处理函数serverCron,这个函数其实已经和网络监听没多大关系了,当时因为其绑定在Redis自定义的事件库的定时事件上,所以放到一起来讲。


是介绍Redis网络监听的最后一篇文章,着重分析定时时间处理函数serverCron,这个函数其实已经和网络监听没多大关系了,当时因为其绑定在Redis自定义的事件库的定时事件上,所以放到一起来讲。serverCron的这个函数对Redis的正常运行来说很重要,对于Redis的使用者来说,最重要的就是能够迅速直观地看到Redis的当前的运行状况(keys,sizes,memory等),serverCron就能够使用户得知这些信息,此外,serverCron这个方法定时周期地运行,还承担了AOF Write,VM Swap,BGSAVE,Rehash的操作,使得Redis的运行更加平稳。还是来直接通过代码来分析:

C代码 复制代码  收藏代码
  1. int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {  
  2.     int j, loops = server.cronloops;  
  3.     REDIS_NOTUSED(eventLoop);  
  4.     REDIS_NOTUSED(id);  
  5.     REDIS_NOTUSED(clientData);  
  6.   
  7.     /* We take a cached value of the unix time in the global state because 
  8.      * with virtual memory and aging there is to store the current time 
  9.      * in objects at every object access, and accuracy is not needed. 
  10.      * To access a global var is faster than calling time(NULL) */  
  11.     server.unixtime = time(NULL);  
  12.     /* We have just 22 bits per object for LRU information. 
  13.      * So we use an (eventually wrapping) LRU clock with 10 seconds resolution. 
  14.      * 2^22 bits with 10 seconds resoluton is more or less 1.5 years. 
  15.      * 
  16.      * Note that even if this will wrap after 1.5 years it's not a problem, 
  17.      * everything will still work but just some object will appear younger 
  18.      * to Redis. But for this to happen a given object should never be touched 
  19.      * for 1.5 years. 
  20.      * 
  21.      * Note that you can change the resolution altering the 
  22.      * REDIS_LRU_CLOCK_RESOLUTION define. 
  23.      */  
  24.     updateLRUClock();  
  25.   
  26.     /* We received a SIGTERM, shutting down here in a safe way, as it is 
  27.      * not ok doing so inside the signal handler. */  
  28.     if (server.shutdown_asap) {  
  29.         if (prepareForShutdown() == REDIS_OK) exit(0);  
  30.         redisLog(REDIS_WARNING,"SIGTERM received but errors trying to shut down the server, check the logs for more information");  
  31.     }  
  32.   
  33.     /* Show some info about non-empty databases */  
  34.     for (j = 0; j < server.dbnum; j++) {  
  35.         long long size, used, vkeys;  
  36.   
  37.         size = dictSlots(server.db[j].dict);  
  38.         used = dictSize(server.db[j].dict);  
  39.         vkeys = dictSize(server.db[j].expires);  
  40.         if (!(loops % 50) && (used || vkeys)) {  
  41.             redisLog(REDIS_VERBOSE,"DB %d: %lld keys (%lld volatile) in %lld slots HT.",j,used,vkeys,size);  
  42.             /* dictPrintStats(server.dict); */  
  43.         }  
  44.     }  
  45.   
  46.     /* We don't want to resize the hash tables while a bacground saving 
  47.      * is in progress: the saving child is created using fork() that is 
  48.      * implemented with a copy-on-write semantic in most modern systems, so 
  49.      * if we resize the HT while there is the saving child at work actually 
  50.      * a lot of memory movements in the parent will cause a lot of pages 
  51.      * copied. */  
  52.     if (server.bgsavechildpid == -1 && server.bgrewritechildpid == -1) {  
  53.         if (!(loops % 10)) tryResizeHashTables();  
  54.         if (server.activerehashing) incrementallyRehash();  
  55.     }  
  56.   
  57.     /* Show information about connected clients */  
  58.     if (!(loops % 50)) {  
  59.         redisLog(REDIS_VERBOSE,"%d clients connected (%d slaves), %zu bytes in use",  
  60.             listLength(server.clients)-listLength(server.slaves),  
  61.             listLength(server.slaves),  
  62.             zmalloc_used_memory());  
  63.     }  
  64.   
  65.     /* Close connections of timedout clients */  
  66.     if ((server.maxidletime && !(loops % 100)) || server.bpop_blocked_clients)  
  67.         closeTimedoutClients();  
  68.   
  69.     /* Check if a background saving or AOF rewrite in progress terminated */  
  70.     if (server.bgsavechildpid != -1 || server.bgrewritechildpid != -1) {  
  71.         int statloc;  
  72.         pid_t pid;  
  73.   
  74.         if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {  
  75.             if (pid == server.bgsavechildpid) {  
  76.                 backgroundSaveDoneHandler(statloc);  
  77.             } else {  
  78.                 backgroundRewriteDoneHandler(statloc);  
  79.             }  
  80.             updateDictResizePolicy();  
  81.         }  
  82.     } else {  
  83.         /* If there is not a background saving in progress check if 
  84.          * we have to save now */  
  85.          time_t now = time(NULL);  
  86.          for (j = 0; j < server.saveparamslen; j++) {  
  87.             struct saveparam *sp = server.saveparams+j;  
  88.   
  89.             if (server.dirty >= sp->changes &&  
  90.                 now-server.lastsave > sp->seconds) {  
  91.                 redisLog(REDIS_NOTICE,"%d changes in %d seconds. Saving...",  
  92.                     sp->changes, sp->seconds);  
  93.                 rdbSaveBackground(server.dbfilename);  
  94.                 break;  
  95.             }  
  96.          }  
  97.     }  
  98.   
  99.     /* Expire a few keys per cycle, only if this is a master. 
  100.      * On slaves we wait for DEL operations synthesized by the master 
  101.      * in order to guarantee a strict consistency. */  
  102.     if (server.masterhost == NULL) activeExpireCycle();  
  103.   
  104.     /* Swap a few keys on disk if we are over the memory limit and VM 
  105.      * is enbled. Try to free objects from the free list first. */  
  106.     if (vmCanSwapOut()) {  
  107.         while (server.vm_enabled && zmalloc_used_memory() >  
  108.                 server.vm_max_memory)  
  109.         {  
  110.             int retval = (server.vm_max_threads == 0) ?  
  111.                         vmSwapOneObjectBlocking() :  
  112.                         vmSwapOneObjectThreaded();  
  113.             if (retval == REDIS_ERR && !(loops % 300) &&  
  114.                 zmalloc_used_memory() >  
  115.                 (server.vm_max_memory+server.vm_max_memory/10))  
  116.             {  
  117.                 redisLog(REDIS_WARNING,"WARNING: vm-max-memory limit exceeded by more than 10%% but unable to swap more objects out!");  
  118.             }  
  119.             /* Note that when using threade I/O we free just one object, 
  120.              * because anyway when the I/O thread in charge to swap this 
  121.              * object out will finish, the handler of completed jobs 
  122.              * will try to swap more objects if we are still out of memory. */  
  123.             if (retval == REDIS_ERR || server.vm_max_threads > 0) break;  
  124.         }  
  125.     }  
  126.   
  127.     /* Replication cron function -- used to reconnect to master and 
  128.      * to detect transfer failures. */  
  129.     if (!(loops % 10)) replicationCron();  
  130.   
  131.     server.cronloops++;  
  132.     return 100;  
  133. }  
  134.   
  135. /* This function gets called every time Redis is entering the 
  136.  * main loop of the event driven library, that is, before to sleep 
  137.  * for ready file descriptors. */  
  138. void beforeSleep(struct aeEventLoop *eventLoop) {  
  139.     REDIS_NOTUSED(eventLoop);  
  140.     listNode *ln;  
  141.     redisClient *c;  
  142.   
  143.     /* Awake clients that got all the swapped keys they requested */  
  144.     if (server.vm_enabled && listLength(server.io_ready_clients)) {  
  145.         listIter li;  
  146.   
  147.         listRewind(server.io_ready_clients,&li);  
  148.         while((ln = listNext(&li))) {  
  149.             c = ln->value;  
  150.             struct redisCommand *cmd;  
  151.   
  152.             /* Resume the client. */  
  153.             listDelNode(server.io_ready_clients,ln);  
  154.             c->flags &= (~REDIS_IO_WAIT);  
  155.             server.vm_blocked_clients--;  
  156.             aeCreateFileEvent(server.el, c->fd, AE_READABLE,  
  157.                 readQueryFromClient, c);  
  158.             cmd = lookupCommand(c->argv[0]->ptr);  
  159.             redisAssert(cmd != NULL);  
  160.             call(c,cmd);  
  161.             resetClient(c);  
  162.             /* There may be more data to process in the input buffer. */  
  163.             if (c->querybuf && sdslen(c->querybuf) > 0)  
  164.                 processInputBuffer(c);  
  165.         }  
  166.     }  
  167.   
  168.     /* Try to process pending commands for clients that were just unblocked. */  
  169.     while (listLength(server.unblocked_clients)) {  
  170.         ln = listFirst(server.unblocked_clients);  
  171.         redisAssert(ln != NULL);  
  172.         c = ln->value;  
  173.         listDelNode(server.unblocked_clients,ln);  
  174.         c->flags &= ~REDIS_UNBLOCKED;  
  175.   
  176.         /* Process remaining data in the input buffer. */  
  177.         if (c->querybuf && sdslen(c->querybuf) > 0)  
  178.             processInputBuffer(c);  
  179.     }  
  180.   
  181.     /* Write the AOF buffer on disk */  
  182.     flushAppendOnlyFile();  
  183. }  
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    int j, loops = server.cronloops;
    REDIS_NOTUSED(eventLoop);
    REDIS_NOTUSED(id);
    REDIS_NOTUSED(clientData);

    /* We take a cached value of the unix time in the global state because
     * with virtual memory and aging there is to store the current time
     * in objects at every object access, and accuracy is not needed.
     * To access a global var is faster than calling time(NULL) */
    server.unixtime = time(NULL);
    /* We have just 22 bits per object for LRU information.
     * So we use an (eventually wrapping) LRU clock with 10 seconds resolution.
     * 2^22 bits with 10 seconds resoluton is more or less 1.5 years.
     *
     * Note that even if this will wrap after 1.5 years it's not a problem,
     * everything will still work but just some object will appear younger
     * to Redis. But for this to happen a given object should never be touched
     * for 1.5 years.
     *
     * Note that you can change the resolution altering the
     * REDIS_LRU_CLOCK_RESOLUTION define.
     */
    updateLRUClock();

    /* We received a SIGTERM, shutting down here in a safe way, as it is
     * not ok doing so inside the signal handler. */
    if (server.shutdown_asap) {
        if (prepareForShutdown() == REDIS_OK) exit(0);
        redisLog(REDIS_WARNING,"SIGTERM received but errors trying to shut down the server, check the logs for more information");
    }

    /* Show some info about non-empty databases */
    for (j = 0; j < server.dbnum; j++) {
        long long size, used, vkeys;

        size = dictSlots(server.db[j].dict);
        used = dictSize(server.db[j].dict);
        vkeys = dictSize(server.db[j].expires);
        if (!(loops % 50) && (used || vkeys)) {
            redisLog(REDIS_VERBOSE,"DB %d: %lld keys (%lld volatile) in %lld slots HT.",j,used,vkeys,size);
            /* dictPrintStats(server.dict); */
        }
    }

    /* We don't want to resize the hash tables while a bacground saving
     * is in progress: the saving child is created using fork() that is
     * implemented with a copy-on-write semantic in most modern systems, so
     * if we resize the HT while there is the saving child at work actually
     * a lot of memory movements in the parent will cause a lot of pages
     * copied. */
    if (server.bgsavechildpid == -1 && server.bgrewritechildpid == -1) {
        if (!(loops % 10)) tryResizeHashTables();
        if (server.activerehashing) incrementallyRehash();
    }

    /* Show information about connected clients */
    if (!(loops % 50)) {
        redisLog(REDIS_VERBOSE,"%d clients connected (%d slaves), %zu bytes in use",
            listLength(server.clients)-listLength(server.slaves),
            listLength(server.slaves),
            zmalloc_used_memory());
    }

    /* Close connections of timedout clients */
    if ((server.maxidletime && !(loops % 100)) || server.bpop_blocked_clients)
        closeTimedoutClients();

    /* Check if a background saving or AOF rewrite in progress terminated */
    if (server.bgsavechildpid != -1 || server.bgrewritechildpid != -1) {
        int statloc;
        pid_t pid;

        if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
            if (pid == server.bgsavechildpid) {
                backgroundSaveDoneHandler(statloc);
            } else {
                backgroundRewriteDoneHandler(statloc);
            }
            updateDictResizePolicy();
        }
    } else {
        /* If there is not a background saving in progress check if
         * we have to save now */
         time_t now = time(NULL);
         for (j = 0; j < server.saveparamslen; j++) {
            struct saveparam *sp = server.saveparams+j;

            if (server.dirty >= sp->changes &&
                now-server.lastsave > sp->seconds) {
                redisLog(REDIS_NOTICE,"%d changes in %d seconds. Saving...",
                    sp->changes, sp->seconds);
                rdbSaveBackground(server.dbfilename);
                break;
            }
         }
    }

    /* Expire a few keys per cycle, only if this is a master.
     * On slaves we wait for DEL operations synthesized by the master
     * in order to guarantee a strict consistency. */
    if (server.masterhost == NULL) activeExpireCycle();

    /* Swap a few keys on disk if we are over the memory limit and VM
     * is enbled. Try to free objects from the free list first. */
    if (vmCanSwapOut()) {
        while (server.vm_enabled && zmalloc_used_memory() >
                server.vm_max_memory)
        {
            int retval = (server.vm_max_threads == 0) ?
                        vmSwapOneObjectBlocking() :
                        vmSwapOneObjectThreaded();
            if (retval == REDIS_ERR && !(loops % 300) &&
                zmalloc_used_memory() >
                (server.vm_max_memory+server.vm_max_memory/10))
            {
                redisLog(REDIS_WARNING,"WARNING: vm-max-memory limit exceeded by more than 10%% but unable to swap more objects out!");
            }
            /* Note that when using threade I/O we free just one object,
             * because anyway when the I/O thread in charge to swap this
             * object out will finish, the handler of completed jobs
             * will try to swap more objects if we are still out of memory. */
            if (retval == REDIS_ERR || server.vm_max_threads > 0) break;
        }
    }

    /* Replication cron function -- used to reconnect to master and
     * to detect transfer failures. */
    if (!(loops % 10)) replicationCron();

    server.cronloops++;
    return 100;
}

/* This function gets called every time Redis is entering the
 * main loop of the event driven library, that is, before to sleep
 * for ready file descriptors. */
void beforeSleep(struct aeEventLoop *eventLoop) {
    REDIS_NOTUSED(eventLoop);
    listNode *ln;
    redisClient *c;

    /* Awake clients that got all the swapped keys they requested */
    if (server.vm_enabled && listLength(server.io_ready_clients)) {
        listIter li;

        listRewind(server.io_ready_clients,&li);
        while((ln = listNext(&li))) {
            c = ln->value;
            struct redisCommand *cmd;

            /* Resume the client. */
            listDelNode(server.io_ready_clients,ln);
            c->flags &= (~REDIS_IO_WAIT);
            server.vm_blocked_clients--;
            aeCreateFileEvent(server.el, c->fd, AE_READABLE,
                readQueryFromClient, c);
            cmd = lookupCommand(c->argv[0]->ptr);
            redisAssert(cmd != NULL);
            call(c,cmd);
            resetClient(c);
            /* There may be more data to process in the input buffer. */
            if (c->querybuf && sdslen(c->querybuf) > 0)
                processInputBuffer(c);
        }
    }

    /* Try to process pending commands for clients that were just unblocked. */
    while (listLength(server.unblocked_clients)) {
        ln = listFirst(server.unblocked_clients);
        redisAssert(ln != NULL);
        c = ln->value;
        listDelNode(server.unblocked_clients,ln);
        c->flags &= ~REDIS_UNBLOCKED;

        /* Process remaining data in the input buffer. */
        if (c->querybuf && sdslen(c->querybuf) > 0)
            processInputBuffer(c);
    }

    /* Write the AOF buffer on disk */
    flushAppendOnlyFile();
}

 i.    首先将server.cronloops的值赋给loops,server.cronloops指的是serverCron函数的运行次数,每运行一次serverCron函数,server.cronloops++,server.cronloops的内部执行逻辑随着server.cronloops值的不同而改变;
ii.    用server.unixtime = time(NULL)来保存当前时间,因为在virtual memory and aging的时候,需要知道每次Object的access时间,但是这个时间不需要很精确,所以通过全局变量来获取时间比调用time(NULL)快多了;
iii.    记录Redis的最大内存使用量;如果收到了SIGTERM信号,则试图终止Redis
iv.    serverCron方法每运行50次显示Redis内各个非空的DB的使用情况(used,keys,sizes)及当前连接的clients,使用的内存大小;
v.    serverCron方法每运行10次,将试图进行一次Rehash,如果一个a bacground saving正在进行,则不进行rehash,以免造成部分数据丢失;
vi.    关闭timeout的clients;
vii.    如果在执行BGSAVE期间,client执行了bgrewriteaof这个命令,则在serverCron将开始执行a scheduled AOF rewrite
viii.    如果当前Redis正在进行BGSAVE或者AOF rewrite,则check BGSAVE或者AOF rewrite是否已经终止,如果终止则调用相应的函数处理(backgroundSaveDoneHandler/backgroundRewriteDoneHandler),如果当前没有BGSAVE或者AOF rewrite操作,则判断是否进行此类操作,如果需要,则触发此类操作;
ix.    如果有AOF buffer flush操作被暂停了,则每次调用serverCron的时候,恢复AOF buffer flush操作
x.    如果是Master,则周期性地使某些key(随即挑选的)过期,注意这个操作仅仅只针对Master,如果是slaves,则只有通过master的del操作来同步key,以做到强一致性;
xi.    VM的Swap操作
xii.    每运行10次,进行replicationCron,如果存在slaves的话
xiii.    返回100,表示serverCron方法每100毫秒被调用一次,这一点在processTimeEvent这个方法里得以体现:

C代码 复制代码  收藏代码
  1. if (retval != AE_NOMORE) {  
  2.                 aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);  
  3.             } else {  
  4.                 aeDeleteTimeEvent(eventLoop, id);  
  5.             }  
if (retval != AE_NOMORE) {
                aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
            } else {
                aeDeleteTimeEvent(eventLoop, id);
            }
            

通过上面的分析,ServerCron侧重在Rehash,VM Swap, AOF write,BGSAVE等操作,而这些操作都是耗时,而且影响Redis对Clients的响应速度的,因此我们在实际应用的时候可以根据具体情况通过改变类似这样的操作:”loops % 10“来决定上述耗时操作的执行频率,有空我会测试下在不同频率下,redis在压力测试下的性能。

 

此次, Redis的网络监听部分都介绍完了。再回过头来看前面提到的几个问题:

1.         Redis支持 epoll select kquque,,通过配置文件来决定采取哪一种

2.         支持文件读写事件和定时事件

3.         采用数组来维护文件事件,链表来保存定时事件(在查找定时事件时,性能不高,有待提高)

4.         Redis Server单线程响应事件,按照先后顺序来响应事件,因此单台 Redis服务器的吞吐量会随着连接的 clients越来越多而下降,可以通过增加更多的 Redis服务器来解决这个问题

5.         Redis在很多代码里都考虑到了尽快地响应各种事件,如在 aeProcessEvent里面,轮询的 wait时间等于当前时间和最近的定时事件响应时间的差值;每次进入轮询 wait之前,在 beforesleep方法里先响应刚刚 unblock clients等。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
3月前
|
NoSQL Redis
Redis原理之网络通信协议笔记
1. RESP协议 ​2. 自定义Socket连接Redis
|
3月前
|
NoSQL Linux Redis
Redis原理之网络模型笔记
Redis采用单线程模型,这意味着一个Redis服务器在任何时刻都只会处理一个请求。Redis的网络模型涉及到阻塞I/O(Blocking I/O)、非阻塞I/O(Non-blocking I/O)、I/O多路复用(I/O Multiplexing)、信号驱动I/O(Signal-driven I/O)以及异步I/O(Asynchronous I/O)。
|
3月前
|
缓存 NoSQL 应用服务中间件
2.2.2 redis,memcached,nginx网络组件
2.2.2 redis,memcached,nginx网络组件
|
3月前
|
NoSQL Java 应用服务中间件
4.网络设计与redis、memcached、nginx组件(二)
4.网络设计与redis、memcached、nginx组件(二)
28 0
|
3月前
|
存储 NoSQL 应用服务中间件
4.网络设计与redis、memcached、nginx组件(一)
4.网络设计与redis、memcached、nginx组件(一)
76 0
|
1天前
|
存储 NoSQL Linux
Redis入门到通关之Redis5种网络模型详解
Redis入门到通关之Redis5种网络模型详解
|
2月前
|
NoSQL Java Redis
Spring boot 实现监听 Redis key 失效事件
【2月更文挑战第2天】 Spring boot 实现监听 Redis key 失效事件
79 0
|
2月前
|
NoSQL Java Redis
springboot整合redis过期key监听实现订单过期操作
springboot整合redis过期key监听实现订单过期操作
73 0
|
3月前
|
NoSQL 算法 关系型数据库
redis与mysql的数据一致性问题( 网络分区)
redis与mysql的数据一致性问题( 网络分区)
23 0
|
3月前
|
NoSQL 网络协议 关系型数据库
Redis(二)网络协议和异步方式(乐观锁&悲观锁、事务)
Redis(二)网络协议和异步方式(乐观锁&悲观锁、事务)
36 0

热门文章

最新文章