Redis 源代码]7RDB Persistence-4 核心代码
最编程
2024-09-30 08:03:20
...
rdbSave
/* Save the DB on disk. Return C_ERR on error, C_OK on success. */
int rdbSave(char *filename, rdbSaveInfo *rsi) {
char tmpfile[256];
char cwd[MAXPATHLEN]; /* Current working dir path for error messages. */
FILE *fp = NULL;
rio rdb;
int error = 0;
/*
创建一个临时文件名 temp-<pid>.rdb,其中 <pid> 是当前进程的 PID。
打开临时文件进行写操作。如果打开失败,记录错误日志并返回 C_ERR。
*/
snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
fp = fopen(tmpfile,"w");
if (!fp) {
char *cwdp = getcwd(cwd,MAXPATHLEN);
serverLog(LL_WARNING,
"Failed opening the RDB file %s (in server root dir %s) "
"for saving: %s",
filename,
cwdp ? cwdp : "unknown",
strerror(errno));
return C_ERR;
}
/*
初始化 RIO 结构体
使用 rioInitWithFile 初始化 RIO(Redis I/O)结构体,使其与文件关联。
调用 startSaving 函数开始保存过程,并传递标志 RDBFLAGS_NONE 表示没有特殊标志。
*/
rioInitWithFile(&rdb,fp);
startSaving(RDBFLAGS_NONE);
if (server.rdb_save_incremental_fsync)
rioSetAutoSync(&rdb,REDIS_AUTOSYNC_BYTES);
/*
调用 rdbSaveRio 函数将数据写入 RIO 结构体。如果保存过程中出现错误,设置 errno 并跳转到 werr 标签。
*/
if (rdbSaveRio(&rdb,&error,RDBFLAGS_NONE,rsi) == C_ERR) {
errno = error;
goto werr;
}
/* Make sure data will not remain on the OS's output buffers */
/*
刷新文件缓冲区,确保所有数据都被写入文件。
对文件描述符执行 fsync,确保数据被持久化到磁盘。
关闭文件。如果任何一步失败,跳转到 werr 标签。
*/
if (fflush(fp)) goto werr;
if (fsync(fileno(fp))) goto werr;
if (fclose(fp)) { fp = NULL; goto werr; }
fp = NULL;
/* Use RENAME to make sure the DB file is changed atomically only
* if the generate DB file is ok. */
//将临时文件重命名为最终的目标文件名。如果重命名失败,记录错误日志,删除临时文件,并返回 C_ERR。
if (rename(tmpfile,filename) == -1) {
char *cwdp = getcwd(cwd,MAXPATHLEN);
serverLog(LL_WARNING,
"Error moving temp DB file %s on the final "
"destination %s (in server root dir %s): %s",
tmpfile,
filename,
cwdp ? cwdp : "unknown",
strerror(errno));
unlink(tmpfile);
stopSaving(0);
return C_ERR;
}
serverLog(LL_NOTICE,"DB saved on disk");
server.dirty = 0;
server.lastsave = time(NULL);
server.lastbgsave_status = C_OK;
stopSaving(1);
return C_OK;
werr:
serverLog(LL_WARNING,"Write error saving DB on disk: %s", strerror(errno));
if (fp) fclose(fp);
unlink(tmpfile);
stopSaving(0);
return C_ERR;
}
/*
rdb:指向 rio 结构体的指针,用于写入数据。
error:指向整数的指针,用于存储错误代码。
rdbflags:标志位,包含一些额外的信息(如是否为 AOF 重写前言)。
rsi:指向 rdbSaveInfo 结构体的指针,包含一些额外的信息(如复制偏移量等)。
*/
int rdbSaveRio(rio *rdb, int *error, int rdbflags, rdbSaveInfo *rsi) {
dictIterator *di = NULL;
dictEntry *de;
char magic[10];
uint64_t cksum;
size_t processed = 0;
int j;
long key_count = 0;
long long info_updated_time = 0;
char *pname = (rdbflags & RDBFLAGS_AOF_PREAMBLE) ? "AOF rewrite" : "RDB";
if (server.rdb_checksum)
rdb->update_cksum = rioGenericUpdateChecksum;
snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);
if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;
if (rdbSaveInfoAuxFields(rdb,rdbflags,rsi) == -1) goto werr;
if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_BEFORE_RDB) == -1) goto werr;
for (j = 0; j < server.dbnum; j++) {
redisDb *db = server.db+j;
dict *d = db->dict;
if (dictSize(d) == 0) continue;
di = dictGetSafeIterator(d);
/* Write the SELECT DB opcode */
if (rdbSaveType(rdb,RDB_OPCODE_SELECTDB) == -1) goto werr;
if (rdbSaveLen(rdb,j) == -1) goto werr;
/* Write the RESIZE DB opcode. */
uint64_t db_size, expires_size;
db_size = dictSize(db->dict);
expires_size = dictSize(db->expires);
if (rdbSaveType(rdb,RDB_OPCODE_RESIZEDB) == -1) goto werr;
if (rdbSaveLen(rdb,db_size) == -1) goto werr;
if (rdbSaveLen(rdb,expires_size) == -1) goto werr;
/* Iterate this DB writing every entry */
while((de = dictNext(di)) != NULL) {
sds keystr = dictGetKey(de);
robj key, *o = dictGetVal(de);
long long expire;
initStaticStringObject(key,keystr);
expire = getExpire(db,&key);
if (rdbSaveKeyValuePair(rdb,&key,o,expire) == -1) goto werr;
/* When this RDB is produced as part of an AOF rewrite, move
* accumulated diff from parent to child while rewriting in
* order to have a smaller final write. */
if (rdbflags & RDBFLAGS_AOF_PREAMBLE &&
rdb->processed_bytes > processed+AOF_READ_DIFF_INTERVAL_BYTES)
{
processed = rdb->processed_bytes;
aofReadDiffFromParent();
}
/* Update child info every 1 second (approximately).
* in order to avoid calling mstime() on each iteration, we will
* check the diff every 1024 keys */
if ((key_count++ & 1023) == 0) {
long long now = mstime();
if (now - info_updated_time >= 1000) {
sendChildInfo(CHILD_INFO_TYPE_CURRENT_INFO, key_count, pname);
info_updated_time = now;
}
}
}
dictReleaseIterator(di);
di = NULL; /* So that we don't release it again on error. */
}
/* If we are storing the replication information on disk, persist
* the script cache as well: on successful PSYNC after a restart, we need
* to be able to process any EVALSHA inside the replication backlog the
* master will send us. */
if (rsi && dictSize(server.lua_scripts)) {
di = dictGetIterator(server.lua_scripts);
while((de = dictNext(di)) != NULL) {
robj *body = dictGetVal(de);
if (rdbSaveAuxField(rdb,"lua",3,body->ptr,sdslen(body->ptr)) == -1)
goto werr;
}
dictReleaseIterator(di);
di = NULL; /* So that we don't release it again on error. */
}
if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_AFTER_RDB) == -1) goto werr;
/* EOF opcode */
if (rdbSaveType(rdb,RDB_OPCODE_EOF) == -1) goto werr;
/* CRC64 checksum. It will be zero if checksum computation is disabled, the
* loading code skips the check in this case. */
cksum = rdb->cksum;
memrev64ifbe(&cksum);
if (rioWrite(rdb,&cksum,8) == 0) goto werr;
return C_OK;
werr:
if (error) *error = errno;
if (di) dictReleaseIterator(di);
return C_ERR;
}
推荐阅读
-
Redis 源代码]2 项目结构描述 - 核心文件中的 src
-
Redis 源代码]7RDB Persistence-4 核心代码
-
Java 版企业电子招标采购系统源代码 Spring Cloud + Spring Boot + 二次开发 + MybatisPlus + Redis
-
大聪明教你学Java|Mybatis的一级缓存和二级缓存--🍊作者简介:不愿过河东,一个来自二线城市的程序员,致力于用 "猥琐 "的方法解决琐碎的问题,让复杂的问题变得简单易懂。支持作者:喜欢👍,关注💖,留言💌~! 前言。 在计算机世界中,缓存无处不在;操作系统有操作系统缓存,数据库会有数据库缓存,我们还可以利用中间件(如 Redis)来充当缓存。MyBatis 作为一个优秀的 ORM 框架,也用于缓存,所以今天我们就来谈谈 Mybatis 的一级缓存和二级缓存。 Mybatis 一级缓存 首先,我们来看一张图片👇。 我们在开发项目的过程中,如果打开Mybatis的SQL语句打印,经常会看到这样一句话:创建一个新的 SqlSession,其实这就是我们常说的 Mybatis 一级缓存。 Mybatis 的一级缓存也就是在执行一次 SQL 查询或 SQL 更新后,这条 SQL 语句并不会消失,而是被 MyBatis 缓存起来,当再次执行同样的 SQL 语句时,就会直接从缓存中提取出来,而不用再次执行 SQL 命令。第一级缓存也称为 SqlSession 级缓存。对数据库进行操作时,需要构建一个 SqlSession 对象,其中有一个用于存储缓存数据的数据结构(HashMap)。对象中有一个用于存储缓存数据的数据结构(HashMap)。不同 SqlSession 之间的缓存数据区域(HashMap)互不影响。 在我们的应用系统运行过程中,我们可能会在一个数据库会话中,执行多条查询条件相同的 SQL 语句,那么对于这种情况,你来进行设计,那么你会如何考虑呢?没错,就是加入缓存,MyBatis也是这么来处理的,如果是同一条SQL语句,会优先打入一级缓存,避免直接查询数据库,给数据库造成压力,提高性能。具体实现过程如下图所示👇 SqlSession 是一个接口,提供了一些 CRUD 方法,SqlSession 的默认实现类是 DefaultSqlSession,DefaultSqlSession 类持有 Executor 接口对象,Executor 的默认实现是 BaseExecutor 对象,每个 BaseExecutor 对象都有一个 PerpetualCache 缓存,即上图中的本地缓存。当用户发起查询时,MyBatis 会根据当前执行的语句生成一个 MappedStatement,并在本地缓存中进行查询,如果缓存被命中,查询结果会直接返回给用户;如果缓存未被命中,查询结果会直接返回给用户。如果缓存未命中,则查询数据库,将结果写入本地缓存,最后将结果返回给用户。这时候可能有小伙伴要说了:我还在控制台中看到 "关闭非事务性 SqlSession "这句话,说明我每次创建一个 SqlSession 到 SqlSession 结束都是关闭的,那么我的缓存还是有毛线用!😥 事情肯定不是我们想的那样,让我们继续👇。 🍊 getSqlSession 源代码
-
Three.js 源代码阅读笔记(基本核心 Core 对象)
-
Redis 源代码分析深入了解 Makefile 文件-摘要
-
讲述 KV 模型的 Redis 源代码 - 哈希数据类型
-
[Redis 实验室 1] Redis 源代码分析 100W 数据的内存使用和优化
-
Kafka PMC 核心成员+腾讯+新浪微博=《Apache Kafka 真实世界源代码解析笔记》?
-
Redis 源代码学习日志:string-sdsrange