redis客戶端管理

晴天哥發表於2018-06-11

寫在前面

 這一章節涉及的內容比較簡單,主要是為了講清楚redis server端如何維持和client相關的連線的,基本從以下幾個方面展開描述:

  • redis server端採用何種資料結構維護連線資訊
  • redis server端建立和client相關連線的過程
  • redis client相關的資料結構定義

redis server核心資料結構

 redis server作為redis最核心的資料結構,基本上redis所有相關的資訊都會在這裡有體現,這裡列出幾大塊。

  • 服務本身執行資訊
  • 網路監聽連線等
  • AOF/RDB持久化資訊
  • redis慢查詢日誌
  • 主從同步資訊
  • 叢集cluster資訊

 redis客戶端管理相關的資料結構主要有

  • list *clients; // 一個連結串列,儲存了所有客戶端狀態結構
  • list *clients_to_close; // 連結串列,儲存了所有待關閉的客戶端
  • list *slaves, *monitors; // 連結串列,儲存了所有從伺服器,以及所有監視器

redis server端使用列表來管理客戶端發起的連線,以list儲存所有客戶端發起的連線,以及待關閉的客戶端連線。

struct redisServer {

    // serverCron() 每秒呼叫的次數
    int hz;                     /* serverCron() calls frequency in hertz */

    // 資料庫
    redisDb *db;

    // 事件狀態
    aeEventLoop *el;

    /* redis 網路相關的資料結構 */

    // TCP 監聽埠
    int port;                   /* TCP listening port */

    int tcp_backlog;            /* TCP listen() backlog */

    // 地址
    char *bindaddr[REDIS_BINDADDR_MAX]; /* Addresses we should bind to */
    // 地址數量
    int bindaddr_count;         /* Number of addresses in server.bindaddr[] */

    // 一個連結串列,儲存了所有客戶端狀態結構
    list *clients;              /* List of active clients */
    // 連結串列,儲存了所有待關閉的客戶端
    list *clients_to_close;     /* Clients to close asynchronously */

    // 連結串列,儲存了所有從伺服器,以及所有監視器
    list *slaves, *monitors;    /* List of slaves and MONITORs */

    /* RDB / AOF 載入資訊相關資料結構 */

    // 這個值為真時,表示伺服器正在進行載入
    int loading;                /* We are loading data from disk if true */

    // 正在載入的資料的大小
    off_t loading_total_bytes;

    // 已載入資料的大小
    off_t loading_loaded_bytes;

    // 開始進行載入的時間
    time_t loading_start_time;
    off_t loading_process_events_interval_bytes;

    /* Fast pointers to often looked up command */
    // 常用命令的快捷連線
    struct redisCommand *delCommand, *multiCommand, *lpushCommand, *lpopCommand,
                        *rpopCommand;


    /* server端相關的統計資訊  */

    // 伺服器啟動時間
    time_t stat_starttime;          /* Server start time */

    // 已處理命令的數量
    long long stat_numcommands;     /* Number of processed commands */

    // 伺服器接到的連線請求數量
    long long stat_numconnections;  /* Number of connections received */

    // 已過期的鍵數量
    long long stat_expiredkeys;     /* Number of expired keys */


    /*  慢查詢日誌 */

    // 儲存了所有慢查詢日誌的連結串列
    list *slowlog;                  /* SLOWLOG list of commands */

    // 下一條慢查詢日誌的 ID
    long long slowlog_entry_id;     /* SLOWLOG current entry ID */

    // 伺服器配置 slowlog-log-slower-than 選項的值
    long long slowlog_log_slower_than; /* SLOWLOG time limit (to get logged) */

    // 伺服器配置 slowlog-max-len 選項的值
    unsigned long slowlog_max_len;    
    size_t resident_set_size;      
  
    // 最後一次進行抽樣的時間
    long long ops_sec_last_sample_time;
    // 最後一次抽樣時,伺服器已執行命令的數量
    long long ops_sec_last_sample_ops; 
    // 抽樣結果
    long long ops_sec_samples[REDIS_OPS_SEC_SAMPLES];
    // 陣列索引,用於儲存抽樣結果,並在需要時迴繞到 0
    int ops_sec_idx;

    /* AOF 持久化資訊*/

    // AOF 狀態(開啟/關閉/可寫)
    int aof_state;                  /* REDIS_AOF_(ON|OFF|WAIT_REWRITE) */

    // 所使用的 fsync 策略(每個寫入/每秒/從不)
    int aof_fsync;                  /* Kind of fsync() policy */
    char *aof_filename;             /* Name of the AOF file */
    int aof_no_fsync_on_rewrite;    /* Don`t fsync if a rewrite is in prog. */
    int aof_rewrite_perc;           /* Rewrite AOF if % growth is > M and... */
    off_t aof_rewrite_min_size;     /* the AOF file is at least N bytes. */

    // 最後一次執行 BGREWRITEAOF 時, AOF 檔案的大小
    off_t aof_rewrite_base_size;    /* AOF size on latest startup or rewrite. */

    // AOF 檔案的當前位元組大小
    off_t aof_current_size;         /* AOF current size. */
    int aof_rewrite_scheduled;      /* Rewrite once BGSAVE terminates. */

    
    /* RDB 持久化資訊 */

    // 自從上次 SAVE 執行以來,資料庫被修改的次數
    long long dirty;                /* Changes to DB from the last save */

    // BGSAVE 執行前的資料庫被修改次數
    long long dirty_before_bgsave;  /* Used to restore dirty on failed BGSAVE */

    // 負責執行 BGSAVE 的子程式的 ID
    // 沒在執行 BGSAVE 時,設為 -1
    pid_t rdb_child_pid;            /* PID of RDB saving child */
    struct saveparam *saveparams;   /* Save points array for RDB */
    int saveparamslen;              /* Number of saving points */
    char *rdb_filename;             /* Name of RDB file */
    int rdb_compression;            /* Use compression in RDB? */
    int rdb_checksum;               /* Use RDB checksum? */


    /* 日誌相關資訊 */
    char *logfile;                  /* Path of log file */
    int syslog_enabled;             /* Is syslog enabled? */
    char *syslog_ident;             /* Syslog ident */
    int syslog_facility;            /* Syslog facility */


    /* 主從複製 master的配置資訊 */
    int slaveseldb;                 /* Last SELECTed DB in replication output */
    // 全域性複製偏移量(一個累計值)
    long long master_repl_offset;   /* Global replication offset */
    // 主伺服器傳送 PING 的頻率
    int repl_ping_slave_period;     /* Master pings the slave every N seconds */


    /* 主從賦值 slave的配置資訊 */
    // 主伺服器的驗證密碼
    char *masterauth;               /* AUTH with this password with master */
    // 主伺服器的地址
    char *masterhost;               /* Hostname of master */
    // 主伺服器的埠
    int masterport;                 /* Port of master */
    // 超時時間
    int repl_timeout;               /* Timeout after N seconds of master idle */
   

    /* 釋出訂閱資訊 */
    // 字典,鍵為頻道,值為連結串列
    // 連結串列中儲存了所有訂閱某個頻道的客戶端
    // 新客戶端總是被新增到連結串列的表尾
    dict *pubsub_channels;  /* Map channels to list of subscribed clients */

    // 這個連結串列記錄了客戶端訂閱的所有模式的名字
    list *pubsub_patterns;  /* A list of pubsub_patterns */

    int notify_keyspace_events; /* Events to propagate via Pub/Sub. This is an
                                   xor of REDIS_NOTIFY... flags. */


    /* 叢集資訊 */

    int cluster_enabled;      /* Is cluster enabled? */
    mstime_t cluster_node_timeout; /* Cluster node timeout. */
    char *cluster_configfile; /* Cluster auto-generated config file name. */
    struct clusterState *cluster;  /* State of the cluster */

    int cluster_migration_barrier; /* Cluster replicas migration barrier. */
    
  /* 指令碼資訊 */

    // Lua 環境
    lua_State *lua; /* The Lua interpreter. We use just one for all clients */
    
    // 複製執行 Lua 指令碼中的 Redis 命令的偽客戶端
    redisClient *lua_client;   /* The "fake client" to query Redis from Lua */

    // 當前正在執行 EVAL 命令的客戶端,如果沒有就是 NULL
    redisClient *lua_caller;   /* The client running EVAL right now, or NULL */
};

redis 客戶端建立過程

 當客戶端第一次向server發起連線的時候,也就是在監聽socket的accept的處理函式當中acceptCommonHandler的內部通過createClient建立對應client連線的socket。

/*
 * TCP 連線 accept 處理器
 */
#define MAX_ACCEPTS_PER_CALL 1000
static void acceptCommonHandler(int fd, int flags) {

    // 建立客戶端
    redisClient *c;
    if ((c = createClient(fd)) == NULL) {
        redisLog(REDIS_WARNING,
            "Error registering fd event for the new client: %s (fd=%d)",
            strerror(errno),fd);
        close(fd); /* May be already closed, just ignore errors */
        return;
    }

    // 如果新新增的客戶端令伺服器的最大客戶端數量達到了
    // 那麼向新客戶端寫入錯誤資訊,並關閉新客戶端
    // 先建立客戶端,再進行數量檢查是為了方便地進行錯誤資訊寫入
    if (listLength(server.clients) > server.maxclients) {
        char *err = "-ERR max number of clients reached
";

        /* That`s a best effort error message, don`t check write errors */
        if (write(c->fd,err,strlen(err)) == -1) {
            /* Nothing to do, Just to avoid the warning... */
        }
        // 更新拒絕連線數
        server.stat_rejected_conn++;
        freeClient(c);
        return;
    }

    // 更新連線次數
    server.stat_numconnections++;

    // 設定 FLAG
    c->flags |= flags;
}

 createClient內部進行按照以下步驟建立了client的socket並新增list列表當中。

  • 分配redisClient的記憶體大小
  • 根據fd執行aeCreateFileEvent將socket新增到對應的eventloop當中實現事件監聽
  • 初始化一堆client相關的屬性
  • 執行listAddNodeTail方法將client新增到客戶端相關的列表當中
/*
 * 建立一個新客戶端
 */
redisClient *createClient(int fd) {

    // 分配空間
    redisClient *c = zmalloc(sizeof(redisClient));

    // 當 fd 不為 -1 時,建立帶網路連線的客戶端
    // 如果 fd 為 -1 ,那麼建立無網路連線的偽客戶端
    // 因為 Redis 的命令必須在客戶端的上下文中使用,所以在執行 Lua 環境中的命令時
    // 需要用到這種偽終端
    if (fd != -1) {
        // 非阻塞
        anetNonBlock(NULL,fd);
        // 禁用 Nagle 演算法
        anetEnableTcpNoDelay(NULL,fd);
        // 設定 keep alive
        if (server.tcpkeepalive)
            anetKeepAlive(NULL,fd,server.tcpkeepalive);
        // 繫結讀事件到事件 loop (開始接收命令請求)
        if (aeCreateFileEvent(server.el,fd,AE_READABLE,
            readQueryFromClient, c) == AE_ERR)
        {
            close(fd);
            zfree(c);
            return NULL;
        }
    }

    // 初始化各個屬性

    // 預設資料庫
    selectDb(c,0);
    // 套接字
    c->fd = fd;
    // 名字
    c->name = NULL;
    // 回覆緩衝區的偏移量
    c->bufpos = 0;
    // 查詢緩衝區
    c->querybuf = sdsempty();
    // 查詢緩衝區峰值
    c->querybuf_peak = 0;
    // 命令請求的型別
    c->reqtype = 0;
    // 命令引數數量
    c->argc = 0;
    // 命令引數
    c->argv = NULL;
    // 當前執行的命令和最近一次執行的命令
    c->cmd = c->lastcmd = NULL;
    // 查詢緩衝區中未讀入的命令內容數量
    c->multibulklen = 0;
    // 讀入的引數的長度
    c->bulklen = -1;
    // 已傳送位元組數
    c->sentlen = 0;
    // 狀態 FLAG
    c->flags = 0;
    // 建立時間和最後一次互動時間
    c->ctime = c->lastinteraction = server.unixtime;
    // 認證狀態
    c->authenticated = 0;
    // 複製狀態
    c->replstate = REDIS_REPL_NONE;
    // 複製偏移量
    c->reploff = 0;
    // 通過 ACK 命令接收到的偏移量
    c->repl_ack_off = 0;
    // 通過 AKC 命令接收到偏移量的時間
    c->repl_ack_time = 0;
    // 客戶端為從伺服器時使用,記錄了從伺服器所使用的埠號
    c->slave_listening_port = 0;
    // 回覆連結串列
    c->reply = listCreate();
    // 回覆連結串列的位元組量
    c->reply_bytes = 0;
    // 回覆緩衝區大小達到軟限制的時間
    c->obuf_soft_limit_reached_time = 0;
    // 回覆連結串列的釋放和複製函式
    listSetFreeMethod(c->reply,decrRefCountVoid);
    listSetDupMethod(c->reply,dupClientReplyValue);
    // 阻塞型別
    c->btype = REDIS_BLOCKED_NONE;
    // 阻塞超時
    c->bpop.timeout = 0;
    // 造成客戶端阻塞的列表鍵
    c->bpop.keys = dictCreate(&setDictType,NULL);
    // 在解除阻塞時將元素推入到 target 指定的鍵中
    // BRPOPLPUSH 命令時使用
    c->bpop.target = NULL;
    c->bpop.numreplicas = 0;
    c->bpop.reploffset = 0;
    c->woff = 0;
    // 進行事務時監視的鍵
    c->watched_keys = listCreate();
    // 訂閱的頻道和模式
    c->pubsub_channels = dictCreate(&setDictType,NULL);
    c->pubsub_patterns = listCreate();
    c->peerid = NULL;
    listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
    listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
    // 如果不是偽客戶端,那麼新增到伺服器的客戶端連結串列中
    if (fd != -1) listAddNodeTail(server.clients,c);
    // 初始化客戶端的事務狀態
    initClientMultiState(c);

    // 返回客戶端
    return c;
}

 redisClient對應的資料結構定義,很多變數我們看著都很熟悉。

  • 跟請求相關的變數包括:(redisDb *db)、(int argc)、( robj **argv)
  • 跟訂閱相關的變數包括:(dict *pubsub_channels)、(list *pubsub_patterns)。
/* 
 *
 * 因為 I/O 複用的緣故,需要為每個客戶端維持一個狀態。
 *
 * 多個客戶端狀態被伺服器用連結串列連線起來。
 */
typedef struct redisClient {

    // 套接字描述符
    int fd;

    // 當前正在使用的資料庫
    redisDb *db;

    // 當前正在使用的資料庫的 id (號碼)
    int dictid;

    // 客戶端的名字
    robj *name;             /* As set by CLIENT SETNAME */

    // 查詢緩衝區
    sds querybuf;

    // 查詢緩衝區長度峰值
    size_t querybuf_peak;   /* Recent (100ms or more) peak of querybuf size */

    // 引數數量
    int argc;

    // 引數物件陣列
    robj **argv;

    // 記錄被客戶端執行的命令
    struct redisCommand *cmd, *lastcmd;

    // 請求的型別:內聯命令還是多條命令
    int reqtype;

    // 剩餘未讀取的命令內容數量
    int multibulklen;       /* number of multi bulk arguments left to read */

    // 命令內容的長度
    long bulklen;           /* length of bulk argument in multi bulk request */

    // 回覆連結串列
    list *reply;

    // 回覆連結串列中物件的總大小
    unsigned long reply_bytes; /* Tot bytes of objects in reply list */

    // 已傳送位元組,處理 short write 用
    int sentlen;            /* Amount of bytes already sent in the current
                               buffer or object being sent. */

    // 建立客戶端的時間
    time_t ctime;           /* Client creation time */

    // 客戶端最後一次和伺服器互動的時間
    time_t lastinteraction; /* time of the last interaction, used for timeout */

    // 客戶端的輸出緩衝區超過軟性限制的時間
    time_t obuf_soft_limit_reached_time;

    // 客戶端狀態標誌
    int flags;              /* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... */

    // 當 server.requirepass 不為 NULL 時
    // 代表認證的狀態
    // 0 代表未認證, 1 代表已認證
    int authenticated;      /* when requirepass is non-NULL */

    // 複製狀態
    int replstate;          /* replication state if this is a slave */
    // 用於儲存主伺服器傳來的 RDB 檔案的檔案描述符
    int repldbfd;           /* replication DB file descriptor */

    // 讀取主伺服器傳來的 RDB 檔案的偏移量
    off_t repldboff;        /* replication DB file offset */
    // 主伺服器傳來的 RDB 檔案的大小
    off_t repldbsize;       /* replication DB file size */
    
    sds replpreamble;       /* replication DB preamble. */

    // 主伺服器的複製偏移量
    long long reploff;      /* replication offset if this is our master */
    // 從伺服器最後一次傳送 REPLCONF ACK 時的偏移量
    long long repl_ack_off; /* replication ack offset, if this is a slave */
    // 從伺服器最後一次傳送 REPLCONF ACK 的時間
    long long repl_ack_time;/* replication ack time, if this is a slave */
    // 主伺服器的 master run ID
    // 儲存在客戶端,用於執行部分重同步
    char replrunid[REDIS_RUN_ID_SIZE+1]; /* master run id if this is a master */
    // 從伺服器的監聽埠號
    int slave_listening_port; /* As configured with: SLAVECONF listening-port */

    // 事務狀態
    multiState mstate;      /* MULTI/EXEC state */

    // 阻塞型別
    int btype;              /* Type of blocking op if REDIS_BLOCKED. */
    // 阻塞狀態
    blockingState bpop;     /* blocking state */

    // 最後被寫入的全域性複製偏移量
    long long woff;         /* Last write global replication offset. */

    // 被監視的鍵
    list *watched_keys;     /* Keys WATCHED for MULTI/EXEC CAS */

    // 這個字典記錄了客戶端所有訂閱的頻道
    // 鍵為頻道名字,值為 NULL
    // 也即是,一個頻道的集合
    dict *pubsub_channels;  /* channels a client is interested in (SUBSCRIBE) */

    // 連結串列,包含多個 pubsubPattern 結構
    // 記錄了所有訂閱頻道的客戶端的資訊
    // 新 pubsubPattern 結構總是被新增到表尾
    list *pubsub_patterns;  /* patterns a client is interested in (SUBSCRIBE) */
    sds peerid;             /* Cached peer ID. */

    /* Response buffer */
    // 回覆偏移量
    int bufpos;
    // 回覆緩衝區
    char buf[REDIS_REPLY_CHUNK_BYTES];

} 


相關文章