抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

持久化

持久化导读:redis是内存数据库,它将自己的数据库状态储存在内存中。所以当redis服务进程退出时,服务器中的数据也将消失,故而需要将其数据保存到磁盘中。

RDB

RDB(Redis DataBase)是redis默认的持久化方式。通过快照(snapshotting)完成。从上述的实现方式看,它所保存的数据是某一时刻的数据,即RDB保存redis某一时刻的数据快照

触发方式

  • 符合自定义配置的快照规则
  • 执行了save或者bgsave命令
  • 执行了flushall(清空)命令
  • 执行主从复制操作(第一次)

特点

  • 优点
    • RDB是二进制压缩文件,占用空间小,便于传输
    • 主进程fork子进程,可以最大化redis性能
    • 使用RDB文件来恢复数据较快
  • 缺点
    • 不能保证数据的完整性,会丢失最后一次快照以后更改的数据
    • 父进程在fork子进程的时候,如果主进程较大,容易阻塞

实现方式

  • windows环境:

    手动保存

    1. pushd [your redis path]
    2. redis-cli.exe
    3. save

    保存之后,会在redis目录下,默认生成dump.rdb文件。

    redis.conf 配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 900s后有1次更新,进行持久化
    save 900 1
    # 300s后有10次更新,进行持久化
    save 300 10
    # 60秒后有10_000次更新,进行持久化
    save 60 10000

    ## 配置默认文件名
    dbfilename dump.rdb
    ## 文件存储位置
    dir ./ #根目录下
  • Linux环境:

    待续

AOF

AOF(append only file),redis默认情况下不开启。开启AOF持久化后,redis会将所有的写操作以及参数(RESP)记录到AOF文件中。

实现方式

1
2
3
4
5
6
7
# redis.conf配置文件中
## 开启AOF的持久化
appendonly yes
## AOF文件的保存位置(RDB AOF共用)
dir ./
## 配置默认文件名
appendfilename appendonly.aof

高可用

高可用导读:高可用,也称HA(High Availability),是分布式系统架构设计中必须考虑的因素之一,通常指通过设计减少系统不提供服务的时间。

在Redis中表现为,单一的Redis服务节点出现故障时,整个服务都不能提供服务。以下是Redis三种高可用模式:


Lua脚本

Redis从2.6版本开始引入对Lua脚本的支持,通过在服务器中嵌入Lua环境,Redis客户端可以使用Lua脚本,直接在服务器端原子地执行多个Redis命令

1
2
3
eval "return 'hello lua'" 0
script load "return 1+1" -- 返回一串SHA1校验码
evalsha "sha1校验码"

创建Lua环境

redis服务器内嵌了一个Lua环境(environ-ment),并对这个Lua环境进行了一系列的修改,从而确保Lua环境可以满足Redis服务器的使用。

  1. 创建一个基本的Lua环境。

    服务器调用Lua的C语言的API函数lua_open

  2. 载入多个函数库到Lua环境。

    • 基本库

      包含Lua的核心(core)函数,比如assert、error、pairs、tostring、pcall等。另外,为了防止用户从外部文件中引入不安全的代码,库中的loadfile函数会被删除。

    • 表格库

      包含用于处理表格的通用函数,比如table.concat、table.insert、table.remove、table.sort等。

    • 字符串库

      包含用于处理字符串的通用函数,比如用于对字符串进行查找的string.find函数,对字符串进行格式化的string.format函数,查看字符串长度的string.len函数,对字符串进行翻转的string.reverse函数等。

    • 数学库

      是标准C语言数学库的接口,它包括计算绝对值的math.abs函数,返回多个数中的最大值和最小值的math.max函数和math.min函数,计算二次方根的math.sqrt函数,计算对数的math.log函数等。

    • 调试库

      提供了对程序进行调试所需的函数,比如对程序设置钩子和取得钩子的debug.sethook函数和debug.gethook函数,返回给定函数相关信息的debug.getinfo函数,为对象设置元数据的debug.setmetatable函数,获取对象元数据的debug.getmetatable函数等。

    • Lua CJSON

      🔗用于处理UTF-8编码的JSON格式,其中cjson.decode函数将一个JSON格式的字符串转换为一个Lua值,而cjson.encode函数将一个Lua值序列化为JSON格式的字符串。

    • Struct

      🔗用于在Lua值和C结构(struct)之间进行转换,函数struct.pack将多个Lua值打包成一个类结构(struct-like)字符串,而函数struct.unpack则从一个类结构字符串中解包出多个Lua值。

    • Lua cmsgpack

      🔗用于处理MessagePack格式的数据,其中cmsgpack.pack函数将Lua值转换为MessagePack数据,而cmsgpack.unpack函数则将MessagePack数据转换为Lua值。

  3. 创建全局表格redis,包含了对redis进行操作的函数。

    服务器将在Lua环境中创建一个redis表格(table),并将它设为全局变量。这个redis表格包含以下函数:

    • 用于执行Redis命令的redis.callredis.pcall函数。

    • 用于记录Redis日志(log)的redis.log函数,以及相应的日志级别(level)常量:redis.LOG_DEBUGredis.LOG_VERBOSEredis.LOG_NOTICEredis.LOG_WARNING

    • 用于计算SHA1校验和的redis.sha1hex函数。❑用于返回错误信息的redis.error_reply函数和redis.status_reply函数。在这些函数里面,最常用也最重要的要数redis.call函数和redis.pcall函数,通过这两个函数,用户可以直接在Lua脚本中执行Redis命令:

      1
      2
      EVAL "return redis.call('PING')" 0
      # PONG
  4. 使用redis自制的随机函数替换掉Lua自带的带有副作用的随机函数。

    为了保证相同的脚本可以在不同的机器上产生相同的结果,Redis要求所有传入服务器的Lua脚本,以及Lua环境中的所有函数,都必须是无副作用(side effect)的纯函数(pure function)。

    但是,在之前载入Lua环境的math函数库中,用于生成随机数的math.random函数和math.randomseed函数都是带有副作用的,它们不符合Redis对Lua环境的无副作用要求。

    因为这个原因,Redis使用自制的函数替换了math库中原有的math.random函数和math.randomseed函数,替换之后的两个函数有以下特征:

    • 对于相同的seed来说,math.random总产生相同的随机数序列,这个函数是一个纯函数。
    • 除非在脚本中使用math.randomseed显式地修改seed,否则每次运行脚本时,Lua环境都使用固定的math.randomseed(0)语句来初始化seed。
  5. 创建排序辅助函数。

    为了防止带有副作用的函数令脚本产生不一致的数据,Redis对math库的math.random函数和math.randomseed函数进行了替换。对于Lua脚本来说,另一个可能产生不一致数据的地方是那些带有不确定性质的命令。比如对于一个集合键来说,因为集合元素的排列是无序的,所以即使两个集合的元素完全相同,它们的输出结果也可能并不相同。

    Redis将SMEMBERS这种在相同数据集上可能会产生不同输出的命令称为“带有不确定性的命令”,这些命令包括:SINTER、SUNION、SDIFF、SMEMBERS、HKEYS、HVALS、KEYS为了消除这些命令带来的不确定性,服务器会为Lua环境创建一个排序辅助函数__redis__compare_helper,当Lua脚本执行完一个带有不确定性的命令之后,程序会使用__redis__compare_helper作为对比函数,自动调用table.sort函数对命令的返回值做一次排序,以此来保证相同的数据集总是产生相同的输出。

    举个例子,如果我们在Lua脚本中对fruit集合和another-fruit集合执行SMEMBERS命令,那么两个脚本将得出相同的结果,因为脚本已经对SMEMBERS命令的输出进行过排序了:

    1
    2
    3
    4
    5
    6
    7
    8
    redis> EVAL "return redis.call('SMEMBERS', KEYS[1])" 1 fruit
    1) "apple"
    2) "banana"
    3) "cherry"
    redis> EVAL "return redis.call('SMEMBERS', KEYS[1])" 1 another-fruit
    1) "apple"
    2) "banana"
    3) "cherry"
  6. 创建 redis.pcall函数的册数报告辅助函数。

    服务器将为Lua环境创建一个名为__redis__err__handler的错误处理函数,当脚本调用redis.pcall函数执行Redis命令,并且被执行的命令出现错误时,__redis__err__handler就会打印出错代码的来源和发生错误的行数,为程序的调试提供方便。

  7. 对Lua环境中的全局环境进行保护,防止用户在执行Lua脚本的时候,将额外的全局变量添加到Lua环境中。

    服务器将对Lua环境中的全局环境进行保护,确保传入服务器的脚本不会因为忘记使用local关键字而将额外的全局变量添加到Lua环境里面。

  8. 将完成修改的Lua环境保存到服务器状态的Lua属性中,等待执行服务器传来的Lua脚本。

    经过以上的一系列修改,Redis服务器对Lua环境的修改工作到此就结束了,在最后的这一步,服务器会将Lua环境和服务器状态的lua属性关联起来。

    因为Redis使用串行化的方式来执行Redis命令,所以在任何特定时间里,最多都只会有一个脚本能够被放进Lua环境里面运行,因此,整个Redis服务器只需要创建一个Lua环境即可


本站(Redis|x标题)系列的文章是来源于站长阅读黄健宏《Redis设计与实现》后的总结和摘录。


评论