1. 程式人生 > >Redis原始碼(十)——Redis的客戶端和伺服器

Redis原始碼(十)——Redis的客戶端和伺服器

在前面的部落格中,有些內容已經涉及到了Redis伺服器或者客戶端的一些屬性,如上一篇部落格關於Redis的RDB持久化中關於save選項來設定伺服器的狀態等。那麼接下來這篇部落格中就分析下Redis的客戶端以及伺服器的屬性及操作。

一、Redis客戶端

redis.h中的redisClient結構定義了Redis的客戶端:

/* With multiplexing we need to take per-clientstate.
 * Clientsare taken in a liked list.
 *
 * 因為 I/O 複用的緣故,需要為每個客戶端維持一個狀態。
 *
 * 多個客戶端狀態被伺服器用連結串列連線起來。
 */
typedef struct redisClient {
 
    // 套接字描述符
    int fd;
 
    // 當前正在使用的資料庫
    redisDb*db;
 
    // 當前正在使用的資料庫的 id (號碼)
    intdictid;
 
    // 客戶端的名字
    robj*name;             /* As set by CLIENTSETNAME */
 
    // 查詢緩衝區
    sdsquerybuf;
 
    // 查詢緩衝區長度峰值
    size_tquerybuf_peak;   /* Recent (100ms ormore) peak of querybuf size */
 
    // 引數數量
    intargc;
 
    // 引數物件陣列
    robj**argv;
 
    // 記錄被客戶端執行的命令
    structredisCommand *cmd, *lastcmd;
 
    // 請求的型別:內聯命令還是多條命令
    intreqtype;
 
    // 剩餘未讀取的命令內容數量
    intmultibulklen;       /* number of multibulk arguments left to read */
 
    // 命令內容的長度
    longbulklen;           /* length of bulkargument in multi bulk request */
 
    // 回覆連結串列
    list*reply;
 
    // 回覆連結串列中物件的總大小
   unsigned long reply_bytes; /* Tot bytes of objects in reply list */
 
    // 已傳送位元組,處理 short write 用
    intsentlen;            /* Amount of bytes already sent in thecurrent
                               buffer or objectbeing sent. */
 
    // 建立客戶端的時間
    time_tctime;           /* Client creation time*/
 
    // 客戶端最後一次和伺服器互動的時間
    time_tlastinteraction; /* time of the last interaction, used for timeout */
 
    // 客戶端的輸出緩衝區超過軟性限制的時間
    time_tobuf_soft_limit_reached_time;
 
    // 客戶端狀態標誌
    intflags;              /* REDIS_SLAVE |REDIS_MONITOR | REDIS_MULTI ... */
 
    // 當 server.requirepass 不為 NULL 時
    // 代表認證的狀態
    // 0 代表未認證, 1 代表已認證
    intauthenticated;      /* when requirepassis non-NULL */
 
    // 複製狀態
    intreplstate;          /* replication stateif this is a slave */
    // 用於儲存主伺服器傳來的 RDB 檔案的檔案描述符
    intrepldbfd;           /* replication DBfile descriptor */
 
    // 讀取主伺服器傳來的 RDB 檔案的偏移量
    off_trepldboff;        /* replication DB fileoffset */
    // 主伺服器傳來的 RDB 檔案的大小
    off_trepldbsize;       /* replication DB filesize */
   
    sdsreplpreamble;       /* replication DBpreamble. */
 
    // 主伺服器的複製偏移量
    longlong reploff;      /* replication offsetif this is our master */
    // 從伺服器最後一次傳送 REPLCONF ACK 時的偏移量
    longlong repl_ack_off; /* replication ack offset, if this is a slave */
    // 從伺服器最後一次傳送 REPLCONF ACK 的時間
    longlong repl_ack_time;/* replication ack time, if this is a slave */
    // 主伺服器的 master run ID
    // 儲存在客戶端,用於執行部分重同步
    charreplrunid[REDIS_RUN_ID_SIZE+1]; /* master run id if this is a master */
    // 從伺服器的監聽埠號
    intslave_listening_port; /* As configured with: SLAVECONF listening-port */
 
    // 事務狀態
   multiState mstate;      /*MULTI/EXEC state */
 
    // 阻塞型別
    intbtype;              /* Type of blockingop if REDIS_BLOCKED. */
    // 阻塞狀態
   blockingState bpop;     /*blocking state */
 
    // 最後被寫入的全域性複製偏移量
    longlong woff;         /* Last write globalreplication offset. */
 
    // 被監視的鍵
    list*watched_keys;     /* Keys WATCHED forMULTI/EXEC CAS */
 
    // 這個字典記錄了客戶端所有訂閱的頻道
    // 鍵為頻道名字,值為 NULL
    // 也即是,一個頻道的集合
    dict*pubsub_channels;  /* channels a clientis interested in (SUBSCRIBE) */
 
    // 連結串列,包含多個 pubsubPattern 結構
    // 記錄了所有訂閱頻道的客戶端的資訊
    // 新 pubsubPattern 結構總是被新增到表尾
    list*pubsub_patterns;  /* patterns a client isinterested in (SUBSCRIBE) */
    sdspeerid;             /* Cached peer ID. */
 
    /*Response buffer */
    // 回覆偏移量
    intbufpos;
    // 回覆緩衝區
    charbuf[REDIS_REPLY_CHUNK_BYTES];
 
} redisClient;

Redis是一對多的伺服器,可以有多個客戶端,在Redis伺服器中維護了一個clients連結串列,連結串列中儲存著與伺服器連線的所有客戶端狀態。redisClient結構中主要的屬性如下:

1. fd套接字描述符

fd屬性是客戶端正在使用的套接字描述符,發的的取值可以是-1或者大於-1的整數,這與客戶端的屬性有關:偽客戶端(fake client)fd屬性為-1,目前Redis伺服器在兩個地方用到了偽客戶端,一個用於載入AOF檔案並還原資料庫狀態,另一個用於執行Lua指令碼中的Redis命令;普通客戶端的fd大於-1,普通的客戶端使用套接字來與伺服器進行通訊。

2. flags客戶端標誌

flags屬性可以是單個標誌,也可以“或”多個標誌,每個標誌都代表一個常量:

/* Client flags */
#define REDIS_SLAVE (1<<0)   /* This client is a slave server */
#define REDIS_MASTER (1<<1)  /* This client is a master server */
#define REDIS_MONITOR (1<<2) /* This clientis a slave monitor, see MONITOR */
#define REDIS_MULTI (1<<3)   /* This client is in a MULTI context */
#define REDIS_BLOCKED (1<<4) /* The clientis waiting in a blocking operation */
#define REDIS_DIRTY_CAS (1<<5) /* Watchedkeys modified. EXEC will fail. */
#define REDIS_CLOSE_AFTER_REPLY (1<<6) /*Close after writing entire reply. */
#define REDIS_UNBLOCKED (1<<7) /* Thisclient was unblocked and is stored in
                                 server.unblocked_clients */
#define REDIS_LUA_CLIENT (1<<8) /* This isa non connected client used by Lua */
#define REDIS_ASKING (1<<9)     /* Client issued the ASKING command */
#define REDIS_CLOSE_ASAP (1<<10)/* Closethis client ASAP */
#define REDIS_UNIX_SOCKET (1<<11) /* Clientconnected via Unix domain socket */
#define REDIS_DIRTY_EXEC (1<<12)  /* EXEC will fail for errors while queueing*/
#define REDIS_MASTER_FORCE_REPLY(1<<13)  /* Queue replies even ifis master */
#define REDIS_FORCE_AOF (1<<14)   /* Force AOF propagation of current cmd. */
#define REDIS_FORCE_REPL (1<<15)  /* Force replication of current cmd. */
#define REDIS_PRE_PSYNC (1<<16)   /* Instance don't understand PSYNC. */
#define REDIS_READONLY (1<<17)    /* Cluster client is in read-only state. */

3. querybuf查詢緩衝區

查詢緩衝區儲存著客戶端傳送的命令請求

4. argv命令引數

當查詢緩衝區儲存客戶端傳送的命令請求後,伺服器會對命令請求的內容進行分析,得出的命令引數會放到argv陣列中,命令引數的長度會儲存在argc中。argv是一個數組,陣列第0位放著執行的命令,後面則放著對應命令的引數,例如客戶端傳送“SET hello world”,那麼argv陣列中的狀態如下:

argv[0]

argv[1]

argv[2]

StringObject

“SET”

StringObject

“hello”

StringObject

“world”

此時argc的值為3.

5. cmd命令

當獲取命令的argv屬性及argc屬性後,伺服器將根據argv[0]對應的命令在命令表中查詢對應的命令實現。cmd是一個redisCommand結構

/*
 * Redis 命令
 */
struct redisCommand {
 
    // 命令名字
    char*name;
 
    // 實現函式
   redisCommandProc *proc;
 
    // 引數個數
    intarity;
 
    // 字串表示的 FLAG
    char*sflags; /* Flags as string representation, one char per flag. */
 
    // 實際 FLAG
    intflags;    /* The actual flags, obtainedfrom the 'sflags' field. */
 
    /* Usea function to determine keys arguments in a command line.
     * Usedfor Redis Cluster redirect. */
    // 從命令中判斷命令的鍵引數。在 Redis 叢集轉向時使用。
   redisGetKeysProc *getkeys_proc;
 
    /* Whatkeys should be loaded in background when calling this command? */
    // 指定哪些引數是 key
    intfirstkey; /* The first argument that's a key (0 = no keys) */
    intlastkey;  /* The last argument that's akey */
    intkeystep;  /* The step between first andlast key */
 
    // 統計資訊
    //microseconds 記錄了命令執行耗費的總毫微秒數
    // calls是命令被執行的總次數
    longlong microseconds, calls;
};

redis.c中定義了命令表:

struct redisCommand redisCommandTable[] = {
   {"get",getCommand,2,"r",0,NULL,1,1,1,0,0},
   {"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0},
   {"setnx",setnxCommand,3,"wm",0,NULL,1,1,1,0,0},
   {"setex",setexCommand,4,"wm",0,NULL,1,1,1,0,0},
   {"psetex",psetexCommand,4,"wm",0,NULL,1,1,1,0,0},
   {"append",appendCommand,3,"wm",0,NULL,1,1,1,0,0},
   {"strlen",strlenCommand,2,"r",0,NULL,1,1,1,0,0},
   {"del",delCommand,-2,"w",0,NULL,1,-1,1,0,0},
   {"exists",existsCommand,2,"r",0,NULL,1,1,1,0,0},
   {"setbit",setbitCommand,4,"wm",0,NULL,1,1,1,0,0},
   {"getbit",getbitCommand,3,"r",0,NULL,1,1,1,0,0},
    ...
    ...
   {"pfselftest",pfselftestCommand,1,"r",0,NULL,0,0,0,0,0},
   {"pfadd",pfaddCommand,-2,"wm",0,NULL,1,1,1,0,0},
   {"pfcount",pfcountCommand,-2,"w",0,NULL,1,1,1,0,0},
   {"pfmerge",pfmergeCommand,-2,"wm",0,NULL,1,-1,1,0,0},
   {"pfdebug",pfdebugCommand,-3,"w",0,NULL,0,0,0,0,0}
};

6. buf[REDIS_REPLY_CHUNK_BYTES]回覆緩衝區和bufpos回覆偏移量,reply回覆連結串列

當執行對應的命令後,Redis伺服器會發送回復給客戶端,這個回覆就會儲存在客戶端的輸出緩衝區中,每個客戶端都有兩個輸出緩衝區,一個是大小固定的buf,另一個是大小可變的回覆連結串列reply

buf的大小是REDIS_REPLY_CHUNK_BYTES,預設16*1024即16KB,bufpos儲存著buf陣列已經使用的位元組偏移。當buf陣列空間用完或者回復太大時,伺服器將會把回覆放至可變大小緩衝區reply中。

7. authenticated身份驗證

當伺服器開啟身份驗證時,即server.requirepass不為null時,如果客戶端的authenticated為0,代表客戶端未通過身份驗證,此時除了AUTH身份驗證命令外,其餘所有的命令都會被伺服器拒絕;只有當authenticated為1時,才可執行其他命令請求。

需要注意的是,這僅僅在伺服器開啟身份驗證時生效,若伺服器不開啟身份驗證,即使authenticated為0也可正常執行命令請求

二、Redis伺服器

Redis伺服器負責與多個客戶端建立連線從而可以處理客戶端的命令請求。

伺服器的結構太長了,不列了,,,

1. 命令請求的執行過程

首先我們來分析從客戶端傳送請求到伺服器執行後得到正確結果的整個過程:

1)首先客戶端向伺服器傳送命令請求:

Redis客戶端會將命令請求轉換成協議格式,通過連線到伺服器的套接字,將命令請求傳送給伺服器。

2)伺服器接受請求,並進行相應操作,併發送命令回覆OK給客戶端:

伺服器在將命令請求進行分析後,通過呼叫processCommand命令處理函式對命令請求進行處理,若處理成功會返回OK。

processCommand函式將會先查詢命令,並進行命令合法性檢查,以及命令引數個數檢查;隨後會通過一系列操作比如說:檢查認證資訊、是否超過伺服器最大記憶體等確保命令正確地執行。processCommand函式具體邏輯如下:

/* If this function gets called we already read awhole
 * command,arguments are in the client argv/argc fields.
 *processCommand() execute the command or prepare the
 * serverfor a bulk read from the client.
 *
 * 這個函式執行時,我們已經讀入了一個完整的命令到客戶端,
 * 這個函式負責執行這個命令,
 * 或者伺服器準備從客戶端中進行一次讀取。
 *
 * If 1 isreturned the client is still alive and valid and
 * otheroperations can be performed by the caller. Otherwise
 * if 0 isreturned the client was destroyed (i.e. after QUIT).
 *
 * 如果這個函式返回 1 ,那麼表示客戶端在執行命令之後仍然存在,
 * 呼叫者可以繼續執行其他操作。
 * 否則,如果這個函式返回 0 ,那麼表示客戶端已經被銷燬。
 */
int processCommand(redisClient *c) {
    /* TheQUIT command is handled separately. Normal command procs will
     * gothrough checking for replication and QUIT will cause trouble
     * whenFORCE_REPLICATION is enabled and would be implemented in
     * aregular command proc. */
    // 特別處理 quit 命令
    if (!strcasecmp(c->argv[0]->ptr,"quit")){
       addReply(c,shared.ok);
       c->flags |= REDIS_CLOSE_AFTER_REPLY;
       return REDIS_ERR;
    }
 
    /* Nowlookup the command and check ASAP about trivial error conditions
     * suchas wrong arity, bad command name and so forth. */
    // 查詢命令,並進行命令合法性檢查,以及命令引數個數檢查
   c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
    if(!c->cmd) {
        // 沒找到指定的命令
       flagTransaction(c);
       addReplyErrorFormat(c,"unknown command '%s'",
           (char*)c->argv[0]->ptr);
       return REDIS_OK;
    } elseif ((c->cmd->arity > 0 && c->cmd->arity != c->argc)||
              (c->argc < -c->cmd->arity)) {
        // 引數個數錯誤
       flagTransaction(c);
       addReplyErrorFormat(c,"wrong number of arguments for '%s'command",
           c->cmd->name);
       return REDIS_OK;
    }
 
    /*Check if the user is authenticated */
    // 檢查認證資訊
    if(server.requirepass && !c->authenticated &&c->cmd->proc != authCommand)
    {
       flagTransaction(c);
       addReply(c,shared.noautherr);
       return REDIS_OK;
    }
 
    /* Ifcluster is enabled perform the cluster redirection here.
     *
     * 如果開啟了叢集模式,那麼在這裡進行轉向操作。
     *
     *However we don't perform the redirection if:
     *
     * 不過,如果有以下情況出現,那麼節點不進行轉向:
     *
     * 1)The sender of this command is our master.
     *    命令的傳送者是本節點的主節點
     *
     * 2)The command has no key arguments.
     *    命令沒有 key 引數
     */
    if(server.cluster_enabled &&
       !(c->flags & REDIS_MASTER) &&
       !(c->cmd->getkeys_proc == NULL && c->cmd->firstkey== 0))
    {
        inthashslot;
 
        // 叢集已下線
        if(server.cluster->state != REDIS_CLUSTER_OK) {
           flagTransaction(c);
           addReplySds(c,sdsnew("-CLUSTERDOWN The cluster is down. Use CLUSTERINFO for more information\r\n"));
           return REDIS_OK;
 
        // 叢集運作正常
        }else {
           int error_code;
           clusterNode *n =getNodeByQuery(c,c->cmd,c->argv,c->argc,&hashslot,&error_code);
           // 不能執行多鍵處理命令
           if (n == NULL) {
               flagTransaction(c);
               if (error_code == REDIS_CLUSTER_REDIR_CROSS_SLOT) {
                    addReplySds(c,sdsnew("-CROSSSLOTKeys in request don't hash to the same slot\r\n"));
               } else if (error_code == REDIS_CLUSTER_REDIR_UNSTABLE) {
                    /* The request spawnsmutliple keys in the same slot,
                     * but the slot is not"stable" currently as there is
                     * a migration or import inprogress. */
                   addReplySds(c,sdsnew("-TRYAGAIN Multiple keys request duringrehashing of slot\r\n"));
               } else {
                   redisPanic("getNodeByQuery() unknown error.");
               }
               return REDIS_OK;
 
           // 命令針對的槽和鍵不是本節點處理的,進行轉向
           } else if (n != server.cluster->myself) {
               flagTransaction(c);
                // -<ASK or MOVED> <slot><ip>:<port>
               // 例如 -ASK 10086 127.0.0.1:12345
               addReplySds(c,sdscatprintf(sdsempty(),
                    "-%s %d%s:%d\r\n",
                    (error_code ==REDIS_CLUSTER_REDIR_ASK) ? "ASK" : "MOVED",
                   hashslot,n->ip,n->port));
 
               return REDIS_OK;
           }
 
           // 如果執行到這裡,說明鍵 key 所在的槽由本節點處理
           // 或者客戶端執行的是無引數命令
        }
    }
 
    /*Handle the maxmemory directive.
     *
     *First we try to free some memory if possible (if there are volatile
     * keysin the dataset). If there are not the only thing we can do
     * isreturning an error. */
    // 如果設定了最大記憶體,那麼檢查記憶體是否超過限制,並做相應的操作
    if(server.maxmemory) {
        // 如果記憶體已超過限制,那麼嘗試通過刪除過期鍵來釋放記憶體
        intretval = freeMemoryIfNeeded();
        // 如果即將要執行的命令可能佔用大量記憶體(REDIS_CMD_DENYOOM)
        // 並且前面的記憶體釋放失敗的話
        // 那麼向客戶端返回記憶體錯誤
        if((c->cmd->flags & REDIS_CMD_DENYOOM) && retval == REDIS_ERR){
           flagTransaction(c);
           addReply(c, shared.oomerr);
           return REDIS_OK;
        }
    }
 
    /*Don't accept write commands if there are problems persisting on disk
     * andif this is a master instance. */
    // 如果這是一個主伺服器,並且這個伺服器之前執行 BGSAVE 時發生了錯誤
    // 那麼不執行寫命令
    if(((server.stop_writes_on_bgsave_err &&
         server.saveparamslen > 0 &&
         server.lastbgsave_status == REDIS_ERR) ||
         server.aof_last_write_status == REDIS_ERR) &&
       server.masterhost == NULL &&
       (c->cmd->flags & REDIS_CMD_WRITE ||
        c->cmd->proc == pingCommand))
    {
       flagTransaction(c);
        if(server.aof_last_write_status == REDIS_OK)
           addReply(c, shared.bgsaveerr);
        else
           addReplySds(c,
               sdscatprintf(sdsempty(),
               "-MISCONF Errors writing to the AOF file: %s\r\n",
               strerror(server.aof_last_write_errno)));
       return REDIS_OK;
    }
 
    /*Don't accept write commands if there are not enough good slaves and
     * userconfigured the min-slaves-to-write option. */
    // 如果伺服器沒有足夠多的狀態良好伺服器
    // 並且 min-slaves-to-write 選項已開啟
    if(server.repl_min_slaves_to_write &&
       server.repl_min_slaves_max_lag &&
       c->cmd->flags & REDIS_CMD_WRITE &&
       server.repl_good_slaves_count < server.repl_min_slaves_to_write)
    {
       flagTransaction(c);
       addReply(c, shared.noreplicaserr);
       return REDIS_OK;
    }
 
    /*Don't accept write commands if this is a read only slave. But
     *accept write commands if this is our master. */
    // 如果這個伺服器是一個只讀 slave 的話,那麼拒絕執行寫命令
    if(server.masterhost && server.repl_slave_ro &&
       !(c->flags & REDIS_MASTER) &&
        c->cmd->flags &REDIS_CMD_WRITE)
    {
       addReply(c, shared.roslaveerr);
       return REDIS_OK;
    }
 
    /* Onlyallow SUBSCRIBE and UNSUBSCRIBE in the context of Pub/Sub */
    // 在訂閱於釋出模式的上下文中,只能執行訂閱和退訂相關的命令
    if((dictSize(c->pubsub_channels) > 0 || listLength(c->pubsub_patterns)> 0)
       &&
       c->cmd->proc != subscribeCommand &&
       c->cmd->proc != unsubscribeCommand &&
       c->cmd->proc != psubscribeCommand &&
       c->cmd->proc != punsubscribeCommand) {
       addReplyError(c,"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowedin this context");
       return REDIS_OK;
    }
 
    /* Onlyallow INFO and SLAVEOF when slave-serve-stale-data is no and
     * weare a slave with a broken link with master. */
    if(server.masterhost && server.repl_state != REDIS_REPL_CONNECTED&&
       server.repl_serve_stale_data == 0 &&
       !(c->cmd->flags & REDIS_CMD_STALE))
    {
       flagTransaction(c);
       addReply(c, shared.masterdownerr);
       return REDIS_OK;
    }
 
    /*Loading DB? Return an error if the command has not the
     *REDIS_CMD_LOADING flag. */
    // 如果伺服器正在載入資料到資料庫,那麼只執行帶有 REDIS_CMD_LOADING
    // 標識的命令,否則將出錯
    if(server.loading && !(c->cmd->flags & REDIS_CMD_LOADING)) {
        addReply(c,shared.loadingerr);
       return REDIS_OK;
    }
 
    /* Luascript too slow? Only allow a limited number of commands. */
    // Lua 指令碼超時,只允許執行限定的操作,比如 SHUTDOWN 和SCRIPT KILL
    if(server.lua_timedout &&
         c->cmd->proc != authCommand &&
         c->cmd->proc != replconfCommand &&
       !(c->cmd->proc == shutdownCommand &&
         c->argc == 2 &&
         tolower(((char*)c->argv[1]->ptr)[0]) == 'n') &&
       !(c->cmd->proc == scriptCommand &&
         c->argc == 2 &&
          tolower(((char*)c->argv[1]->ptr)[0])== 'k'))
    {
       flagTransaction(c);
       addReply(c, shared.slowscripterr);
       return REDIS_OK;
    }
 
    /* Execthe command */
    if(c->flags & REDIS_MULTI &&
       c->cmd->proc != execCommand && c->cmd->proc !=discardCommand &&
       c->cmd->proc != multiCommand && c->cmd->proc !=watchCommand)
    {
        // 在事務上下文中
        // 除 EXEC 、DISCARD 、 MULTI 和 WATCH命令之外
        // 其他所有命令都會被入隊到事務佇列中
        queueMultiCommand(c);
       addReply(c,shared.queued);
    } else{
        // 執行命令
       call(c,REDIS_CALL_FULL);
 
       c->woff = server.master_repl_offset;
        // 處理那些解除了阻塞的鍵
        if(listLength(server.ready_keys))
           handleClientsBlockedOnLists();
    }
 
    returnREDIS_OK;
}

3)客戶端收到命令回覆後將回復列印給使用者

2. 伺服器中的serverCron函式

serverCron函式中的所有程式碼都會每秒呼叫 server.hz 次,預設是每隔100ms執行一次。serverCron函式主要執行以下邏輯:更新lru時間、管理客戶端、觸發BGSAVE或者AOF重寫等:

/* This is our timer interrupt, called server.hztimes per second.
 *
 * 這是 Redis 的時間中斷器,每秒呼叫 server.hz 次。
 *
 * Here iswhere we do a number of things that need to be done asynchronously.
 * Forinstance:
 *
 * 以下是需要非同步執行的操作:
 *
 * - Activeexpired keys collection (it is also performed in a lazy way on
 *   lookup).
 *   主動清除過期鍵。
 *
 * -Software watchdog.
 *   更新軟體 watchdog 的資訊。
 *
 * - Updatesome statistic.
 *   更新統計資訊。
 *
 * -Incremental rehashing of the DBs hash tables.
 *   對資料庫進行漸增式 Rehash
 *
 * -Triggering BGSAVE / AOF rewrite, and handling of terminated children.
 *   觸發 BGSAVE 或者 AOF 重寫,並處理之後由 BGSAVE 和 AOF 重寫引發的子程序停止。
 *
 * -Clients timeout of different kinds.
 *   處理客戶端超時。
 *
 * -Replication reconnection.
 *   複製重連
 *
 * - Manymore...
 *   等等。。。
 *
 *Everything directly called here will be called server.hz times per second,
 * so inorder to throttle execution of things we want to do less frequently
 * a macrois used: run_with_period(milliseconds) { .... }
 *
 * 因為 serverCron 函式中的所有程式碼都會每秒呼叫 server.hz 次,
 * 為了對部分程式碼的呼叫次數進行限制,
 * 使用了一個巨集 run_with_period(milliseconds) { ... } ,
 * 這個巨集可以將被包含程式碼的執行次數降低為每 milliseconds 執行一次。
 */
 
int serverCron(struct aeEventLoop *eventLoop,long long id, void *clientData) {
    int j;
   REDIS_NOTUSED(eventLoop);
   REDIS_NOTUSED(id);
   REDIS_NOTUSED(clientData);
 
    /*Software watchdog: deliver the SIGALRM that will reach the signal
     *handler if we don't return here fast enough. */
    if(server.watchdog_period) watchdogScheduleSignal(server.watchdog_period);
 
    /*Update the time cache. */
   updateCachedTime();
 
    // 記錄伺服器執行命令的次數
   run_with_period(100) trackOperationsPerSecond();
 
    /* Wehave just REDIS_LRU_BITS bits per object for LRU information.
     * Sowe use an (eventually wrapping) LRU clock.
     *
     * Notethat even if the counter wraps it's not a big problem,
     *everything will still work but some object will appear younger
     * toRedis. However for this to happen a given object should never be
     *touched for all the time needed to the counter to wrap, which is
     * notlikely.
     *
     * 即使伺服器的時間最終比 1.5 年長也無所謂,
     * 物件系統仍會正常運作,不過一些物件可能會比伺服器本身的時鐘更年輕。
     * 不過這要這個物件在 1.5 年內都沒有被訪問過,才會出現這種現象。
     *
     * Notethat you can change the resolution altering the
     *REDIS_LRU_CLOCK_RESOLUTION define.
     *
     * LRU 時間的精度可以通過修改 REDIS_LRU_CLOCK_RESOLUTION 常量來改變。
     */
   server.lruclock = getLRUClock();
 
    /*Record the max memory used since the server was started. */
    // 記錄伺服器的記憶體峰值
    if(zmalloc_used_memory() > server.stat_peak_memory)
       server.stat_peak_memory = zmalloc_used_memory();
 
    /*Sample the RSS here since this is a relatively slow call. */
   server.resident_set_size = zmalloc_get_rss();
 
    /* Wereceived a SIGTERM, shutting down here in a safe way, as it is
     * notok doing so inside the signal handler. */
    // 伺服器程序收到 SIGTERM 訊號,關閉伺服器
    if(server.shutdown_asap) {
 
        // 嘗試關閉伺服器
        if(prepareForShutdown(0) == REDIS_OK) exit(0);
 
        // 如果關閉失敗,那麼列印 LOG ,並移除關閉標識
       redisLog(REDIS_WARNING,"SIGTERM received but errors trying to shutdown the server, check the logs for more information");
       server.shutdown_asap = 0;
    }
 
    /* Showsome info about non-empty databases */
    // 列印資料庫的鍵值對資訊
   run_with_period(5000) {
        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);
 
           // 用 LOG 列印數量
           if (used || vkeys) {
               redisLog(REDIS_VERBOSE,"DB %d: %lld keys (%lld volatile) in %lldslots HT.",j,used,vkeys,size);
               /* dictPrintStats(server.dict); */
           }
        }
    }
 
    /* Showinformation about connected clients */
    // 如果伺服器沒有執行在 SENTINEL 模式下,那麼列印客戶端的連線資訊
    if(!server.sentinel_mode) {
       run_with_period(5000) {
           redisLog(REDIS_VERBOSE,
               "%lu clients connected (%lu slaves), %zu bytes in use",
               listLength(server.clients)-listLength(server.slaves),
               listLength(server.slaves),
               zmalloc_used_memory());
        }
    }
 
    /* Weneed to do a few operations on clients asynchronously. */
    // 檢查客戶端,關閉超時客戶端,並釋放客戶端多餘的緩衝區
   clientsCron();
 
    /*Handle background operations on Redis databases. */
    // 對資料庫執行各種操作
   databasesCron();
 
    /*Start a scheduled AOF rewrite if this was requested by the user while
     * aBGSAVE was in progress. */
    // 如果 BGSAVE 和 BGREWRITEAOF都沒有在執行
    // 並且有一個 BGREWRITEAOF 在等待,那麼執行 BGREWRITEAOF
    if(server.rdb_child_pid == -1 && server.aof_child_pid == -1 &&
       server.aof_rewrite_scheduled)
    {
       rewriteAppendOnlyFileBackground();
    }
 
    /*Check if a background saving or AOF rewrite in progress terminated. */
    // 檢查 BGSAVE 或者 BGREWRITEAOF 是否已經執行完畢
    if(server.rdb_child_pid != -1 || server.aof_child_pid != -1) {
        intstatloc;
       pid_t pid;
 
        // 接收子程序發來的訊號,非阻塞
        if((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
           int exitcode = WEXITSTATUS(statloc);
           int bysignal = 0;
           
           if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
 
           // BGSAVE 執行完畢
           if (pid == server.rdb_child_pid) {
               backgroundSaveDoneHandler(exitcode,bysignal);
 
           // BGREWRITEAOF 執行完畢
           } else if (pid == server.aof_child_pid) {
               backgroundRewriteDoneHandler(exitcode,bysignal);
 
           } else {
               redisLog(REDIS_WARNING,
                    "Warning, detectedchild with unmatched pid: %ld",
                    (long)pid);
           }
           updateDictResizePolicy();
        }
    } else{
 
        /*If there is not a background saving/rewrite in progress check if
         *we have to save/rewrite now */
        // 既然沒有 BGSAVE 或者 BGREWRITEAOF 在執行,那麼檢查是否需要執行它們
 
        // 遍歷所有儲存條件,看是否需要執行 BGSAVE 命令
        for (j = 0; j < server.saveparamslen; j++) {
           struct saveparam *sp = server.saveparams+j;
 
           /* Save if we reached the given amount of changes,
            * the given amount of seconds, and if the latest bgsave was
            * successful or if, in case of an error, at least
            * REDIS_BGSAVE_RETRY_DELAY seconds already elapsed. */
           // 檢查是否有某個儲存條件已經滿足了
           if (server.dirty >= sp->changes &&
               server.unixtime-server.lastsave > sp->seconds &&
               (server.unixtime-server.lastbgsave_try >
                REDIS_BGSAVE_RETRY_DELAY ||
                server.lastbgsave_status == REDIS_OK))
           {
               redisLog(REDIS_NOTICE,"%d changes in %d seconds. Saving...",
                    sp->changes,(int)sp->seconds);
               // 執行 BGSAVE
               rdbSaveBackground(server.rdb_filename);
               break;
           }
         }
 
         /*Trigger an AOF rewrite if needed */
        // 出發 BGREWRITEAOF
         if(server.rdb_child_pid == -1 &&
            server.aof_child_pid == -1 &&
            server.aof_rewrite_perc &&
            // AOF 檔案的當前大小大於執行 BGREWRITEAOF 所需的最小大小
            server.aof_current_size > server.aof_rewrite_min_size)
         {
           // 上一次完成 AOF 寫入之後,AOF 檔案的大小
           long long base = server.aof_rewrite_base_size ?
                           server.aof_rewrite_base_size : 1;
 
           // AOF 檔案當前的體積相對於 base 的體積的百分比
           long long growth = (server.aof_current_size*100/base) - 100;
 
           // 如果增長體積的百分比超過了 growth ,那麼執行 BGREWRITEAOF
           if (growth >= server.aof_rewrite_perc) {
               redisLog(REDIS_NOTICE,"Starting automatic rewriting of AOF on%lld%% growth",growth);
               // 執行 BGREWRITEAOF
               rewriteAppendOnlyFileBackground();
           }
         }
    }
 
    // 根據 AOF 政策,
    // 考慮是否需要將 AOF 緩衝區中的內容寫入到 AOF 檔案中
    /* AOFpostponed flush: Try at every cron cycle if the slow fsync
     *completed. */
    if(server.aof_flush_postponed_start) flushAppendOnlyFile(0);
 
    /* AOFwrite errors: in this case we have a buffer to flush as well and
     *clear the AOF error in case of success to make the DB writable again,
     *however to try every second is enough in case of 'hz' is set to
     * anhigher frequency. */
   run_with_period(1000) {
        if(server.aof_last_write_status == REDIS_ERR)
           flushAppendOnlyFile(0);
    }
 
    /*Close clients that need to be closed asynchronous */
    // 關閉那些需要非同步關閉的客戶端
   freeClientsInAsyncFreeQueue();
 
    /*Clear the paused clients flag if needed. */
   clientsArePaused(); /* Don't check return value, just use the sideeffect. */
 
    /*Replication cron function -- used to reconnect to master and
     * todetect transfer failures. */
    // 複製函式
    // 重連線主伺服器、向主伺服器傳送 ACK 、判斷資料傳送失敗情況、斷開本伺服器超時的從伺服器,等等
   run_with_period(1000) replicationCron();
 
    /* Runthe Redis Cluster cron. */
    // 如果伺服器執行在叢集模式下,那麼執行叢集操作
   run_with_period(100) {
        if(server.cluster_enabled) clusterCron();
    }
 
    /* Runthe Sentinel timer if we are in sentinel mode. */
    // 如果伺服器執行在 sentinel 模式下,那麼執行 SENTINEL 的主函式
   run_with_period(100) {
        if (server.sentinel_mode) sentinelTimer();
    }
 
    /*Cleanup expired MIGRATE cached sockets. */
    // 叢集。。。TODO
   run_with_period(1000) {
       migrateCloseTimedoutSockets();
    }
 
    // 增加 loop 計數器
   server.cronloops++;
 
    return1000/server.hz;
}

3. 關於RDB持久化的儲存條件

上篇部落格中講了Redis可以設定save條件來設定RDB的檔案儲存條件。在redisServer中維護了一個saveparams陣列,此陣列是一個saveparam結構

// 伺服器的儲存條件(BGSAVE 自動執行的條件)
struct saveparam {
 
    // 多少秒之內
    time_tseconds;
 
    // 發生多少次修改
    intchanges;
 
};

並且redisServer結構中還有一個dirty計數器及lastsave屬性當伺服器對資料庫進行操作時,dirty計數器都會進行自增,lastsave屬性是一個時間戳,記錄了上一次伺服器成功執行了SAVE或者BGSAVE命令的時間

相關推薦

Redis原始碼——Redis客戶伺服器

在前面的部落格中,有些內容已經涉及到了Redis伺服器或者客戶端的一些屬性,如上一篇部落格關於Redis的RDB持久化中關於save選項來設定伺服器的狀態等。那麼接下來這篇部落格中就分析下Redis的客戶端以及伺服器的屬性及操作。 一、Redis客戶端 redis.h中的

Redis叢集JedisClusterJedis客戶命令詳解

1.echo,列印一個特定的資訊 message ,測試時使用 cluster.echo("你好,echo!") 2.ping,使用客戶端向 Redis 伺服器傳送一個 PING ,如果伺服器運作正常的話,會返回一個 PONG ;通常用於測試與伺服器的連線是否仍然生效

企業級Redis開發運維從入門到實踐 25Redis Sentinel哨兵客戶連線

客戶端連線 請求響應流程 既然已經實現高可用為什麼不直接直連? 高可用涉及的是服務高可用、完成自動的故障轉移;故障轉移後客戶端無法感知將無法保證正常的使用。 需要保證的是服務高可用 和 客戶端高可用。

Zookeeper 原始碼Zookeeper 客戶原始碼

Zookeeper 原始碼(三)Zookeeper 客戶端原始碼 Zookeeper 客戶端由以下幾個核心元件組成: 類 說明 Zookeeper Zookeeper 客戶端入口 ClientWatch

曹工說Redis原始碼2-- redis server 啟動過程解析及簡單c語言基礎知識補充

文章導航 Redis原始碼系列的初衷,是幫助我們更好地理解Redis,更懂Redis,而怎麼才能懂,光看是不夠的,建議跟著下面的這一篇,把環境搭建起來,後續可以自己閱讀原始碼,或者跟著我這邊一起閱讀。由於我用c也是好幾年以前了,些許錯誤在所難免,希望讀者能不吝指出。 曹工說Redis原始碼(1)-- redi

曹工說Redis原始碼3-- redis server 啟動過程完整解析

文章導航 Redis原始碼系列的初衷,是幫助我們更好地理解Redis,更懂Redis,而怎麼才能懂,光看是不夠的,建議跟著下面的這一篇,把環境搭建起來,後續可以自己閱讀原始碼,或者跟著我這邊一起閱讀。由於我用c也是好幾年以前了,些許錯誤在所難免,希望讀者能不吝指出。 曹工說Redis原始碼(1)-- redi

曹工說Redis原始碼5-- redis server 啟動過程解析,以及EventLoop每次處理事件前的前置工作解析

文章導航 Redis原始碼系列的初衷,是幫助我們更好地理解Redis,更懂Redis,而怎麼才能懂,光看是不夠的,建議跟著下面的這一篇,把環境搭建起來,後續可以自己閱讀原始碼,或者跟著我這邊一起閱讀。由於我用c也是好幾年以前了,些許錯誤在所難免,希望讀者能不吝指出。 曹工說Redis原始碼(1)-- redi

曹工說Redis原始碼6-- redis server 主迴圈大體流程解析

文章導航 Redis原始碼系列的初衷,是幫助我們更好地理解Redis,更懂Redis,而怎麼才能懂,光看是不夠的,建議跟著下面的這一篇,把環境搭建起來,後續可以自己閱讀原始碼,或者跟著我這邊一起閱讀。由於我用c也是好幾年以前了,些許錯誤在所難免,希望讀者能不吝指出。 曹工說Redis原始碼(1)-- redi

曹工說Redis原始碼7-- redis server 的週期執行任務,到底要做些啥

文章導航 Redis原始碼系列的初衷,是幫助我們更好地理解Redis,更懂Redis,而怎麼才能懂,光看是不夠的,建議跟著下面的這一篇,把環境搭建起來,後續可以自己閱讀原始碼,或者跟著我這邊一起閱讀。由於我用c也是好幾年以前了,些許錯誤在所難免,希望讀者能不吝指出。 曹工說Redis原始碼(1)-- redi

大資料入門7RPC客戶RPC服務通訊

RPC客戶端和RPC服務端通訊: 客戶端:(匯入jar:hdfs,common相關的) LoginControl: public class LoginControl {     public static void main(String[] args) th

Redis 學習redis伺服器叢集、客戶分片

下面是來自知乎大神的一段說明,個人覺得非常清晰,就收藏了。 為什麼叢集? 通常,為了提高網站響應速度,總是把熱點資料儲存在記憶體中而不是直接從後端資料庫中讀取。Redis是一個很好的Cache工具。大型網站應用,熱點資料量往往巨大,幾十G上百G是很正常的事兒,在這種情

3.Redis 學習redis伺服器叢集、客戶分片

下面是來自知乎大神的一段說明,個人覺得非常清晰,就收藏了。 為什麼叢集? 通常,為了提高網站響應速度,總是把熱點資料儲存在記憶體中而不是直接從後端資料庫中讀取。Redis是一個很好的Cache工具。大型網站應用,熱點資料量往往巨大,幾十G上百G是很正常的事兒,在這種

Redis原始碼——Redis的主從複製

Redis中可以傳送SLAVEOF命令讓一個伺服器A成為另一個伺服器B的“slave”,從而去複製B,稱A為從伺服器(slave),B為主伺服器(master) 關於複製,2.8版本以前及以後實現有一些不同,2.8版本以前的複製在斷線重連後效率較低,所以我們在這裡只分析

redis使用基礎 ——Redis存儲Session

art 方式 pda 公眾 pub 存儲位置 center eight cal redis使用基礎(十) ——Redis存儲Session (轉載請附上本文鏈接——linhxx) 一、概述 PHP默認是將session存於服務器的文件中。當並發量大

Redis學習記錄------Redis 指令碼

Redis 指令碼 Redis 指令碼使用 Lua 直譯器來執行指令碼。 Redis 2.6 版本通過內嵌支援 Lua 環境。執行指令碼的常用命令為 EVAL。 語法 Eval 命令的基本語法如下: redis 127.0.0.1:6379> EVAL scri

Spring Cloud系列Config客戶—Finchley版本

URI指定配置中心 Spring Cloud Config客戶端在啟動的時候,預設會從工程的classpath中載入配置資訊並啟動應用。只有當我們配置spring.cloud.config.uri的時候,客戶端才會嘗試連線Spring Cloud Config 的服務端來獲

Redis教程如何搭建Redis叢集

Redis叢集搭建環境:  CentOS 7.0 6臺redis,埠號分別分配為7001、7002、7003、7004、7005、7006 設定7001、7002、7003為主機,7004、7005、7006分別依次為從機。1、  在Linux中建立cluster-redi

Docker系列~配置外部訪問容器中的Redis服務

最近想通過在centos映象中配置redis服務,從而使外部機器能訪問 1. 建立並啟動映象(我的映象名字叫做:mycentos-redis:1.0) docker run -i -t -p 192.168.0.152:56379:6379 mycentos-redis

redis 系列java結合redis+lua 實現搶紅包經典案例

使用lua指令碼來實現一個搶紅包的過程,lua具有原子特性,可以避免資料併發時多執行緒同時操作的問題 java程式碼結合lua實現搶紅包案例 單機版redis package bhz.redis01; import java.util.Random; import java

dubbo遠端呼叫原始碼分析客戶傳送請求

dubbo遠端呼叫的原始碼分析,分成了三篇文章地址分別如下:本文分為三部分,分別是:消費端註冊部分消費端動態代理部分消費端事件處理器部分消費端註冊部分在分析dubbo遠端呼叫的時候,要從dubbo消費端(consumer)註冊開始說起dubbo的consumer在註冊的時候,