当前位置: 首页 > news >正文

【重学Reids 缓存】之Reids 缓存之RDB 持久化

Reids 缓存之RDB 持久化


目录

  • Reids 缓存之RDB 持久化
    • @[TOC](目录)
  • 一、Redis RDB是什么?
  • 二、初始化环境
    • 1.创建配置/数据/日志目录
      • 1.1 创建日志目录
      • 1.2 创建配置文件
      • 1.3 准备数据
      • 1.4 开启RBD
  • 三.RDB工作原理
      • 总结:
  • 四.RBD的优点
  • 五.RDB的缺点

一、Redis RDB是什么?

快照,顾名思义可以理解为拍照一样,把整个内存数据映射到硬盘中,保存一份到硬盘,因此恢复数据起来比较快,把数据映射回去即可,不像AOF,一条条的执行操作命令。

快照是默认的持久化方式。这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。可以通过配置设置自动做快照持久化的方式。

产生快照的情况有以下几种:
•执行bgsave命令(此时Redis会fork一个子进程,子进程负责生成硬盘文件,父进程负责继续接收命令)
•或执行save命令(和bgsave命令不同,发送save命令后,系统创建快照完成之前系统不会再接收任何新的命令,换句话说save命令会阻塞后面的命令,而bgsave不会)
•根据用户在配置文件中配置的快照触发时间执行
•客户端发送shutdown,系统会先执行save命令阻塞客户端,然后关闭服务器
•当有主从架构时,从服务器向主服务器发送sync命令来执行复制操作时,主服务器会执行bgsave操作

二、初始化环境

1.创建配置/数据/日志目录

代码如下(示例):

1.1 创建日志目录

# 创建配置目录
mkdir -p /usr/local/redis/conf
# 创建数据目录
mkdir -p /usr/local/redis/data
# 创建日志目录
mkdir -p /usr/local/redis/log

1.2 创建配置文件

配置文件(示例):
创建一份配置文件至 conf 目录。

vim /usr/local/redis/conf/redis.conf

文件内容如下

# 放行访问IP限制
bind 0.0.0.0
# 后台启动
daemonize yes
# 日志存储目录及日志文件名
logfile "/usr/local/redis/log/redis.log"
# rdb数据文件名
dbfilename dump.rdb
# rdb数据文件和aof数据文件的存储目录
dir /usr/local/redis/data
# 设置密码
requirepass 123456

1.3 准备数据

配置文件(示例):
在 /usr/local/redis/bin 目录下创建 initdata.py,内容如下

#!/usr/bin/env python
# -*- coding:utf-8 -*-


class Token(object):
    def __init__(self, value):
        if isinstance(value, Token):
            value = value.value
        self.value = value

    def __repr__(self):
        return self.value

    def __str__(self):
        return self.value

def b(x):
    return x


SYM_STAR = b('*')
SYM_DOLLAR = b('$')
SYM_CRLF = b('\r\n')
SYM_EMPTY = b('')


class RedisProto(object):
    def __init__(self, encoding='utf-8', encoding_errors='strict'):
        self.encoding = encoding
        self.encoding_errors = encoding_errors

    def pack_command(self, *args):
        """将redis命令安装redis的协议编码,返回编码后的数组,如果命令很大,返回的是编码后chunk的数组"""
        output = []
        command = args[0]
        if ' ' in command:
            args = tuple([Token(s) for s in command.split(' ')]) + args[1:]
        else:
            args = (Token(command),) + args[1:]

        buff = SYM_EMPTY.join(
            (SYM_STAR, b(str(len(args))), SYM_CRLF))

        for arg in map(self.encode, args):
            """数据量特别大的时候,分成部分小的chunk"""
            if len(buff) > 6000 or len(arg) > 6000:
                buff = SYM_EMPTY.join((buff, SYM_DOLLAR, b(str(len(arg))), SYM_CRLF))
                output.append(buff)
                output.append(arg)
                buff = SYM_CRLF
            else:
                buff = SYM_EMPTY.join((buff, SYM_DOLLAR, b(str(len(arg))), SYM_CRLF, arg, SYM_CRLF))

        output.append(buff)
        return output

    def encode(self, value):
        if isinstance(value, Token):
            return b(value.value)
        elif isinstance(value, bytes):
            return value
        elif isinstance(value, int):
            value = b(str(value))
        elif not isinstance(value, str):
            value = str(value)
        if isinstance(value, str):
            value = value.encode(self.encoding, self.encoding_errors)
        return value


if __name__ == '__main__':
    for i in range(5000000):
        commands_args = [('SET', 'key_' + str(i), 'value_' + str(i))]
        commands = ''.join([RedisProto().pack_command(*args)[0] for args in commands_args])
        print commands

在 /usr/local/redis/bin 目录下执行以下命令加载数据(根据机器不同可能需要30s~60s):

python initdata.py | ./redis-cli -a 123456 --pipe

1.4 开启RBD

配置文件(示例):
我们可以配置Redis在n秒内如果超过m个key被修改就自动做快照,下面是默认的快照保存配置(这3个选项都屏蔽,则RDB禁用):

# 900秒内如果超过1个key改动,则发起快照保存
save 900 1
# 300秒内如果超过10个key改动,则发起快照保存
save 300 10
# 60秒内如果超过1W个key改动,则发起快照保存
save 60 10000

三.RDB工作原理

Redis默认会将快照文件存储在Redis当前进程的工作目录中的dump.rdb文件中,可以通过配置dir和dbfilename两个参数分别指定快照文件的存储路径和文件名。
•Redis使用fork函数复制一份当前进程(父进程)的副本(子进程);
•父进程继续接收并处理客户端发来的命令,而子进程开始将内存中的数据写入硬盘中的临时文件;
•当子进程写入完所有数据后会用该临时文件替换旧的 RDB 文件,至此一次快照操作完成。

在执行 fork 的时候操作系统(类 Unix 操作系统)会使用写时复制(copy-on-write)策略,即fork函数发生的一刻父子进程共享同一内存数据,当父进程要更改其中某片数据时(如执行一个写命令),操作系统会将该片数据复制一份以保证子进程的数据不受影响,所以新的RDB文件存储的是执行fork一刻的内存数据。

另外需要注意的是,当进行快照的过程中,如果写入操作较多,造成 fork 前后数据差异较大,是会使得内存使用量显著超过实际数据大小的,因为内存中不仅保存了当前的数据库数据,而且还保存着 fork 时刻的内存数据。进行内存用量估算时很容易忽略这一问题,造成内存用量超限。

通过上述过程可以发现Redis在进行快照的过程中不会修改RDB文件,只有快照结束后才会将旧的文件替换成新的,也就是说任何时候 RDB 文件都是完整的。这使得我们可以通过定时备份 RDB 文件来实现 Redis 数据库备份。RDB 文件是经过压缩(可以配置rdbcompression 参数以禁用压缩节省CPU占用)的二进制格式,所以占用的空间会小于内存中的数据大小,更加利于传输。

Redis启动后会读取RDB快照文件,将数据从硬盘载入到内存。根据数据量大小与结构和服务器性能不同,这个时间也不同。通常将一个记录1000万个字符串类型键、大小为1 GB 的快照文件载入到内存中需要花费20~30秒。

总结:

a、内存资源风险:Redis fork子进程做RDB持久化,由于写的比例为80%,那么在持久化过程中,“写实复制”会重新分配整个实例80%的内存副本,大约需要重新分配1.6GB内存空间,这样整个系统的内存使用接近饱和,如果此时父进程又有大量新key写入,很快机器内存就会被吃光,如果机器开启了Swap机制,那么Redis会有一部分数据被换到磁盘上,当Redis访问这部分在磁盘上的数据时,性能会急剧下降,已经达不到高性能的标准(可以理解为武功被废)。如果机器没有开启Swap,会直接触发OOM,父子进程会面临被系统kill掉的风险。

b、CPU资源风险:虽然子进程在做RDB持久化,但生成RDB快照过程会消耗大量的CPU资源,虽然Redis处理处理请求是单线程的,但Redis Server还有其他线程在后台工作,例如AOF每秒刷盘、异步关闭文件描述符这些操作。由于机器只有2核CPU,这也就意味着父进程占用了超过一半的CPU资源,此时子进程做RDB持久化,可能会产生CPU竞争,导致的结果就是父进程处理请求延迟增大,子进程生成RDB快照的时间也会变长,整个Redis Server性能下降。

c、另外,可以再延伸一下,没有提到Redis进程是否绑定了CPU,如果绑定了CPU,那么子进程会继承父进程的CPU亲和性属性,子进程必然会与父进程争夺同一个CPU资源,整个Redis Server的性能必然会受到影响!所以如果Redis需要开启定时RDB和AOF重写,进程一定不要绑定CPU。

四.RBD的优点

•一旦采用RDB方式,那么你的整个Redis数据库将只包含一个紧凑压缩的二进制文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。

•对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。

•性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。

•相比于AOF机制,如果数据集很大,RDB的启动效率会更高。

五.RDB的缺点

•如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。

•由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。

相关文章:

  • 30分钟吃掉pytorch中的各种归一化层
  • Three.js 这样写就有阴影效果啦
  • Cravatar头像
  • Python-爬虫 (BS4数据解析)
  • java基于ssm+vue+elementui的多用户博客管理系统
  • java毕业设计网站swing mysql实现的仓库商品管理系统[包运行成功]
  • java毕业设计论文题目基于SSM实现的小区物业管理系统[包运行成功]
  • “蔚来杯“2022牛客暑期多校训练营10 EF题解
  • 人工智能科学计算库—Numpy教程
  • i.MX6ULL应用移植 | 基于ubuntu base 16.04搭建python3.9+pip3环境
  • vim文本编辑器
  • 网课搜题接口
  • 网课查题API接口(免费)
  • 超分辨率重建DRRN
  • MacOS 环境编译 JVM 源码
  • 4月23日世界读书日 网络营销论坛推荐《正在爆发的营销革命》
  • ES6语法详解(一)
  • go append函数以及写入
  • Javascript编码规范
  • Java-详解HashMap
  • maven工程打包jar以及java jar命令的classpath使用
  • Otto开发初探——微服务依赖管理新利器
  • Promise面试题,控制异步流程
  • puppeteer stop redirect 的正确姿势及 net::ERR_FAILED 的解决
  • Redis学习笔记 - pipline(流水线、管道)
  • 飞驰在Mesos的涡轮引擎上
  • 给新手的新浪微博 SDK 集成教程【一】
  • 给自己的博客网站加上酷炫的初音未来音乐游戏?
  • 跨域
  • 实战|智能家居行业移动应用性能分析
  • 微信小程序--------语音识别(前端自己也能玩)
  • 小试R空间处理新库sf
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • 优化 Vue 项目编译文件大小
  • ​linux启动进程的方式
  • ​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​
  • #stm32整理(一)flash读写
  • (AngularJS)Angular 控制器之间通信初探
  • (ZT)一个美国文科博士的YardLife
  • (草履虫都可以看懂的)PyQt子窗口向主窗口传递参数,主窗口接收子窗口信号、参数。
  • (附源码)springboot人体健康检测微信小程序 毕业设计 012142
  • (附源码)计算机毕业设计ssm本地美食推荐平台
  • (附源码)计算机毕业设计SSM疫情居家隔离服务系统
  • (汇总)os模块以及shutil模块对文件的操作
  • (论文阅读31/100)Stacked hourglass networks for human pose estimation
  • (十二)python网络爬虫(理论+实战)——实战:使用BeautfulSoup解析baidu热搜新闻数据
  • (五)IO流之ByteArrayInput/OutputStream
  • (一)u-boot-nand.bin的下载
  • (转载)微软数据挖掘算法:Microsoft 时序算法(5)
  • .NET中统一的存储过程调用方法(收藏)
  • .net专家(张羿专栏)
  • [2009][note]构成理想导体超材料的有源THz欺骗表面等离子激元开关——
  • [bzoj4010][HNOI2015]菜肴制作_贪心_拓扑排序
  • [CF494C]Helping People
  • [cocos2d-x]关于CC_CALLBACK