【redis源码学习】持久化机制(1):RDB,抖音后端面试

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

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;

}

}

/* Trigger an AOF rewrite if needed. */

if (server.aof_state == AOF_ON &&

!hasActiveChildProcess() &&

server.aof_rewrite_perc &&

server.aof_current_size > server.aof_rewrite_min_size)

{

long long base = server.aof_rewrite_base_size ?

server.aof_rewrite_base_size : 1;

long long growth = (server.aof_current_size*100/base) - 100;

if (growth >= server.aof_rewrite_perc) {

serverLog(LL_NOTICE,“Starting automatic rewriting of AOF on %lld%% growth”,growth);

rewriteAppendOnlyFileBackground();

}

}

}

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

}

/* Clear the paused clients flag if needed. */

clientsArePaused(); /* Don’t check return value, just use the side effect.*/

/* Replication cron function – used to reconnect to master,

  • detect transfer failures, start background RDB transfers and so forth. */

run_with_period(1000) replicationCron();

/* Run the Redis Cluster cron. */

run_with_period(100) {

if (server.cluster_enabled) clusterCron();

}

/* Run the Sentinel timer if we are in sentinel mode. */

if (server.sentinel_mode) sentinelTimer();

/* Cleanup expired MIGRATE cached sockets. */

run_with_period(1000) {

migrateCloseTimedoutSockets();

}

/* Stop the I/O threads if we don’t have enough pending work. */

stopThreadedIOIfNeeded();

/* Resize tracking keys table if needed. This is also done at every

  • command execution, but we want to be sure that if the last command

  • executed changes the value via CONFIG SET, the server will perform

  • the operation even if completely idle. */

if (server.tracking_clients) trackingLimitUsedSlots();

/* Start a scheduled BGSAVE if the corresponding flag is set. This is

  • useful when we are forced to postpone a BGSAVE because an AOF

  • rewrite is in progress.

  • Note: this code must be after the replicationCron() call above so

  • make sure when refactoring this file to keep this order. This is useful

  • because we want to give priority to RDB savings for replication. */

if (!hasActiveChildProcess() &&

server.rdb_bgsave_scheduled &&

(server.unixtime-server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY ||

server.lastbgsave_status == C_OK))

{

rdbSaveInfo rsi, *rsiptr;

rsiptr = rdbPopulateSaveInfo(&rsi);

if (rdbSaveBackground(server.rdb_filename,rsiptr) == C_OK)

server.rdb_bgsave_scheduled = 0;

}

/* Fire the cron loop modules event. */

RedisModuleCronLoopV1 ei = {REDISMODULE_CRON_LOOP_VERSION,server.hz};

moduleFireServerEvent(REDISMODULE_EVENT_CRON_LOOP,

0,

&ei);

server.cronloops++;

return 1000/server.hz;

}


后台生成RDB文件

rdb 通过 rdbSaveBackground 函数负责在后台生成 RDB 文件(bigsave的底层也是这个),创建一个子进程(前面那个函数最终也会调用到这里),由子进程将数据快照保存到磁盘中,父进程继续该干嘛干嘛。

int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {

pid_t childpid;

if (hasActiveChildProcess()) return C_ERR;

server.dirty_before_bgsave = server.dirty;

server.lastbgsave_try = time(NULL);

openChildInfoPipe();

if ((childpid = redisFork()) == 0) {

int retval;

/* Child */

redisSetProcTitle(“redis-rdb-bgsave”);

redisSetCpuAffinity(server.bgsave_cpulist);

retval = rdbSave(filename,rsi);

if (retval == C_OK) {

sendChildCOWInfo(CHILD_INFO_TYPE_RDB, “RDB”);

}

exitFromChild((retval == C_OK) ? 0 : 1);

} else {

/* Parent */

if (childpid == -1) {

closeChildInfoPipe();

server.lastbgsave_status = C_ERR;

serverLog(LL_WARNING,“Can’t save in background: fork: %s”,

strerror(errno));

return C_ERR;

}

serverLog(LL_NOTICE,“Background saving started by pid %d”,childpid);

server.rdb_save_time_start = time(NULL);

server.rdb_child_pid = childpid;

server.rdb_child_type = RDB_CHILD_TYPE_DISK;

return C_OK;

}

return C_OK; /* unreached */

}


生成RDB文件

上面的函数最终会执行如下代码(这个代码属于save命令,后面再说关于这个命令):

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

rio rdb;

int error = 0;

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;

}

rioInitWithFile(&rdb,fp);

startSaving(RDBFLAGS_NONE);

if (server.rdb_save_incremental_fsync)

rioSetAutoSync(&rdb,REDIS_AUTOSYNC_BYTES);

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

if (fflush(fp) == EOF) goto werr;

if (fsync(fileno(fp)) == -1) goto werr;

if (fclose(fp) == EOF) goto werr;

/* Use RENAME to make sure the DB file is changed atomically only

  • if the generate DB file is ok. */

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));

fclose(fp);

unlink(tmpfile);

stopSaving(0);

return C_ERR;

}

如果在生产环节中直接使用save,会导致主进程长时间阻塞,所以不应在生产环节中使用该命令。


将redis数据写入RDB文件中

上面那个函数最终乎调用到这个函数(真实一环扣一环呀):

/* Produces a dump of the database in RDB format sending it to the specified

  • Redis I/O channel. On success C_OK is returned, otherwise C_ERR

  • is returned and part of the output, or all the output, can be

  • missing because of I/O errors.

  • When the function returns C_ERR and if ‘error’ is not NULL, the

  • integer pointed by ‘error’ is set to the value of errno just after the I/O

  • error. */

int rdbSaveRio(rio *rdb, int *error, int rdbflags, rdbSaveInfo *rsi) {

dictIterator *di = NULL;

dictEntry *de;

char magic[10];

int j;

uint64_t cksum;

size_t processed = 0;

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();

}

}

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;

}


对每个键值对的写入

好家伙,又继续下放。在上面的函数中,最终会调用到 rdbSaveKeyValuePair 函数,将每一个键值对写入到RDB文件中:

/* Save a key-value pair, with expire time, type, key, value.

  • On error -1 is returned.

  • On success if the key was actually saved 1 is returned, otherwise 0

  • is returned (the key was already expired). */

int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime) {

int savelru = server.maxmemory_policy & MAXMEMORY_FLAG_LRU;

int savelfu = server.maxmemory_policy & MAXMEMORY_FLAG_LFU;

/* Save the expire time */

if (expiretime != -1) {

if (rdbSaveType(rdb,RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;

if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return -1;

总结

机会是留给有准备的人,大家在求职之前应该要明确自己的态度,熟悉求职流程,做好充分的准备,把一些可预见的事情做好。

对于应届毕业生来说,校招更适合你们,因为绝大部分都不会有工作经验,企业也不会有工作经验的需求。同时,你也不需要伪造高大上的实战经验,以此让自己的简历能够脱颖而出,反倒会让面试官有所怀疑。

你在大学时期应该明确自己的发展方向,如果你在大一就确定你以后想成为Java工程师,那就不要花太多的时间去学习其他的技术语言,高数之类的,不如好好想着如何夯实Java基础。下图涵盖了应届生乃至转行过来的小白要学习的Java内容:

请转发本文支持一下

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
_EXPIRETIME_MS) == -1) return -1;

if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return -1;

总结

机会是留给有准备的人,大家在求职之前应该要明确自己的态度,熟悉求职流程,做好充分的准备,把一些可预见的事情做好。

对于应届毕业生来说,校招更适合你们,因为绝大部分都不会有工作经验,企业也不会有工作经验的需求。同时,你也不需要伪造高大上的实战经验,以此让自己的简历能够脱颖而出,反倒会让面试官有所怀疑。

你在大学时期应该明确自己的发展方向,如果你在大一就确定你以后想成为Java工程师,那就不要花太多的时间去学习其他的技术语言,高数之类的,不如好好想着如何夯实Java基础。下图涵盖了应届生乃至转行过来的小白要学习的Java内容:

请转发本文支持一下

[外链图片转存中…(img-syf03WGi-1713438315704)]

[外链图片转存中…(img-3tsqIwB9-1713438315705)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-92DRQkbA-1713438315705)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值