redis 服务器端默认保存着 16 个数据库,客户端可以通过 SELECT (0~15)来选择不同的服务器。
1. 服务器中的数据库结构
Redis 服务器将所有数据库都保存在服务器状态 redis.h/redisServer 结构的 db 数组中, db 数组每一项都是一个 redis.h/redisDB 结构,每个 redisDB 结构代表一个数据库
struct redisServer{
//......
//一个数组,保存着服务器中的所有数据库
redisDB *db;
//......
};
在初始化服务器的时候,程序会根据服务器状态 dbnum 属性来决定创建多少个数据库。
struct redisServer{
//......
//服务器的数据库的数量
int dbnum;
//.......
};
2. 客户端切换数据库
客户端通过 SElECT (数据库编号) 0 ~ 15 命令来切换不同的数据库,切换的原理是服务器改变 redisDB *db 的指针。
3. 数据库键空间
redisServer 中的 redisDB 结构中的 dict 字典保存了数据库中的所有键值对,这个字典被称为键空间(key space)。
键空间和用户所见的数据库是直接对应的:
- 键空间的键也就是数据库的键,每个键都是一个字符串对象
- 键空间的值也就是数据库的值,每个值可以是字符串对象、列表对象、哈希表对象、集合对象或者有序集合对象中的任何一种 Redis 对象。
typedef struct redisDB{
//..........
//数据库键空间,保存着数据库中的所有键值对
dict *dict;
//.........
}reidsDB;
读取键空间时的维护操作
当使用 Reids 命令对数据库进行读写时,服务器不仅会对 键空间执行指定的读写操作,还会进行一个额外的操作:
- 在读取一个键之后,服务器会根据键是否存在来更新服务器的键空间命中(hit)次数和键空间不命中(miss)次数。
- 在读取一个键之后,服务器会更新这个键的 LRU(最后一次使用)时间,这个值可以用来计算键的闲置时间(当前时间 - LRU)。
- 如果服务器在读取一个键的时候已经过期,那么服务器会先删除这个过期键,然后才执行剩下的其他操作
- 如果有客户端使用 WATCH 命令监视了某个键,那么服务器在对被监视的键进行修改之后,会将这个键标记为脏(dirty)
- 服务器每次修改一个键之后,都会对脏(dirty)键计数器的值增 1,这个计数器会触发服务器的持久化操作以及复制操作。
4. 设置键的生存时间或过期时间
通过 EXPIRE 命令或者 PEXPIRE 命令,客户端可以以秒或者毫秒精度为数据库中的某个键设置生存时间(Time To Live, TTL),在经过执行的秒数或者毫秒数之后,服务器会自动删除生存时间为 0 的键。
过期时间是一个 Unix 时间戳,当键的过期时间来临时,服务器就会自动从数据库中删除这个键。
使用 TTL 或者 PTTL 命令可以查看一个键剩余生存时间,也就是这个键将在多久之后被删除。
4.1 设置过期时间
设置过期时间的命令:
- EXPIRE 设置 key 的生存时间为 ttl 秒
- PEXPIRE 设置 key 的生存时间为 ttl 毫秒
- EXPIREAT 设置 key 的过期时间为 timestamp 指定的秒数时间戳(精度为秒)
- PEXPIREAT 设置 key 的过期时间为 timestamp 指定的毫秒数时间戳 (精度为毫秒)
EXPIRE、PEXPIRE、EXPIREAT、PEXPIRE 四条指令的执行(都会转换成 PEXPIREAT 去执行)
- EXPIRE:EXPIRE 会先转换成 PEXPIRE 然后再转换成 PEXPIREAT 去执行
- PEXPIRE:PEXPIRE 会转换成 PEXPIRE 去执行
- EXPIREAT:EXPIREAT 会转换成 PEXPIREAT 去执行
4.2 保存过期时间
redisDB 结构中的 expires 字典保存了数据库中所有键的过期时间,称之为 “过期字典”:
- 过期字典的键为指向键空间某个键对象的指针
- 过期字典的值为 long long 类型的整数,保存一个毫秒级的 Unix 时间戳 ———— 键的过期时间
typedef struct redisDb{
//.....
//过期字典
dict *expires;
//.....
}redisDb;
4.3 移除过期时间
PERSIST <key>
,这个命令可以移除 key 上的过期时间
4.4 判定键是否过期
判断键是否过期分为两个步骤:
- 判断键是否存在于过期字典中
- 如果键存在与过期字典中,则判断这个键的过期时间戳是否小于当前时间,小于则这个键过期。反之,这个键没有过期。
5. 过期键的删除策略
redis 中有三种过期键的删除策略:
- 定时删除:在设置键的过期时间的同时,设置一个定时器(timer),让定时器在键的过期时间来临时,立即执行对键的删除操作。
- 惰性删除: 放任过期的键不管,当每一个键被访问的之后,就查看这个键是否过期。如果过期,就删除这个键;如果没有过期,就返回这个键。
- 定期删除:每隔一段时间就检查数据库的过期字典,删除掉过期的键。这个策略在扫描键的时候,会扫描所有的数据库,但是只会扫描每隔数据库中随机选择(一部分)的键。
6. AOF,RDB 和复制功能对过期键的处理。
RDB 和 AOF 为redis 数据库的两种持久化方式。
6.1 RDB 对过期键的处理
在生成 RDB 文件和载入 RDB 文件的时候对过期键都有相应的处理策略:
- 生成 RDB 文件: 在生成 RDB 文件的时候(SAVE命令,BGSAVE 命令)的时候,会对数据库的键进行检查,过期的键不会被加入到 RDB 文件中。
- 载入RDB:
- 主服务器载入 RDB 文件时,过期的键不会被载入到服务器中
- 从服务器载入 RDB 文件时,无论键是否过期,都会被载入到数据库中。(在主从服务器同步的时候,从服务器的数据就会被清空,所以这个没有影响)。
6.2 AOF 文件对过期键的处理
- AOF 文件写入时:在一个键被惰性删除或者定期删除之后,程序会向 AOF 文件追加(appen)一条 DEL 命令,来表示这个键被删除。
- AOP 文件重写时:在执行 AOF 重写的时候,已经过期的键不会被保存到重写后的 AOF 文件中。
6.3 主从服务器复制对过期键的处理
从服务器之后接受从主服务器传来的 DEL 命令的时候,才会删除过期键:
- 主服务器再删除一个过期键之后,会显示地向所有的从服务器发送一条 DEL 命令。
- 从服务器收到客户端传来的读命令时,即使碰到过期键也不会将过期键删除,而是正常返回
- 从服务器只有接收到主服务器传来的 DEL 命令之后,才会删除过期键。
参考资料
[1].《Redis 设计与实现》