Redis设计与实现第11章 -- AOF持久化 总结(实现 重写)

RDB持久化通过保存数据库中的键值对来记录数据库状态的不同,AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态

11.1 AOF持久化的实现

分为命令追加、文件写入和文件同步三个步骤

11.1.1 命令追加

当AOF持久化功能处于打开状态时,服务器在执行完一个写命令后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区的末尾
struct redisServer {
    sds aof_buf;//AOF缓冲区 
};

11.1.2 AOF文件的写入和同步

Redis的服务器进程就是一个事件循环loop,这个循环中的文件事件负责接受客户端的命令请求,以及向客户端发送命令请求,而时间事件负责执行向serverCron函数这样的定时任务

服务器每次结束一个事件循环之前,都会调用flushAppendOnlyFile函数,考虑是否需要将aof_buf缓冲区的内容写入和保存到AOF文件里

def eventLoop():
    while True :
        #处理文件事件,接收命令请求以及发送命令回复
        #处理命令请求时可能会有新内容被追加到aof_buf缓冲区中
        processFileEvents()
        #处理时间事件
        processTimeEvents()
        #考虑是否要将 aof_buf中的内容写入和保存到 AOF 文件里面
        flushAppendOnlyFile()

flushAppendOnlyFile函数的行为由服务器配置的appendfsync选项的值来决定,各个不同值产生的行为如下所示

  • always:将aof_buf缓冲区中的所有内容写入并同步到AOF文件
  • everysec:将aof_buf缓冲区的所有内容写入到AOF文件里,如果上次同步AOF文件的事件距离现在超过一秒钟,那么再次对AOF文件进行同步,同步操作由一个专门的线程负责执行
  • no:将aof_buf缓冲区中的所有内容**写入到AOF文件里但是不同步 **

默认值为everysec

AOF持久化的效率和安全性

  • appendfsync = always:效率最慢但是最安全
  • appendfsync = everysec:足够快,最多丢失一秒的数据
  • appendfsync = no:写入速度最快,但是最不安全11.2 AOF文件的载入与数据还原
    服务器只需要读取并重新执行一遍AOF文件里面保存的写命令就可以还原服务器关闭之前的数据库状态
    详细步骤
  1. 创建一个不带网络连接的伪客户端:因为Redis的命令只能在客户端上下文中执行
  2. 从AOF文件里分析并读取一条写命令
  3. 使用伪客户端执行被读取的写命令
    4.一直重复步骤2和3,直到所有的写命令被处理完毕

11.3 AOF重写

随着服务器运行时间的增加,AOF文件里的内容会越来越多,文件的体积也会越来越大,会对Redis服务器甚至宿主机造成影响,而且AOF文件的体积越大,使用AOF文件来进行数据还原所需的时间就越多
因此Redis提供了AOF重写功能,Redis服务器创建了一个新的AOF文件来替代现有的AOF文件,两个文件的AOF保存的数据库状态相同但是新的AOF文件不包含任何浪费空间的冗余命令,所以体积会小

11.3.1 AOF文件重写的实现

这个功能实际上是通过读取服务器当前的数据库状态实现的,比如向list里写入了6条数据,如果写到AOF文件里的话,就是6行数据,但是如果读取list的值,就可以用1行数据实现。
基本原理就是从数据库读取现在键的值,然后用一条命令去记录键值对,代替之前记录这个键值对的多条命令

def aof_rewrite(new_aof_file_name):
    #创建新 AOF 文件
    f=create_file(new_aof_file_name)
    #遍历数据库 
    for db in redisServer.db:
        #忽略空数据库
        if db.is_empty(): 
            continue 
        #写入SELECT命令 指定数据库号码 
        f.write_command("SELECT" + db.id)
        #遍历数据库中的所有键
        for key in db:
            # 忽略已过期的键
            if key.is_expired(): continue
            # 根据键的类型进行重写
            if key.type == string:
                rewrite_string(key)
            elif key.type == List:
                rewrite_list(key)
            elif key.type == Hash:
                rewrite_hash(key)
            elif key.type == Set:
                rewrite_set(key)
            elif key.type == SortedSet:
                rewrite_sorted_set(key)
            #如果键带有过期时间,那么过期时间也要被重写
            if key.have expire time():
                rewrite_expire_time(key)

    #写入完毕 关闭
    f.close()
        
def rewrite_string(key):
    #使用GET命令获取字符串键的值
    value = GET(key)
    #使用SET命令重写字符串键
    f.write_command(SET,key,value)
    
def rewrite list(key):
    #使用LRANGE 命令获取列表键包含的所有元素
    iteml,item2,……itemN=LRANGE(key,0-1)
    #使用RPUSH命令重写列表键
    f.write_command(RPUSH,key,iteml,item2,...itemN)
def rewrite_hash(key):
    #使用HGETALL命令获取哈希键包含的所有键值对
    fieldl,valuel,field2,value2,……fieldN,valueN = HGETALL(key)
    #使用HMSET命令重写哈希键
    f.write_command(HMSET, key,fieldl,valuel,field2,value2,...fieldN,valueN)
def rewrite_set(key):
    #使用SMEMBERS 命令获取集合键包含的所有元素
    eleml,elem2,...elemN = SMEMBERS(key)
    #使用SADD 命令重写集合键
    f.write_command(SADD,key,eleml,elem2,..elemN)
def rewrite_sorted_set(key):
    #使用ZRANGE命令获取有序集合键包含的所有元素
    member1,score1,member2,score2,……,memberN,scoreN = ZRANGE(key,0,-1,"WITHSCORES")
    #使用ZADD命令重写有序集合键
    f.write_command(ZADD,key,score1,member1,score2,member2,……,scoreN,memberN)
    
def rewrite_expire_time(key):
    #获取毫秒精度的键过期时间戳
    timestamp=get_expire_time_in_unixstamp(key)
    #使用PEXPIREAT命令重写键的过期时间
    f.write_command(PEXPIREAT,key,timestamp)
    
        
        
    
    

在实际中,为了避免在执行命令时造成客户端输入缓冲区溢出,重写程序在列表、哈希表、集合、有序集合这四种可能带有多个元素的键时,会先检查键所包含的元素数量,如果元素数量超过了redis.h/REDIS_AOF_REWRITE_ITEMS_PRE_CMD常量的值,程序会用多条命令来记录,而不是单一命令。目前默认值为64

11.3.2 AOF后台重写

Redis如果用主线程处理重写的话,会造成长时间阻塞,所以使用子进程来执行。 子进程在进行AOF重写的时候,服务器进程(父进程)可以继续处理命令请求;子进程带有服务器进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下保证数据的安全性。 但是要解决的一个问题是,当子进程在进行AOF重写期间,服务器进程还需要继续处理命令请求,而新的命令可能会对现有的数据库状态进行修改,从而使得服务器当前数据库状态和重写后的AOF文件所保存的数据库状态不一致。 为了解决这个问题,Redis服务器设置了一个AOF重写缓冲区,这个缓冲区在服务器创建子进程之后开始使用,当Redis服务器执行完一个写命令或,会同时把这个命令发送给AOF缓冲区和AOF重写缓冲区。 这样就可以保证:AOF缓冲区的内容会定期被写入和同步到AOF文件,对现有AOF文件的处理工作也会如常进行;从创建子进程开始,服务器执行的所有写命令都会被记录到AOF缓冲区里,当子进程完成AOF重写工作后,会向父进程发送一个信号,父进程收到信号后,调用信号处理函数并执行以下工作:
  1. 将AOF重写缓冲区的所有内容写入到新AOF文件里
  2. 对新的AOF文件进行改名,原子性地覆盖现有地AOF文件,完成替换

在整个过程里,只有信号处理函数执行时会对服务器进程造成阻塞,对服务器性能的影响降到了最低。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值