键空间

❑数据库主要由dict和expires两个字典构成,其中dict字典负责保存键值对,而expires字典则负责保存键的过期时间。❑因为数据库由字典构成,所以对数据库的操作都是建立在字典操作之上的。

redisDb结构的dict字典保存了数据库中的所有键值对,我们将这个字典称为键空间(key space)。

键空间和用户所见的数据库是直接对应的:

❑键空间的键也就是数据库的键,每个键都是一个字符串对象。

❑键空间的值也就是数据库的值,每个值可以是字符串对象、列表对象、哈希表对象、集合对象和有序集合对象中的任意一种Redis对象。

数据库的键空间是一个字典,所以所有针对数据库的操作,比如添加一个键值对到数据库,或者从数据库中删除一个键值对,又或者在数据库中获取某个键值对等,实际上都是通过对键空间字典进行操作来实现的

除了添加、删除、更新、取值操作之外,还有很多针对数据库本身的Redis命令,也是通过对键空间进行处理来完成的,随机返回数据库中某个键的RANDOMKEY命令,就是通过在键空间中随机返回一个键来实现的。另外,用于返回数据库键数量的DBSIZE命令,就是通过返回键空间中包含的键值对的数量来实现的。类似的命令还有EXISTS、RENAME、KEYS等,这些命令都是通过对键空间进行操作来实现的。

设置键的生存时间与过期时间

EXPIRE KEY TTL:设置经过ttl毫秒后,服务器将自动删除生存时间为0的键。

Redis有四个不同的命令可以用于设置键的生存时间(键可以存在多久)或过期时间(键什么时候会被删除):

  • EXPIRE <key> <ttl> 命令用于将键key的生存时间设置为ttl秒(基本用该命令)</ttl></key>
  • PEXPIRE <key> <ttl> 命令用于将键key的生存时间设置为ttl毫秒</ttl></key>
  • EXPIREAT<key><timestamp>命令用于将键key的过期时间设置为timestamp所指定的秒数时间戳。
  • PEXPIREAT<key><timestamp>命令用于将键key的过期时间设置为timestamp所指定的毫秒数时间戳。

虽然有多种不同单位和不同形式的设置命令,但实际上EXPIRE、PEXPIRE、EXPIREAT三个命令都是使用PEXPIREAT命令来实现的:无论客户端执行的是以上四个命令中的哪一个,经过转换之后,最终的执行效果都和执行PEXPIREAT命令一样

保存过期时间

redisDb结构的expires字典保存了数据库中所有键的过期时间,我们称这个字典为过期字典:

❑过期字典的键是一个指针,这个指针指向键空间中的某个键对象(也即是某个数据库键)。

❑过期字典的值是一个long long类型的整数,这个整数保存了键所指向的数据库键的过期时间——一个毫秒精度的UNIX时间戳。

键空间的键和过期字典的键都指向同一个键对象,所以不会出现任何重复对象,也不会浪费任何空间。

移除过期时间

PERSIST <key> :移除一个键的过期时间</key>

PERSIST命令就是PEXPIREAT命令的反操作:PERSIST命令在过期字典中查找给定的键,并解除键和值(过期时间)在过期字典中的关联。

例如:PERSIST A 当PERSIST命令执行之后,过期字典中原来的A键值对消失了,这代表数据库键A的过期时间已经被移除。

计算并返回剩下生存时间

TTL KEY:以秒为单位返回键的剩余生存时间

PTTL KEY:以毫秒为单位返回键的剩余生存时间

过期键的判定

通过过期字典,程序可以用以下步骤检查一个给定键是否过期:1)检查给定键是否存在于过期字典:如果存在,那么取得键的过期时间。2)检查当前UNIX时间戳是否大于键的过期时间:如果是的话,那么键已经过期;否则的话,键未过期。

实现过期键判定的另一种方法是使用TTL命令或者PTTL命令,比如说,如果对某个键执行TTL命令,并且命令返回的值大于等于0,那么说明该键未过期。在实际中,Redis检查键是否过期的方法和is_expired函数所描述的方法一致,因为直接访问字典比执行一个命令稍微快一些。

过期键的删除策略

如果一个键过期了,那么它什么时候会被删除呢?有三种不同的删除策略:

❑定时删除:在设置键的过期时间的同时,创建一个定时器(timer),让定时器在键的过期时间来临时,立即执行对键的删除操作。

惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。

❑定期删除:每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。

在这三种策略中,第一种和第三种为主动删除策略,而第二种则为被动删除策略

定时删除:内存友好,CPU不友好,需创建大量的定时器,不现实。

惰性删除:对CPU最友好,对内存不友好,--可能过期的键永远不会被删除(不被调用),可看做内存泄漏的点,与时间相关的数据(日志等),需注意可能导致严重问题。

定期删除:用定期删除策略的话,服务器必须根据情况,合理地设置删除操作的执行时长和执行频率。

Redis服务器实际使用的是惰性删除和定期删除两种策略:通过配合使用这两种删除策略,服务器可以很好地在合理使用CPU时间和避免浪费内存空间之间取得平衡。

AOF、ROB和复制功能对过期键的处理

在执行SAVE命令或者BGSAVE命令创建一个新的RDB文件时,程序会对数据库中的键进行检查,已过期的键不会被保存到新创建的RDB文件中。

RDB文件写入:在启动Redis服务时,若服务器开启了RDB功能,则服务器将对RDB文件进行载入。

AOF文件写入:当服务器以AOF持久化模式运行时,如果数据库中的某个键已经过期,但它还没有被惰性删除或者定期删除,那么AOF文件不会因为这个过期键而产生任何影响。(过期键依然存在)

AOF文件重写:和生成RDB文件时类似,在执行AOF重写的过程中,程序会对数据库中的键进行检查,已过期的键不会被保存到重写后的AOF文件中。(忽略过期键的信息)

主从复制:当服务器运行在复制模式下时,从服务器的过期键删除动作由主服务器控制:❑主服务器在删除一个过期键之后,会显式地向所有从服务器发送一个DEL命令,告知从服务器删除这个过期键。❑从服务器在执行客户端发送的读命令时,即使碰到过期键也不会将过期键删除,而是继续像处理未过期的键一样来处理过期键。❑从服务器只有在接到主服务器发来的DEL命令之后,才会删除过期键。

通过由主服务器来控制从服务器统一地删除过期键,可以保证主从服务器数据的一致性,也正是因为这个原因,当一个过期键仍然存在于主服务器的数据库时,这个过期键在从服务器里的复制品也会继续存在。

注意点:客户端向从服务器发送命令GET message,那么从服务器将发现message键已经过期,但从服务器并不会删除message键,而是继续将message键的值返回给客户端,就好像message键并没有过期一样

Redis数据库持久化

RDB持久化

SAVE:SAVE命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求。

BGSAVE:BGSAVE命令会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程(父进程)继续处理命令请求。

RDB文件的载入工作是在服务器启动时自动执行的,载入RDB文件期间,会一直处于阻塞状态,直到载入工作完成为止。

因为AOF文件的更新频率通常比RDB文件的更新频率高,所以:

❑如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据库状态。

只有在AOF持久化功能处于关闭状态时,服务器才会使用RDB文件来还原数据库状态。

设置自动间隔性保存

SAVE number1 number2:服务器在number1秒之内,对数据库进行了至少number2次修改。

例如:SAVE 900 100:服务器在900秒之内,对数据库进行了至少100次修改

od -c dump.rdb:使用od命令来分析Redis服务器产生的RDB文件,该命令可以用给定的格式转存(dump)并打印输入文件。

AOF持久化

除了RDB持久化功能之外,Redis还提供了AOF(Append Only File)持久化功能。与RDB持久化通过保存数据库中的键值对来记录数据库状态不同,AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的

AOF持久化功能的实现可以分为命令追加(append)、文件写入、文件同步(sync)三个步骤。

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

AOF文件写入:AOF文件里面包含了重建数据库状态所需的所有写命令,所以服务器只要读入并重新执行一遍AOF文件里面保存的写命令,就可以还原服务器关闭之前的数据库状态。

AOF文件重写

AOF文件重写原因:为了解决AOF文件体积膨胀的问题,Redis提供了AOF文件重写(rewrite)功能。通过该功能,Redis服务器可以创建一个新的AOF文件来替代现有的AOF文件,新旧两个AOF文件所保存的数据库状态相同,但新AOF文件不会包含任何浪费空间的冗余命令,所以新AOF文件的体积通常会比旧AOF文件的体积要小得多。

Redis将生成新AOF文件替换旧AOF文件的功能命名为“AOF文件重写”,但实际上,AOF文件重写并不需要对现有的AOF文件进行任何读取、分析或者写入操作,这个功能是通过读取服务器当前的数据库状态来实现的。

AOF文件重写原理:从数据库中读取键现在的值,然后用一条命令去记录键值对,代替之前记录这个键值对的多条命令,这就是AOF重写功能的实现原理。

AOF后台重写

Redis不希望AOF重写造成服务器无法处理请求,所以Redis决定将AOF重写程序放到子进程里执行,这样做可以同时达到两个目的:

❑子进程进行AOF重写期间,服务器进程(父进程)可以继续处理命令请求。

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

如何解决其间父进程添加数据导致数据不一致问题呢?

Redis服务器设置了一个AOF重写缓冲区,这个缓冲区在服务器创建子进程之后开始使用,当Redis服务器执行完一个写命令之后,它会同时将这个写命令发送给AOF缓冲区和AOF重写缓冲区

子进程执行AOF重写期间,服务器进程需要执行以下三个工作:

1)执行客户端发来的命令。

2)将执行后的写命令追加到AOF缓冲区

3)将执行后的写命令追加到AOF重写缓冲区。

Sentinel(哨兵)

Sentinel(哨岗、哨兵)是Redis的高可用性(high availability)解决方案:由一个或多个Sentinel实例(instance)组成的Sentinel系统(system)可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。

启动sentinel

启动一个Sentinel可以使用命令:redis-sentinel /sentinel.config

redis-server /sentinel.conf --sentinel

当一个Sentinel启动时,它需要执行以下步骤:

1)初始化服务器。

2)将普通Redis服务器使用的代码替换成Sentinel专用代码。(PING、SENTINEL、INFO、SUBSCRIBE、UNSUBSCRIBE、PSUBSCRIBE和PUNSUBSCRIBE这七个命令就是客户端可以对Sentinel执行的全部命令)

3)初始化Sentinel状态。(Sentinel状态中的masters字典记录了所有被Sentinel监视的主服务器的相关信息)

4)根据给定的配置文件,初始化Sentinel的监视主服务器列表。

5)创建连向主服务器的网络连接。(对于每个被Sentinel监视的主服务器来说,Sentinel会创建两个连向主服务器的异步网络连接:❑一个是命令连接,这个连接专门用于向主服务器发送命令,并接收命令回复。❑另一个是订阅连接,这个连接专门用于订阅主服务器的__ sentinel __:hello频道。)

Sentinel默认会以每十秒一次的频率,通过命令连接向被监视的主服务器发送INFO命令,并通过分析INFO命令的回复来获取主服务器的当前信息。

对于每个与Sentinel连接的服务器,Sentinel既通过命令连接向服务器的__ sentinel :hello频道发送信息,又通过订阅连接从服务器的 sentinel __:hello频道接收信息。

检测状态

检测主观下线状态

在默认情况下,Sentinel会以每秒一次的频率向所有与它创建了命令连接的实例(包括主服务器、从服务器、其他Sentinel在内)发送PING命令,并通过实例返回的PING命令回复来判断实例是否在线。

检测客观下线状态

当Sentinel将一个主服务器判断为主观下线之后,为了确认这个主服务器是否真的下线了,它会向同样监视这一主服务器的其他Sentinel进行询问,看它们是否也认为主服务器已经进入了下线状态**(可以是主观下线或者客观下线)。当Sentinel从其他Sentinel那里接收到足够数量的已下线判断之后,Sentinel就会将从服务器判定为客观下线,并对主服务器执行故障转移操作。

选举领头sentinel

当一个主服务器被判断为客观下线时,监视这个下线主服务器的各个Sentinel会进行协商,选举出一个领头Sentinel,并由领头Sentinel对下线主服务器执行故障转移操作。

Sentinel设置局部领头Sentinel的规则是先到先得:最先向目标Sentinel发送设置要求的源Sentinel将成为目标Sentinel的局部领头Sentinel,而之后接收到的所有设置要求都会被目标Sentinel拒绝。

如果有某个Sentinel被半数以上的Sentinel设置成了局部领头Sentinel,那么这个Sentinel成为领头Sentinel。

故障转移

在选举产生出领头Sentinel之后,领头Sentinel将对已下线的主服务器执行故障转移操作,该操作包含以下三个步骤:

1)在已下线主服务器属下的所有从服务器里面,挑选出一个从服务器,并将其转换为主服务器。

2)让已下线主服务器属下的所有从服务器改为复制新的主服务器。

3)将已下线主服务器设置为新的主服务器的从服务器,当这个旧的主服务器重新上线时,它就会成为新的主服务器的从服务器。(因为旧的主服务器已经下线,所以这种设置是保存在其对应的实例结构里面的,当其重新上线时,Sentinel就会向它发送SLAVEOF命令,让它成为主服务器的从服务器。)

总结:

​ ❑Sentinel会读入用户指定的配置文件,为每个要被监视的主服务器创建相应的实例结构,并创建连向主服务器的命令连接和订阅连接,其中命令连接用于向主服务器发送命令请求,而订阅连接则用于接收指定频道的消息。

❑Sentinel只会与主服务器和从服务器创建命令连接和订阅连接,Sentinel与Sentinel之间则只创建命令连接。