Redis 入门教程之一五大数据类型

系统级命令

获取符合规则的键名列表

KEYS 命令需要遍历 Redis 中的所有键,当键的数量较多时会严重影响性能,在生产环境中应该禁用该命令。

1
KEYS pattern

示例:

1
2
redis> KEYS *
1) "book"

pattern 支持 Glob 风格的通配符格式:

符号含义
? 匹配一个字符
* 匹配任意个(包括 0 个)字符
[ ] 匹配括号间的任一字符,可以使用 “-” 符号来表示一个范围,如 a [b-d] 可以匹配 “ab”、”ac”、”ad”
\x 匹配字符 x,用于转义字符,如需要匹配 “?” 就需要使用 \?

判断一个键是否存在

如果键存在则返回整数类型 1,否则返回 0。

1
EXISTS key

示例:

1
2
3
4
5
redis> EXISTS book
(integer) 1

redis> EXISTS noexists
(integer) 0

删除键

可以删除一个或多个键,返回值是删除的键的个数。

1
DEL key

示例:

1
2
3
4
5
redis> DEL book
(integer) 1

redis> DEL noexists
(integer) 0

DEL 命令的参数不支持通配符,但可以结合 Linux 的管道和 xargs 命令实现删除所有符合规则的键。比如要删除以 “user:” 开头的键,就可以执行以下命令:

1
$ redis-cli KEYS "user:*" | xargs redis-cli DEL

另外由于 DEL 命令支持多个键作为参数,所以还可以执行以下命令来达到同样的效果,但是性能更好:

1
$ redis-cli DEL `redis-cli KEYS "user:*"`

获得键值的数据类型

1
TYPE key

示例:

1
2
127.0.0.1:6379> type book
string

清空当前数据库

1
FLUSHDB [ASYNC]

清空所有数据库

1
FLUSHALL [ASYNC]

设置过期时间

1
EXPIRE key seconds

查看剩余存活时间

1
TTL key

数据类型

字符串类型(String)

字符串类型是 Redis 中最基础的数据类型,它能存储任何形式的字符串,包括二进制数据。例如存储用户的邮箱、JSON 化的对象甚至是一张图片。一个字符串类型键允许存储的最大容量是 512 MB。字符串类型是其他 4 种数据类型的基础,其他数据类型和字符串类型的差别从某种角度来说只是组织字符串的形式不同。例如,列表类型(List)是以列表的形式组织字符串,而集合类型是以集合的形式组织字符串。

赋值与取值(字符串)

1
2
3
SET key value

GET key

示例:

1
2
3
4
5
redis> SET book java
OK

redis> GET book
"java"

取值时,当键不存在时,会返回空结果。

递增数字

字符串类型可以存储任何形式的字符串,当存储的字符串是整数形式时,Redis 提供了一个实用的命令 INCR,其作用是让当前键值递增 1,并返回递增后的值。

1
INCR key

示例:

1
2
3
4
5
redis> INCR num
(integer) 1

redis> INCR num
(integer) 2

当要操作的键不存在时,会创建该键并设置值为 0,所以第一次递增后的结果为 1。

1
2
3
4
redis> SET foo bar

redis> INCR foo
(error) ERR value is not an integer or out of range

当键值不是整数时,Redis 会提示错误。

增加指定的整数

INCRBY 命令与 INCR 命令级别一样,只不过前者可以通过 increment 参数指定一次增加的数值。

1
INCRBY key increment

示例:

1
2
3
4
5
redis> INCRBY bar 2
(integer) 2

redis> INCRBY bar 3
(integer) 5

减少指定的整数

DECR 命令与 INCR 命令的用法相同,只不过是让键值递减 1。

1
DECR key

示例:

1
2
redis> DECR car
(integer) -1

DECRBY 与 INCRBY 命令的用法相同,可以通过 increment 参数指定一次递减的数值。

1
DECRBY key increment

示例:

1
2
3
4
5
redis> DECRBY cat 3
(integer) -3

redis> DECRBY cat 5
(integer) -8

增加指定的浮点数

INCRBYFLOAT 命令类似 INCRBY 命令,差别是 INCRBYFLOAT 可以递增一个双精度浮点数。

1
INCRBYFLOAT key pattern

示例:

1
2
3
4
5
redis> INCRBYFLOAT bar 2.7
"2.7"

redis> INCRBYFLOAT bar 5E+4
"50002.69999999999999929"

向尾部追加值

APPEND 的作用是向键值的末尾追加 value。如果键不存在,则会创建该键并设置值为 value,返回值是追加后字符串的总长度。

1
APPEND key value

示例:

1
2
3
4
5
6
7
8
redis> set key hello
OK

redis> APPEND key " world!"
(integer) 12

redis> GET key
"hello world!"

获取字符串长度

STRLEN 命令返回键值的长度,如果键不存在则返回 0。

1
STRLEN key

示例:

1
2
3
4
5
6
7
8
redis> STRLEN key
(integer) 12

redis> set key 你好
OK

redis> STRLEN key
(integer) 6

同时获取 / 设置多个键值

1
2
3
MGET key [key ...]

MSET key value [key value ...]

示例:

1
2
3
4
5
6
redis> MSET key1 v1 key2 v2 key3 v3

redis> MGET key1 key2 key3
1) "v1"
2) "v2"
3) "v3"

位操作

位操作命令:

1
2
3
4
5
6
7
8
9
GETBIT key offset

SETBIT key offset value

BITCOUNT key [start] [end]

BITOP operation destkey key [key ...]

BITPOS key value [start] [end]

一个字节由 8 个二进制位组成,Redis 提供了上述 4 个命令可以直接对二进制位进行操作。为了演示,首先将 foo 键赋值为 bar:

1
2
redis> SET foo bar
(integer) 0

bar 的 3 个字母 “b”、”a”、”r” 对应的 ASCII 码分别是 98、97 和 114,转换成二进制后分别为 1100010、1100001、1110010,所以 foo 键中的二进制位结构图如下:

redis-bit-1

GETBIT 命令可以获取一个字符串类型键指定位置的二进制位的值(0 或 1),索引从 0 开始。如果需要获取的二进制位的索引超出了键值的二进制位的实际长度,则默认值为 0。

1
2
3
4
5
6
7
8
redis> GETBIT foo 0
(integer) 0

redis> GETBIT foo 6
(integer) 1

redis> GETBIT foo 100000
(integer) 0

SETBIT 命令可以设置字符串类型键指定位置的二进制位的值,返回值是该位置的旧值。如果要将 foo 键值设置为 aar,那么可以通过位操作将 foo 键的二进制位的索引第 6 位设置为 0,第 7 位设置为 1。

1
2
3
4
5
6
7
8
redis> SETBIT foo 6 0
(integer) 1

redis> SETBIT foo 7 1
(integer) 0

redis> GET foo
"aar"

如果要设置的位置超过了键值的二进制的长度,SETBIT 命令会自动将中间的二进制位设置为 0;同理设置一个不存在的键的指定二进制位的值,会自动将前面的位赋值为 0。

1
2
3
4
5
redis> SETBIT nofoo 10 1
(integer) 0

redis> GETBIT nofoo 5
(integer) 0

BITCOUNT 命令可以获得字符串类型键中值是 1 的二进制位个数,例如:

1
2
3
4
5
redis> set foo bar
OK

redis> BITCOUNT foo
(integer) 10

BITCOUNT 可以通过参数限制统计的字节范围,例如只希望统计前两个字节(即 “fo”),字节范围从 0 开始:

1
2
3
4
5
redis> set foo bar
OK

redis> BITCOUNT foo 0 1
(integer) 6

BITOP 命令可以对多个字符串类型键进行位运算,并将结果存储在 destkey 参数指定的键中。BITOP 命令支持的运算操作有 AND、OR、XOR 和 NOT。例如可以对 bar 和 aar 进行 OR 运算:

1
2
3
4
5
6
7
8
9
10
11
redis> set foo1 bar
OK

redis> set foo2 aar
OK

redis> BITOP OR res foo1 foo2
(integer) 3

redis> GET res
"car"

具体的位运算过程如下图:

redis-bit-2

BITPOS 命令可以获得指定键的第一个位值是 0 或者 1 的位置。以 “bar” 这个键值为例,如果想获取键值中的第一个二进制位值为 1 的位置(从 0 开始算起),则可以执行:

1
2
3
4
5
redis> SET foo bar
OK

redis> BITPOS foo 1
(integer) 1

对比上面位运算的过程图,正如 BITPOS 命令的执行结果所示,”bar” 中第一个值为 1 的二进制位的位置为 1(同其他命令一样,BITPOS 命令的索引也是从 0 开始算起)。如果希望指定二进制位的查询范围,那么可以使用 BITPOS 命令的第二个和第三个参数,它们分别用来指定要查询的起始字节(从 0 开始算起)和结束字节。特别注意,这里第二个和第三个参数的单位不再是二进制位,而是字节。而返回的结果(位置)是从头开始算起的,与起始字节无关。如果不设置结束字节且键值的所有二进制位都是 1 的时候,则当要查询值为 0 的二进制位的位置时,返回结果会是键值长度的下一个字位的位置,这是因为 Redis 会认为键值长度之后的二进制位都是 0。举个例子,如果想查询第二个字节到第三个字节之间(即 “a” 和 “r”)出现的第一个值为 1 的二进制位的位置,则可以执行:

1
2
3
4
5
redis> SET foo bar
OK

redis> BITPOS foo 1 1 2
(integer) 9

位操作应用举例:

利用位操作命令可以非常紧凑地存储布尔值。比如假设网站的每个用户都有一个递增的整数 ID,如果使用一个字符串类型键配合位操作来记录每个用户的性别(用户 ID 作为索引,二进制位值 1 和 0 表示男性和女性),那么记录 100 万个用户的性别只需占用 100 KB 多的空间,而且由于 GETBIT 和 SETBIT 的时间复杂度都是 O (1),所以读取二进制位值性能很高。

SETBIT 命令使用注意事项:

使用 SETBIT 命令时,如果当前键的键值长度小于要设置的二进制位的位置时,Redis 会自动分配内存并将键值的当前长度到指定的位置之间的二进制位都设置为 0。此时如果要分配的内存过大,则很可能会造成服务器的暂时阻塞而无法处理同一时间的其他请求。还是举刚才存储网站用户性别的例子,如果这个网站的用户 ID 是从 100000001 开始的,那么会造成 10 多 MB 的浪费,正确的做法是给每个用户的 ID 减去 100000000 再进行存储。

散列类型(Hash)

Redis 是采用字典结构以键值对的形式存储数据的,而散列类型(Hash)的键值是一种字典结构,其存储了字段(Field)和字段值的映射,但字段值只能是字符串,不支持其他数据类型,即散列类型不能嵌套其他的数据类型。一个散列类型键可以包含之多 2^32 - 1 个字段。除了散列类型,Redis 的其他数据类型同样不支持数据类型嵌套。比如集合类型的每个元素都只能是字符串,不能是另一个集合或散列表等。散列类型适合存储对象:使用对象类别和 ID 构成键名,使用字段表示对象的属性,而字段值则存储属性值。例如要存储 ID 为 2 的汽车对象,可以分别使用名为 color、name 和 price 的 3 个字段来存储该辆汽车的颜色、名称和价格,具体存储结构图如下:

redis-hash-1

对比关系数据库中存储的汽车对象:

redis-hash-2

关系型数据库中,数据是以二维表的形式存储的,这就要求所有的记录都拥有相同的属性,无法单独为某条记录增减属性。如果想为 ID 为 1 的汽车增加生产日期的属性,就需要吧数据表更改为如上图所示的结构。增加一个属性后对于 ID 为 2 和 3 的两条记录而言 data 字段是冗余的。而 Redis 的散列类型则不存在这个问题,上图中描述了汽车对象的存储结构,但是这个结构只是人为的约定,Redis 并不强制要求每个键都依据此结构存储,完全可以自由地为任何键增减字段而不影响其他键。

赋值与取值(散列)

赋值与取值命令:

1
2
3
4
5
6
7
8
9
HSET key field value

HGET key field

HMSET key field value [field value ...]

HMGET key field [field ...]

HGETALL key

示例:

1
2
3
4
5
6
7
8
redis> HSET car price 500
(integer) 1

redis> HSET car name BMW
(integer) 1

redis> HGET car name
"BMW"

HSET 命令的方便之处在于不区分新增和更新操作,这意味着修改数据时不用事先判断字段是否存在来决定要执行的是新增操作(inert)还是更新操作(update)。当执行的是新增操作时(即之前字段不存在)HSET 命令会返回 1,当执行的是更新操作时(即之前字段已经存在)HSET 命令会返回 0。更进一步,当键本身不存在时,HSET 命令还会自动创建它。值得注意的是,Redis 中每个键都属于一个明确的数据类型,如通过 HSET 命令建立的键是散列类型,通过 SET 命令建立的键是字符串类型等等。使用一种数据类型的命令操作另一种数据类型的键会提示错误:”ERR Operation against a key holding the wrong kind of value”;但并不是所有命令都如此,比如 SET 命令可以覆盖已经存在的键而不管原来的键是什么类型

若需要同时设置、获取多个字段的值时,可以使用 HMSET、HMGET 命令:

1
2
3
4
5
6
redis> HMSET car2 price 500 name BMW
OK

redis> HMGET car2 price name
1) "500"
2) "BMW"

若想获取键中所有字段和字段值却不知道键中有哪些字段,则应该使用 HGETALL 命令:

1
2
3
4
5
redis> HGETALL car
1) "price"
2) "500"
3) "name"
4) "BMW"

判断字段是否存在

HEXISTS 命令用来判断一个字段是否存在,如果存在则返回 1,否则返回 0(如果键不存在也会返回 0)。

1
HEXISTS key field

示例:

1
2
3
4
5
6
7
8
redis> HEXISTS car model
(integer) 0

redis> HSET car model c200
(integer) 1

redis> HEXISTS car model
(integer) 1

当字段不存在时赋值

HSETNX 命令与 HSET 命令类似,区别在于如果字段已经存在,HSETNX 命令将不执行任何操作。HSETNX 命令中的 “NX” 表示 “If Not Exists”(如果不存在),同时 HSETNX 命令是原子操作,不用担心竞态条件,可以用作分布式锁的实现。

1
HSETNX key field value

示例:

1
2
3
4
5
6
7
8
redis> HSET car model c200
(integer) 1

redis> HSETNX car model c300
(integer) 0

redis> HGET car model
"c200"

增加字段

HINCRBY 与 INCR、INCRBY 命令类似,可以使字段值增加指定的整数。散列类型没有 HINCR 命令,但可以通过 HINCRBY key field 1 来实现。

1
HINCRBY key field increment

示例:

1
2
3
4
5
redis> HINCRBY person score 60
(integer) 60

redis> HGET person score
"60"

当 persion 键不存在时,HINCRBY 命令会自动建立该键,并设置字段 score 的默认值为 0,然后再执行自增操作,命令的返回结果是增值后的字段值。

删除字段

HDEL 命令可以删除一个或多个字段,返回值是被删除的字段个数。

1
HDEL key field [field ...]

示例:

1
2
3
4
5
redis> HDEL car name
(integer) 1

redis> HDEL car name
(integer) 0

只获取字段名或字段值

若仅仅需要获取键中所有字段的名称或者字段值,那么可以使用 HKEYS、HVALS 命令:

1
2
3
HKEYS key

HVALS key

示例:

1
2
3
4
5
6
7
redis> HKEYS car
1) "price"
2) "model"

redis> HVALS car
1) "500"
2) "c200"

列表类型(List)

列表类型(List)可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素,或者获得列表的某一个片段。列表类型内部是使用双向链表(double linked list)实现的,所以向列表两端添加元素的时间复杂度为 O (1),获取越接近两端的元素速度就越快。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的 10 条记录也是极快的(和从只有 20 个元素的列表中获取头部或尾部的 10 条记录的速度是一样的),不过使用链表的代价是通过索引访问元素比较慢,其元素遍历速度要远慢于数组。这种特性使列表类型能非常快速地完成关系数据库难以应付的场景:如社交网站的新鲜事,用户关心的只是最新的内容,使用列表类型存储,即使新鲜事的总数达到几千万个,获取其中最新的 100 条数据也是极快的。同样因为在两端插入记录的时间复杂度是 O (1),列表类型也适合用来记录日志,可以保证加入新日志的速度不会受到已有日志数量的影响。与散列类型键最多能容纳的字段数量相同,一个列表类型键最多能容纳 2^32 − 1 个元素。借助列表类型,Redis 还可以作为队列使用。

向列表两端添加元素

LPUSH 命令用来向列表左边添加元素,返回值表示添加元素后列表的总长度。

1
2
3
LPUSH key value [value …]

RPUSH key value [value …]

示例:

1
2
3
4
5
redis> LPUSH numbers 1
(integer) 1

redis> LPUSH numbers 2 3
(integer) 3

当通过 LPUSH 命令往列表中依次添加 “1”、”2“、”3“ 时,numbers 键中的数据如下图所示:

redis-list-1

使用 RPUSH 命令向列表右边添加元素的话,其用法和 LPUSH 命令一样:

1
2
redis> RPUSH numbers 0 -1
(integer) 3

此时 numbers 键中的数据如下图所示:

redis-list-2

从列表两端弹出元素

有进有出,LPOP 命令可以从列表左边弹出一个元素。LPOP 命令执行两步操作:第一步是将列表左边的元素从列表中移除,第二步是返回被移除的元素值。

1
2
3
LPOP key

RPOP key

示例:

1
2
3
4
5
redis> LPOP numbers
"3"

redis> RPOP numbers
"-1"

从 numbers 列表左边弹出一个元素(也就是 ”3“),同时列表右边也弹出一个元素(即”-1“),此时 numbers 键中的数据如下图所示:

redis-list-3

综合 LPUSH、RPUSH、LPOP、RPOP 命令,可以使用列表类型来模拟栈和队列的操作。如果想把列表当做栈,则搭配使用 LPUSH、LPOP 或 RPUSH、RPOP。如果想当成队列,则搭配使用 LPUSH、RPOP 或 RPUSH、LPOP。

获取列表中元素的个数

当键不存在时,LLEN 命令会返回 0。

1
LLEN key

示例:

1
2
redis> LLEN numbers
(integer) 3

LLEN 命令的功能类似 SQL 语句 SELECT COUNT(*) FROM table_name,但是 LLEN 的时间复杂度为 O (1),使用时 Redis 会直接读取现成的值,而不需要像部分关系数据库(如使用 InnoDB 存储引擎的 MySQL 表)那样需要遍历一遍数据表来统计条目数量。

获得列表片段

LRANGE 命令是列表类型最常用的命令之一,它能够获得列表中的某一片段。LRANGE 命令将返回索引从 start 到 stop 之间的所有元素(包含两端的元素:start、stop),Redis 的列表起始索引为 0。LRANGE 命令在取得列表片段时,不会像 LPOP 一样删除该片段。

1
LRANGE key start stop

示例:

1
2
3
4
redis> LRANGE numbers 0 2
1) "2"
2) "1"
3) "0"

LRANGE 命令也支持负索引,表示从右边开始计算序数,如 “-1” 表示最右边第一个元素,”-2” 表示最右边第二个元素,依次类推。显然,LRANGE numbers 0 -1 可以获取列表中的所有元素。

1
2
3
4
5
6
7
8
redis> LRANGE numbers 0 -1
1) "2"
2) "1"
3) "0"

redis> LRANGE numbers -2 -1
1) "1"
2) "0"

虽然 LRANGE numbers 0 -1 可以获取列表中的所有元素,但存在一些特殊情况如下:

  • 如果 start 的索引位置比 stop 的索引位置靠后,则会返回空列表
  • 如果 stop 大于实际的索引范围,则会返回到列表最右边的元素

删除列表中指定的值

1
LREM key count value

LREM 命令会删除列表中前 count 个值为 value 的元素,返回值是实际删除的元素个数。根据 count 值的不同,LREM 命令的执行方式会略有差异,具体如下:

  • 当 count > 0 时 LREM 命令会从列表左边开始删除前 count 个值为 value 的元素。
  • 当 count < 0 时 LREM 命令会从列表右边开始删除前 |count| 个值为 value 的元素。
  • 当 count = 0 是 LREM 命令会删除所有值为 value 的元素。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
redis> LRANGE numbers 0 -1
1) "2"
2) "1"
3) "0"
4) "2"

# 从右边开始删除第一个值为”2“的元素
redis> LREM numbers -1 2
(integer) 1

redis> LRANGE numbers 0 -1
1) "2"
2) "1"
3) "0"

获取与设置指定索引的元素值

如果要将列表类型当作数组来用,LINDEX 命令是必不可少的。LINDEX 命令用来返回指定索引的元素,索引从 0 开始。

1
2
3
LINDEX key index

LSET key index value

示例:

1
2
3
4
5
6
7
8
redis> LRANGE numbers 0 -1
1) "4"
2) "3"
3) "2"
4) "1"

redis> LINDEX numbers 1
"3"

LSET 是另一个通过索引操作列表的命令,它会将索引为 index 的元素赋值为 value。

1
2
3
4
5
6
7
8
9
10
11
redis> LRANGE numbers 0 -1
1) "4"
2) "3"
3) "2"
4) "1"

redis> LSET numbers 1 10
OK

redis> LINDEX numbers 1
"10"

只保留列表指定片段

LTRIM 命令可以删除指定索引范围之外的所有元素,其指定列表范围的方法和 LRANGE 命令相同,即保留索引从 start 到 stop 之间的所有元素(包含两端的元素:start、stop)。

1
LTRIM key start stop

示例:

1
2
3
4
5
6
7
8
9
10
11
12
redis> LRANGE numbers 0 -1
1) "4"
2) "3"
3) "2"
4) "1"

redis> LTRIM numbers 1 2
OK

redis> LRANGE numbers 0 -1
1) "3"
2) "2"

LTRIM 命令常和 LPUSH 命令一起使用来限制列表中元素的数量,比如记录日志时希望只保留最近的 100 条日志,则每次加入新元素时调用一次 LTRIM 命令即可:

1
2
3
LPUSH logs $newLog

LTRIM logs 0 99

向列表中插入元素

LINSERT 命令首先会在列表中从左到右查找值为 pivot 的元素,然后根据第二个参数是 BEFORE 还是 AFTER 来决定将 value 插入到该元素的前面还是后面,命令的返回值是插入后列表的元素个数。

1
LINSERT key BEFORE|AFTER pivot value

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
redis> LRANGE numbers 0 -1
1) "4"
2) "3"
3) "2"
4) "1"

redis> LINSERT numbers after 1 0
(integer) 5

redis> LRANGE numbers 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
5) "0"

将元素从一个列表转到另一个列表

RPOPLPUSH 是个很有意思的命令,从名字就可以看出它的功能:先执行 RPOP 命令再执行 LPUSH 命令。RPOPLPUSH 命令会先从 source 列表类型键的右边弹出一个元素,然后将其加入到 destination 列表类型键的左边,并返回这个元素的值,整个过程是原子的。

1
RPOPLPUSH source destination

当把列表类型作为队列使用时,RPOPLPUSH 命令可以很直观地在多个队列中传递数据。当 source 和 destination 相同时,RPOPLPUSH 命令会不断地将队尾的元素移到队首,借助这个特性可以实现一个网站监控系统:使用一个队列存储需要监控的网址,然后监控程序不断地使用 RPOPLPUSH 命令循环取出一个网址来测试可用性。这里使用 RPOPLPUSH 命令的好处在于在程序执行过程中仍然可以不断地向网址列表中加入新网址,而且整个系统容易扩展,允许多个客户端同时处理队列。

列表阻塞操作

BLPOP 命令是 LPOP 命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,Redis 连接将被 BLPOP 命令阻塞,直到等待超时或发现可弹出元素为止。超时参数 timeout 接受一个以秒为单位的数字作为值,设为 0 表示阻塞时间可以无限期延迟。当给定多个 Key 参数时,BLPOP 命令会按参数 Key 的先后顺序依次检查各个列表,弹出第一个非空列表的头元素,并和被弹出元素所属的列表的名字一起,组成结果返回给调用者。如果所有给定 Key 都不存在或包含空列表,那么 BLPOP 命令将阻塞连接直到等待超时,或者有另一个客户端对给定 Key 的任意一个执行 LPUSH 或 RPUSH 命令为止。BRPOP、BRPOPLPUSH 命令与 BLPOP 命令类似,这里不再累述。

1
2
3
4
5
BLPOP key [key ...] timeout

BRPOP key [key ...] timeout

BRPOPLPUSH source destination timeout

示例:假设现在有 job 、 command 和 request 三个列表,其中 job 不存在, command 和 request 都持有非空列表。

1
2
3
4
5
6
7
8
9
redis> LPUSH command "update system"
(integer) 1

redis> LPUSH request "visit page"
(integer) 1

redis> BLPOP job command request 0
1) "command"
2) "update system..."

上面的例子中,BLPOP 命令返回的元素来自 command 列表,因为它是按” 查找 job -> 查找 command -> 查找 request “这样的顺序,找到第一个非空列表 command。

集合类型(Set)

在集合中的每个元素都是不同的,且没有顺序。一个集合类型键可以存储至多 2^32 - 1 个字符串。集合类型与散列类型的对比如下:

比较内容集合类型列表类型
存储内容至多 2^32 - 1 个字符串至多 2^32 - 1 个字符串
有序性
唯一性

集合类型的常用操作是向集合中加入或删除元素、判断某个元素是否存在等。由于集合类型在 Redis 内部是使用值为空的散列表(Hash Table)实现的,所以这些操作的时间复杂度都是 O (1)。最方便的是多个集合类型键之间还可以进行并集、交集和差集运算。

增加、删除元素

SADD 命令用来向集合中增加一个或多个元素,如果键不存在则会自动创建。因为在一个集合中不能有相同的元素,所以如果要加入的元素已经存在于集合中就会忽略这个元素。该命令的返回值是成功加入的元素数量(忽略的元素不计算在内)。

1
SADD key member [member …]

示例:

1
2
3
4
5
redis> SADD letters a
(integer) 1

redis> SADD letters a b c
(integer) 2

SREM 命令用来从集合中删除一个或多个元素,并返回删除成功的个数。

1
SREM key member [member …]

示例:

1
2
redis> SREM letters b c
(integer) 2

获取集合中的所有元素

SMEMBERS 命令会返回集合中的所有元素。

1
SMEMBERS key

示例:

1
2
3
redis> SMEMBERS letters
1) "a"
2) "b"

判断元素是否在集合中

判断一个元素是否在集合中是一个时间复杂度为 O (1) 的操作,无论集合中有多少个元素,SISMEMBER 命令始终可以极快地返回结果。当值存在时 SISMEMBER 命令返回 1,当值不存在或键不存在时返回 0。

1
SISMEMBER key member

示例:

1
2
redis> SISMEMBER letters a
(integer) 1

集合间运算

集合间运算命令(差集、交集、并集):

1
2
3
4
5
SDIFF key [key ...]

SINTER key [key ...]

SUNION key [key ...]

SDIFF 命令用来对多个集合执行差集运算。集合 A 与集合 B 的差集表示为 A−B,代表所有属于 A 且不属于 B 的元素构成的集合。

1
2
3
4
5
6
7
8
9
10
11
redis> SADD setA 1 2 3
(integer) 3

redis> SADD setB 2 3 4
(integer) 3

redis> SDIFF setA setB
1) "1"

redis> SDIFF setB setA
1) "4"

SDIFF 命令自持同时传入多个键,下面的例子中,计算顺序是先计算 setA 与 setB 的差集,再计算结果与 setC 的差集。

1
2
3
4
5
6
7
8
9
10
11
redis> SADD setA 1 2 3
(integer) 3

redis> SADD setB 2 3 4
(integer) 3

redis> SADD setC 2 3
(integer) 2

redis> SDIFF setA setB setC
1) "1"

SINTER 命令用来对多个集合执行交集运算。集合 A 与集合 B 的交集表示为 A ∩ B,代表所有属于 A 且属于 B 的元素构成的集合。SINTER 同样支持同时传入多个键。

1
2
3
4
5
6
7
8
9
redis> SADD setA 1 2 3
(integer) 3

redis> SADD setB 2 3 4
(integer) 3

redis> SINTER setA setB
1) "2"
2) "3"

SUNION 命令用来对多个集合执行并集运算。集合 A 与集合 B 的并集表示为 A ∪ B,代表所有属于 A 或者属于 B 的元素构成的集合。SUNION 同样支持同时传入多个键。

1
2
3
4
5
6
7
8
9
10
11
redis> SADD setA 1 2 3
(integer) 3

redis> SADD setB 2 3 4
(integer) 3

redis> SUNION setA setB
1) "1"
2) "2"
3) "3"
4) "4"

获取集合中元素的个数

SCARD 命令用来获得集合中的元素个数。

1
SCARD key

示例:

1
2
3
4
5
6
7
8
redis> SMEMBERS setA
1) "b"
2) "d"
3) "a"
4) "c"

redis> SCARD setA
(integer) 4

进行集合运算并将结果存储

1
2
3
4
5
SDIFFSTORE destination key [key …]

SINTERSTORE destination key [key …]

SUNIONSTORE destination key [key …]
  • SDIFFSTORE 命令和 SDIFF 命令功能一样,唯一的区别就是前者不会直接返回运算结果,而是将结果存储在 destination 键中。
  • SDIFFSTORE 命令常用于需要进行多步集合运算的场景中,如需要先计算差集再将结果和其他键计算交集。
  • SINTERSTORE 、 SUNIONSTORE 命令与 SDIFFSTORE 类似,不再赘述。

随机获得集合中的元素

SRANDMEMBER 命令用来随机从集合中获取一个元素,还可以传递 count 参数来一次随机获得多个元素。根据 count 的正负不同,SRANDMEMBER 命令的具体表现也不同:

  • 当 count 为正数时,SRANDMEMBER 会随机从集合里获得 count 个不重复的元素。如果 count 的值大于集合中的元素个数,则 SRANDMEMBER 会返回集合中的全部元素。
  • 当 count 为负数时,SRANDMEMBER 会随机从集合里获得 |count| 个的元素,这些元素有可能相同。
  • SRANDMEMBER 命令返回的结果并不是非常随机的,根本原因是由集合类型的存储结构(Hash Table)决定的,点击查看详细解释
1
SRANDMEMBER key [count]

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
redis> SMEMBERS setA
1) "b"
2) "d"
3) "a"
4) "c"

redis> SRANDMEMBER setA
"b"

redis> SRANDMEMBER setA 1
1) "c"

redis> SRANDMEMBER setA -2
1) "c"
2) "c"

redis> SRANDMEMBER setA 5
1) "b"
2) "a"
3) "d"
4) "c"

从集合中弹出一个元素

由于集合类型的元素是无序的,所以 SPOP 命令会从集合中随机选择一个元素弹出。

1
SPOP key

示例:

1
2
3
4
5
6
7
8
redis> SMEMBERS setA
1) "b"
2) "d"
3) "a"
4) "c"

redis> SPOP setA
"c"

有序集合类型(Sorted Set)

在集合类型的基础上有序集合类型为集合中的每个元素都关联了一个分数,这使得不仅可以完成插入、删除和判断元素是否存在等集合类型支持的操作,还能够获得分数最高(或最低)的前 N 个元素、获得指定分数范围内的元素等与分数有关的操作。虽然集合中每个元素都是不同的,但是它们的分数却可以相同。

有序集合类型和列表类型的相同点:

  • 都是有序的
  • 都可以获得某一范围的元素

有序集合类型和列表类型的不同点:

  • 列表类型是通过双向链表实现的,获取靠近两端的数据速度极快,而当元素增多后,访问中间数据的速度会较慢,所以它更加适合实现如 “新鲜事” 或 “日志” 这样很少访问中间元素的应用
  • 有序集合类型是使用散列表(Hash Table)和跳跃表(Skip List)实现的,所以即使读取位于中间部分的数据速度也很快,时间复杂度是 O (log (N))
  • 列表中不能简单地调整某个元素的位置,但是有序集合可以(通过更改这个元素的分数)
  • 有序集合要比列表类型更耗费内存

增加元素

ZADD 命令用来向有序集合中加入一个元素和该元素的分数,如果该元素已经存在则会用新的分数替换原有的分数。ZADD 命令的返回值是新加入到集合中的元素个数(不包含之前已经存在的元素)。

1
ZADD key score member [score member …]

示例:

1
2
3
4
5
redis> ZADD scoreboard 89 Tom 67 Peter 100 Jim
(integer) 3

redis> ZADD scoreboard 80 Peter
(integer) 0

分数不仅可以是整数,还支持双精度浮点数,其中 +inf-inf 分别表示正无穷和负无穷。

1
2
3
4
5
6
7
8
9
10
11
redis> ZADD testboard 17E+307 a
(integer) 1

redis> ZADD testboard 1.5 b
(integer) 1

redis> ZADD testboard `+inf`c
(integer) 1

redis> ZADD testboard `-inf` d
(integer) 1

获取元素的分数

1
ZSCORE key member

示例:

1
2
redis> ZSCORE scoreboard Tom
"89"

获得排名在某个范围的元素列表

ZRANGE 命令会按照元素分数从小到大的顺序返回索引从 start 到 stop 之间的所有元素(包含两端的元素:start、stop)。ZRANGE 命令与 LRANGE 命令十分类似,如索引都是从 0 开始,负数代表从后向前查找(−1 表示最后一个元素)。

1
2
3
ZRANGE key start stop [WITHSCORES]

ZREVRANGE key start stop [WITHSCORES]

示例:

1
2
3
4
5
6
7
8
9
10
11
redis> ZADD scoreboard 89 Tom 67 Peter 100 Jim
(integer) 3

redis> ZRANGE scoreboard 0 2
1) "Peter"
2) "Tom"
3) "Jim"

redis> ZRANGE scoreboard 1 -1
1) "Tom"
2) "Jim"

如果需要同时获得元素的分数的话,可以在 ZRANGE 命令的尾部加上 WITHSCORES 参数:

1
2
3
4
5
6
7
redis> ZRANGE scoreboard 0 -1 WITHSCORES
1) "Peter"
2) "67"
3) "Tom"
4) "89"
5) "Jim"
6) "100"

ZRANGE 命令的时间复杂度为 O (log n+m),其中 n 为有序集合的基数,m 为返回的元素个数。如果两个元素的分数相同,Redis 会按照字典顺序(即 0 < 9 < A < Z < a < z 的顺序)来进行排列。如果元素的值是中文,那么排列顺序取决于中文的编码方式,例如使用 UTF-8 编码时排列顺序如下,可见此时 Redis 依然是按照字典顺序排列这些元素。

1
2
3
4
5
6
7
8
redis> ZADD chineseName 0 马华 0 刘墉 0 司马光 0 赵哲
(integer) 4

redis> ZRANGE chineseName 0 -1
1) "\xe5\x88\x98\xe5\xa2\x89"
2) "\xe5\x8f\xb8\xe9\xa9\xac\xe5\x85\x89"
3) "\xe8\xb5\xb5\xe5\x93\xb2"
4) "\xe9\xa9\xac\xe5\x8d\x8e"

ZREVRANGE 命令与 ZRANGE 命令的唯一不同在于 ZREVRANGE 命令是按照元素的分数从大到小的顺序输出结果。

1
2
3
4
5
6
7
redis> ZREVRANGE scoreboard 0 -1 WITHSCORES
1) "Jim"
2) "100"
3) "Tom"
4) "89"
5) "Peter"
6) "67"

获得指定分数范围的元素

ZRANGEBYSCORE 命令参数虽然多,但是都很好理解。该命令按照元素分数从小到大的顺序返回分数在 min 和 max 之间(包含 min 和 max )的元素。值得注意的是,ZREVRANGEBYSCORE 命令不仅是按照元素分数从大往小的顺序输出结果,而且它的 min 和 max 参数的位置与 ZRANGEBYSCORE 命令是相反的。

1
2
3
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]

示例:

1
2
3
4
5
6
redis> ZADD scoreboard 89 Tom 67 Peter 100 Jim
(integer) 3

redis> ZRANGEBYSCORE scoreboard 80 100
1) "Tom"
2) "Jim"

如果希望分数范围不包含端点值,可以在分数前加上 “(” 符号。例如,希望返回 80 分到 100 分的数据,可以含 80 分,但不包含 100 分,则命令如下:

1
2
redis> ZRANGEBYSCORE scoreboard 80 (100
1) "Tom"

min 和 max 还支持无穷值,这和 ZADD 命令一样,其中 +inf-inf 分别表示正无穷和负无穷。比如希望得到分数高于 80 分(不包含 80 分)的人的名单,但却不知道最高分是多少,这时候就可以用上 +inf:

1
2
3
redis> ZRANGEBYSCORE scoreboard (80 +inf
1) "Tom"
2) "Jim"

LIMIT offset count 与 SQL 中的用法基本相同,即在获得的元素列表的基础上向后偏移 offset 个元素,并且只获取 count 个元素。例如下面的例子中,表示获得分数高于 60 分的,并从第二个人开始的 3 个人:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
redis> ZRANGE scoreboard 0 -1 WITHSCORES
1) "Jerry"
2) "56"
3) "Peter"
4) "67"
5) "Yvonne"
6) "67"
7) "Tom"
8) "89"
9) "Wendy"
10) "92"
11) "Jim"
12) "100"

redis> ZRANGEBYSCORE scoreboard 60 +inf LIMIT 1 3
1) "Yvonne"
2) "Tom"
3) "Wendy"

如果想获取分数低于或等于 100 分的前 3 个人,可以借助 ZREVRANGEBYSCORE 命令实现。ZREVRANGEBYSCORE 命令不仅是按照元素分数从大往小的顺序输出结果,而且它的 min 和 max 参数的位置与 ZRANGEBYSCORE 命令是相反的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
redis> ZREVRANGE scoreboard 0 -1 WITHSCORES
1) "Jim"
2) "100"
3) "Wendy"
4) "92"
5) "Tom"
6) "89"
7) "Yvonne"
8) "67"
9) "Peter"
10) "67"
11) "Jerry"
12) "56"

redis> ZREVRANGEBYSCORE scoreboard 100 0 LIMIT 0 3
1) "Jim"
2) "Wendy"
3) "Tom"

增减某个元素的分数

ZINCRBY 命令可以增加一个元素的分数,返回值是更改后的分数。

1
ZINCRBY key increment member

示例:

1
2
3
4
5
6
7
8
redis> ZSCORE scoreboard Peter
"67"

redis> ZINCRBY scoreboard 6 Peter
"73"

redis> ZSCORE scoreboard Peter
"73"

increment 也可以是个负数表示减分,例如给 Peter 减 4 分:

1
2
redis> ZINCRBY scoreboard -4 Peter
"69"

如果指定的元素不存在,Redis 在执行命令前会先建立它并将它的分数值赋为 0,然后再执行增减操作。

获取集合中元素的数量

1
ZCARD key

示例:

1
2
redis> ZCARD scoreboard
(integer) 6

获得指定分数范围内的元素个数

ZCOUNT 命令的 min 和 max 参数的特性与 ZRANGEBYSCORE 命令中的一样。

1
ZCOUNT key min max

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
redis> ZRANGE scoreboard 0 -1 WITHSCORES
1) "Jerry"
2) "56"
3) "Yvonne"
4) "67"
5) "Peter"
6) "69"
7) "Tom"
8) "89"
9) "Wendy"
10) "92"
11) "Jim"
12) "100"

redis> ZCOUNT scoreboard 90 100
(integer) 2

redis> ZCOUNT scoreboard (80 +inf
(integer) 3

删除一个或多个元素

ZREM 命令的返回值是成功删除的元素数量(不包含本来就不存在的元素)。

1
ZREM key member [member …]

示例:

1
2
3
4
5
redis> ZREM scoreboard Wendy
(integer) 1

redis> ZCARD scoreboard
(integer) 5

按照排名范围删除元素

ZREMRANGEBYRANK 命令按照元素分数从小到大的顺序(即索引 0 表示最小的值)删除处在指定排名范围内的所有元素,并返回删除的元素数量。

1
ZREMRANGEBYRANK key start stop

示例:

1
2
3
4
5
6
7
8
9
10
redis> ZADD testRem 1 a 2 b 3 c 4 d 5 e 6 f
(integer) 6

redis> ZREMRANGEBYRANK testRem 0 2
(integer) 3

redis> ZRANGE testRem 0 -1
1) "d"
2) "e"
3) "f"

按照分数范围删除元素

ZREMRANGEBYSCORE 命令会删除指定分数范围内的所有元素,参数 min 和 max 的特性和 ZRANGEBYSCORE 命令中的一样,返回值是删除的元素数量。

1
ZREMRANGEBYSCORE key min max

示例:

1
2
3
4
5
6
7
8
9
10
11
12
redis> ZADD testRem 1 a 2 b 3 c 4 d 5 e 6 f
(integer) 6

redis> ZREMRANGEBYSCORE testRem (4 5
(integer) 1

redis> ZRANGE testRem 0 -1
1) "a"
2) "b"
3) "c"
4) "d"
5) "f"

获得元素的排名

1
2
3
ZRANK key member

ZREVRANK key member

ZRANK 命令会按照元素分数从小到大的顺序获得指定的元素的排名(从 0 开始,即分数最小的元素排名为 0)。

1
2
3
4
5
redis> ZADD testRem 1 a 2 b 3 c 4 d 5 e 6 f
(integer) 1

redis> ZRANK testRem b
(integer) 1

ZREVRANK 命令则与 ZRANK 命令相反,分数最大的元素排名为 0。

1
2
3
4
5
redis> ZADD testRem 1 a 2 b 3 c 4 d 5 e 6 f
(integer) 6

redis> ZREVRANK testRem f
(integer) 0

计算有序集合的交集

ZINTERSTORE 命令用来计算多个有序集合的交集并将结果存储在 destination 键中(同样以有序集合类型存储),返回值为 destination 键中的元素个数,若 destination 键已存在则会被覆盖。其中 destination 键中元素的分数是由 AGGREGATE 参数决定的。

1
ZINTERSTORE destination numkeys key [key …] [WEIGHTS weight [weight…]] [AGGREGATE SUM|MIN|MAX]

当 AGGREGATE 是 SUM 时(也就是默认值),destination 键中元素的分数是每个参与计算的集合中该元素分数的和。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
redis> ZADD sortedSets1 1 a 2 b
(integer) 2

redis> ZADD sortedSets2 10 a 20 b
(integer) 2

redis> ZINTERSTORE sortedSetsResult 2 sortedSets1 sortedSets2
(integer) 2

redis> ZRANGE sortedSetsResult 0 -1 WITHSCORES
1) "a"
2) "11"
3) "b"
4) "22"

当 AGGREGATE 是 MIN 时,destination 键中元素的分数是每个参与计算的集合中该元素分数的最小值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
redis> ZADD sortedSets1 1 a 2 b
(integer) 2

redis> ZADD sortedSets2 10 a 20 b
(integer) 2

redis> ZINTERSTORE sortedSetsResult 2 sortedSets1 sortedSets2 AGGREGATE MIN
(integer) 2

redis> ZRANGE sortedSetsResult 0 -1 WITHSCORES
1) "a"
2) "1"
3) "b"
4) "2"

当 AGGREGATE 是 MAX 时,destination 键中元素的分数是每个参与计算的集合中该元素分数的最大值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
redis> ZADD sortedSets1 1 a 2 b
(integer) 2

redis> ZADD sortedSets2 10 a 20 b
(integer) 2

redis> ZINTERSTORE sortedSetsResult 2 sortedSets1 sortedSets2 AGGREGATE MAX
(integer) 2

redis> ZRANGE sortedSetsResult 0 -1 WITHSCORES
1) "a"
2) "10"
3) "b"
4) "20"

计算有序集合的并集

ZUNIONSTORE 命令用于计算集合间的并集,与 ZINTERSTORE 命令的使用方法一样,这里不再累述。

1
ZUNIONSTORE destination numkeys key [key …] [WEIGHTS weight [weight…]] [AGGREGATE SUM|MIN|MAX]

数据类型使用总结

数据类型结构存储的值结构的读写能力博客系统中的应用
字符串类型可以是字符串、整数或者浮点数对整个字符串或字符串的其中一部分执行操作;对整数和浮点数执行自增或者自减操作(1) 博客文章访问量统计(2)生成自增 ID
散列类型包含键值对的无序散列表添加、获取、移除单个键值对;获取所有键值对(1)存储文章数据(2)存储文章缩略名
列表类型一个双向链表,链表上的每个节点都包含了一个字符串从链表的两端推入或者弹出元素;根据偏移量对链表进行修剪(Trim);读取单个或多个元素;根据值查找或者移除元素(1)存储文章 ID 列表(2)存储评论列表
集合类型包含字符串的无序收集器,并且被包含的每个字符串都不可重复添加、获取、移除单个元素;检查一个元素是否存在于集合中;计算交集、并集、差集;从集合里面随机获取元素(1)存储文章标签(2)通过标签搜索文章
有序集合类型字符串成员与浮点数分值之间的有序映射,元素的排列顺序由分值的大小决定添加、获取、删除单个元素;根据分值范围或者成员来获取元素(1)实现按点击量排序(2)更改文章发布时间和获得指定时间范围内的文章列表