Redis 設計與實現 (四)--事件、客戶端

K戰神發表於2018-02-02

事件

一、檔案事件

  檔案事件處理器使用I/O多路複用程式來同時監聽多個套接字,

  監聽套接字,分配對應的處理事件。

  四個組成部分:套接字 、I/O多路複用 、 檔案事件分派器 、 事件處理器

  連線應答處理器:redis伺服器初始化,將連線應答處理器和伺服器監聽套接字的事件慣量,當客戶端使用connect 函式連結伺服器,套接字產生事件,觸發連線應答處理器。

  命令請求處理器:客戶端向伺服器傳送命令請求的時候,套接字產生事件,觸發命令請求處理器處理請求。

  命令回覆處理器:伺服器命令回覆傳給客戶端,伺服器將命令回覆處理器和事件關聯,客戶端準備接收回傳命令後,觸發事件引發回覆處理器執行。

二、時間事件

  當被監聽的套接字準備好執行連結應答、讀取、寫入、關閉等操作時,

  與操作相對性的檔案事件就會產生。

  定時事件:指定時間執行一次程式

  週期事件:隔一定時間執行一次程式

  id -- 伺服器為時間事件建立的全域性唯一ID,遞增

  when -- 毫秒,記錄事件的觸發時間

  timeProc -- 時間事件處理器

 

客戶端

struct redisServer {
    /* General */
    pid_t pid;                  /* Main process pid. */
    char *configfile;           /* Absolute config file path, or NULL */
    int hz;                     /* serverCron() calls frequency in hertz */
    redisDb *db;
    dict *commands;             /* Command table */
    dict *orig_commands;        /* Command table before command renaming. */
    aeEventLoop *el;
    unsigned lruclock:REDIS_LRU_BITS; /* Clock for LRU eviction */
    int shutdown_asap;          /* SHUTDOWN needed ASAP */
    int activerehashing;        /* Incremental rehash in serverCron() */
    char *requirepass;          /* Pass for AUTH command, or NULL */
    char *pidfile;              /* PID file path */
    int arch_bits;              /* 32 or 64 depending on sizeof(PORT_LONG) */
    int cronloops;              /* Number of times the cron function run */
    char runid[REDIS_RUN_ID_SIZE+1];  /* ID always different at every exec. */
    int sentinel_mode;          /* True if this instance is a Sentinel. */
    /* Networking */
    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[] */
    char *unixsocket;           /* UNIX socket path */
    mode_t unixsocketperm;      /* UNIX socket permission */
    int ipfd[REDIS_BINDADDR_MAX]; /* TCP socket file descriptors */
    int ipfd_count;             /* Used slots in ipfd[] */
    int sofd;                   /* Unix socket file descriptor */
    int cfd[REDIS_BINDADDR_MAX];/* Cluster bus listening socket */
    int cfd_count;              /* Used slots in cfd[] */
    list *clients;              /* List of active clients */
    list *clients_to_close;     /* Clients to close asynchronously */
    list *slaves, *monitors;    /* List of slaves and MONITORs */
    redisClient *current_client; /* Current client, only used on crash report */
    int clients_paused; 
View Code

 一、輸入緩衝區

  客戶端狀態的輸入緩衝區(sds  querybuf)用於儲存客戶端傳送的命令請求

  命令:robj **argv  命令引數

       int    argc  命令引數個數

     從伺服器協議中分析引數和個數,根據argv[0]查詢對應的命令函式 

 

二、輸出緩衝區

  執行命令後的回覆被儲存再客戶端狀態的輸出緩衝區裡面。

  --固定緩衝區,長度較小的回覆

  --可變緩衝區,長度較大的回覆

 

三、身份驗證

  int  authenticated

  ==0 未驗證  ; ==1 客戶端通過了驗證

四、時間

  timt_t    ctime  --建立客戶端的時間,可計算連線時長(秒)

  timt_t  lastinteraction  --客戶端和伺服器最後一次通訊時間,可用來計算客戶端空轉時間

  time_t    obuf_soft_limit_reached_time  --輸出緩衝區第一次到達軟性限制的時間

五、客戶端的建立與關閉

  伺服器使用不同的方式來建立和關閉不同型別的客戶端

  5.1 建立普通客戶端

  5.2 關閉普通客戶端

    --客戶端程式退出或者殺死

    --客戶端向伺服器傳送不符合協議格式的命令請求

    --客戶端設定了timeout 配置選項

    --客戶端傳送的命令請求大小超過了輸出緩衝區限制大小,預設1G  (硬性限制和軟性限制)

  5.3 Lua指令碼的偽客戶端

    伺服器初始化建立負責執行Lua指令碼中包含redis命令的偽客戶端

  5.4 AOF 檔案的偽客戶端

    伺服器再載入AOF檔案時,建立用於執行AOF檔案包含的redis命令的偽客戶端,並在載入完成後關閉。

六、伺服器

  6.1 命令請求的執行過程

  客戶端傳送命令 set key value  收到回覆 ok     

  1、客戶端傳送 set key value 

  2、服務端接收處理客戶端的命令請求,產生回覆ok

  3、將命令回覆給客戶端

  4、客戶端接收回復,列印展示

  6.2 傳送命令請求

  使用者-> 請求-> 客戶端-> 協議轉換-> 伺服器

  6.3讀取命令請求

  讀取命令->儲存到客戶狀態的緩衝區->分析命令->呼叫命令執行器

  6.4 將命令回覆發給客戶端

  6.5客戶端接收並列印命令回覆

  伺服器->客戶端->使用者

七、serverCron函式

  serverCron 預設100毫秒執行一次,管理伺服器資源。

  7.1、更新伺服器時間快取

  因為獲取系統當前時間操作比較頻繁,所以快取系統時間。

  所以快取當前時間:

  unixtime //秒級

  mstime  //毫秒

  7.2、更新LRU時鐘

  lruclock  伺服器的LRU時鐘

  7.3、更新伺服器每秒執行命令次數

  trackOperationsPerSecond  //估算伺服器再最近一秒鐘的處理請求數量

  7.4、更新伺服器記憶體峰值記錄

  程式會檢視當前使用的記憶體的數量

  7.5、處理sigterm訊號

  7.6、管理客戶端資源

  超時--釋放資源

  超出輸入緩衝區長度--釋放資源

  7.7、管理伺服器資源

  刪除過期鍵,收縮字典

  7.8、執行延遲的bgrewriteaof

  執行延遲的重寫aof操作

  7.9、檢查持久化操作的執行狀態

  rdb_child_pid --  bgsave命令子程式id

  aof_child_pid  --bgrewriteaof命令子程式id

  可以檢查是否有正在執行以上命令。

  7.10 AOF緩衝區的內容寫入AOF檔案

  啟動持久化,會將緩衝區內容寫入到aof檔案

  7.11 關閉非同步客戶端

  7.12 增加 cronloops 計數器值

  cronloops --記錄serverCron函式執行次數

八、初始化伺服器

  redis 伺服器啟動到接受客戶端命令,需要一系列初始化和設定過程。

  8.1 初始化伺服器狀態結構

  建立一個 redisServer結構,並賦值預設值initServerConfig函式。

struct redisServer
void initServerConfig(void) {
    int j;

    getRandomHexChars(server.runid,REDIS_RUN_ID_SIZE);
    server.configfile = NULL;
    server.hz = REDIS_DEFAULT_HZ;
    server.runid[REDIS_RUN_ID_SIZE] = '\0';
    server.arch_bits = (sizeof(PORT_LONG) == 8) ? 64 : 32;
    server.port = REDIS_SERVERPORT;
    server.tcp_backlog = REDIS_TCP_BACKLOG;
    server.bindaddr_count = 0;
    server.unixsocket = NULL;
    server.unixsocketperm = REDIS_DEFAULT_UNIX_SOCKET_PERM;
    server.ipfd_count = 0;
    server.sofd = -1;
    server.dbnum = REDIS_DEFAULT_DBNUM;
    server.verbosity = REDIS_DEFAULT_VERBOSITY;
    WIN32_ONLY(setLogVerbosityLevel(server.verbosity);)
    server.maxidletime = REDIS_MAXIDLETIME;
    server.tcpkeepalive = REDIS_DEFAULT_TCP_KEEPALIVE;
    server.active_expire_enabled = 1;
    server.client_max_querybuf_len = REDIS_MAX_QUERYBUF_LEN;
    server.saveparams = NULL;
    server.loading = 0;
    server.logfile = zstrdup(REDIS_DEFAULT_LOGFILE);
    server.syslog_enabled = REDIS_DEFAULT_SYSLOG_ENABLED;
    server.syslog_ident = zstrdup(REDIS_DEFAULT_SYSLOG_IDENT);
    POSIX_ONLY(server.syslog_facility = LOG_LOCAL0;)
    server.daemonize = REDIS_DEFAULT_DAEMONIZE;
    server.aof_state = REDIS_AOF_OFF;
    server.aof_fsync = REDIS_DEFAULT_AOF_FSYNC;
    server.aof_no_fsync_on_rewrite = REDIS_DEFAULT_AOF_NO_FSYNC_ON_REWRITE;
    server.aof_rewrite_perc = REDIS_AOF_REWRITE_PERC;
    server.aof_rewrite_min_size = REDIS_AOF_REWRITE_MIN_SIZE;
    server.aof_rewrite_base_size = 0;
    server.aof_rewrite_scheduled = 0;
    server.aof_last_fsync = time(NULL);
    server.aof_rewrite_time_last = -1;
    server.aof_rewrite_time_start = -1;
    server.aof_lastbgrewrite_status = REDIS_OK;
    server.aof_delayed_fsync = 0;
    server.aof_fd = -1;
    server.aof_selected_db = -1; /* Make sure the first time will not match */
    server.aof_flush_postponed_start = 0;
    server.aof_rewrite_incremental_fsync = REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC;
    server.aof_load_truncated = REDIS_DEFAULT_AOF_LOAD_TRUNCATED;
    server.pidfile = zstrdup(REDIS_DEFAULT_PID_FILE);
    server.rdb_filename = zstrdup(REDIS_DEFAULT_RDB_FILENAME);
    server.aof_filename = zstrdup(REDIS_DEFAULT_AOF_FILENAME);
    server.requirepass = NULL;
    server.rdb_compression = REDIS_DEFAULT_RDB_COMPRESSION;
    server.rdb_checksum = REDIS_DEFAULT_RDB_CHECKSUM;
    server.stop_writes_on_bgsave_err = REDIS_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR;
    server.activerehashing = REDIS_DEFAULT_ACTIVE_REHASHING;
    server.notify_keyspace_events = 0;
    server.maxclients = REDIS_MAX_CLIENTS;
    server.bpop_blocked_clients = 0;
    server.maxmemory = REDIS_DEFAULT_MAXMEMORY;
    server.maxmemory_policy = REDIS_DEFAULT_MAXMEMORY_POLICY;
    server.maxmemory_samples = REDIS_DEFAULT_MAXMEMORY_SAMPLES;
    server.hash_max_ziplist_entries = REDIS_HASH_MAX_ZIPLIST_ENTRIES;
    server.hash_max_ziplist_value = REDIS_HASH_MAX_ZIPLIST_VALUE;
    server.list_max_ziplist_entries = REDIS_LIST_MAX_ZIPLIST_ENTRIES;
    server.list_max_ziplist_value = REDIS_LIST_MAX_ZIPLIST_VALUE;
    server.set_max_intset_entries = REDIS_SET_MAX_INTSET_ENTRIES;
    server.zset_max_ziplist_entries = REDIS_ZSET_MAX_ZIPLIST_ENTRIES;
    server.zset_max_ziplist_value = REDIS_ZSET_MAX_ZIPLIST_VALUE;
    server.hll_sparse_max_bytes = REDIS_DEFAULT_HLL_SPARSE_MAX_BYTES;
    server.shutdown_asap = 0;
    server.repl_ping_slave_period = REDIS_REPL_PING_SLAVE_PERIOD;
    server.repl_timeout = REDIS_REPL_TIMEOUT;
    server.repl_min_slaves_to_write = REDIS_DEFAULT_MIN_SLAVES_TO_WRITE;
    server.repl_min_slaves_max_lag = REDIS_DEFAULT_MIN_SLAVES_MAX_LAG;
    server.cluster_enabled = 0;
    server.cluster_node_timeout = REDIS_CLUSTER_DEFAULT_NODE_TIMEOUT;
    server.cluster_migration_barrier = REDIS_CLUSTER_DEFAULT_MIGRATION_BARRIER;
    server.cluster_slave_validity_factor = REDIS_CLUSTER_DEFAULT_SLAVE_VALIDITY;
    server.cluster_require_full_coverage = REDIS_CLUSTER_DEFAULT_REQUIRE_FULL_COVERAGE;
    server.cluster_configfile = zstrdup(REDIS_DEFAULT_CLUSTER_CONFIG_FILE);
    server.lua_caller = NULL;
    server.lua_time_limit = REDIS_LUA_TIME_LIMIT;
    server.lua_client = NULL;
    server.lua_timedout = 0;
    server.migrate_cached_sockets = dictCreate(&migrateCacheDictType,NULL);
    server.next_client_id = 1; /* Client IDs, start from 1 .*/
    server.loading_process_events_interval_bytes = (1024*1024*2);

    server.lruclock = getLRUClock();
    resetServerSaveParams();

    appendServerSaveParams(60*60,1);  /* save after 1 hour and 1 change */
    appendServerSaveParams(300,100);  /* save after 5 minutes and 100 changes */
    appendServerSaveParams(60,10000); /* save after 1 minute and 10000 changes */
    /* Replication related */
    server.masterauth = NULL;
    server.masterhost = NULL;
    server.masterport = 6379;
    server.master = NULL;
    server.cached_master = NULL;
    server.repl_master_initial_offset = -1;
    server.repl_state = REDIS_REPL_NONE;
    server.repl_syncio_timeout = REDIS_REPL_SYNCIO_TIMEOUT;
    server.repl_serve_stale_data = REDIS_DEFAULT_SLAVE_SERVE_STALE_DATA;
    server.repl_slave_ro = REDIS_DEFAULT_SLAVE_READ_ONLY;
    server.repl_down_since = 0; /* Never connected, repl is down since EVER. */
    server.repl_disable_tcp_nodelay = REDIS_DEFAULT_REPL_DISABLE_TCP_NODELAY;
    server.repl_diskless_sync = REDIS_DEFAULT_REPL_DISKLESS_SYNC;
    server.repl_diskless_sync_delay = REDIS_DEFAULT_REPL_DISKLESS_SYNC_DELAY;
    server.slave_priority = REDIS_DEFAULT_SLAVE_PRIORITY;
    server.master_repl_offset = 0;

    /* Replication partial resync backlog */
    server.repl_backlog = NULL;
    server.repl_backlog_size = REDIS_DEFAULT_REPL_BACKLOG_SIZE;
    server.repl_backlog_histlen = 0;
    server.repl_backlog_idx = 0;
    server.repl_backlog_off = 0;
    server.repl_backlog_time_limit = REDIS_DEFAULT_REPL_BACKLOG_TIME_LIMIT;
    server.repl_no_slaves_since = time(NULL);

    /* Client output buffer limits */
    for (j = 0; j < REDIS_CLIENT_TYPE_COUNT; j++)
        server.client_obuf_limits[j] = clientBufferLimitsDefaults[j];

    /* Double constants initialization */
    R_Zero = 0.0;
    R_PosInf = 1.0/R_Zero;
    R_NegInf = -1.0/R_Zero;
    R_Nan = R_Zero/R_Zero;

    /* Command table -- we initiialize it here as it is part of the
     * initial configuration, since command names may be changed via
     * redis.conf using the rename-command directive. */
    server.commands = dictCreate(&commandTableDictType,NULL);
    server.orig_commands = dictCreate(&commandTableDictType,NULL);
    populateCommandTable();
    server.delCommand = lookupCommandByCString("del");
    server.multiCommand = lookupCommandByCString("multi");
    server.lpushCommand = lookupCommandByCString("lpush");
    server.lpopCommand = lookupCommandByCString("lpop");
    server.rpopCommand = lookupCommandByCString("rpop");

    /* Slow log */
    server.slowlog_log_slower_than = REDIS_SLOWLOG_LOG_SLOWER_THAN;
    server.slowlog_max_len = REDIS_SLOWLOG_MAX_LEN;

    /* Latency monitor */
    server.latency_monitor_threshold = REDIS_DEFAULT_LATENCY_MONITOR_THRESHOLD;

    /* Debugging */
    server.assert_failed = "<no assertion failed>";
    server.assert_file = "<no file>";
    server.assert_line = 0;
    server.bug_report_start = 0;
    server.watchdog_period = 0;
}
View Code

 

  設定伺服器執行ID

  設定伺服器的預設執行頻率

  設定伺服器的預設配置檔案路徑

  設定伺服器執行架構

  設定伺服器預設的埠號

  設定伺服器的預設rdb持久化條件和aof持久化條件

  初始化伺服器的LRU時鐘

  建立命令表

  8.2 載入配置選項

  使用者可以通過給定的配置引數或者指定配置檔案路徑覆蓋伺服器預設配置

  8.3 初始化伺服器的資料結構

    server.clients  連結串列 , 記錄所有與伺服器連線的客戶端狀態結構

    server.db 陣列,包含所有資料庫

    server.pubsub_channels 字典,儲存頻道訂閱資訊

    server.pubsub_patterns 欄位,儲存模式訂閱資訊

    server.lua 執行lua指令碼的lua環境

    server.slowlog 屬性,儲存慢查詢的日誌

  8.4 還原資料庫狀態    

    開啟AOF持久化,aof檔案來還原

    沒有開啟AOF,使用本地RDB檔案還原

相關文章