Redis设计与实现 笔记 第九章 数据库

数据库

本章主要是围绕着 redisServer 结构简单的介绍了一下大致情况,包括客户端切换数据库的方法,数据库保存键值对的方法,以及真对数据的添加,查看,删除,更新操作的实现方法等,还包括键的过期处理,事件的通知,

9.1 服务器中的数据库

struct redisServer {

    /* General */

    // 配置文件的绝对路径
    char *configfile;           /* Absolute config file path, or NULL */

    // serverCron() 每秒调用的次数
    int hz;                     /* serverCron() calls frequency in hertz */

    // 数据库
    redisDb *db;

    // 命令表(受到 rename 配置选项的作用)
    dict *commands;             /* Command table */
    // 命令表(无 rename 配置选项的作用)
    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 */

    // 在执行 serverCron() 时进行渐进式 rehash
    int activerehashing;        /* Incremental rehash in serverCron() */

    // 是否设置了密码
    char *requirepass;          /* Pass for AUTH command, or NULL */

    // PID 文件
    char *pidfile;              /* PID file path */

    // 架构类型
    int arch_bits;              /* 32 or 64 depending on sizeof(long) */

    // serverCron() 函数的运行次数计数器
    int cronloops;              /* Number of times the cron function run */

    // 本服务器的 RUN ID
    char runid[REDIS_RUN_ID_SIZE+1];  /* ID always different at every exec. */

    // 服务器是否运行在 SENTINEL 模式
    int sentinel_mode;          /* True if this instance is a Sentinel. */


    /* Networking */

    // 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[] */

    // UNIX 套接字
    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[] */

    // UNIX 套接字文件描述符
    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;         /* True if clients are currently paused */
    mstime_t clients_pause_end_time; /* Time when we undo clients_paused */

    // 网络错误
    char neterr[ANET_ERR_LEN];   /* Error buffer for anet.c */

    // MIGRATE 缓存
    dict *migrate_cached_sockets;/* MIGRATE cached sockets */


    /* RDB / AOF loading information */

    // 这个值为真时,表示服务器正在进行载入
    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;


    /* Fields used only for stats */

    // 服务器启动时间
    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 */

    // 因为回收内存而被释放的过期键的数量
    long long stat_evictedkeys;     /* Number of evicted keys (maxmemory) */

    // 成功查找键的次数
    long long stat_keyspace_hits;   /* Number of successful lookups of keys */

    // 查找键失败的次数
    long long stat_keyspace_misses; /* Number of failed lookups of keys */

    // 已使用内存峰值
    size_t stat_peak_memory;        /* Max used memory record */

    // 最后一次执行 fork() 时消耗的时间
    long long stat_fork_time;       /* Time needed to perform latest fork() */

    // 服务器因为客户端数量过多而拒绝客户端连接的次数
    long long stat_rejected_conn;   /* Clients rejected because of maxclients */

    // 执行 full sync 的次数
    long long stat_sync_full;       /* Number of full resyncs with slaves. */

    // PSYNC 成功执行的次数
    long long stat_sync_partial_ok; /* Number of accepted PSYNC requests. */

    // PSYNC 执行失败的次数
    long long stat_sync_partial_err;/* Number of unaccepted PSYNC requests. */


    /* slowlog */

    // 保存了所有慢查询日志的链表
    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;     /* SLOWLOG max number of items logged */
    size_t resident_set_size;       /* RSS sampled in serverCron(). */
    /* The following two are used to track instantaneous "load" in terms
     * of operations per second. */
    // 最后一次进行抽样的时间
    long long ops_sec_last_sample_time; /* Timestamp of last sample (in ms) */
    // 最后一次抽样时,服务器已执行命令的数量
    long long ops_sec_last_sample_ops;  /* numcommands in last sample */
    // 抽样结果
    long long ops_sec_samples[REDIS_OPS_SEC_SAMPLES];
    // 数组索引,用于保存抽样结果,并在需要时回绕到 0
    int ops_sec_idx;


    /* Configuration */

    // 日志可见性
    int verbosity;                  /* Loglevel in redis.conf */

    // 客户端最大空转时间
    int maxidletime;                /* Client timeout in seconds */

    // 是否开启 SO_KEEPALIVE 选项
    int tcpkeepalive;               /* Set SO_KEEPALIVE if non-zero. */
    int active_expire_enabled;      /* Can be disabled for testing purposes. */
    size_t client_max_querybuf_len; /* Limit for client query buffer length */
    int dbnum;                      /* Total number of configured DBs */
    int daemonize;                  /* True if running as a daemon */
    // 客户端输出缓冲区大小限制
    // 数组的元素有 REDIS_CLIENT_LIMIT_NUM_CLASSES 个
    // 每个代表一类客户端:普通、从服务器、pubsub,诸如此类
    clientBufferLimitsConfig client_obuf_limits[REDIS_CLIENT_LIMIT_NUM_CLASSES];


    /* AOF persistence */

    // 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. */

    // 负责进行 AOF 重写的子进程 ID
    pid_t aof_child_pid;            /* PID if rewriting process */

    // AOF 重写缓存链表,链接着多个缓存块
    list *aof_rewrite_buf_blocks;   /* Hold changes during an AOF rewrite. */

    // AOF 缓冲区
    sds aof_buf;      /* AOF buffer, written before entering the event loop */

    // AOF 文件的描述符
    int aof_fd;       /* File descriptor of currently selected AOF file */

    // AOF 的当前目标数据库
    int aof_selected_db; /* Currently selected DB in AOF */

    // 推迟 write 操作的时间
    time_t aof_flush_postponed_start; /* UNIX time of postponed AOF flush */

    // 最后一直执行 fsync 的时间
    time_t aof_last_fsync;            /* UNIX time of last fsync() */
    time_t aof_rewrite_time_last;   /* Time used by last AOF rewrite run. */

    // AOF 重写的开始时间
    time_t aof_rewrite_time_start;  /* Current AOF rewrite start time. */

    // 最后一次执行 BGREWRITEAOF 的结果
    int aof_lastbgrewrite_status;   /* REDIS_OK or REDIS_ERR */

    // 记录 AOF 的 write 操作被推迟了多少次
    unsigned long aof_delayed_fsync;  /* delayed AOF fsync() counter */

    // 指示是否需要每写入一定量的数据,就主动执行一次 fsync()
    int aof_rewrite_incremental_fsync;/* fsync incrementally while rewriting? */
    int aof_last_write_status;      /* REDIS_OK or REDIS_ERR */
    int aof_last_write_errno;       /* Valid if aof_last_write_status is ERR */
    /* RDB persistence */

    // 自从上次 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? */

    // 最后一次完成 SAVE 的时间
    time_t lastsave;                /* Unix time of last successful save */

    // 最后一次尝试执行 BGSAVE 的时间
    time_t lastbgsave_try;          /* Unix time of last attempted bgsave */

    // 最近一次 BGSAVE 执行耗费的时间
    time_t rdb_save_time_last;      /* Time used by last RDB save run. */

    // 数据库最近一次开始执行 BGSAVE 的时间
    time_t rdb_save_time_start;     /* Current RDB save start time. */

    // 最后一次执行 SAVE 的状态
    int lastbgsave_status;          /* REDIS_OK or REDIS_ERR */
    int stop_writes_on_bgsave_err;  /* Don't allow writes if can't BGSAVE */


    /* Propagation of commands in AOF / replication */
    redisOpArray also_propagate;    /* Additional command to propagate. */


    /* Logging */
    char *logfile;                  /* Path of log file */
    int syslog_enabled;             /* Is syslog enabled? */
    char *syslog_ident;             /* Syslog ident */
    int syslog_facility;            /* Syslog facility */


    /* Replication (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 */

    // backlog 本身
    char *repl_backlog;             /* Replication backlog for partial syncs */
    // backlog 的长度
    long long repl_backlog_size;    /* Backlog circular buffer size */
    // backlog 中数据的长度
    long long repl_backlog_histlen; /* Backlog actual data length */
    // backlog 的当前索引
    long long repl_backlog_idx;     /* Backlog circular buffer current offset */
    // backlog 中可以被还原的第一个字节的偏移量
    long long repl_backlog_off;     /* Replication offset of first byte in the
                                       backlog buffer. */
    // backlog 的过期时间
    time_t repl_backlog_time_limit; /* Time without slaves after the backlog
                                       gets released. */

    // 距离上一次有从服务器的时间
    time_t repl_no_slaves_since;    /* We have no slaves since that time.
                                       Only valid if server.slaves len is 0. */

    // 是否开启最小数量从服务器写入功能
    int repl_min_slaves_to_write;   /* Min number of slaves to write. */
    // 定义最小数量从服务器的最大延迟值
    int repl_min_slaves_max_lag;    /* Max lag of <count> slaves to write. */
    // 延迟良好的从服务器的数量
    int repl_good_slaves_count;     /* Number of slaves with lag <= max_lag. */


    /* Replication (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 */
    // 主服务器所对应的客户端
    redisClient *master;     /* Client that is master for this slave */
    // 被缓存的主服务器,PSYNC 时使用
    redisClient *cached_master; /* Cached master to be reused for PSYNC. */
    int repl_syncio_timeout; /* Timeout for synchronous I/O calls */
    // 复制的状态(服务器是从服务器时使用)
    int repl_state;          /* Replication status if the instance is a slave */
    // RDB 文件的大小
    off_t repl_transfer_size; /* Size of RDB to read from master during sync. */
    // 已读 RDB 文件内容的字节数
    off_t repl_transfer_read; /* Amount of RDB read from master during sync. */
    // 最近一次执行 fsync 时的偏移量
    // 用于 sync_file_range 函数
    off_t repl_transfer_last_fsync_off; /* Offset when we fsync-ed last time. */
    // 主服务器的套接字
    int repl_transfer_s;     /* Slave -> Master SYNC socket */
    // 保存 RDB 文件的临时文件的描述符
    int repl_transfer_fd;    /* Slave -> Master SYNC temp file descriptor */
    // 保存 RDB 文件的临时文件名字
    char *repl_transfer_tmpfile; /* Slave-> master SYNC temp file name */
    // 最近一次读入 RDB 内容的时间
    time_t repl_transfer_lastio; /* Unix time of the latest read, for timeout */
    int repl_serve_stale_data; /* Serve stale data when link is down? */
    // 是否只读从服务器?
    int repl_slave_ro;          /* Slave is read only? */
    // 连接断开的时长
    time_t repl_down_since; /* Unix time at which link with master went down */
    // 是否要在 SYNC 之后关闭 NODELAY ?
    int repl_disable_tcp_nodelay;   /* Disable TCP_NODELAY after SYNC? */
    // 从服务器优先级
    int slave_priority;             /* Reported in INFO and used by Sentinel. */
    // 本服务器(从服务器)当前主服务器的 RUN ID
    char repl_master_runid[REDIS_RUN_ID_SIZE+1];  /* Master run id for PSYNC. */
    // 初始化偏移量
    long long repl_master_initial_offset;         /* Master PSYNC offset. */


    /* Replication script cache. */
    // 复制脚本缓存
    // 字典
    dict *repl_scriptcache_dict;        /* SHA1 all slaves are aware of. */
    // FIFO 队列
    list *repl_scriptcache_fifo;        /* First in, first out LRU eviction. */
    // 缓存的大小
    int repl_scriptcache_size;          /* Max number of elements. */

    /* Synchronous replication. */
    list *clients_waiting_acks;         /* Clients waiting in WAIT command. */
    int get_ack_from_slaves;            /* If true we send REPLCONF GETACK. */
    /* Limits */
    int maxclients;                 /* Max number of simultaneous clients */
    unsigned long long maxmemory;   /* Max number of memory bytes to use */
    int maxmemory_policy;           /* Policy for key eviction */
    int maxmemory_samples;          /* Pricision of random sampling */


    /* Blocked clients */
    unsigned int bpop_blocked_clients; /* Number of clients blocked by lists */
    list *unblocked_clients; /* list of clients to unblock before next loop */
    list *ready_keys;        /* List of readyList structures for BLPOP & co */


    /* Sort parameters - qsort_r() is only available under BSD so we
     * have to take this state global, in order to pass it to sortCompare() */
    int sort_desc;
    int sort_alpha;
    int sort_bypattern;
    int sort_store;


    /* Zip structure config, see redis.conf for more information  */
    size_t hash_max_ziplist_entries;
    size_t hash_max_ziplist_value;
    size_t list_max_ziplist_entries;
    size_t list_max_ziplist_value;
    size_t set_max_intset_entries;
    size_t zset_max_ziplist_entries;
    size_t zset_max_ziplist_value;
    size_t hll_sparse_max_bytes;
    time_t unixtime;        /* Unix time sampled every cron cycle. */
    long long mstime;       /* Like 'unixtime' but with milliseconds resolution. */


    /* Pubsub */
    // 字典,键为频道,值为链表
    // 链表中保存了所有订阅某个频道的客户端
    // 新客户端总是被添加到链表的表尾
    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. */


    /* Cluster */

    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. */
    /* Scripting */

    // 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 */

    // 一个字典,值为 Lua 脚本,键为脚本的 SHA1 校验和
    dict *lua_scripts;         /* A dictionary of SHA1 -> Lua scripts */
    // Lua 脚本的执行时限
    mstime_t lua_time_limit;  /* Script timeout in milliseconds */
    // 脚本开始执行的时间
    mstime_t lua_time_start;  /* Start time of script, milliseconds time */

    // 脚本是否执行过写命令
    int lua_write_dirty;  /* True if a write command was called during the
                             execution of the current script. */

    // 脚本是否执行过带有随机性质的命令
    int lua_random_dirty; /* True if a random command was called during the
                             execution of the current script. */

    // 脚本是否超时
    int lua_timedout;     /* True if we reached the time limit for script
                             execution. */

    // 是否要杀死脚本
    int lua_kill;         /* Kill the script if true. */


    /* Assert & bug reporting */

    char *assert_failed;
    char *assert_file;
    int assert_line;
    int bug_report_start; /* True if bug report header was already logged. */
    int watchdog_period;  /* Software watchdog period in ms. 0 = off */
};

数据库的结构如上,当服务器启动时, 会从 database 选项中获取当前服务器的 dbnum (默认为16),在生成具体的对象存入 db 数组中,

// 数据库
redisDb *db;
//数据库数量
int dbnum;                      
9.2 切换数据库

每个客户端都有自己的目标数据库,

/* With multiplexing we need to take per-client state.
 * Clients are taken in a liked list.
 *
 * 因为 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];

} redisClient;

切换数据库,实际上就是更改 db 指向的对象,

// 当前正在使用的数据库
redisDb *db;

通过下面指令进行切换

SELECT 2

书中提到的一点值得注意的: 如果在非 redis 客户端进行数据库的查询, 可能并不会显示指出当前所在指向的数据库标记,在一个连续输入命令的过程中,如果在开始切换了数据库,又大意的未进行切回,也没有相对应的提示, 再输入了 FLUSHDB 这种清空危险操作,那世界可不就完了,所以书中建议,在进行危险操作时,输入 SELECT 进行数据库的正确切换.

9.3 数据库键空间
typedef struct redisDb {

    // 数据库键空间,保存着数据库中的所有键值对
    dict *dict;                 /* The keyspace for this DB */

    // 键的过期时间,字典的键为键,字典的值为过期事件 UNIX 时间戳
    dict *expires;              /* Timeout of keys with a timeout set */

    // 正处于阻塞状态的键
    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP) */

    // 可以解除阻塞的键
    dict *ready_keys;           /* Blocked keys that received a PUSH */

    // 正在被 WATCH 命令监视的键
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */

    struct evictionPoolEntry *eviction_pool;    /* Eviction pool of keys */

    // 数据库号码
    int id;                     /* Database ID */

    // 数据库的键的平均 TTL ,统计信息
    long long avg_ttl;          /* Average TTL, just for stats */

} redisDb;
dict* dict;

字典中存放着所有的键值对,键的为一个字符串对象,值可以是 字符串对象,列表对象,哈希表对象.

9.3.1 添加新键

添加到字典中,键是一个字符串,值是根据命令来具体生成值对象,再放入字典中

9.3.2 删除键

已知数据存在一个字典中, 所以删除键就是找到对应的键值对进行删除操作

9.3.3 更新键

通过键找到对象,再修改对应值,需要注意修改命令是否符合键值对的类型,不符合时,会出现以下错误.

(error) WRONGTYPE Operation against a key holding the wrong kind of value
9.3.4 对键取值

通过字典找到对应键,返回值

9.3.5 其他键的空间操作

FLUSHDB 删除所有键值对
RANDOMKEY 随机返回一个值
EXISTS 是否存在键
RENAME 更改名字
KEYS 返回所有符合条件的值

9.3.6 读写键空间时的维护操作

1): 在读取一个键之后,服务器会根据键是否存在来更新键空间命中和不命中次数,这两个值可以在 INFO stats 命令的 keyspace_hits 属性和 keyspace_misses 属性中查看.

// 成功查找键的次数
long long stat_keyspace_hits;   /* Number of successful lookups of keys */

// 查找键失败的次数
long long stat_keyspace_misses; /* Number of failed lookups of keys */

2): 在读取一个键之后,服务器会更新键的 LRU 时间,这个值可以计算键的空闲时间,使用 OBJECT idletime 命令可以查看键 key 的限制时间.

3): 当读取一个键,发现键过期时,会先进行删除操作,然后以键为空执行后续命令

4): 如果客户端用 WATCH 命令监视了某个键,那么服务器在对被监视的剑进行修改之后,汇集为 dirty, 从而让事物程序注意到该键

5): 服务器每次修改一个键之后,都会对 dirty 键计数器的值增加1,这个计数器会触发服务器的持久化操作以及赋值操作.

6): 如果服务器开启了数据库通知功能,那么在对键进行修改之后,服务器将按配置发送相应的数据库通知.

9.4 设置键的生存时间或过期时间

通过 EXPIRE 命令或者 PEXPIRE 命令, 客户端可以以秒或者毫秒精度为数据库中的某个键的设置生存时间(TIME TO LIVE) TTL,经过这么多事件后,数据库会自动删除超时的键.

通过 EXPIREAT 命令或者 PEXPIREAT 命令, 客户端可以设置具体的日期时间,到期后自动删除.

TTL 命令和 PTTL 命令接受一个带有生存时间或者过期时间的键,并返回剩余的生存时间.

9.4.1 设置过期时间

EXPIRE 设置生存时间为 ttl 秒
PEXPIRE 设置生存时间为 ttl 毫秒
EXPIREAT 设置过期时间为 timestamp 秒级时间戳
PEXPIREAT 设置过期时间为 timestamp 毫秒级时间戳

实际上在代码实现中, EXPIRE PEXPIRE EXPIREAT 都是根据 PEXPIREAT 实现的,这也符合我们编写代码的思路,类似的代码需要有个高复用的设计.

9.4.2 保存过期时间

通常来说,面对该种需求有两种整体思路,
一个思路是我现在所在公司使用比较多的, 将所有属性都存放在一个对象上,然后去这个对象中存取,这样的设计固然是有好处的,增量起来简单,往上添加模块属性时,没什么负担,熟练后可以以一个熟能生巧的方式去进行代码的添加, 不会出错,不会崩溃,但这样的代码基本上无法交给他人维护的.大量的属性堆叠会造成更大的负担,且当开发人员交接后,接手的人员很难下判断去删除可能实际上已经不再使用的属性,从而造成效率的略微低下,但其实对于一个稳定的服务器来说也无伤大雅.

第二个思路是 Redis 这种, 用额外的结构来进行过期时间的维护,可以减少键值对的负担,代码结构还清晰,将多层结构平铺到单层.

回到话题中,在 redisDb 中有个专门保存过期时间的,过期字典.

// 键的过期时间,字典的键为键,字典的值为过期事件 UNIX 时间戳
dict *expires;              /* Timeout of keys with a timeout set */

键为相同字符串,值为过期时间,时间为具体时间戳

9.4.3 移除过期时间

PERSIST 命令 和 PEXPREAT 命令 可以用来移除过期时间, 也就是去删除 expires 字典中键值对,也就完成了移除过期时间的操作

9.4.4 计算并返回剩余生存时间

TTL
PTTL

通过搜索 expires 找到对应的键,然后用当前时间减去值,返回具体值,即为剩余生存时间

9.4.5 过期键的判定

过期键的判定流程:
首先判断过期字典中是否存在具体键,如果没有,那也没必要往下走了.
在用当前时间错与值进行比对.
就完成了判定

9.4.6 过期键的删除策略

1): 惰性删除,在下一次进行键存取的时,再进行删除操作,方案的优点是,CPU友好,但是内存不友好.
2): 定时删除,在进行设置时,再建一个定时事件,当时间到期后,执行定时事件,直接删除,方案的优点是内存友好,CPU不友好,CPU需要花大量的时间段去进行是否超时判断,
3): 定期删除,每过一段时间,就去数据库中检查,删除过期键.相当于上述两种方案的折中处理

9.5.1 定时删除

定时删除对内存非常友好,能及时删除所有的过期键,但是及时删除是有代价,删除过期键这个行为可能会占用一部分 CPU 时间,将 CPU 时间用在删除和当前任务无关的过期键上,无疑会对服务器的响应时间和吞吐量造成印象概念股

9.5.2 惰性删除

惰性删除是一个对内存不友好的策略,当一个键到了过期事件后,只有在下一次进行存取更新操作时前,再进行键删除操作,这个前提下,如果一批键过期后都只有到下一次内存存取的时候再进行删除操作,那内存会造成一个浪费,例如日志系统,如果一个日志系统只存最近10分钟的日志数据,在没有出现事情的情况下,进行惰性删除策略,会造成内存的合理泄露.

9.5.3 定期删除

总结定时删除和惰性删除,两者皆有可取之处,也有自己的不足.
所以定期删除策略进行了两者的整合调节:
1): 定期删除策略每隔一段时间执行一个删除过期操作,并通过限制删除操作执行时长和频率来减少删除操作对 CPU 时间的影响.
2): 除此之外,通过定期删除过期键,定期删除策略有效地减少了因为过期键而带来的内存浪费

当然如何确定删除操作的执行时长和频率也是需要解决的一个问题:
1): 如果删除操作执行的太平凡,或者执行的时间太长,定期删除策略回退化成定时删除策略,以至于将CPU时间过多的消耗在删除过期见上面.
2): 如果删除操作执行的太少,或者执行时间太短,定期删除策略又会和惰性删除一样,出现内存浪费

9.6 Redis 的过期键删除策略

Redis 过期键删除策略为,两种策略的组合: 惰性删除 加上 定期删除,优化定期删除的算法,来平衡达到CPU和内存消耗的平衡

9.6.1 惰性删除策略的实现

惰性删除是一个无法取代的策略,无论使用什么策略,肯定是需要带上惰性删除策略,只有这样,才能完全达到检查是否过期的目的,其他策略总有一个间隔时间的限制,当进行指定键的读写时,需要通过 expireifneed 进行检查,来进行保底,如果过期就进行删除,再继续执行命令

9.6.2 定期删除策略的实现

定期删除策略由 activeExpireCycle 函数实现,在 serverCorn 周期函数操作时,进行调用,算法规则总结如下:
1): 函数每次运行时,都从一定数量的数据库中取出一数量的随机键进行检查,并删除其中的过期键.
2): 全局变量 current_db 惠济路当前 activeEpireCyle 函数检查进度,并在下一次调用时,接着上一次的进度进行处理,当前进度处理到第10号元素,下一次调用会从第11号开始执行.
3):随着 activeExpireCycle 的执行,服务器中所有数据库都会被检查一遍, 这时函数将 current_db 变量充值为0,然后再次开始新一轮的检查

9.7 AOF RDB 和复制功能对 过期键的处理

了解过期键在这几个环境下的处理

9.7.1 生成 RDB 文件

当使用 SAVE 或 BGSAVE 命令创建一个新的 RDB 文件时,程序会进行过期键检查,如果已过期将不进行保存

9.7.2 载入 RDB 文件

文件的载入,分为 主服务器载入和从服务器载入,主服务器载入时会忽略掉过期键,而从服务器是不会判断是否过期直接载入,在从服务器和主服务器进行同步时,会对从服务器进行清空,所以一般情况下,过期键也不会造成影响.

9.7.3 AOF 文件写入

AOF 写入是命令追加存储,所以不会像 RDB 文件一样进行忽略操作,而是到期后执行一个 DEL 命令进行删除.
1): 从数据库中删除 message 键.
2): 追加一条 DEL message 命令到 AOF 文件.
3): 向执行 GET 命令的客户端返回空回复.

9.7.4 AOF 重写

当进行重写时,在重写的时会进行忽略操作,所以不会造成影响

9.7.5 复制

在主从服务器之间进行复制时,从服务器将不进行过期键判断操作,过期键删除由主服务器发过来的 DEL 消息进行操作,所以从服务器中的键虽然在主服务器中已经过期,但是因为某些原因没有收到 DEL 的消息,所以,如果此时去从服务器去查询该键,还是能拿到具体值的.

9.8 数据库通知

数据库通知是可以进行某些事件发生的订阅,当事件发生后可以收到一个通知,

SUBSCRIBE __keyspace@0__:message

当 message 字段发生改变时,会收到对应的通知

需要注意的是,需要在对应的服务器配置值中进行配置,通知功能默认是关闭的

在 redis 配置的 notify-keyspace-events 选项进行相关配置
例如:
想让发送所有键空间通知和键事件通知可以将选项的值设置为AKE.
想让服务器发送所有类型的键空间通知,可以将选项的值设置为AK
想让服务器发送所有类型的键事件通知,可以将选项的值设置为AE

9.8.1 发送通知
void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid);

在发生具体情况后,进行该函数的调用,会通知所有进行订阅的客户端事件已经发生.

9.8.2 发送通知的实现

1): server.notiry_keyspace_events 就是所配置的值, 会先判断值的 type 是否与该配置符合,再决定是否继续
2): 如果给定通知允许,那么下一步函数会检测是否需要发送键空间.
3): 然后再是键空间的判断,允许后发送

pubsubpublishMessage 函数是 PUBLISH 命令的实现函数,相当于执行 PUBLISH .

总结

本章介绍了一下方面
1): 数据库都保存在 redisServer.db 数组中,数量 由 redisServer.dbnum 保存
2): 客户端通过修改目标数据库指针,来切换不同的数据库
3): 数据库主要由 dict 和 expires 组成,用来维护数据和过期表
4): 数据库是由字典构成的,所有数据库的操作都是建立在字典上的
5): 数据库键值对对象,键是字符串,值可以是任意一种 Redis 对象类型,包括字符串对象, 哈希表对象,集合对象,列表对象和有序集合对象
6): expires 字典的键指向数据库的某个键,而值记录了过期时间,过期时间是一个毫秒单位的具体时间的时间戳
7): Redis 使用惰性删除和定期删除策略来进行删除.
8): 执行 SAVE 命令或 BGSAVE 命令的 RDB 文件会忽视已经过期的键
9): 执行 BGREWRITEAOF 命令也会忽视过期的键
10): 当一个过期键删除后,会追加 DEL 到现有 AOF 文件的末尾,显示的删除
11): 当主服务器删除一个过期键之后,会向所有服务器发送一条 DEL 命令,显示的删除
12): 从服务器发现键过期也不会进行删除,而是等待主服务器发来 DEL 命令,从而保证主从服务器一致性
13): 当 Redis 进行键值对修改后,服务器可以根据配置想客户端发送各种通知

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值