类型检查与命令多态

    其中一种命令可以对任何类型的键执行,比如说 DEL 命令、 EXPIRE 命令、 RENAME 命令、 TYPE 命令、 OBJECT 命令,等等。

    举个例子,以下代码就展示了使用 DEL 命令来删除三种不同类型的键:

    而另一种命令只能对特定类型的键执行,比如说:

    • 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 将向我们返回一个类型错误:

    1. redis> SET msg "hello world"
    2.  
    3. redis> GET msg
    4. "hello world"
    5.  
    6. redis> APPEND msg " again!"
    7. (integer) 18
    8.  
    9. redis> GET msg
    10.  
    11. redis> LLEN msg
    12. (error) WRONGTYPE Operation against a key holding the wrong kind of value

    类型特定命令所进行的类型检查是通过 结构的 type 属性来实现的:

    • 在执行一个类型特定命令之前,服务器会先检查输入数据库键的值对象是否为执行命令所需的类型,如果是的话,服务器就对键执行指定的命令;
    • 否则,服务器将拒绝执行命令,并向客户端返回一个类型错误。

    举个例子,对于 LLEN 命令来说:

    • 在执行 LLEN 命令之前,服务器会先检查输入数据库键的值对象是否为列表类型,也即是,检查值对象 redisObject 结构 type 属性的值是否为 ,如果是的话,服务器就对键执行 LLEN 命令;
    • 否则的话,服务器就拒绝执行命令并向客户端返回一个类型错误;

    图 8-18 展示了这一类型检查过程。

    其他类型特定命令的类型检查过程也和这里展示的 LLEN 命令的类型检查过程类似。

    多态命令的实现

    举个例子,在前面介绍列表对象的编码时我们说过,列表对象有 ziplistlinkedlist 两种编码可用,其中前者使用压缩列表 API 来实现列表命令,而后者则使用双端链表 API 来实现列表命令。

    现在,考虑这样一个情况,如果我们对一个键执行 LLEN 命令,那么服务器除了要确保执行命令的是列表键之外,还需要根据键的值对象所使用的编码来选择正确的 LLEN 命令实现:

    • 如果列表对象的编码为 ziplist ,那么说明列表对象的实现为压缩列表,程序将使用 函数来返回列表的长度;

    借用面向对象方面的术语来说,我们可以认为 LLEN 命令是多态())的:只要执行 LLEN 命令的是列表键,那么无论值对象使用的是 ziplist 编码还是 编码,命令都可以正常执行。

    图 8-19 展示了 LLEN 命令从类型检查到根据编码选择实现函数的整个执行过程,其他类型特定命令的执行过程也是类似的。

    digraph { label = "\n 图 8-19 LLEN 命令的执行过程"; // node [shape = box]; call_command [label = "客户端发送 LLEN <key> 命令"]; check_type [label = "服务器检查 \n 键 key 的值对象\n是否列表对象", shape = diamond]; //execute_command [label = "对键 key 执行 LLEN 命令"]; select_encoding [label = "对象的编码是 \n ziplist 还是 linkedlist ?", shape = diamond]; ziplist [label = "调用 ziplistLen 函数 \n 返回压缩列表的长度"]; linkedlist [label = "调用 listLength 函数 \n 返回双端链表的长度"]; type_error [label = "返回一个类型错误"]; // call_command -> check_type; //check_type -> execute_command [label = "是"]; check_type -> type_error [label = "否"]; //execute_command -> select_encoding; check_type -> select_encoding [label = "是"]; select_encoding -> ziplist [label = "ziplist \n 编码"]; select_encoding -> linkedlist [label = "linkedlist \n 编码"];}

    DEL 、 EXPIRE 等命令和 LLEN 等命令的区别在于,前者是基于类型的多态 —— 一个命令可以同时用于处理多种不同类型的键,而后者是基于编码的多态 —— 一个命令可以同时用于处理多种不同编码。