Redis 中用于操作键的命令基本上可以分为两种类型。
其中一种命令可以对任何类型的键执行, 比如说 DEL 命令、 EXPIRE 命令、 RENAME 命令、 TYPE 命令、 OBJECT 命令, 等等。
举个例子, 以下代码就展示了使用 DEL 命令来删除三种不同类型的键:
~~~
# 字符串键
redis> SET msg "hello"
OK
# 列表键
redis> RPUSH numbers 1 2 3
(integer) 3
# 集合键
redis> SADD fruits apple banana cherry
(integer) 3
redis> DEL msg
(integer) 1
redis> DEL numbers
(integer) 1
redis> DEL fruits
(integer) 1
~~~
而另一种命令只能对特定类型的键执行, 比如说:
* SET 、 GET 、 APPEND 、 STRLEN 等命令只能对字符串键执行;
* HDEL 、 HSET 、 HGET 、 HLEN 等命令只能对哈希键执行;
* RPUSH 、 LPOP 、 LINSERT 、 LLEN 等命令只能对列表键执行;
* SADD 、 SPOP 、 SINTER 、 SCARD 等命令只能对集合键执行;
* ZADD 、 ZCARD 、 ZRANK 、 ZSCORE 等命令只能对有序集合键执行;
诸如此类。
举个例子, 我们可以用 SET 命令创建一个字符串键, 然后用 GET 命令和 APPEND 命令操作这个键, 但如果我们试图对这个字符串键执行只有列表键才能执行的 LLEN 命令, 那么 Redis 将向我们返回一个类型错误:
~~~
redis> SET msg "hello world"
OK
redis> GET msg
"hello world"
redis> APPEND msg " again!"
(integer) 18
redis> GET msg
"hello world again!"
redis> LLEN msg
(error) WRONGTYPE Operation against a key holding the wrong kind of value
~~~
## 类型检查的实现
从上面发生类型错误的代码示例可以看出, 为了确保只有指定类型的键可以执行某些特定的命令, 在执行一个类型特定的命令之前, Redis 会先检查输入键的类型是否正确, 然后再决定是否执行给定的命令。
类型特定命令所进行的类型检查是通过 `redisObject` 结构的 `type` 属性来实现的:
* 在执行一个类型特定命令之前, 服务器会先检查输入数据库键的值对象是否为执行命令所需的类型, 如果是的话, 服务器就对键执行指定的命令;
* 否则, 服务器将拒绝执行命令, 并向客户端返回一个类型错误。
举个例子, 对于 LLEN 命令来说:
* 在执行 LLEN 命令之前, 服务器会先检查输入数据库键的值对象是否为列表类型, 也即是, 检查值对象 `redisObject` 结构 `type` 属性的值是否为 `REDIS_LIST` , 如果是的话, 服务器就对键执行 LLEN 命令;
* 否则的话, 服务器就拒绝执行命令并向客户端返回一个类型错误;
图 8-18 展示了这一类型检查过程。
![](https://box.kancloud.cn/2015-09-13_55f520d77776d.png)
其他类型特定命令的类型检查过程也和这里展示的 LLEN 命令的类型检查过程类似。
## 多态命令的实现
Redis 除了会根据值对象的类型来判断键是否能够执行指定命令之外, 还会根据值对象的编码方式, 选择正确的命令实现代码来执行命令。
举个例子, 在前面介绍列表对象的编码时我们说过, 列表对象有 `ziplist` 和 `linkedlist` 两种编码可用, 其中前者使用压缩列表 API 来实现列表命令, 而后者则使用双端链表 API 来实现列表命令。
现在, 考虑这样一个情况, 如果我们对一个键执行 LLEN 命令, 那么服务器除了要确保执行命令的是列表键之外, 还需要根据键的值对象所使用的编码来选择正确的 LLEN 命令实现:
* 如果列表对象的编码为 `ziplist` , 那么说明列表对象的实现为压缩列表, 程序将使用 `ziplistLen` 函数来返回列表的长度;
* 如果列表对象的编码为 `linkedlist` , 那么说明列表对象的实现为双端链表, 程序将使用 `listLength` 函数来返回双端链表的长度;
借用面向对象方面的术语来说, 我们可以认为 LLEN 命令是多态([polymorphism](http://en.wikipedia.org/wiki/Polymorphism_(computer_science)))的: 只要执行 LLEN 命令的是列表键, 那么无论值对象使用的是 `ziplist` 编码还是 `linkedlist` 编码, 命令都可以正常执行。
图 8-19 展示了 LLEN 命令从类型检查到根据编码选择实现函数的整个执行过程, 其他类型特定命令的执行过程也是类似的。
![](https://box.kancloud.cn/2015-09-13_55f520da65c5b.png)
实际上, 我们可以将 DEL 、 EXPIRE 、 TYPE 等命令也称为多态命令, 因为无论输入的键是什么类型, 这些命令都可以正确地执行。
DEL 、 EXPIRE 等命令和 LLEN 等命令的区别在于, 前者是基于类型的多态 —— 一个命令可以同时用于处理多种不同类型的键, 而后者是基于编码的多态 —— 一个命令可以同时用于处理多种不同编码。
- 介绍
- 前言
- 致谢
- 简介
- 第一部分:数据结构与对象
- 简单动态字符串
- SDS 的定义
- SDS 与 C 字符串的区别
- SDS API
- 重点回顾
- 参考资料
- 链表
- 链表和链表节点的实现
- 链表和链表节点的 API
- 重点回顾
- 字典
- 字典的实现
- 哈希算法
- 解决键冲突
- rehash
- 渐进式 rehash
- 字典 API
- 重点回顾
- 跳跃表
- 跳跃表的实现
- 跳跃表 API
- 重点回顾
- 整数集合
- 整数集合的实现
- 升级
- 升级的好处
- 降级
- 整数集合 API
- 重点回顾
- 压缩列表
- 压缩列表的构成
- 压缩列表节点的构成
- 连锁更新
- 压缩列表 API
- 重点回顾
- 对象
- 对象的类型与编码
- 字符串对象
- 列表对象
- 哈希对象
- 集合对象
- 有序集合对象
- 类型检查与命令多态
- 内存回收
- 对象共享
- 对象的空转时长
- 重点回顾
- 第二部分:单机数据库的实现
- 数据库
- 数据库键空间
- 重点回顾
- RDB 持久化
- RDB 文件结构
- 重点回顾
- AOF 持久化
- AOF 持久化的实现
- 重点回顾
- 事件
- 文件事件
- 重点回顾
- 参考资料
- 客户端
- 客户端属性
- 重点回顾
- 服务器
- 命令请求的执行过程
- 重点回顾
- 第三部分:多机数据库的实现
- 复制
- 旧版复制功能的实现
- 重点回顾
- Sentinel
- 启动并初始化 Sentinel
- 重点回顾
- 参考资料
- 集群
- 节点
- 重点回顾
- 第四部分:独立功能的实现
- 发布与订阅
- 频道的订阅与退订
- 重点回顾
- 参考资料
- 事务
- 事务的实现
- 重点回顾
- Lua 脚本
- 创建并修改 Lua 环境
- 重点回顾
- 排序
- SORT <key> 命令的实现
- 重点回顾
- 二进制位数组
- GETBIT 命令的实现
- 重点回顾
- 慢查询日志
- 慢查询记录的保存
- 慢查询日志的阅览和删除
- 添加新日志
- 重点回顾
- 监视器
- 成为监视器
- 向监视器发送命令信息
- 重点回顾
- 源码、相关资源和勘误