Fork me on GitHub

七、Redis-数据库

前言

终于上岸了,今天来总结一下Redis中的数据库这一节,建立起整个Redis数据库的架构体系

服务器中的数据库

Redis服务器将所有数据库都保存在服务器状态 redisServer 结构的 db 数组中,db 数组的每个项都是一个 redisDb 结构,每个 redisDb 结构代表一个数据库

1
2
3
4
5
6
7
struct redisServer{
// 一个数组,保存着服务器中的所有数据库
redisDb *db;

// 服务器的数据库数量
int dbnum;
};
  • db:db数组的每个数据项代表的就是一个数据库
  • dbnum:初始化服务器时,程序会根据服务器状态的 dbnum 属性决定应该创建多少个数据库(默认为 16)

客户端

之前咱们说了,服务端有很多数据库默认有16个,而每个客户端都有自己的目标数据库,默认情况下 Redis 客户端的目标数据库是 0 号数据库,但是客户端可以通过 SELECT 命令来切换数据库

在服务器内部,客户端状态 redisClient 结构的 db 属性记录了客户端当前的目标数据库,这个属性是一个指向 redisDb 结构的指针

1
2
3
4
5
6
7
struct redisClient{

//记录客户端当前正在使用的数据库
redisDb *db;

//...
} redisClient;

image

代表客户端目前操作的数据库为1号数据库

SELECT 命令就是通过修改 redisClient 中的 db 指针的指向来切换数据库的

数据库键空间

Redis是一个键值对数据库服务器,服务器中的每个数据库都由一个 redisDb 结构表示,redisDb 结构中的 dict 字典保存了数据库中的所有键值对,这个字典叫做键空间

1
2
3
4
5
6
struct redisDb{
// 数据库键空间,保存着数据库中的所有键值对
dict *dict;

// ...
}redisDb;
  • 键空间的键:都是字符串对象
  • 键空间的值:每个值可以是 字符串、列表对象、哈希表对象、集合对象、有序集合对象

image

  • LRANGE alphabet 0 -1 :操作链表
  • FlushDB:删除键空间所有键值对
  • RANDOMKEY:随机返回数据库中某个键
  • DBSIZE:返回数据库键数量

读写键空间时的维护操作

当使用 Redis 命令对数据库进行读写时,服务器不仅会对键空间执行指定的读写操作,还会执行一些额外的维护操作

  • 读取一个键后(读操作和写操作),服务器会根据键是否存在来更新服务器的键空间命中次数或键空间不命中次数;
  • 读取一个键后,服务器会更新键的 LRU(最后一次使用) 时间,用于计算键的闲置时间;
  • 当服务器读取一个键时发现该键已经过期,那么服务器会先删除这个过期键,然后才执行余下的操作;
  • 如果有客户端使用 WATCH命令监视了某个键,那么服务器在堆被监视键修改后,会将这个键标记为脏(dirty)键,让事务程序注意到这个键已经被修改过
  • 服务器每修改一个键后,都会对脏键计数器增1,这个计数器会触发服务器的持久化以及复制操作
  • 如果服务器开启了数据库通知功能,对键进行修改后,服务器将按配置发送相应的数据库通知

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

生存时间

  • EXPIRE:客户端以为精度,为数据库中某个键设置生存时间
  • PEXPIRE:客户端以毫秒为精度,为数据库中某个键设置生存时间

过期时间

  • EXPIREAT:客户端以为精度,为数据库中某个键设置过期时间
  • PEXPIREAT:客户端以毫秒为精度,为数据库中某个键设置过期时间

过期时间是一个 UNIX 时间戳,当键的过期时间来临时,服务器会自动从数据库中删除这个键

TTL命令和PTTL命令接受一个带有生存时间或者过期时间的键,返回这个键的剩余生存时间(就是返回距离这个键被服务器自动删除还有多少时长)

EXPIRE、PEXPIRE、EXPIREAT三个命令都会转换成PEXPIREAT命令来执行

#### 保存过期时间

RedisDb 结构的 expires 字典保存了数据库中所有键的过期时间,我们将这个称为过期字典。过期字典的键是一个指针,指向键空间的某个键对象,值是一个 long 类型的整数,这个整数保存了键所指向的数据库键的过期时间

1
2
3
4
5
6
7
struct redisDb{

//过期字典,保存着过期时间
dict *expires;

//...
}redisDb;


#### 移除过期时间

PERSIST命令可以移除一个键的过期时间

#### 过期键的判定

- 检查给定键是否存在于过期字典:如果存在,那么取得键的过期时间
- 检查当前 UNIX 时间戳是否大于键的过期时间:如果是的话,那么已经过期;否则的话,键未过期

### 过期键删除策略

- 定时删除:设置键的过期时间的同时,创建一个定时器,让定时器在过期时间来临时,立即执行对键的删除
- 惰性删除:对键的过期时间不管,当获取键时,查看键是否过期,如果过期则删除
- 定期删除:每隔一段时间进行一次检查,删除里面的过期键

#### 定时删除

优点

对内存友好,通过定时器保证过期键及时被删除,并释放内存

缺点

①:对CPU不友好,删除键会占用CPU,如果当前请求过多,你却去删除过期键

②:创建一个定时器需要用到 Redis 服务器中时间事件,而时间事件的实现方式是无序链表,查找一事件的时间复杂度为O(N)

#### 惰性删除

优点

对CPU友好

缺点

对内存不友好,可能会造成内存泄漏(Redis是内存服务器,这样很不好)

#### 定期删除

两种策略的折中

Redis使用的是惰性删除和定期删除两种策略

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

生成RDB文件

在执行 SAVE 命令或者 BGSAVE 命令创建一个新的 RDB 文件时,已经过期的键不会保存到新创建的 RDB 文件中。

载入RDB文件

启动Redis服务器时,如果Redis开启了RDB功能,那么服务器会对RDB文件进行载入

  • 若以主服务器模式运行,载入RDB文件时,会忽略RDB中的过期键
  • 若以从服务器运行,载入RDB文件时,不论键是否过期,都会被载入数据库

AOF文件写入

当过期键被惰性删除或者定期删除之后,程序会向AOF文件追加一条 DEL 命令,显示记录改键被删除

AOF文件重写

在执行AOF文件重写时,程序会对数据库中的键进行检查,已过期的键不会被保存到重写后的AOF文件中