Redis 概览

来源:
三产
最后修订:
2017年06月08日 15:45:28
 402
Redis 概览

Redis 键相关的通用命令:

1. 查看所有键

KEYS

自1.0.0起可用。

时间复杂度: O(N), N 为数据库中 key 的数量。

语法:KEYS pattern
说明:

查找所有符合给定模式 patternkey

KEYS * 匹配数据库中所有 key

KEYS h?llo 匹配 hellohallohxllo 等。

KEYS h*llo 匹配 hlloheeeeello 等。

KEYS h[ae]llo 匹配 hellohallo ,但不匹配 hillo

特殊符号用 隔开

KEYS 的速度非常快,但在一个大的数据库中使用它仍然可能造成性能问题,如果你需要从一个数据集中查找特定的 key ,你最好还是用 Redis 的集合结构 (set)来代替。

返回值:

符合给定模式的 key 列表。

示例:
coderknock> KEYS *
 1) "name:45"
 2) "like_blog_num"
 3) "user:3"
 4) "40Key"
 5) "test"
 6) "coderknockCounter"
 7) "user:45"
 8) "str"
 9) "strHash"
10) "39Key"
11) "testChinese"
12) "user:5"
13) "intKey"
14) "embstrKey"
15) "map"
16) "user:34"
coderknock> KEYS user:*
1) "user:3"
2) "user:45"
3) "user:5"
4) "user:34"
coderknock> KEYS [34][09]Key
1) "40Key"
2) "39Key"
coderknock> KEYS user:[3]?
1) "user:34"
coderknock> KEYS user:[35]
1) "user:3"
2) "user:5"
coderknock> KEYS user:[3]*
1) "user:3"
2) "user:34"
coderknock> KEYS *Key
1) "40Key"
2) "39Key"
3) "intKey"
4) "embstrKey"

2. 键总数

DBSIZE

自1.0.0起可用。

时间复杂度: O(1)。

语法:DBSIZE
说明:

返回当前数据库的 key 的数量。

返回值:

当前数据库的 key 的数量。

示例:
coderknock> DBSIZE
(integer) 16
coderknock> DBSIZE
(integer) 16
coderknock> DBSIZE
(integer) 16
# 添加一个 key
coderknock> SET addKey name
coderknock> DBSIZE
(integer) 17
注意!:

DBSIZE 命令在计算键总数时不会遍历所有键,而是直接获取 Redis 内置的键总数变量,所以 DBSIZE 命令的时间复杂度是 O(1)。而 KEYS 命令会遍历所有键,所以它的时间复杂度是 O(n),当 Redis 保存了大量键时,线上环境最好禁止使用 KEYS

3. 检查键是否存在

EXISTS

自1.0.0起可用。

时间复杂度: O(1)。

语法:EXISTS key [key …] Redis 3.0.3 及以上版本才可以输入多个 key
说明:

检查给定 key 是否存在。

返回值:

key 存在,返回存在的 key 的个数,否则返回 0

示例:
coderknock> EXISTS addKey
(integer) 1
coderknock> EXISTS a
(integer) 0
# 同时查询多个
coderknock> EXISTS addKey user:3 user:5 a embstrKey
(integer) 4

4. 删除键

DEL

自1.0.0起可用。

时间复杂度: O(N), N 为被删除的 key 的数量。

删除单个字符串类型的 key ,时间复杂度为O(1)。

删除单个列表、集合、有序集合或哈希表类型的 key ,时间复杂度为O(M), M 为以上数据结构内的元素数量。

语法:DEL key [key …]
说明:

删除给定的一个或多个 key

不存在的 key 会被忽略。

DEL 是一个通用命令,无论值是什么数据结构类型,DEL 命令都可以将其删除

返回值:

被删除 key 的数量。

示例:
coderknock> HGETALL user:5
1) "name"
2) "coverSanchan"
3) "website"
4) "https://www.coderknock.com"
5) "user"
6) "sanchan"
coderknock> DEL user:5
(integer) 1
coderknock> DEL user:5
(integer) 0

5. 键过期

EXPIRE

自1.0.0起可用。

时间复杂度: O(1)

语法:EXPIRE key seconds
说明:

为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。

在 Redis 中,带有生存时间的 key 被称为『易失的』(volatile)。

生存时间可以通过使用 DEL 命令来删除整个 key 来移除,或者被 SETGETSET 命令覆写(overwrite),这意味着,如果一个命令只是修改(alter)一个带生存时间的 key 的值而不是用一个新的 key 值来代替(replace)它的话,那么生存时间不会被改变。

比如说,对一个 key 执行 INCR 命令,对一个列表进行 LPUSH 命令,或者对一个哈希表执行 HSET 命令,这类操作都不会修改 key 本身的生存时间。

另一方面,如果使用 RENAME 对一个 key 进行改名,那么改名后的 key 的生存时间和改名前一样。

RENAME 命令的另一种可能是,尝试将一个带生存时间的 key 改名成另一个带生存时间的 another_key ,这时旧的 another_key (以及它的生存时间)会被删除,然后旧的 key 会改名为 another_key ,因此,新的 another_key 的生存时间也和原本的 key 一样。

使用 PERSIST 命令可以在不删除 key 的情况下,移除 key 的生存时间,让 key 重新成为一个『持久的』(persistent) key

更新生存时间

可以对一个已经带有生存时间的 key 执行 EXPIRE 命令,新指定的生存时间会取代旧的生存时间。

过期时间的精确度

在 Redis 2.4 版本中,过期时间的延迟在 1 秒钟之内 —— 也即是,就算 key 已经过期,但它还是可能在过期之后一秒钟之内被访问到,而在新的 Redis 2.6 版本中,延迟被降低到 1 毫秒之内。

Redis 2.1.3 之前的不同之处

在 Redis 2.1.3 之前的版本中,修改一个带有生存时间的 key 会导致整个 key 被删除,这一行为是受当时复制(replication)层的限制而作出的,现在这一限制已经被修复。

返回值:

设置成功返回 1

key 不存在或者不能为 key 设置生存时间时(比如在低于 2.1.3 版本的 Redis 中你尝试更新 key 的生存时间),返回 0

示例:
redis> SET cache_website "www.coderknock.com"
OK

redis> EXPIRE cache_website 30  # 设置过期时间为 30 秒
(integer) 1

redis> TTL cache_website    # 查看剩余生存时间
(integer) 23

redis> EXPIRE cache_website 30000   # 更新过期时间
(integer) 1

redis> TTL cache_website
(integer) 29996

# 在 SET 命令中也可以直接 设置过期时间【SET 命令在 《Redis 字符串》中会讲解】
coderknock> SET cache_website "www.coderknock.com" EX 30
OK
coderknock> TTL cache_website
(integer) 13
# 30 秒后该键被删除
coderknock> EXISTS cache_website
(integer) 0

6. TTL 命令会返回键的剩余过期时间,它有3种返回值:

TTL

自1.0.0起可用。

时间复杂度: O(1)

语法:TTL key
说明:

以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。

返回值:

key 不存在时,返回 -2

key 存在但没有设置剩余生存时间时,返回 -1

否则,以秒为单位,返回 key 的剩余生存时间。

在 Redis 2.8 以前,当 key 不存在,或者 key 没有设置剩余生存时间时,命令都返回 -1

示例:
#TTL 正常使用在上面一个命令中已经有示例,下面演示一个对不存在 key 使用的情况以及对没有设置过期时间可以使用的情况
# 不存在 key
coderknock> TTL cache_website
(integer) -2
# 存在但是未设置过期时间的 key
coderknock> TTL user:3
(integer) -1

7. 查询键对应的值的类型

TYPE

自1.0.0起可用。

时间复杂度: O(1)

语法:TTL key
说明:

返回 key 所储存的值的类型。

返回值:

none (key不存在)

string (字符串)

list (列表)

set (集合)

zset (有序集)

hash (哈希表)

示例:
# 哈希
coderknock> TYPE user:3
hash
# 不存在
coderknock> TYPE a
none
# 字符串
coderknock> TYPE embstrKey
string

8. 扫描

SCAN

自2.8.0起可用。

时间复杂度: 增量式迭代命令每次执行的复杂度为 O(1) , 对数据集进行一次完整迭代的复杂度为 O(N), 其中 N 为数据集中的元素数量。

语法:SCAN cursor [MATCH pattern][COUNT count]
说明:
SCAN

SCAN 命令及其相关的 SSCAN 命令、 HSCAN 命令和 ZSCAN 命令都用于增量地迭代(incrementally iterate)一集元素(a collection of elements):

  • SCAN 命令用于迭代当前数据库中的数据库键。
  • SSCAN 命令用于迭代集合键中的元素。
  • HSCAN 命令用于迭代哈希键中的键值对。
  • ZSCAN 命令用于迭代有序集合中的元素(包括元素成员和元素分值)。

以上列出的四个命令都支持增量式迭代, 它们每次执行都只会返回少量元素, 所以这些命令可以用于生产环境, 而不会出现像 KEYS 命令、 SMEMBERS 命令带来的问题 —— 当 KEYS 命令被用于处理一个大的数据库时, 又或者 SMEMBERS 命令被用于处理一个大的集合键时, 它们可能会阻塞服务器达数秒之久。

不过, 增量式迭代命令也不是没有缺点的: 举个例子, 使用 SMEMBERS 命令可以返回集合键当前包含的所有元素, 但是对于 SCAN 这类增量式迭代命令来说, 因为在对键进行增量式迭代的过程中, 键可能会被修改, 所以增量式迭代命令只能对被返回的元素提供有限的保证 (offer limited guarantees about the returned elements)。

因为 SCANSSCANHSCANZSCAN 四个命令的工作方式都非常相似, 所以这个文档会一并介绍这四个命令, 但是要记住:

  • SSCAN 命令、 HSCAN 命令和 ZSCAN 命令的第一个参数总是一个数据库键。
  • SCAN 命令则不需要在第一个参数提供任何数据库键 —— 因为它迭代的是当前数据库中的所有数据库键。
基本用法

SCAN 命令是一个基于游标的迭代器(cursor based iterator): SCAN 命令每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数, 以此来延续之前的迭代过程。

SCAN 命令的游标参数被设置为 0 时, 服务器将开始一次新的迭代, 而当服务器向用户返回值为 0 的游标时, 表示迭代已结束。

以下是一个 SCAN 命令的迭代过程示例:
coderknock> SCAN 0
1) "13" # 这个为下一个 SCAN 使用的 cursor
2)  1) "name:45"
    2) "coderknockCounter"
    3) "intKey"
    4) "addKey"
    5) "like_blog_num"
    6) "map"
    7) "str"
    8) "user:45"
    9) "40Key"
   10) "user:3"
coderknock> SCAN 13
1) "0"
2) 1) "embstrKey"
   2) "testChinese"
   3) "strHash"
   4) "39Key"
   5) "test"
   6) "user:34"

在上面这个例子中, 第一次迭代使用 0 作为游标, 表示开始一次新的迭代。

第二次迭代使用的是第一次迭代时返回的游标, 也即是命令回复第一个元素的值 —— 13

从上面的示例可以看到, SCAN 命令的回复是一个包含两个元素的数组, 第一个数组元素是用于进行下一次迭代的新游标, 而第二个数组元素则是一个数组, 这个数组中包含了所有被迭代的元素。

在第二次调用 SCAN 命令时, 命令返回了游标 0 , 这表示迭代已经结束, 整个数据集(collection)已经被完整遍历过了。

0 作为游标开始一次新的迭代, 一直调用 SCAN 命令, 直到命令返回游标 0 , 我们称这个过程为一次完整遍历(full iteration)。

SCAN 命令的保证(guarantees)

SCAN 命令, 以及其他增量式迭代命令, 在进行完整遍历的情况下可以为用户带来以下保证: 从完整遍历开始直到完整遍历结束期间, 一直存在于数据集内的所有元素都会被完整遍历返回; 这意味着, 如果有一个元素, 它从遍历开始直到遍历结束期间都存在于被遍历的数据集当中, 那么 SCAN 命令总会在某次迭代中将这个元素返回给用户。

然而因为增量式命令仅仅使用游标来记录迭代状态, 所以这些命令带有以下缺点:

  • 同一个元素可能会被返回多次。 处理重复元素的工作交由应用程序负责, 比如说, 可以考虑将迭代返回的元素仅仅用于可以安全地重复执行多次的操作上。
  • 如果一个元素是在迭代过程中被添加到数据集的, 又或者是在迭代过程中从数据集中被删除的, 那么这个元素可能会被返回, 也可能不会, 这是未定义的(undefined)。
SCAN 命令每次执行返回的元素数量

增量式迭代命令并不保证每次执行都返回某个给定数量的元素。

增量式命令甚至可能会返回零个元素, 但只要命令返回的游标不是 0 , 应用程序就不应该将迭代视作结束。

不过命令返回的元素数量总是符合一定规则的, 在实际中:

  • 对于一个大数据集来说, 增量式迭代命令每次最多可能会返回数十个元素;
  • 而对于一个足够小的数据集来说, 如果这个数据集的底层表示为编码数据结构(encoded data structure,适用于是小集合键、小哈希键和小有序集合键), 那么增量迭代命令将在一次调用中返回数据集中的所有元素。

最后, 用户可以通过增量式迭代命令提供的 COUNT 选项来指定每次迭代返回元素的最大值。

COUNT 选项

虽然增量式迭代命令不保证每次迭代所返回的元素数量, 但我们可以使用 COUNT 选项, 对命令的行为进行一定程度上的调整。

基本上, COUNT 选项的作用就是让用户告知迭代命令, 在每次迭代中应该从数据集里返回多少元素。

虽然 COUNT 选项只是对增量式迭代命令的一种提示(hint), 但是在大多数情况下, 这种提示都是有效的。

  • COUNT 参数的默认值为 10
  • 在迭代一个足够大的、由哈希表实现的数据库、集合键、哈希键或者有序集合键时, 如果用户没有使用 MATCH 选项, 那么命令返回的元素数量通常和 COUNT 选项指定的一样, 或者比 COUNT 选项指定的数量稍多一些。
  • 在迭代一个编码为整数集合(intset,一个只由整数值构成的小集合)、 或者编码为压缩列表(ziplist,由不同值构成的一个小哈希或者一个小有序集合)时, 增量式迭代命令通常会无视 COUNT 选项指定的值, 在第一次迭代就将数据集包含的所有元素都返回给用户。

并非每次迭代都要使用相同的 COUNT 值。

用户可以在每次迭代中按自己的需要随意改变 COUNT 值, 只要记得将上次迭代返回的游标用到下次迭代里面就可以了。

coderknock> SCAN 0 COUNT 20
1) "0"
2)  1) "name:45"
    2) "coderknockCounter"
    3) "intKey"
    4) "addKey"
    5) "like_blog_num"
    6) "map"
    7) "str"
    8) "user:45"
    9) "40Key"
   10) "user:3"
   11) "embstrKey"
   12) "testChinese"
   13) "strHash"
   14) "39Key"
   15) "test"
   16) "user:34"
coderknock> SCAN 0 COUNT 5
1) "10"
2) 1) "name:45"
   2) "coderknockCounter"
   3) "intKey"
   4) "addKey"
   5) "like_blog_num"
coderknock> SCAN 10
1) "15"
2)  1) "map"
    2) "str"
    3) "user:45"
    4) "40Key"
    5) "user:3"
    6) "embstrKey"
    7) "testChinese"
    8) "strHash"
    9) "39Key"
   10) "test"
coderknock> SCAN 15
1) "0"
2) 1) "user:34"
MATCH 选项

KEYS 命令一样, 增量式迭代命令也可以通过提供一个 glob 风格的模式参数, 让命令只返回和给定模式相匹配的元素, 这一点可以通过在执行增量式迭代命令时, 通过给定 MATCH <pattern> 参数来实现。

以下是一个使用 MATCH 选项进行迭代的示例:

coderknock> SADD test 1 2 3 4 11 12 13 14 a ab abc abcd
(integer) 12
coderknock> SSCAN test 0 MATCH ab*
1) "3"
2) 1) "abcd"
   2) "abc"
coderknock> SSCAN test 3 MATCH ab*
1) "0"
2) 1) "ab"
# 下面的语句说明要注意 调用 cursor 时 可选项一致,不然获取的数据没有意义
coderknock> SSCAN test 3
1) "0"
2) 1) "ab"
   2) "12"
coderknock>

需要注意的是, 对元素的模式匹配工作是在命令从数据集中取出元素之后, 向客户端返回元素之前的这段时间内进行的, 所以如果被迭代的数据集中只有少量元素和模式相匹配, 那么迭代命令或许会在多次执行中都不返回任何元素。

以下是这种情况的一个例子:

coderknock> keys *
 1) "name:45"
 2) "addKey"
 3) "40Key"
 4) "coderknockCounter"
 5) "testChinese"
 6) "strHash"
 7) "embstrKey"
 8) "map"
 9) "like_blog_num"
10) "user:3"
11) "test"
12) "str"
13) "user:45"
14) "39Key"
15) "intKey"
16) "user:34"
# 可选项的关键字不能输入错误,否则会报错
coderknock> SCAN 0 MACH em
(error) ERR syntax error
coderknock> SCAN 0 MATCH em
1) "13"
2) (empty list or set)
coderknock> SCAN 13 MATCH 5
1) "0"
2) (empty list or set)

如你所见, 以上的大部分迭代都不返回任何元素。

并发执行多个迭代

在同一时间, 可以有任意多个客户端对同一数据集进行迭代, 客户端每次执行迭代都需要传入一个游标, 并在迭代执行之后获得一个新的游标, 而这个游标就包含了迭代的所有状态, 因此, 服务器无须为迭代记录任何状态。

中途停止迭代

因为迭代的所有状态都保存在游标里面, 而服务器无须为迭代保存任何状态, 所以客户端可以在中途停止一个迭代, 而无须对服务器进行任何通知。

即使有任意数量的迭代在中途停止, 也不会产生任何问题。

使用错误的游标进行增量式迭代

使用间断的(broken)、负数、超出范围或者其他非正常的游标来执行增量式迭代并不会造成服务器崩溃, 但可能会让命令产生未定义的行为。

未定义行为指的是, 增量式命令对返回值所做的保证可能会不再为真。

只有两种游标是合法的:

  1. 在开始一个新的迭代时, 游标必须为 0
  2. 增量式迭代命令在执行之后返回的, 用于延续(continue)迭代过程的游标。
迭代终结的保证

增量式迭代命令所使用的算法只保证在数据集的大小有界(bounded)的情况下, 迭代才会停止, 换句话说, 如果被迭代数据集的大小不断地增长的话, 增量式迭代命令可能永远也无法完成一次完整迭代。

从直觉上可以看出, 当一个数据集不断地变大时, 想要访问这个数据集中的所有元素就需要做越来越多的工作, 能否结束一个迭代取决于用户执行迭代的速度是否比数据集增长的速度更快。

返回值:

SCAN 命令、 SSCAN 命令、 HSCAN 命令和 ZSCAN 命令都返回一个包含两个元素的 multi-bulk 回复: 回复的第一个元素是字符串表示的无符号 64 位整数(游标), 回复的第二个元素是另一个 multi-bulk 回复, 这个 multi-bulk 回复包含了本次被迭代的元素。

SCAN 命令返回的每个元素都是一个数据库键。

SSCAN 命令返回的每个元素都是一个集合成员。

HSCAN 命令返回的每个元素都是一个键值对,一个键值对由一个键和一个值组成。

ZSCAN 命令返回的每个元素都是一个有序集合元素,一个有序集合元素由一个成员(member)和一个分值(score)组成。

Redis 中的 7 种数据结构:

string(字符串)

字符串是一种最基本的Redis值类型。key 都是字符串类型,其他几种数据结构都是在字符串类型的基础上构建的。Redis字符串是二进制安全的,这意味着一个Redis字符串能包含任意类型的数据,例如: 一张JPEG格式的图片或者一个序列化的 Ruby 对象等。

一个字符串类型的值最多能存储 512M 字节的内容。

字符串类型的值实际上可以以下几种:

  1. 字符串(包括 JSON、XML 等)

  2. 数字(整数、浮点数)

  3. 二进制(图片、音频、视频)

你可以用Redis字符串做许多有趣的事,例如你可以:

  • 利用 INCR 命令簇(INCR DECR INCRBY)来把字符串当作原子计数器使用。
  • 使用 APPEND 命令在字符串后添加内容。
  • 将字符串作为 GETRANGESETRANGE 的随机访问向量。
  • 在小空间里编码大量数据,或者使用 GETBITSETBIT 创建一个Redis支持的 Bloom 过滤器。

本系列教程后续会有 字符串 专题会对常用命令以及场景进行介绍

list(列表)

Redis 列表是简单的字符串列表,按照插入顺序排序。 你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

LPUSH 命令插入一个新元素到列表头部,而 RPUSH 命令 插入一个新元素到列表的尾部。当 对一个空 key 执行其中某个命令时,将会创建一个新表。 类似的,如果一个操作要清空列表,那么 key 会从对应的 key 空间删除。这是个非常便利的语义, 因为如果使用一个不存在的 key 作为参数,所有的列表命令都会像在对一个空表操作一样。

一个列表最多可以包含2 ^ 32 - 1个元素(4294967295,每个表超过40亿个元素)。

从时间复杂度的角度来看,Redis 列表主要的特性就是支持时间常数的 插入和靠近头尾部元素的删除,即使是需要插入上百万的条目。 访问列表两端的元素是非常快的,但如果你试着访问一个非常大 的列表的中间元素仍然是十分慢的,因为那是一个时间复杂度为 O(N) 的操作。

你可以用 Redis 列表做许多有趣的事,例如你可以:

  • 在社交网络中建立一个时间线模型,使用 LPUSH 去添加新的元素到用户时间线中,使用 LRANGE 去检索一些最近插入的条目。
  • 你可以同时使用 LPUSHLTRIM 去创建一个永远不会超过指定元素数目的列表并同时记住最后的 N 个元素。
  • 列表可以用来当作消息传递的基元(primitive),例如,众所周知的用来创建后台任务的 Resque Ruby 库。
  • 你可以使用列表做更多事,这个数据类型支持许多命令,包括像 BLPOP 这样的阻塞命令。请查看所有可用的列表操作命令获取更多的信息。

本系列教程后续会有 列表 专题会对常用命令以及场景进行介绍

set(集合)

Redis 集合是一个无序的字符串合集。你可以以O(1) 的时间复杂度(无论集合中有多少元素时间复杂度都为常量)完成 添加,删除以及测试元素是否存在的操作。

Redis 集合有着不允许相同成员存在的优秀特性。向集合中多次添加同一元素,在集合中最终只会存在一个此元素。实际上这就意味着,在添加元素前,你并不需要事先进行检验此元素是否已经存在的操作。

一个 Redis 列表十分有趣的事是,它们支持一些服务端的命令从现有的集合出发去进行集合运算。 所以你可以在很短的时间内完成合并(union),求交(intersection), 找出不同元素的操作。

一个集合最多可以包含2 ^ 32 - 1个元素(4294967295,每个集合超过40亿个元素)。

你可以用 Redis 集合做很多有趣的事,例如你可以:

  • 用集合跟踪一个独特的事。想要知道所有访问某个博客文章的独立IP?只要每次都用 来处理一个页面访问。那么你可以肯定重复的IP是不会插入的。
  • Redis 集合能很好的表示关系。你可以创建一个 tagging 系统,然后用集合来代表单个 tag 。接下来你可以用SADD命令把所有拥有 tag 的对象的所有 ID 添加进集合,这样来表示这个特定的 tag 。如果你想要同时有3个不同 tag 的所有对象的所有 ID ,那么你需要使用 SINTER
  • 使用 SPOP 或者 SRANDMEMBER 命令随机地获取元素。

本系列教程后续会有 集合 专题会对常用命令以及场景进行介绍

zset(有序集合)

Redis 有序集合和 Redis 集合类似,是不包含 相同字符串的合集。它们的差别是,每个有序集合 的成员都关联着一个评分,这个评分用于把有序集 合中的成员按最低分到最高分排列。

使用有序集合,你可以非常快地(O(log(N)))完成添加,删除和更新元素的操作。 因为元素是在插入时就排好序的,所以很快地通过评分(score)或者 位次(position)获得一个范围的元素。 访问有序集合的中间元素同样也是非常快的,因此你可以使用有序集合作为一个没用重复成员的智能列表。 在这个列表中, 你可以轻易地访问任何你需要的东西: 有序的元素,快速的存在性测试,快速访问集合中间元素!

简而言之,使用有序集合你可以很好地完成 很多在其他数据库中难以实现的任务。

使用有序集合你可以:

  • 在一个巨型在线游戏中建立一个排行榜,每当有新的记录产生时,使用 ZADD 来更新它。你可以用 ZRANGE 轻松地获取排名靠前的用户, 你也可以提供一个用户名,然后用 ZRANK 取他在排行榜中的名次。 同时使用 ZRANKZRANGE 你可以获得与指定用户有相同分数的用户名单。 所有这些操作都非常迅速。
  • 有序集合通常用来索引存储在 Redis 中的数据。 例如:如果你有很多的 hash来表示用户,那么你可以使用一个有序集合,这个集合的年龄字段用来当作评分,用户 ID 当作值。用 ZRANGEBYSCORE 可以简单快速地检索到给定年龄段的所有用户。

有序集合或许是最高级的 Redis 数据类型,本系列教程后续会有 有序集合 专题会对常用命令以及场景进行介绍

hash(哈希)

Redis Hashes 是字符串字段和字符串值之间的映射,所以它们是完美的表示对象的数据类型。

一个拥有少量(100个左右)字段的 hash 需要 很少的空间来存储,所以你可以在一个小型的 Redis 实例中存储上百万的对象。

尽管 Hashes 主要用来表示对象,但它们也能够存储许多元素,所以你也可以用 Hashes 来完成许多其他的任务。

一个 hash 最多可以包含 2 ^ 32 - 1 个key-value键值对(超过40亿)。 本系列教程后续会有 哈希 专题会对常用命令以及场景进行介绍

Bitmaps 和 HyperLogLogs

Bit arrays (或者说 simply bitmaps): 通过特殊的命令,你可以将 String 值当作一系列 bits 处理:可以设置和清除单独的 bits,数出所有设为 1 的 bits 的数量,找到最前的被设为 1 或 0 的 bit,等等。

HyperLogLogs: 这是被用于估计一个 set 中元素数量的概率性的数据结构。

Redis 同样支持 Bitmaps 和 HyperLogLogs 数据类型,实际上是基于字符串的基本类型的数据类型,但有自己的语义。

内部编码

每种数据结构都有自己底层的内部编码实现,而且是多种实现,Redis 会在合适的场景选择合适的内部编码。这样设计的好处:

Redis 常用数据结构

  1. 可以改进内部编码,而对外的数据结构和命令不会受到影响,利于版本的兼容,性能的提高
  2. 多种内部编码实现可以在不同场景下发挥各自的优势

可以通过 object encoding 命令查询内部编码

Redis 的单线程架构

单线程架构

Redis 是使用了 单线程架构I/O多路复用模型 来实现高性能的内存数据库服务。

Redis 客户端交互

Redis 是单线程来处理命令的,所以命令到达 Redis 后并不会立即执行,而是进入队列之后逐个执行。对于差不多同时到达的命令执行的顺序是无法确定的。

单线程还保持高性能的秘密

  1. 纯内存访问。Redis 将所有的数据放在内存中,内存的响应时长大约为 100 纳秒,这是 Redis 可以达到每秒万级别访问的基础。

  2. 非阻塞 I/O。Redis 使用 epoll 作为 I/O 多路复用技术的实现;Redis 自身的事件处理模型将 epoll 中的连接、读写、关闭都转为时间,从而避免了在网络 I/O 上时间的浪费。

Redis 使用 IO 多路复用 和 自身实际模型示意

  1. 避免了线程切换和竞态产生的消耗。

单线程的优点

  1. 单线程可以简化数据结构和算法的实现
  2. 单线程避免了线程切换和竞态产生的消耗。对于服务端开发来说,锁和线程切换通常是性能杀手。

单线程的缺点

由于命令是逐个执行的,如果某个命令执行时间过长,则会造成其他命令的阻塞。所以 Redis 是面向快速执行场景的数据库。

Redis 使用注意事项

  1. 当进行多个 key 操作时,尽可能使用批量命令,如:MSET、MGET等

  2. Redis 中只有库的概念以及键的概念,所以在设计键名时要注意规范,推荐使用:

业务名:对象名:id

的格式来声明键,键名应该尽可能简短。