《redis设计与实现》读书笔记--第二部分

第二部分 单机数据库的实现

第九章 数据库

  1. Redis将所有数据库都保存在redisServer结构体的db数组中,db数组的每一项都是一个redisDb结构,代表一个数据库。
    struct redisServer{
        //...
        redisDb * db;
        int dbnum       //要创建的服务器数量,默认16
        //...
    }
  1. 每个redis客户端都有自己的target数据库,默认情况下都是0号库,可以使用SELECT X来切换数据库。
  2. redis是一个键值对数据库服务器,每个数据库对应一个redisDb结构体,redisDb结构体中的dict字典保存了所有的键值对,称为键空间。键空间的键是字符串对象,值可以是5大对象中的任意一个。对数据库的增删改查其实就是对这个dict字典进行增删改查。
  3. 当使用redis命令对数据库读写时,不仅会对键空间进行指定的操作,还会执行一些额外操作:

    1. 在读取一个键之后,会根据键是否存在来更新服务器键空间中的命中次数和不命中次数
    2. 在读取一个键之后,服务器会更新键的LRU时间
    3. 如果服务器在读取一个键的时候发现该键已经过期,则会先删除这个过期的键
    4. 如果有客户端使用WATCH监视了某个键,那么服务器会对被监视的键进行修改后,将其标记为dirty来让事务程序注意到键已被修改
    5. 服务器每修改一个键都会对dirty计数器+1,会触发持久化和复制操作
  4. 通过EXPIREPEXPIRE可以用秒或者毫秒对key设置生存时间,服务器会自动删除生存时间(TTL)为0的key。
    还可以通过EXPIREATPEXPIREAT对key设置过期时间,过期时间是UNIX时间戳,如1377257300,当时间到了就自动删除key。可以使用PERSIST移除一个key的过期时间。
    以上4个关于过期时间的命令实际上都是通过PEXPIREAT来实现的。

  5. redisDb结构中的expires字典保存了所有键的过期时间,称为过期字典。
  6. 过期键的删除策略
    1. 定时删除:在设置key的过期时间的同时,创建一个定时器,让定时器在过期时间到的时候删除。属于主动删除
    2. 惰性删除:过期就过期了,但是每次在键空间要访问该key时检查是否过期,如果过期了就删除。属于被动删除
    3. 定期删除:每个一定时间对数据库检查一次,删除过期的键。属于主动删除
  7. 定时删除:对内存最友好,但是对CPU时间不友好,会影响吞吐量和响应时间,而且创建定时器是O(n)的操作,所以大规模的定时删除是不现实的。
  8. 惰性删除:对CPU时间最友好,就是费内存,而且如果某些过期键不会被访问的话就永远不会被删除了,相当于内存泄漏
  9. 定期删除是前两种方法的折中,难点是怎么确定定期删除的时长和频率。如果删除操作太频繁,就退化成定时删除了,如果执行的太少,就会浪费内存
  10. redis的过期键删除策略
    1. redis实际使用的是惰性删除和定期删除两种策略,通过结合使用这两种能在cpu时间和内存方面取得平衡的效果。
    2. 惰性删除没啥特殊的,就是在访问某key的时候如果过期了就由expireIfNeeded函数删除,然后返回不存在,如果没过期就正常访问。
    3. 定期删除:activeExpireCycle函数会周期执行,在规定时间内多次遍历服务器中的每个数据库,在expires字典中随机检查一部分key的过期时间,并删除过期的。
  11. AOF、RDB和复制功能对过期键的处理(RDB,AOF等之后会详细讲):
    1. 执行SAVE或者GBSAVE命令会创建一个新的RDB文件,程序会对数据库中的key进行检查,不会把过期的键保存在RDB文件中。当启动redis服务器时,如果开启了RDB功能,则会对RDB文件进行载入,如果是主服务器(master),程序会对文件中保存的key进行检查,未过期的key才会载入到数据库,所以未过期的key对master是不会造成影响的。如果是从服务器(slave),不论是否过期,所有的key都会被载入。不过因为master在数据同步的时候slave会被清空,所以说到底也没啥影响。
    2. 当服务器已AOF持久化模式运行时,如果某个键已经过期,但是还没有被惰性删除或者定期删除,那么AOF文件不会因为这个过期键而产生影响。当这个键被删除后,程序会向AOF文件append一条DEL命令来记录这个键已经被删除。在执行AOF重写时,会对数据库的键进行检查,过期的键不会保存到重写后的AOF文件中。
    3. 服务器运行在复制模式下时,slave的过期删除动作由master控制。master在删除一个过期键后,会显式的向所有slave发送一个DEL命令来删除这个过期键。slave在执行客户端的读命令时,就算键过期了也不会删除,只有收到master的DEL命令之后才会删除。这样可以保证主从服务器的数据一致性。

第十章 RDB持久化

  1. 因为redis是内存数据库,所以一旦退出进程,数据库状态就没了,所以需要持久化。
  2. RDB持久化既可以手动执行,也可以根据服务器配置选项定期执行。经过RDB持久化将生成一个RDB二进制文件,是通过压缩的,可以还原生成RDB文件时的数据库状态。
  3. SAVEBGSAVE两个命令都可以生成RDB文件。SAVE命令会阻塞当前redis进程,直到RDB文件创建完毕,在阻塞期间,不能处理任何命令。BGSAVE会创建一个子进程在后台创建RDB文件,此时redis主进程能继续处理请求。这两个命令都是通过rdbSave函数完成。
  4. redis服务器启动时就会自动载入RDB文件,所以没有专门的载入RDB文件的命令。
  5. 因为AOF文件的更新频率比RDB文件高,所以如果开启了AOF持久化功能的话会优先使用AOF文件来还原数据库状态。如果AOF功能关闭的话会使用RDB文件。
  6. 执行BGSAVE期间SAVE命令会被拒绝,为了避免父进程和子进程都执行rdbSave函数产生竞态条件。执行BGSAVE期间BGSAVE命令会被拒绝,同样因为竞态条件。执行BGSAVE期间BGWRITEAOF命令会阻塞,如果BGSAVE在执行,则BGWRITEAOF要等待BGSAVE执行完,如果BGWRITEAOF在执行,则BGSAVE会被拒绝。
  7. 服务器在载入RDB文件时是出于阻塞态的。
  8. 用户可以设置多个保存条件来让服务器自动执行BGSAVE。比如 save 900 1//表示如果900秒内发生了1次修改则执行BGSAVE。这些条件保存在redisServer结构的saveparams数组中,每个数组项就是一个saveparam,包含修改次数和时间。
  9. 除了saveparams数组,redisServer结构还维护一个dirty计数器和lasesave属性,dirty计数器记录上一次成功执行SAVE或者BGSAVE命令之后,服务器对数据库状态进行了多少次修改。lastsave属性是一个时间戳,记录上一次执行SAVE或者BGSAVE命令的时间。
  10. RDB文件结构:了解一哈,没必要记。

第十一章 AOF持久化

  1. AOF(append only file)是通过保存redis服务器所执行的写命令来记录数据库状态的。
  2. 在服务器启动时,可以通过载入和执行AOF文件中保存的命令还远之前的数据库状态。
  3. AOF持久化功能可以分为append,文件写入,文件同步三个步骤。
    1. append:当AOF功能打开的状态下,服务器美执行一个写命令,都会以协议格式将被执行的写命令追加到aof_buf缓冲区的末尾。
    2. 文件写入和同步:服务器在每结束一个事件循环之前,都会调用flushAppendOnlyFile函数来考虑是否将aof_buf中的内容写入AOF文件中。flushAppendOnlyFile函数的行为由服务器配置的appendfsync选项来决定,具体有always表示将aof_buf中所有内容都写入,并同步AOF文件;evertsec表示将aof_buf所有内容写入,如果上次同步AOF文件时间距离当前超过1S,则再次同步;no表示全部写入,但是不同步。默认的选项是evertsec
  4. 现代操作系统调用write时一般都会讲数据写内存缓冲,知道缓冲区满或者大小超出才会真正写磁盘,这样可以提高效率。
  5. appendfsync的配置直接影响AOF的效率和安全性:如果是always,那肯定是最安全的,因为每个命令都会写入并同步,但是效率会很慢;everysec的话如果出现问题也就丢失一秒钟,可以接受,而且不会频繁的同步,效率也还行;no的模式下什么时候对文件进行同步依赖于OS,所以安全性非常差,而且因为会在缓存中积累很多写入数据,所以单词的同步时间最久,而且很容易丢失很多命令,不过写入效率是最快的。
  6. AOF重写:因为AOF是通过保存被执行的写命令来记录状态的,因此文件内容会越来越多,太大的话会影响性能。因为AOF重写功能可以创建一个新的AOF文件来替代现有的文件,而且不会包含冗余的命令。
  7. AOF重写并不需要对现有的AOF文件进行分析或者读取,而是根据当前的数据库状态来实现的。比如在list中先添加1个元素,在添加一个元素,初始的AOF文件中这就是2条命令,然而这两条命令可以压缩成直接添加2个元素的一条命令,所以AOF重写就是直接从数据库中读取list的键值,用一条语句就把当前的数据全部保存了。这就是AOF重写的原理。生成的AOF重写文件叫做aof_rewrite
  8. 当然,如果某些列表或者集合的元素太多了,超出了64(可以配置的选项),那么也不会强行用一条命令就全部搞定,可以用多条命令来记录,防止单条命令太长了。
  9. AOF重写是在子进程中执行的,主要目的是:子进程在AOF重写时,服务器进程还能继续处理请求;子进程是带有服务器进程的数据副本的,不用线程是因为这样可以避免加锁
  10. AOF的存在问题是子进程和主进程并发的,会存在数据不一致性,因为主进程可以在子进程重写的时候又对数据库状态做修改了。为了解决这个,redis服务器设置了一个AOF重写缓冲区,当redis执行完一个写命令之后,会将这个写命令同时发送给AOF缓冲区和AOF重写缓冲区。这样可以保证AOF缓冲区的内容会被定期写入和同步到AOF文件,对现有AOF文件的处理工作照常进行;从创建子进程来时,所执行的所有写命令会被记录到AOF重写缓冲区中,当子进程完成重写后,会通知主进程,将AOF重写缓冲区中的所有内容写入新的AOF文件中,所以当前新的AOF文件保存的命令就和当前的数据库状态一致了。只有会对新的AOF文件进行改名,并替换当前的旧AOF文件(原子操作),整个重写过程就OK了。

第十二章 事件

  1. redis服务器主要处理两类事件:文件事件和时间事件。
  2. redis服务器通过套接字与client连接,文件事件就是对套接字操作的抽象,server和client质检的通信会产生相应的文件事件,而server就通过监听并处理这些事件来完成网络通信操作。
  3. server中的一些操作比如serverCron函数需要在指定的时间点运行,时间事件就是对这类定时操作的抽象。
  4. Redis基于Reactor模式来开发自己的网络事件处理器,称为文件事件处理器:
    1. 文件事件处理器使用I/O多路复用程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
    2. 当被监听的套接字准备好执行accept、read、write、closr等操作时,文件事件就会产生,文件事件处理器就会调用这些事件处理器来处理事件。
    3. 虽然文件事件处理器是以单线程的方式运行的,但是通过使用I/O多路复用程序监听多个套接字,文件事件处理器既实现了高性能的网络通信模型,又可以很好的与redis服务器中的其他单线程模块进行对接。
  5. I/O多路复用程序负责监听多个套接字,冰箱文件时间分派器传送那些产生了事件的套接字。尽管多个文件事件可能会并发的出现,但I/O多路复用程序会将所有产生事件的套接字都放入一个队列,通过这个队列以有序、同步、一次一个套接字的方式向分派器传送套接字,当这个套接字处理完毕,I/O多路复用程序才会分派下一个套接字。
  6. I/O多路复用程序的实现:通过包装常见的selectepollevportkqueue这些库函数实现的。
  7. redis的时间事件可以分为定时事件和周期事件。一般只执行serverCron这一个函数。
  8. 文件事件和时间事件是合作关系,会轮流处理这两种事件,不会发生抢占

第十三章 客户端

没啥特别的

第十四章 服务器

没啥特别的

猜你喜欢

转载自blog.csdn.net/lengyuedanhen/article/details/80267167