Redis 是一个键值对(key-value pair)数据库服务器, 服务器中的每个数据库都由一个 `redis.h/redisDb` 结构表示, 其中, `redisDb` 结构的`dict` 字典保存了数据库中的所有键值对, 我们将这个字典称为键空间(key space):
~~~
typedef struct redisDb {
// ...
// 数据库键空间,保存着数据库中的所有键值对
dict *dict;
// ...
} redisDb;
~~~
键空间和用户所见的数据库是直接对应的:
* 键空间的键也就是数据库的键, 每个键都是一个字符串对象。
* 键空间的值也就是数据库的值, 每个值可以是字符串对象、列表对象、哈希表对象、集合对象和有序集合对象在内的任意一种 Redis 对象。
举个例子, 如果我们在空白的数据库中执行以下命令:
~~~
redis> SET message "hello world"
OK
redis> RPUSH alphabet "a" "b" "c"
(integer) 3
redis> HSET book name "Redis in Action"
(integer) 1
redis> HSET book author "Josiah L. Carlson"
(integer) 1
redis> HSET book publisher "Manning"
(integer) 1
~~~
那么在这些命令执行之后, 数据库的键空间将会是图 IMAGE_DB_EXAMPLE 所展示的样子:
* `alphabet` 是一个列表键, 键的名字是一个包含字符串 `"alphabet"` 的字符串对象, 键的值则是一个包含三个元素的列表对象。
* `book` 是一个哈希表键, 键的名字是一个包含字符串 `"book"` 的字符串对象, 键的值则是一个包含三个键值对的哈希表对象。
* `message` 是一个字符串键, 键的名字是一个包含字符串 `"message"` 的字符串对象, 键的值则是一个包含字符串 `"hello world"` 的字符串对象。
![](https://box.kancloud.cn/2015-09-13_55f522687f6c7.png)
因为数据库的键空间是一个字典, 所以所有针对数据库的操作 —— 比如添加一个键值对到数据库, 或者从数据库中删除一个键值对, 又或者在数据库中获取某个键值对, 等等, 实际上都是通过对键空间字典进行操作来实现的, 以下几个小节将分别介绍数据库的添加、删除、更新、取值等操作的实现原理。
## 添加新键
添加一个新键值对到数据库, 实际上就是将一个新键值对添加到键空间字典里面, 其中键为字符串对象, 而值则为任意一种类型的 Redis 对象。
举个例子, 如果键空间当前的状态如图 IMAGE_DB_EXAMPLE 所示, 那么在执行以下命令之后:
~~~
redis> SET date "2013.12.1"
OK
~~~
键空间将添加一个新的键值对, 这个新键值对的键是一个包含字符串 `"date"` 的字符串对象, 而键值对的值则是一个包含字符串 `"2013.12.1"`的字符串对象, 如图 IMAGE_DB_AFTER_ADD_NEW_KEY 所示。
![](https://box.kancloud.cn/2015-09-13_55f5227cb91e3.png)
## 删除键
删除数据库中的一个键, 实际上就是在键空间里面删除键所对应的键值对对象。
举个例子, 如果键空间当前的状态如图 IMAGE_DB_EXAMPLE 所示, 那么在执行以下命令之后:
~~~
redis> DEL book
(integer) 1
~~~
键 `book` 以及它的值将从键空间中被删除, 如图 IMAGE_DB_AFTER_DEL 所示。
![](https://box.kancloud.cn/2015-09-13_55f5227f18ab6.png)
## 更新键
对一个数据库键进行更新, 实际上就是对键空间里面键所对应的值对象进行更新, 根据值对象的类型不同, 更新的具体方法也会有所不同。
举个例子, 如果键空间当前的状态如图 IMAGE_DB_EXAMPLE 所示, 那么在执行以下命令之后:
~~~
redis> SET message "blah blah"
OK
~~~
键 `message` 的值对象将从之前包含 `"hello world"` 字符串更新为包含 `"blah blah"` 字符串, 如图 IMAGE_DB_UPDATE_CAUSE_SET 所示。
![](https://box.kancloud.cn/2015-09-13_55f52286b5ffd.png)
再举个例子, 如果我们继续执行以下命令:
~~~
redis> HSET book page 320
(integer) 1
~~~
那么键空间中 `book` 键的值对象(一个哈希对象)将被更新, 新的键值对 `page` 和 `320` 会被添加到值对象里面, 如图 IMAGE_UPDATE_BY_HSET 所示。
![](https://box.kancloud.cn/2015-09-13_55f52289259bf.png)
## 对键取值
对一个数据库键进行取值, 实际上就是在键空间中取出键所对应的值对象, 根据值对象的类型不同, 具体的取值方法也会有所不同。
举个例子, 如果键空间当前的状态如图 IMAGE_DB_EXAMPLE 所示, 那么当执行以下命令时:
~~~
redis> GET message
"hello world"
~~~
GET 命令将首先在键空间中查找键 `message` , 找到键之后接着取得该键所对应的字符串对象值, 之后再返回值对象所包含的字符串 `"helloworld"` , 取值过程如图 IMAGE_FETCH_VALUE_VIA_GET 所示。
![](https://box.kancloud.cn/2015-09-13_55f5228ad7f7c.png)
再举一个例子, 当执行以下命令时:
~~~
redis> LRANGE alphabet 0 -1
1) "a"
2) "b"
3) "c"
~~~
LRANGE 命令将首先在键空间中查找键 `alphabet` , 找到键之后接着取得该键所对应的列表对象值, 之后再返回列表对象中包含的三个字符串对象的值, 取值过程如图 IMAGE_FETCH_VALUE_VIA_LRANGE 所示。
![](https://box.kancloud.cn/2015-09-13_55f5228e1597e.png)
## 其他键空间操作
除了上面列出的添加、删除、更新、取值操作之外, 还有很多针对数据库本身的 Redis 命令, 也是通过对键空间进行处理来完成的。
比如说, 用于清空整个数据库的 FLUSHDB 命令, 就是通过删除键空间中的所有键值对来实现的。
又比如说, 用于随机返回数据库中某个键的 RANDOMKEY 命令, 就是通过在键空间中随机返回一个键来实现的。
另外, 用于返回数据库键数量的 DBSIZE 命令, 就是通过返回键空间中包含键值对的数量来实现的。
类似的命令还有 EXISTS 、 RENAME 、 KEYS , 等等, 这些命令都是通过对键空间进行操作来实现的。
## 读写键空间时的维护操作
当使用 Redis 命令对数据库进行读写时, 服务器不仅会对键空间执行指定的读写操作, 还会执行一些额外的维护操作, 其中包括:
* 在读取一个键之后(读操作和写操作都要对键进行读取), 服务器会根据键是否存在, 以此来更新服务器的键空间命中(hit)次数或键空间不命中(miss)次数, 这两个值可以在 INFO stats 命令的 `keyspace_hits` 属性和 `keyspace_misses` 属性中查看。
* 在读取一个键之后, 服务器会更新键的 LRU (最后一次使用)时间, 这个值可以用于计算键的闲置时间, 使用命令 OBJECT idletime 命令可以查看键 `key` 的闲置时间。
* 如果服务器在读取一个键时, 发现该键已经过期, 那么服务器会先删除这个过期键, 然后才执行余下的其他操作, 本章稍后对过期键的讨论会详细说明这一点。
* 如果有客户端使用 WATCH 命令监视了某个键, 那么服务器在对被监视的键进行修改之后, 会将这个键标记为脏(dirty), 从而让事务程序注意到这个键已经被修改过, 《事务》一章会详细说明这一点。
* 服务器每次修改一个键之后, 都会对脏(dirty)键计数器的值增一, 这个计数器会触发服务器的持久化以及复制操作执行, 《RDB 持久化》、《AOF 持久化》和《复制》这三章都会说到这一点。
* 如果服务器开启了数据库通知功能, 那么在对键进行修改之后, 服务器将按配置发送相应的数据库通知, 本章稍后讨论数据库通知功能的实现时会详细说明这一点。
- 介绍
- 前言
- 致谢
- 简介
- 第一部分:数据结构与对象
- 简单动态字符串
- SDS 的定义
- SDS 与 C 字符串的区别
- SDS API
- 重点回顾
- 参考资料
- 链表
- 链表和链表节点的实现
- 链表和链表节点的 API
- 重点回顾
- 字典
- 字典的实现
- 哈希算法
- 解决键冲突
- rehash
- 渐进式 rehash
- 字典 API
- 重点回顾
- 跳跃表
- 跳跃表的实现
- 跳跃表 API
- 重点回顾
- 整数集合
- 整数集合的实现
- 升级
- 升级的好处
- 降级
- 整数集合 API
- 重点回顾
- 压缩列表
- 压缩列表的构成
- 压缩列表节点的构成
- 连锁更新
- 压缩列表 API
- 重点回顾
- 对象
- 对象的类型与编码
- 字符串对象
- 列表对象
- 哈希对象
- 集合对象
- 有序集合对象
- 类型检查与命令多态
- 内存回收
- 对象共享
- 对象的空转时长
- 重点回顾
- 第二部分:单机数据库的实现
- 数据库
- 数据库键空间
- 重点回顾
- RDB 持久化
- RDB 文件结构
- 重点回顾
- AOF 持久化
- AOF 持久化的实现
- 重点回顾
- 事件
- 文件事件
- 重点回顾
- 参考资料
- 客户端
- 客户端属性
- 重点回顾
- 服务器
- 命令请求的执行过程
- 重点回顾
- 第三部分:多机数据库的实现
- 复制
- 旧版复制功能的实现
- 重点回顾
- Sentinel
- 启动并初始化 Sentinel
- 重点回顾
- 参考资料
- 集群
- 节点
- 重点回顾
- 第四部分:独立功能的实现
- 发布与订阅
- 频道的订阅与退订
- 重点回顾
- 参考资料
- 事务
- 事务的实现
- 重点回顾
- Lua 脚本
- 创建并修改 Lua 环境
- 重点回顾
- 排序
- SORT <key> 命令的实现
- 重点回顾
- 二进制位数组
- GETBIT 命令的实现
- 重点回顾
- 慢查询日志
- 慢查询记录的保存
- 慢查询日志的阅览和删除
- 添加新日志
- 重点回顾
- 监视器
- 成为监视器
- 向监视器发送命令信息
- 重点回顾
- 源码、相关资源和勘误