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.
- 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.
//执行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会被拒绝
- 原因:都有大量的磁盘写入操作,影响性能
- fork之后返回的pid=0,表示当前是子进程在执行;pid>0,表示当前是父进程在执行,这个pid是父进程中记录的子进程的pid
- 何时载入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进行进一步转换
- RDB_TYPE_STRING(参考code6):
//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常量
- 376 \0表示selectdb, 切换到0号数据库
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