加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

Redis之AOF重写及其实现原理

(2015-04-17 23:32:10)
分类: Redis

所谓“重写”其实是一个有歧义的词语,实际上,AOF重写并不需要对原有AOF文件进行任何写入和读取,它针对的是数据库中键的当前值。

 

假设服务器对键list执行了以下四条命令:

127.0.0.1:6379[1]> RPUSH list 1 2 3 4    //[1,2,3,4]

127.0.0.1:6379[1]> RPOP list                    //[1,2,3]

127.0.0.1:6379[1]> LPOP list            //[2,3]

当前列表键list在数据库中的值就为[2,3]。要保存这个列表的当前状态,并且尽量减少使用的命令数,最简单的方式不是去AOF文件分析前面执行的三条命令,而是直接读取list键在数据库中的当前值,然后用一条RPUSH 23代替前面的三条命令。

 

根据键的类型,使用适当的写入命令来重现键的当前值,这就是AOF重写的实现原理。整个重写过程用伪代码表示如下:

=======================================================

def AOF_REWRITE(tmp_tile_name):

 

  f = create(tmp_tile_name)

 

  # 遍历所有数据库

  for db in redisServer.db:

 

    # 如果数据库为空,那么跳过这个数据库

    if db.is_empty(): continue

 

    # 写入 SELECT 命令,用于切换数据库

    f.write_command("SELECT " + db.number)

 

    # 遍历所有键

    for key in db:

 

      # 如果键带有过期时间,并且已经过期,那么跳过这个键

      if key.have_expire_time() and key.is_expired(): continue

 

      if key.type == String:

 

        # SET key value 命令来保存字符串键

 

        value = get_value_from_string(key)

 

        f.write_command("SET " + key + value)

 

      elif key.type == List:

 

        # RPUSH key item1 item2 ... itemN 命令来保存列表键

 

        item1, item2, ..., itemN = get_item_from_list(key)

 

        f.write_command("RPUSH " + key + item1 + item2 + ... + itemN)

 

      elif key.type == Set:

 

        # SADD key member1 member2 ... memberN 命令来保存集合键

 

        member1, member2, ..., memberN = get_member_from_set(key)

 

        f.write_command("SADD " + key + member1 + member2 + ... + memberN)

 

      elif key.type == Hash:

 

        # HMSET key field1 value1 field2 value2 ... fieldN valueN 命令来保存哈希键

 

        field1, value1, field2, value2, ..., fieldN, valueN =\

        get_field_and_value_from_hash(key)

 

        f.write_command("HMSET " + key + field1 + value1 + field2 + value2 +\

                        ... + fieldN + valueN)

 

      elif key.type == SortedSet:

 

        # ZADD key score1 member1 score2 member2 ... scoreN memberN

        # 命令来保存有序集键

 

        score1, member1, score2, member2, ..., scoreN, memberN = \

        get_score_and_member_from_sorted_set(key)

 

        f.write_command("ZADD " + key + score1 + member1 + score2 + member2 +\

                        ... + scoreN + memberN)

 

      else:

 

        raise_type_error()

 

      # 如果键带有过期时间,那么用 EXPIREAT key time 命令来保存键的过期时间

      if key.have_expire_time():

        f.write_command("EXPIREAT " + key + key.expire_time_in_unix_timestamp())

 

    # 关闭文件

f.close()

========================================================

Redis不希望AOF重写会造成服务器无法处理请求,所以Redis决定将AOF重写程序放到(后台)子进程里执行。这样处理的最大好处是:

1、   子进程进行AOF重写期间,主进程可以继续处理命令请求;

2、   子进程带有主进程的数据副本,使用子进程而不是线程,可以避免在锁的情况下,保证数据的安全性。

 

子进程在进行AOF重写期间,主进程还要继续处理命令请求,而新的命令可能对现有的数据进行修改,这会让当前数据库的数据和重写后的AOF文件中的数据不一致。

 

为了解决这个问题,Redis增加了一个AOF重写缓存,这个缓存在fork出子进程之后开始启用,Redis主进程在接到新的写命令之后,除了会将这个写命令的内容追加到现有的AOF文件之外,还会追加到这个缓存中:

http://s8/mw690/002XPXs5gy6RzHgdgxNe7&690

也就是说,子进程在执行AOF重写时,主进程需要执行以下三个工作:

1、处理命令请求;

2、将写命令追加到现有的AOF文件中;

3、将写命令追加到AOF重写缓存中。

如此可以保证:

1)、现有的AOF功能继续执行,即使AOF重写期间发生停机,也不会有任何数据丢失;

2)、所有对数据库进行修改的命令都会被记录到AOF重写缓存中。

 

当子进程完成对AOF文件重写之后,它会向父进程发送一个完成信号,父进程接到该完成信号之后,会调用一个信号处理函数,该函数完成以下工作:

1)、将AOF重写缓存中的内容全部写入到新的AOF文件中;

2)、对新的AOF文件进行改名,覆盖原有的AOF文件。

 

当“1)、将AOF重写缓存中的内容全部写入到新的AOF文件中;”执行完毕后,现有AOF文件、新的AOF文件和数据库三者的状态就完全一致了。

当“2)、对新的AOF文件进行改名,覆盖原有的AOF文件。”执行完毕后,程序就完成了新旧两个AOF文件的替换。

当这个信号处理函数执行完毕之后,主进程就可以继续像往常一样接收命令请求了。在整个AOF后台重写过程中,只有最后的“主进程写入命令到AOF缓存”和“对新的AOF文件进行改名,覆盖原有的AOF文件。”这两个步骤会造成主进程阻塞,在其他时候,AOF后台重写都不会对主进程造成阻塞,这将AOF重写对性能造成的影响降到最低。

 

以上阐述,就是AOF后台重写,也就是BGREWRITEAOF命令的工作原理。

 

触发AOF后台重写的条件

AOF重写可以由用户通过调用BGREWRITEAOF手动触发。

另外,服务器在AOF功能开启的情况下,会维持以下三个变量:

l   记录当前AOF文件大小的变量aof_current_size

l   记录最后一次AOF重写之后,AOF文件大小的变量aof_rewrite_base_size

l   增长百分比变量aof_rewrite_perc

 

每次当serverCron(服务器常规操作)函数执行时,它会检查以下条件是否全部满足,如果全部满足的话,就触发自动的AOF重写操作:

1)、没有BGSAVE命令(RDB持久化)/AOF持久化在执行;

2)、没有BGREWRITEAOF在进行;

3)、当前AOF文件大小要大于server.aof_rewrite_min_size(默认为1MB),或者在redis.conf配置了auto-aof-rewrite-min-size大小;

4)、当前AOF文件大小和最后一次重写后的大小之间的比率等于或者等于指定的增长百分比(在配置文件设置了auto-aof-rewrite-percentage参数,不设置默认为100%

 

如果前面三个条件都满足,并且当前AOF文件大小比最后一次AOF重写时的大小要大于指定的百分比,那么触发自动AOF重写。

 

小结

l   AOF重写的目的是用更小的体积来保存数据库状态,整个重写过程基本上不影响Redis主进程处理命令请求;

l   AOF重写其实是一个有歧义的名字,实际上重写工作是针对数据库的当前值来进行的,重写过程中不会读写、也不适用原来的AOF文件;

 

l   AOF可以由用户手动触发,也可以由服务器自动触发。


0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有