Redis源码解析--Persistence

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云解析 DNS,旗舰版 1个月
简介:         Redis支持两种方式的持久化,分别是定时快照(rdb)和语句追加(aof),下面会详细分析这两种持久化方式。一、定时快照 1、原理         定时快照即rdb(snapshotting),Redis内部定时器事件触发时,检查当前数据发生改变的次数与时间是否满足配置文件中指定的持久化条件,如果满足则fork出一个子进程来完成快照任务,而主进程任然提供服务,当有写入操作时由系统以内存页(page)为单位进行copy-on-write。
        Redis支持两种方式的持久化,分别是定时快照(rdb)和语句追加(aof),下面会详细分析这两种持久化方式。
一、定时快照
1、原理
        定时快照即rdb(snapshotting),Redis内部定时器事件触发时,检查当前数据发生改变的次数与时间是否满足 配置文件中指定的持久化条件,如果满足则fork出一个子进程来完成快照任务,而主进程任然提供服务,当有写入操作时由系统以内存页(page)为单位进行copy-on-write。
 2、流程  
        
(1)save命令
        save命令执行一个同步保存操作,将当前Redis实例的所有数据快照以rdb文件的形式保存到磁盘,这个操作会阻塞主线程的工作,通常在生产环境上很少执行save而是执行bgsave来完成快照。收到客户端发送的save命令后,会执行saveCommand(Rdb.c/1160),进而执行rdbSave(Rdb.c/597),rdbSave函数的主脉络如下:
        /* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success */
        int rdbSave(char *filename) {
            snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
            fp = fopen(tmpfile,"w");
            ...
            rioInitWithFile(&rdb,fp);
            if (rdbWriteRaw(&rdb,magic,9) == -1) goto werr;
            ...
            for (j = 0; j                 di = dictGetSafeIterator(d);
                ...
                /* Write the SELECT DB opcode */
                if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_SELECTDB) == -1) goto werr;
                if (rdbSaveLen(&rdb,j) == -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,now) == -1) goto werr;
                }
                dictReleaseIterator(di);
            }
            ...
            /* EOF opcode */
            if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_EOF) == -1) goto werr;
            ...
            /* Make sure data will not remain on the OS's output buffers */
            fflush(fp);
            fsync(fileno(fp));
            fclose(fp);

            /* Use RENAME to make sure the DB file is changed atomically only
            * if the generate DB file is ok. */
            if (rename(tmpfile,filename) == -1) {
                ...
                return REDIS_ERR;
            }
            redisLog(REDIS_NOTICE,"DB saved on disk");
            server.dirty = 0;
            server.lastsave = time(NULL);
            server.lastbgsave_status = REDIS_OK;
            return REDIS_OK;
            ...
        }
        (2)bgsave命令
        bgsave命令用于在后台异步快照数据到磁盘,收到该命令后调用bgsaveCommand(Rdb.c/1172)函数进而调用rdbSaveBackground(Rdb.c/685)函数,在该函数中Redis fork(Rdb.c/694)出一个子进程,主进程继续处理客户端请求,而子进程则调用rdbSave(Rdb.c/597)函数来负责完成快照,然后退出,子进程的退出状态由serverCron (Redis.c/756 调用backgroundSaveDoneHandler(Rdb.c/1138)来判断,具体可参见 这里,也可以看源码。具体处理流程如图1所示。
        (3)sync命令
        master收到slave发送的sync命令后,调用syncCommand(Replication.c/83),进而调用rdbSaveBackground (Rdb.c/685 函数以完成快照,具体流程如图1所示。
        (4)数据变化
         在redis.conf配置文件中如下设置以开启rdb:
        save 900 1
        save 300 10
        save 60 10000
        也可以通过命令来达到上面效果,如:
        config set save "900 1 300 10 60 10000"
 
        当数据在多少秒内出现了多少次变化则触发一次bgsave,触发规则用如上所示方式配置。触发机制由Redis内部定时检测serverCron(Redis.c/756),具体代码如下:
        /* If there is not a background saving/rewrite in progress check if
         * we have to save/rewrite now */
         for (j = 0; j             struct saveparam *sp = server.saveparams+j;


            if (server.dirty >= sp->changes &&
                server.unixtime-server.lastsave > sp->seconds) {
                redisLog(REDIS_NOTICE,"%d changes in %d seconds. Saving...",
                    sp->changes, sp->seconds);
                rdbSaveBackground(server.rdb_filename);
                break;
            }
         }
        如上代码中rdbSaveBackground是亮点,根据前面分析,接下来的事情,你懂的 ,具体流程如图1所示。
        (5)flushall命令
        收到flushall命令后,调用flushallCommand(Db.c/188)函数,再调用rdbSave (Rdb.c/597 函数清空rdb数据,以免crash后重新加载数据时载入旧数据。
        (6)shutdown命令
        收到shutdown命令后,调用shutdownCommand(Db.c/305)函数,再调用prepareFroShutdown(Redis.c/1584)函数,进入调用 rdbSave (Rdb.c/597 函数以在关闭Redis时持久化rdb数据。

图1 快照rdb流程图

二、语句追加
1、原理
        语句追加即aof(append-only file)类似于MySQL的binlog方式,每条会使Redis内存数据发生改变的命令都会追加到log文件中以完成持久化。
2、流程
        在redis.conf配置文件中如下设置以开启aof:
        appendonly yes
        在redis.conf 配置文件中如下设置以指定从page cache刷新数据到磁盘的策略:
        #appendfsync always
        appendfsync everysec
        #appendfsync no

        也可以通过命令来达到上面效果,如:
        config set appendonly yes
        config set appendfsync everysec
        如上配置后,在每次收到并执行命令后如果数据发生变化,会调用函数feedAppendOnlyFile(Aof.c/233),将数据命令写入server.aof_buf (Aof.c/271 ,下一次主循环调用before_sleep(Redis.c/915)函数时会通过调用flushAppendOnlyFile(Redis.c/85)函数把server.aof_buf(Aof.c/271)里的数据写到aof文件中,具体流程如下图所示:

图2 追加aof流程图
         Redis在crash之后,重新启动会读取aof文件并执行其中的所有命令以完成数据恢复。aof除了影响性能外还有一个比较严重的问题就是随着时间的推移,数据频繁变更,aof文件会变得很大,所以需要执行bgrewriteaof命令来重新整理aof文件,只保留最新的kv数据。bgrewriteaof命令执行aof文件重写操作,重写操作只会在没有其它持久化操作正在进行时才会触发,如果有快照则操作会被预定,等到快照完成后再执行,该函数的返回值会告知OK且带上额外信息以说明这一情况,如果已经有别的aof操作,则会该函数会返回一个错误且不会被预定到下次再执行。具体请参看brrewriteaofCommand(Aof.c/834)函数源码。       

三、总结
        不持久化会带来高性能,充当纯粹的cache时非常合适,但如果需要持久化的场景,就需要二选一了,定时快照对性能影响相对低,但是在两次快照之间存在数据丢失的风险,语句追加丢失数据的风险取决于持久化策略,但性能也会大打折扣。这之间的平衡是由架构师去考量。
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
113 2
|
4天前
基于springboot+thymeleaf+Redis仿知乎网站问答项目源码
基于springboot+thymeleaf+Redis仿知乎网站问答项目源码
53 36
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
mindspeed-llm源码解析(一)preprocess_data
mindspeed-llm是昇腾模型套件代码仓,原来叫"modelLink"。这篇文章带大家阅读一下数据处理脚本preprocess_data.py(基于1.0.0分支),数据处理是模型训练的第一步,经常会用到。
16 0
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
65 12
Ascend Extension for PyTorch的源码解析
本文介绍了Ascend对PyTorch代码的适配过程,包括源码下载、编译步骤及常见问题,详细解析了torch-npu编译后的文件结构和三种实现昇腾NPU算子调用的方式:通过torch的register方式、定义算子方式和API重定向映射方式。这对于开发者理解和使用Ascend平台上的PyTorch具有重要指导意义。
陪玩系统源码开发流程解析,成品陪玩系统源码的优点
我们自主开发的多客陪玩系统源码,整合了市面上主流陪玩APP功能,支持二次开发。该系统适用于线上游戏陪玩、语音视频聊天、心理咨询等场景,提供用户注册管理、陪玩者资料库、预约匹配、实时通讯、支付结算、安全隐私保护、客户服务及数据分析等功能,打造综合性社交平台。随着互联网技术发展,陪玩系统正成为游戏爱好者的新宠,改变游戏体验并带来新的商业模式。
Golang的GMP调度模型与源码解析
【11月更文挑战第11天】GMP 调度模型是 Go 语言运行时系统的核心部分,用于高效管理和调度大量协程(goroutine)。它通过少量的操作系统线程(M)和逻辑处理器(P)来调度大量的轻量级协程(G),从而实现高性能的并发处理。GMP 模型通过本地队列和全局队列来减少锁竞争,提高调度效率。在 Go 源码中,`runtime.h` 文件定义了关键数据结构,`schedule()` 和 `findrunnable()` 函数实现了核心调度逻辑。通过深入研究 GMP 模型,可以更好地理解 Go 语言的并发机制。

推荐镜像

更多
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等