源码位置:db.c/server.h
1. Redis数据库介绍:
在前两个阶段中,我们学习了redis数据结构的实现,而这些数据结构都是为了实现数据库功能做的铺垫,下面,让我们一起来看看redis数据库是如何实现的吧。
Redis服务器在运行的时候会创建大量的redisObject对象,这些对象都是存在redisDb中的,为了快速索引到某个对象,redisDb采用了dict字典结构设计。
启动Redis后,Redis服务器将所有数据库都保存在redisServer结构的db数组中,db数组的每一项都是一个redisDb结构,代表一个数据库。根据配置参数,redis服务器在初始化的时候,默认情况下会创建16个数据库,由dbnum决定(可通过databases
配置修改),每个数据库都是独立的。
客户端可以通过select
命令来切换数据库,如select 1
会切换到数据库号为 1 的数据库,select是通过修改客户端的db指针,指针指向不同的数据库来实现数据库的切换操作的。
操作如下图:
redis服务结构体如下:
redis通过字典保存数据库中的所有键值对,我们称之为键空间(key space)。键空间和用户所见的数据库是直接对应的:
- 键空间的键也就是数据库的键,每个键都是一个字符串对象。
- 键空间的值也就是数据库的值,每个值可以是字符串对象、列表对象、哈希表对象、集合对象和有序集合对象中的任意一种redis对象。
2. redis数据库支持增删改查(curd)操作:
①.添加新键
添加一个新键值对到数据库,实际上就是将一个新键值对添加到键空间的字典里,其中键为字符串对象,值为任意一种类型的redis对象。
②.删除键
删除数据库中的一个键,实际上就是在键空间里面删除键所对应的键值对对象。
③.更新键
对一个数据库键进行更新,实际上就是对键空间里面键所对应的值对象进行更新,根据值对象类型的不同,更新的方法也不同。
④.查询键
对一个数据库键进行查询,实际上就是在键空间中取出键所对应的值对象,根据值对象类型的不同,取值的方法也不同。
除了以上操作外,redis还有很多针对数据库本身的命令,也是通过对键空间进行处理来完成的。
3. 读写键空间时的维护操作:
当使用redis命令对数据库进行读写时,服务器不仅会对键空间执行指定的读写操作,还会执行一些额外的操作,其中包括:
- 在读取一个键之后(读写操作都需要先对键进行读取),服务器会根据键是否存在来更新服务器的键空间命中(hit)次数或不命中(miss)次数,这两个值可以在
INFO stat
命令的keyspace_hits
和keyspace_misses
属性中查看。 - 在读取一个键之后,服务器会更新键的LRU或LFU时间,这个值用于计算键的闲置时间,可以使用
OBJECT idletime [key]
命令查看key的闲置时间。 - 如果服务器在读取一个键时发现该键已经过期,那么服务器会先删除这个过期的键,然后才执行余下操作。
- 如果有客户端使用
WATCH
命令监视了某个键,那么服务器在对被监视的键进行修改之后,会将这个键标记为脏(dirty),从而让事务程序注意到这个键已经被修改过。 - 服务器每次修改一个键后,都会对脏(dirty)键计数器的值增 1,这个计数器会触发服务器的持久化以及复制操作。
- 如果服务器开启了数据库通知功能,那么在对键进行修改之后,服务器会按配置发送相应的数据库通知。
4. 设置键的生存时间或过期时间:
Redis有四个命令可以设置键的过期时间,包括expire,pexpire,expireat,pexpireat,不过这四个命令最后都会转化成pexpireat命令来实现。
RedisDb中,使用一个字典expires来存储带有过期时间的键,称之为过期字典。
- 过期字典的键是一个指针,这个指针指向键空间中的某个键对象。
- 过期字典的值是一个long long类型的整数,这个整数保存了键所指向的数据库键的过期时间(精度为毫秒的unix时间戳)。
5. 过期键的删除策略:
如果一个键过期了,那么它什么时候被删除呢?redis有三种删除策略:
- 定时删除:在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行删除操作。
- 惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,执行删除操作,如果没过期,则返回该键。
- 定期删除:每隔一段时间,程序对数据库进行检查,删除过期的键。至于要删除多少过期键,以及要检查多少个数据库,则由算法实现。
这几种方式各有利弊。
- 定时删除对内存最为友好,当键过期时,会立即删除该键,释放内存。不过对CPU最不友好,因为每一个键都需要创建一个定时器,这种行为可能会占用相当一部分的CPU时间。此外,创建定时器需要用到Redis服务器中的时间时间,而当前时间时间的实现方式-无序链表查找一个事件的时间复杂度为O(N),不能高效地处理大量时间事件。
- 惰性删除策略对CPU是最友好的,但是对内存最不友好。如果一个键已经过期,这个键又保留在数据库中,那么内存就会一直占用不释放,由db.c/expireIfNeeded()函数实现惰性删除。
- 定期删除算是前两种策略的一种整合和折中,定期策略每隔一段时间执行一次删除过期键操作,并通过限制删除操作执行的时长和频率减少删除操作对CPU时间的影响。定期删除过期键可以有效地减少因为过期键带来的内存浪费。
下面我们通过代码来看看redis数据库的实现吧。
结构体与宏定义
1 | // redisServer |
函数功能总览
1 | int removeExpire(redisDb *db, robj *key); // 移除key的过期时间,当只有在db->dict中存在key时,才会移除 |