《redis设计与实现》 第二部分(第9-11章):单机数据库的实现

9.0 第9章 数据库

9.1 服务器中的数据库

  • redis服务器将所有的数据库都保存在服务器状态的db数组中,db数组的每一个元素的数据结构是redisDb
  • 初始化服务器时,会根据服务器状态的dbnum属性来决定数据库的数量。默认情况下,该选项的值为16,Redis默认会创建16个数据库
//code0: server.h
/* Configuration */
    int verbosity;                  /* Loglevel in redis.conf */
    int maxidletime;                /* Client timeout in seconds */
    int tcpkeepalive;               /* Set SO_KEEPALIVE if non-zero. */
    int active_expire_enabled;      /* Can be disabled for testing purposes. */
    int active_expire_effort;       /* From 1 (default) to 10, active effort. */
    int active_defrag_enabled;
    int jemalloc_bg_thread;         /* Enable jemalloc background thread */
    size_t active_defrag_ignore_bytes; /* minimum amount of fragmentation waste to start active defrag */
    int active_defrag_threshold_lower; /* minimum percentage of fragmentation to start active defrag */
    int active_defrag_threshold_upper; /* maximum percentage of fragmentation at which we use maximum effort */
    int active_defrag_cycle_min;       /* minimal effort for defrag in CPU percentage */
    int active_defrag_cycle_max;       /* maximal effort for defrag in CPU percentage */
    unsigned long active_defrag_max_scan_fields; /* maximum number of fields of set/hash/zset/list to process from within the main dict scan */
    _Atomic size_t client_max_querybuf_len; /* Limit for client query buffer length */
    int dbnum;                      /* Total number of configured DBs */
    int supervised;                 /* 1 if supervised, 0 otherwise. */
    int supervised_mode;            /* See SUPERVISED_* */
    int daemonize;                  /* True if running as a daemon */
    clientBufferLimitsConfig client_obuf_limits[CLIENT_TYPE_OBUF_COUNT];
    

9.2 切换数据库

  • 默认情况下,redis客户端的目标数据库为0号数据库,客户端可以通过select命令来切换目标数据库
  • redisClient.db指向redisServer.db数组中的其中一个元素,被指向的元素就是客户端的目标数据库

9.3 数据库键空间

  • redisDb结构中的dict字典中保存了数据库中的所有键值对,这个字典称之为键空间
//code1: server.h
typedef struct redisDb {
    dict *dict;                 /* The keyspace for this DB */
    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 */
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
    int id;                     /* Database ID */
    long long avg_ttl;          /* Average TTL, just for stats */
    unsigned long expires_cursor; /* Cursor of the active expire cycle. */
    list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;
  • 键空间操作
    • FLUSHDB:清空数据库
    • RANDOMKEY:随即返回数据库中的某个键
    • DBSIZE:返回数据库键数量
    • EXISTS
    • RENAME
    • KEYS
  • 读取一个键的维护操作
    • 更新键空间命中次数或键空间不命中次数
    • 更新LRU时间
    • 读取键的过程中发现已经过期,则先删除过期键
    • 如果客户端watch了这个键,如果服务器对它进行了修改。这个键会标记为脏(dirty)
    • 每次修改一个键,都会对脏键计数器增加1,这个计数器会触发服务器的持久化和复制操作

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

  • 客户端可以使用EXPIREAT或者PEXPIREAT命令,通过秒或者毫秒精度给数据库的某个键设置过期时间(unix时间戳)
  • TTL和PTTL接受一个带有生存时间或者过期时间的键,返回这个键的剩余时间
  • 四个设置过期时间的命令(本质经过转换都是PEXPIREAT实现的)
    • EXPIRE: key的生存时间设置为ttl秒
    • PEXPIRE:key的生存时间设置为ttl毫秒
    • EXPIREAT:key的过期时间设置为timestamp所指定的秒数时间戳
    • PEXPIREAT:key的过期时间设置为timestamp所指定的毫秒数时间戳
  • 过期字典:redisDb结构中的expires字典保存了数据库中所有键的过期时间
    • 键:指针,指向键空间中的某个键对象
    • 值:long long类型的整数,保存了键指向数据库键的过期时间(毫秒单位)
  • 移除键的过期时间:PERSIST
  • 计算并返回剩余生存时间
    • TTL:剩余秒数
    • PTTL:剩余毫秒数

9.5 过期键删除策略

  • 定时删除:最快释放内存,占用CPU(创建定时器需要用到redis服务器中的时间时间,目前时间时间的实现方式是无序链表,查找一个事件的复杂度为O(N)
  • 惰性删除:内存不能及时释放
  • 定期删除:比较适中,难点在于确定删除操作执行的时长和频率

9.6 Redis的过期键删除策略

  • 结合惰性删除和定期删除
  • 惰性删除:代码在db.c/expireIfNeeded
  • 定时删除:代码在expire.c/activeExpireCycle, 在规定的时间内分多次遍历各个数据库,随机检查一部分键的过期时间
    • 真正检查dictEntry并最终删除且记录key expired事件的函数:expire.c/activeExpireCycleTryExpire
    • 每次从上一次定时删除的数据库开始处理,具体逻辑参考expire.c/activeExpireCycle

9.7 AOF、RDB持久化功能和复制功能

9.7.1 RDB

参考此链接:https://github.com/sripathikrishnan/redis-rdb-tools/wiki/Redis-RDB-Dump-File-Format

  • 生成RDB文件
    • 执行SAVE、BGSAVE命令创建一个新的RDB文件,程序会对数据库中的键进行检查,过期的键不会被保存在RDB中
  • 载入RDB文件
    • 从主服务器载入RDB文件,程序会对文件中保存的键进行检查,未过期的载入到数据库中,过期键被忽略
    • 从从服务器载入RDB文件,文件中保存的所有键不管是否过期都要被载入到数据库中。之后会主从服务器进行同步
9.7.2 AOF

参考此文章:https://www.jianshu.com/p/1e34fdc51e3b

  • AOF(Append Only File)持久化功能:通过保存Redis所执行的写命令来记录数据库的状态
  • 客户端使用get message命令,试图访问过期的message键,服务器的动作
    • 从数据库中删除message
    • 追加一条del message到AOF文件
    • 向执行get命令的客户端返回空回复
  • AOF重写
    • 程序会检查数据库中的键,已过期的键不会被保存到重写后的AOF文件中
复制
  • 服务器在复制模式下,主服务器控制过期键的删除动作
    • 主服务器删除一个过期键,显式向所有的从服务器发送DEL命令,告诉从服务器删掉过期键
    • 从客户端执行客户端发送的读指令,即使碰到过期键也不会将过期键删除,会继续像处理未过期键一样处理过期键(会返回正常值,好像没有过期)
    • 从服务器只有收到主服务器的DEL命令后,才会删除过期键

9.8 数据库通知

  • 测试通知功能
    • 通道消息
    • keyspace和keyevent事件监听:参考https://www.cnblogs.com/bubu99/p/13222419.html
      • 1.开启redis的通知事件:redis-cli config set notify-keyspace-events KEA
      • 2.redis-cli允许订阅一个通道接收消息:redis-cli config set notify-keyspace-events KEA
      • 3.开启另外一个redis-cli终端执行一些redis数据库操作,在2中的redis-cli就可以收到操作步骤
K     Keyspace events, published with __keyspace@<db>__ prefix.
E     Keyevent events, published with __keyevent@<db>__ prefix.
g     Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ...
$     String commands
l     List commands
s     Set commands
h     Hash commands
z     Sorted set commands
t     Stream commands
x     Expired events (events generated every time a key expires)
e     Evicted events (events generated when a key is evicted for maxmemory)
m     Key miss events (events generated when a key that doesn't exist is accessed)
A     Alias for "g$lshztxe", so that the "AKE" string means all the events except "m".
  • keyspace和keyevent的区别:
    • The first kind of event, with keyspace prefix in the channel is called a Key-space notification, while the second, with the keyevent prefix, is called a Key-event notification.
      • The Key-space channel receives as message the name of the event.
      • The Key-event channel receives as message the name of the key.
//执行del message
./redis-cli --csv psubscribe '*'
Reading messages... (press Ctrl-C to quit)
"psubscribe","*",1
"pmessage","*","__keyspace@0__:message","del"
"pmessage","*","__keyevent@0__:del","message"
9.8.1 发送通知
  • 主要实现函数是:notice.c/notifyKeyspaceEvent函数
    • notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) : 发送的通知类型、事件名称、产生事件的key,产生事件的数据库id
    • 找到一处调用db.c/expireIfNeeded
//code2: db.c/expireIfNeeded
int expireIfNeeded(redisDb *db, robj *key) {
    if (!keyIsExpired(db,key)) return 0;

    /* If we are running in the context of a slave, instead of
     * evicting the expired key from the database, we return ASAP:
     * the slave key expiration is controlled by the master that will
     * send us synthesized DEL operations for expired keys.
     *
     * Still we try to return the right information to the caller,
     * that is, 0 if we think the key should be still valid, 1 if
     * we think the key is expired at this time. */
    if (server.masterhost != NULL) return 1;

    /* Delete the key */
    server.stat_expiredkeys++;
    propagateExpire(db,key,server.lazyfree_lazy_expire);
    notifyKeyspaceEvent(NOTIFY_EXPIRED,
        "expired",key,db->id);
    int retval = server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
                                               dbSyncDelete(db,key);
    if (retval) signalModifiedKey(NULL,db,key);
    return retval;
}

10.0 RDB持久化

  • redis是内存数据库,如果不将内存数据库的状态保存到磁盘中,一旦服务器进程退出,服务器中的数据库状态也会消失不见,所以出现了RDB持久化
  • RED持久化(RDB文件是一个经过压缩的二进制文件,通过该文件可以还原成RDB文件时的数据库状态)
    • 手动进行
    • 服务器配置选项定期执行
  • 两个命令生成RDB文件
    • SAVE:阻塞Redis服务器进程,直到RDB文件创建完毕为止;在阻塞期间,服务器不能处理任何命令请求
    • BGSAVE:会派生一个子进程,由子进程负责创建RDB文件,父进程继续处理命令请求
      • fork之后返回的pid=0,表示当前是子进程在执行;pid>0,表示当前是父进程在执行,这个pid是父进程中记录的子进程的pid
        • 参考此篇优秀blog:https://blog.csdn.net/nan_lei/article/details/81636473
        • 在调用fork()函数前是一个进程在执行这段代码,而调用fork()函数后就变成了两个进程在执行这段代码。两个进程所执行的代码完全相同,都会执行接下来的if-else判断语句块。
        • 父子进程的运行先后顺序是完全随机的(取决于系统的调度),也就是说在使用fork()函数的默认情况下,无法控制父进程在子进程前进行还是子进程在父进程前进行。
        • 当子进程从父进程内复制后,父进程与子进程内都有一个"pid"变量:在父进程中,fork()函数会将子进程的PID返回给父进程,即父进程的pid变量内存储的是一个大于0的整数;而在子进程中,fork()函数会返回0,即子进程的pid变量内存储的是0;如果创建进程出现错误,则会返回-1,不会创建子进程。
      • BGSAVE执行命令期间
        • 客户端发送的SAVE会被拒绝,因为SAVE和BGSAVE都会调用rdsSave
        • 客户端发送的BGSAVE也会被拒绝
        • BGREWRITEAOF和BGSAVE不能同时执行
          • 操作:BGSAVE在执行,BGREWRITEAOF会被延迟到BGSAVE执行之后执行;BGREWRITEAOF在执行,客户端发送的BGSAVE会被拒绝
          • 原因:都有大量的磁盘写入操作,影响性能
  • 何时载入RDB文件
    • 只要Redis服务器在启动的时候监测到RDB的存在,它就会自动载入RDB文件
    • AOF的文件更新频率高于RDB的文件更新频率
      • 如果开启了AOF持久化功能,服务器会优先使用AOF还原数据
      • AOF持久化功能处在关闭状态,服务器使用RDB文件还原数据库状态
    • RDB载入期间,服务器会一直处于阻塞状态,直到载入工作完成

10.1 自动间隔性保存

  • 多个保存条件,只要其中任意一个被满足,服务器就会执行BGSAVE命令:save 300 10(服务器在300秒之内,对数据库进行了至少10次修改)
  • 保存条件存储在:server.h/redisServer的saveparam
  • 记录上一次成功执行SAVE或者BGSAVE后,服务器对数据库进行了多少次修改:server.h/redisServer的dirty(像一个集合键增加三个元素:dirty将会增加3)
  • 记录上一次成功执行SAVE或者BGSAVE后的时间:server.h/redisServer的lastsave(unix时间戳)
//code3: server.h/redisServer
struct saveparam *saveparams;   /* Save points array for RDB */
int saveparamslen;       
serverCron周期性执行
  • 默认每隔100毫秒执行一次,检查save选项保存条件是否满足,满足就执行BGSAVE
//code4: server.c/serverCron
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
             * CONFIG_BGSAVE_RETRY_DELAY seconds already elapsed. */
            if (server.dirty >= sp->changes &&
                server.unixtime-server.lastsave > sp->seconds &&
                (server.unixtime-server.lastbgsave_try >
                 CONFIG_BGSAVE_RETRY_DELAY ||
                 server.lastbgsave_status == C_OK))
            {
                serverLog(LL_NOTICE,"%d changes in %d seconds. Saving...",
                    sp->changes, (int)sp->seconds);
                rdbSaveInfo rsi, *rsiptr;
                rsiptr = rdbPopulateSaveInfo(&rsi);
                rdbSaveBackground(server.rdb_filename,rsiptr);
                break;
            }
        }

10.2 RDB文件结构

  • 文件组成:REDIS、db_version、databases、EOF、check_sum(006版本的)
    • rdb文件是二进制数据,用REDIS五个字符,而不是带’\0’结尾符号的C字符串
    • db_version4字节,记录rdb文件的版本号[最新版本redis6.0.8的db_version是0009]
    • databases包含零个或者多个数据库及其键值对
    • EOF1字节,表示RDB正文结束[377]
    • check_sum8字节长的无符号整数:根据前四个部分计算得出,以此来检查rdb文件是否损坏
10.2.1 databases部分
  • 组成部分:selectdb、db_number、key_value_pairs
    • selectdb常量是1字节,当程序遇到它,会知道接下来要读的是一个数据库号码
    • 程序读入db_number之后,服务器会调用select命令,是的之后读入的键值对载入到正确的数据库
    • key_value_pairs
      • 不带过期时间:TYPE、key、value三部分组成
      • 带过期时间:EXPIRETIME_MS(1字节)、ms(8字节,以毫秒为单位的UNIX时间戳)
        • key:字符串对象
        • value分很多种类型,通过TYPE判断(参考code5),只列举几种
          • RDB_TYPE_STRING(参考code6):
            • 编码为RDB_ENC_INT8、RDB_ENC_INT16、RDB_ENC_INT32,处理函数:rdbLoadLzfStringObject
            • 编码为RDB_ENC_LZF(LZF为字符串压缩算法),处理函数:rdbLoadLzfStringObject
          • RDB_TYPE_SET_INTSET
            • 保存RDB:先将整数集合转换成字符串对象,然后将字符串对象保存在RDB中
            • 写入RDB:先读入字符串对象,再将字符串转换成整数集合
          • RDB_TYPE_LIST_ZIPLIST、RDB_TYPE_ZSET_ZIPLIST、RDB_TYPE_HASH_ZIPLIST
            • 保存RDB:将压缩列表转换成一个字符串对象,然后保存
            • 写入RDB:读入字符串对象,并将其转换成压缩列表,根据TYPE进行进一步转换
//code5: rdb.h 75 line
#define RDB_TYPE_STRING 0
#define RDB_TYPE_LIST   1
#define RDB_TYPE_SET    2
#define RDB_TYPE_ZSET   3
#define RDB_TYPE_HASH   4
#define RDB_TYPE_ZSET_2 5 /* ZSET version 2 with doubles stored in binary. */
#define RDB_TYPE_MODULE 6
#define RDB_TYPE_MODULE_2 7 /* Module value with annotations for parsing without
                               the generating module being loaded. */
/* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType() BELOW */

/* Object types for encoded objects. */
#define RDB_TYPE_HASH_ZIPMAP    9
#define RDB_TYPE_LIST_ZIPLIST  10
#define RDB_TYPE_SET_INTSET    11
#define RDB_TYPE_ZSET_ZIPLIST  12
#define RDB_TYPE_HASH_ZIPLIST  13
#define RDB_TYPE_LIST_QUICKLIST 14
#define RDB_TYPE_STREAM_LISTPACKS 15
//code6: rdb.c
robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
    robj *o = NULL, *ele, *dec;
    uint64_t len;
    unsigned int i;

    if (rdbtype == RDB_TYPE_STRING) {
        /* Read string value */
        if ((o = rdbLoadEncodedStringObject(rdb)) == NULL) return NULL;
        o = tryObjectEncoding(o);
    } else if (rdbtype == RDB_TYPE_LIST) {
        /* Read list value */
        if ((len = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;

        o = createQuicklistObject();
        quicklistSetOptions(o->ptr, server.list_max_ziplist_size,
                            server.list_compress_depth);

        /* Load every single element of the list */
        while(len--) {
            if ((ele = rdbLoadEncodedStringObject(rdb)) == NULL) {
                decrRefCount(o);
                return NULL;
            }
            dec = getDecodedObject(ele);
            size_t len = sdslen(dec->ptr);
            quicklistPushTail(o->ptr, dec->ptr, len);
            decrRefCount(dec);
            decrRefCount(ele);
        }
    }
    //...
}

robj *rdbLoadEncodedStringObject(rio *rdb) {
    return rdbGenericLoadStringObject(rdb,RDB_LOAD_ENC,NULL);
}


void *rdbGenericLoadStringObject(rio *rdb, int flags, size_t *lenptr) {
    int encode = flags & RDB_LOAD_ENC;
    int plain = flags & RDB_LOAD_PLAIN;
    int sds = flags & RDB_LOAD_SDS;
    int isencoded;
    uint64_t len;

    len = rdbLoadLen(rdb,&isencoded);
    if (isencoded) {
        switch(len) {
        case RDB_ENC_INT8:
        case RDB_ENC_INT16:
        case RDB_ENC_INT32:
            return rdbLoadIntegerObject(rdb,len,flags,lenptr);
        case RDB_ENC_LZF:
            return rdbLoadLzfStringObject(rdb,flags,lenptr);
        default:
            rdbExitReportCorruptRDB("Unknown RDB string encoding type %d",len);
            return NULL; /* Never reached. */
        }
    }
    //...
}
    

10.3 分析RDB文件

  • 组成分析
    • 5字节的REDIS
    • 4字节的版本号:0009
    • REDIS-ver:6.0.5
    • redis-bits:值为32或者64
    • redis-ctime:当前时间戳
    • used-mem:使用的内存数
    • aof-preamble:值为0或者1,1表示RDB有效
    • EOF常量:377
    • 校验和8字节:O ; n 023 P R 363 207
0000000    R   E   D   I   S   0   0   0   9 372  \t   r   e   d   i   s
0000020    -   v   e   r 005   6   .   0   .   8 372  \n   r   e   d   i
0000040    s   -   b   i   t   s 300   @ 372 005   c   t   i   m   e 226
0000060  226   n 205   _ 372  \b   u   s   e   d   -   m   e   m 302 200
0000100  306 020  \0 372  \f   a   o   f   -   p   r   e   a   m   b   l
0000120    e 300  \0 377   O   ;   n 023   P   R 363 207  
0000134
  • 执行 set msg “what a nice day!”
  • 执行SAVE,再看数据库,参考code7(特别细致:https://blog.csdn.net/qq_31720329/article/details/103810393)
    • 376 \0表示selectdb, 切换到0号数据库
      • 376是254的八进制表示法(#define RDB_OPCODE_SELECTDB 254 /* DB number of the following keys. */)
    • 373 001 \0 \0
      • 373是251的八进制表示法(#define RDB_OPCODE_RESIZEDB 251 /* Hash table resize hint. */)
      • 001表示当前数据库的大小为1
      • db->expires的大小为0
      • 类型(RDB_TYPE_STRING)=0
    • 003:key的长度
    • 020:value的长度
    • 377:EOF常量
0000000    R   E   D   I   S   0   0   0   9 372  \t   r   e   d   i   s
0000020    -   v   e   r 005   6   .   0   .   8 372  \n   r   e   d   i
0000040    s   -   b   i   t   s 300   @ 372 005   c   t   i   m   e 302
0000060  302   t 205   _ 372  \b   u   s   e   d   -   m   e   m 302  \0
0000100  307 020  \0 372  \f   a   o   f   -   p   r   e   a   m   b   l
0000120    e 300  \0 376  \0 373 001  \0  \0 003   m   s   g 020   w   h
0000140    a   t       a       n   i   c   e       d   a   y   ! 377 201
0000160    q 232 315   r   a 323 373                                    
0000167
//code7: rdb.h
/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
#define RDB_OPCODE_MODULE_AUX 247   /* Module auxiliary data. */
#define RDB_OPCODE_IDLE       248   /* LRU idle time. */
#define RDB_OPCODE_FREQ       249   /* LFU frequency. */
#define RDB_OPCODE_AUX        250   /* RDB aux field. */
#define RDB_OPCODE_RESIZEDB   251   /* Hash table resize hint. */
#define RDB_OPCODE_EXPIRETIME_MS 252    /* Expire time in milliseconds. */
#define RDB_OPCODE_EXPIRETIME 253       /* Old expire time in seconds. */
#define RDB_OPCODE_SELECTDB   254   /* DB number of the following keys. */
#define RDB_OPCODE_EOF        255   /* End of the RDB file. */
  • 查看带有过期时间的字符串键的RDB文件
    • 374是252的八进制表示法(#define RDB_OPCODE_EXPIRETIME_MS 252 /* Expire time in milliseconds. */)
0000000    R   E   D   I   S   0   0   0   9 372  \t   r   e   d   i   s
0000020    -   v   e   r 005   6   .   0   .   8 372  \n   r   e   d   i
0000040    s   -   b   i   t   s 300   @ 372 005   c   t   i   m   e   ¹
0000060   ** 203 205   _ 372  \b   u   s   e   d   -   m   e   m 302 220
0000100  201 020  \0 372  \f   a   o   f   -   p   r   e   a   m   b   l
0000120    e 300  \0 376 001 373 002 001 374   8   i   $   "   u 001  \0
0000140   \0  \0 004   n   a   m   e  \a   l   i       m   i   n   g  \0
0000160  003   m   s   g 020   w   h   a   t       a       n   i   c   e
0000200        d   a   y   ! 377   H   Z   "   < 275   r 264   >        
0000216
  • Redis本身有rdb文件检查工具:redis-check-dump

11.0 第11章 AOF持久化

  • AOF持久化:通过保存Redis服务器所执行的写命令来记录数据库状态
  • 服务器启动时:载入或者执行AOF中的命令来还原服务器关闭之前的数据库状态

11.1 AOF实现模式

  • 执行完命令后,会将写命令追加到redisServer结构体下的变量sds aof_buf
  • Redis服务器进程有一个事件循环,这个循环中接收客户端的命令请求,以及向客户端发送命令回复。其中有时间事件负责执行像serverCron定时运行的函数
  • 服务器每次结束一个事件循环之前,会调用flushAppendOnlyFile函数,考虑是否将aof_buf缓冲区的内容写入或者保存到AOF中
//code8: serverCron
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    // ...
    /* AOF postponed flush: Try at every cron cycle if the slow fsync
     * completed. */
    if (server.aof_flush_postponed_start) flushAppendOnlyFile(0);

    /* AOF write 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
     * an higher frequency. */
    run_with_period(1000) {
        if (server.aof_last_write_status == C_ERR)
            flushAppendOnlyFile(0);
    }
    // ...
}
  • 服务器配置appendfsync选项的值直接决定AOF持久化功能的效率和安全性
    • appendfsync值为always:每个事件循环都要将aof_buf缓冲区的所有内容写入AOF文件,并同步AOF文件,always效率最慢,但最安全;发生故障停机,AOF持久化也只会丢失一个事件循环中所产生的命令数据
    • appendfsync值为everysec:每个事件循环都要将aof_buf缓冲区的所有内容写入AOF文件,每隔1秒就要在子线程中同步AOF文件;发生故障停机,AOF持久化也只会丢失一秒钟的命令数据
    • appendfsync值为no:每个事件循环都要将aof_buf缓冲区的所有内容写入AOF文件,何时同步AOF文件看操作系统;发生故障停机,AOF持久化会丢失系统缓存中积累了一段时间的数据
//code9: aof.c
void flushAppendOnlyFile(int force) {
    ssize_t nwritten;
    int sync_in_progress = 0;
    mstime_t latency;

    if (sdslen(server.aof_buf) == 0) {
        /* Check if we need to do fsync even the aof buffer is empty,
         * because previously in AOF_FSYNC_EVERYSEC mode, fsync is
         * called only when aof buffer is not empty, so if users
         * stop write commands before fsync called in one second,
         * the data in page cache cannot be flushed in time. */
        if (server.aof_fsync == AOF_FSYNC_EVERYSEC &&
            server.aof_fsync_offset != server.aof_current_size &&
            server.unixtime > server.aof_last_fsync &&
            !(sync_in_progress = aofFsyncInProgress())) {
            goto try_fsync;
        } else {
            return;
        }
    }

11.2 AOF文件的载入与数据还原

  • Redis读取AOF文件并还原数据库状态的步骤:
    • 1.创建一个不带网络连接的伪客户端
    • 2.从AOF读出一条命令
    • 3.使用伪客户端执行被读出的命令
    • 循环执行2、3,直到所有的命令被处理完

11.3 AOF文件重写

  • 解决AOF体积膨胀的问题:Redis服务器创建一个新的AOF文件替代现有的AOF文件,新旧AOF保存的数据库状态相同
  • 原理:读取数据库键现在的值,然后用一条命令去记录键值对(为了避免这条命令太长在客户端的缓冲区溢出,设置每条命令元素数量最多64个,超过就多条命令)
  • 后台重写
    • aof_rewrite会进行大量的写入操作,导致线程被长时间阻塞;所以,Redis将AOF重写程序放在子进程中执行
    • 子进程在重写AOF的过程中,可能会存在数据修改。Redis服务器设置了AOF重写缓冲区(为了后来重写)+AOF缓冲区(AOF文件的正常处理)
//code10: server.h
#define AOF_REWRITE_ITEMS_PER_CMD 64
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值