前言
我们来详细了解下Redis,及其一些命令的具体使用方法,并学会如何使用 Redis 的事务、持久化、复制、Sentinel、集群等功能。
主要命令
我们知道Redis支持五种数据类型:string(字符串),hash(哈希表),list(列表),set(集合)及zset(sorted set:有序集合)。先来看下它们的一些操作命令。
字符串
SET
SET key value [EX seconds] [PX milliseconds] [NX|XX]
可用版本: >= 1.0.0
时间复杂度: O(1)
将字符串值 value
关联到 key
。
如果 key
已经持有其他值, SET 就覆写旧值, 无视类型。
当 SET 命令对一个带有生存时间(TTL)的键进行设置之后, 该键原有的 TTL 将被清除。
可选参数
从 Redis 2.6.12 版本开始, SET 命令的行为可以通过一系列参数来修改:
- EX seconds : 将键的过期时间设置为 seconds 秒。 执行
SET key value EX seconds
的效果等同于执行SETEX key seconds value
。 - PX milliseconds : 将键的过期时间设置为 milliseconds 毫秒。 执行
SET key value PX milliseconds
的效果等同于执行PSETEX key milliseconds value
。 - NX : 只在键不存在时, 才对键进行设置操作。 执行
SET key value NX
的效果等同于执行SETNX key value
。 - XX : 只在键已经存在时, 才对键进行设置操作。
Note
因为 SET 命令可以通过参数来实现 SETNX 、 SETEX 以及 PSETEX 命令的效果, 所以 Redis 将来的版本可能会移除并废弃 SETNX 、 SETEX 和 PSETEX 这三个命令。
返回值
在 Redis 2.6.12 版本以前, SET 命令总是返回 OK 。
从 Redis 2.6.12 版本开始, SET 命令只在设置操作成功完成时才返回 OK ; 如果命令使用了 NX 或者 XX 选项, 但是因为条件没达到而造成设置操作未执行, 那么命令将返回空批量回复(NULL Bulk Reply)。
代码示例
对不存在的键进行设置:
1 | redis> SET key "value" |
对已存在的键进行设置:
1 | redis> SET key "new-value" |
使用 EX 选项:
1 | redis> SET key-with-expire-time "hello" EX 10086 |
使用 PX 选项:
1 | redis> SET key-with-pexpire-time "moto" PX 123321 |
使用 NX 选项:
1 | redis> SET not-exists-key "value" NX |
使用 XX 选项:
1 | redis> EXISTS exists-key |
SETNX
SETNX key value
可用版本: >= 1.0.0
时间复杂度: O(1)
只在键 key
不存在的情况下, 将键 key
的值设置为 value
。
若键 key
已经存在, 则 SETNX 命令不做任何动作。
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
返回值
命令在设置成功时返回 1 , 设置失败时返回 0 。
代码示例
1 | redis> EXISTS job # job 不存在 |
SETEX
SETEX key seconds value
可用版本: >= 2.0.0
时间复杂度: O(1)
将键 key
的值设置为 value
, 并将键 key
的生存时间设置为 seconds 秒钟。
如果键 key
已经存在, 那么 SETEX 命令将覆盖已有的值。
SETEX 命令的效果和以下两个命令的效果类似:
SET key value
EXPIRE key seconds
# 设置生存时间
SETEX 和这两个命令的不同之处在于 SETEX 是一个原子(atomic)操作, 它可以在同一时间内完成设置值和设置过期时间这两个操作, 因此 SETEX 命令在储存缓存的时候非常实用。
返回值
命令在设置成功时返回 OK 。
当 seconds 参数不合法时, 命令将返回一个错误。
代码示例
在键 key
不存在的情况下执行 SETEX :
1 | redis> SETEX cache_user_id 60 10086 |
键 key
已经存在, 使用 SETEX 覆盖旧值:
1 | redis> SET cd "timeless" |
PSETEX
PSETEX key milliseconds value
可用版本: >= 2.6.0
时间复杂度: O(1)
这个命令和 SETEX 命令相似, 但它以毫秒为单位设置 key
的生存时间, 而不是像 SETEX 命令那样以秒为单位进行设置。
返回值
命令在设置成功时返回 OK 。
代码示例
1 | redis> PSETEX mykey 1000 "Hello" |
GET
GET key
可用版本: >= 1.0.0
时间复杂度: O(1)
返回与键 key
相关联的字符串值。
返回值
如果键 key
不存在, 那么返回特殊值 nil
; 否则, 返回键 key
的值。
如果键 key
的值并非字符串类型, 那么返回一个错误, 因为 GET 命令只能用于字符串值。
代码示例
对不存在的键 key
或是字符串类型的键 key
执行 GET 命令:
1 | redis> GET db |
对不是字符串类型的键 key
执行 GET 命令:
1 | redis> DEL db |
GETSET
GETSET key value
可用版本: >= 1.0.0
时间复杂度: O(1)
将键 key
的值设为 value
, 并返回键 key
在被设置之前的旧值。
返回值
返回给定键 key
的旧值。
如果键 key
没有旧值, 也即是说, 键 key
在被设置之前并不存在, 那么命令返回 nil
。
当键 key
存在但不是字符串类型时, 命令返回一个错误。
代码示例
1 | redis> GETSET db mongodb # 没有旧值,返回 nil |
STRLEN
STRLEN key
可用版本: >= 2.2.0
复杂度: O(1)
返回键 key
储存的字符串值的长度。
返回值
STRLEN 命令返回字符串值的长度。
当键 key
不存在时, 命令返回 0 。
当 key
储存的不是字符串值时, 返回一个错误。
代码示例
获取字符串值的长度:
1 | redis> SET mykey "Hello world" |
不存在的键的长度为 0 :
1 | redis> STRLEN nonexisting |
APPEND
APPEND key value
可用版本: >= 2.0.0
时间复杂度: 平摊O(1)
如果键 key
已经存在并且它的值是一个字符串, APPEND 命令将把 value
追加到键 key
现有值的末尾。
如果 key
不存在, APPEND 就简单地将键 key
的值设为 value
, 就像执行 SET key value
一样。
返回值
追加 value
之后, 键 key
的值的长度。
示例代码
对不存在的 key
执行 APPEND :
1 | redis> EXISTS myphone # 确保 myphone 不存在 |
对已存在的字符串进行 APPEND :
1 | redis> APPEND myphone " - 1110" # 长度从 5 个字符增加到 12 个字符 |
SETRANGE
SETRANGE key offset value
可用版本: >= 2.2.0
时间复杂度:对于长度较短的字符串,命令的平摊复杂度O(1);对于长度较大的字符串,命令的复杂度为 O(M) ,其中 M 为 value 的长度。
从偏移量 offset
开始, 用 value
参数覆写(overwrite)键 key
储存的字符串值。
不存在的键 key
当作空白字符串处理。
SETRANGE 命令会确保字符串足够长以便将 value
设置到指定的偏移量上, 如果键 key
原来储存的字符串长度比偏移量小(比如字符串只有 5 个字符长,但你设置的 offset
是 10 ), 那么原字符和偏移量之间的空白将用零字节(zerobytes, “\x00” )进行填充。
因为 Redis 字符串的大小被限制在 512 兆(megabytes)以内, 所以用户能够使用的最大偏移量为 2^29-1(536870911) , 如果你需要使用比这更大的空间, 请使用多个 key
。
Warning
当生成一个很长的字符串时, Redis 需要分配内存空间, 该操作有时候可能会造成服务器阻塞(block)。 在2010年出产的Macbook Pro上, 设置偏移量为 536870911(512MB 内存分配)将耗费约 300 毫秒, 设置偏移量为 134217728(128MB 内存分配)将耗费约 80 毫秒, 设置偏移量 33554432(32MB 内存分配)将耗费约 30 毫秒, 设置偏移量为 8388608(8MB 内存分配)将耗费约 8 毫秒。
返回值
SETRANGE 命令会返回被修改之后, 字符串值的长度。
代码示例
对非空字符串执行 SETRANGE 命令:
1 | redis> SET greeting "hello world" |
对空字符串/不存在的键执行 SETRANGE 命令:
1 | redis> EXISTS empty_string |
GETRANGE
GETRANGE key start end
可用版本: >= 2.4.0
时间复杂度: O(N),其中 N 为被返回的字符串的长度。
返回键 key
储存的字符串值的指定部分, 字符串的截取范围由 start
和 end
两个偏移量决定 (包括 start
和 end
在内)。
负数偏移量表示从字符串的末尾开始计数, -1 表示最后一个字符, -2 表示倒数第二个字符, 以此类推。
GETRANGE 通过保证子字符串的值域(range)不超过实际字符串的值域来处理超出范围的值域请求。
Note
GETRANGE 命令在 Redis 2.0 之前的版本里面被称为 SUBSTR 命令。
返回值
GETRANGE 命令会返回字符串值的指定部分。
代码示例
1 | redis> SET greeting "hello, my friend" |
INCRBY
INCRBY key increment
可用版本: >= 1.0.0
时间复杂度: O(1)
为键 key
储存的数字值加上增量 increment 。
如果键 key
不存在, 那么键 key
的值会先被初始化为 0 , 然后再执行 INCRBY 命令。
如果键 key
储存的值不能被解释为数字, 那么 INCRBY 命令将返回一个错误。
本操作的值限制在 64 位(bit)有符号数字表示之内。
返回值
在加上增量 increment 之后, 键 key
当前的值。
代码示例
键存在,并且值为数字:
1 | redis> SET rank 50 |
键不存在:
1 | redis> EXISTS counter |
键存在,但值无法被解释为数字:
1 | redis> SET book "long long ago..." |
INCRBYFLOAT
INCRBYFLOAT key increment
可用版本: >= 2.6.0
时间复杂度: O(1)
为键 key
储存的值加上浮点数增量 increment 。
如果键 key
不存在, 那么 INCRBYFLOAT 会先将键 key
的值设为 0 , 然后再执行加法操作。
如果命令执行成功, 那么键 key
的值会被更新为执行加法计算之后的新值, 并且新值会以字符串的形式返回给调用者。
无论是键 key
的值还是增量 increment , 都可以使用像 2.0e7 、 3e5 、 90e-2 那样的指数符号(exponential notation)来表示, 但是, 执行 INCRBYFLOAT 命令之后的值总是以同样的形式储存, 也即是, 它们总是由一个数字, 一个(可选的)小数点和一个任意长度的小数部分组成(比如 3.14 、 69.768 ,诸如此类), 小数部分尾随的 0 会被移除, 如果可能的话, 命令还会将浮点数转换为整数(比如 3.0 会被保存成 3 )。
此外, 无论加法计算所得的浮点数的实际精度有多长, INCRBYFLOAT 命令的计算结果最多只保留小数点的后十七位。
当以下任意一个条件发生时, 命令返回一个错误:
- 键
key
的值不是字符串类型(因为 Redis 中的数字和浮点数都以字符串的形式保存,所以它们都属于字符串类型); - 键
key
当前的值或者给定的增量 increment 不能被解释(parse)为双精度浮点数。
返回值
在加上增量 increment 之后, 键 key
的值。
代码示例
1 | redis> GET decimal |
DECR
DECR key
可用版本: >= 1.0.0
时间复杂度: O(1)
为键 key
储存的数字值减去一。
如果键 key
不存在, 那么键 key
的值会先被初始化为 0 , 然后再执行 DECR 操作。
如果键 key
储存的值不能被解释为数字, 那么 DECR 命令将返回一个错误。
本操作的值限制在 64 位(bit)有符号数字表示之内。
返回值
DECR 命令会返回键 key
在执行减一操作之后的值。
代码示例
对储存数字值的键 key
执行 DECR 命令:
1 | redis> SET failure_times 10 |
对不存在的键执行 DECR 命令:
1 | redis> EXISTS count |
DECRBY
DECRBY key decrement
可用版本: >= 1.0.0
时间复杂度: O(1)
将键 key
储存的整数值减去减量 decrement 。
如果键 key
不存在, 那么键 key
的值会先被初始化为 0 , 然后再执行 DECRBY 命令。
如果键 key
储存的值不能被解释为数字, 那么 DECRBY 命令将返回一个错误。
本操作的值限制在 64 位(bit)有符号数字表示之内。
返回值
DECRBY 命令会返回键在执行减法操作之后的值。
代码示例
对已经存在的键执行 DECRBY 命令:
1 | redis> SET count 100 |
对不存在的键执行 DECRBY 命令:
1 | redis> EXISTS pages |
MSET
MSET key value [key value …]
可用版本: >= 1.0.1
时间复杂度: O(N),其中 N 为被设置的键数量。
同时为多个键设置值。
如果某个给定键已经存在, 那么 MSET 将使用新值去覆盖旧值, 如果这不是你所希望的效果, 请考虑使用 MSETNX 命令, 这个命令只会在所有给定键都不存在的情况下进行设置。
MSET 是一个原子性(atomic)操作, 所有给定键都会在同一时间内被设置, 不会出现某些键被设置了但是另一些键没有被设置的情况。
返回值
MSET 命令总是返回 OK 。
代码示例
同时对多个键进行设置:
1 | redis> MSET date "2012.3.30" time "11:00 a.m." weather "sunny" |
覆盖已有的值:
1 | redis> MGET k1 k2 |
MSETNX
MSETNX key value [key value …]
可用版本: >= 1.0.1
时间复杂度: O(N), 其中 N 为被设置的键数量。
当且仅当所有给定键都不存在时, 为所有给定键设置值。
即使只有一个给定键已经存在, MSETNX 命令也会拒绝执行对所有键的设置操作。
MSETNX 是一个原子性(atomic)操作, 所有给定键要么就全部都被设置, 要么就全部都不设置, 不可能出现第三种状态。
返回值
当所有给定键都设置成功时, 命令返回 1 ;
如果因为某个给定键已经存在而导致设置未能成功执行, 那么命令返回 0 。
代码示例
对不存在的键执行 MSETNX 命令:
1 | redis> MSETNX rmdbs "MySQL" nosql "MongoDB" key-value-store "redis" |
对某个已经存在的键进行设置:
1 | redis> MSETNX rmdbs "Sqlite" language "python" # rmdbs 键已经存在,操作失败 |
MGET
MGET key [key …]
可用版本: >= 1.0.0
时间复杂度: O(N) ,其中 N 为给定键的数量。
返回给定的一个或多个字符串键的值。
- 如果给定的字符串键里面, 有某个键不存在, 那么这个键的值将以特殊值 nil 表示。
返回值
MGET 命令将返回一个列表, 列表中包含了所有给定键的值。
代码示例
1 | redis> SET redis redis.com |
哈希表
HSET
HSET hash field value
可用版本: >= 2.0.0
时间复杂度: O(1)
将哈希表 hash 中域 field 的值设置为 value
。
如果给定的哈希表并不存在, 那么一个新的哈希表将被创建并执行 HSET 操作。
如果域 field 已经存在于哈希表中, 那么它的旧值将被新值 value
覆盖。
返回值
- 当 HSET 命令在哈希表中新创建 field 域并成功为它设置值时, 命令返回 1 ;
- 如果域 field 已经存在于哈希表, 并且 HSET 命令成功使用新值覆盖了它的旧值, 那么命令返回 0 。
代码示例
设置一个新域:
1 | redis> HSET website google "www.g.cn" |
对一个已存在的域进行更新:
1 | redis> HSET website google "www.google.com" |
HSETNX
HSETNX hash field value
可用版本: >= 2.0.0
时间复杂度: O(1)
当且仅当域 field 尚未存在于哈希表的情况下, 将它的值设置为 value
。
如果给定域已经存在于哈希表当中, 那么命令将放弃执行设置操作。
如果哈希表 hash 不存在, 那么一个新的哈希表将被创建并执行 HSETNX 命令。
返回值
HSETNX 命令在设置成功时返回 1 , 在给定域已经存在而放弃执行设置操作时返回 0 。
代码示例
域尚未存在, 设置成功:
1 | redis> HSETNX database key-value-store Redis |
域已经存在, 设置未成功, 域原有的值未被改变:
1 | redis> HSETNX database key-value-store Riak |
HGET
HGET hash field
可用版本: >= 2.0.0
时间复杂度: O(1)
返回哈希表中给定域的值。
返回值
HGET 命令在默认情况下返回给定域的值。
如果给定域不存在于哈希表中, 又或者给定的哈希表并不存在, 那么命令返回 nil 。
代码示例
域存在的情况:
1 | redis> HSET homepage redis redis.com |
域不存在的情况:
1 | redis> HGET site mysql |
HEXISTS
HEXISTS hash field
可用版本: >= 2.0.0
时间复杂度: O(1)
检查给定域 field 是否存在于哈希表 hash 当中。
返回值
HEXISTS 命令在给定域存在时返回 1 , 在给定域不存在时返回 0 。
代码示例
给定域不存在:
1 | redis> HEXISTS phone myphone |
给定域存在:
1 | redis> HSET phone myphone nokia-1110 |
HDEL
HDEL key field [field …]
可用版本:>= 2.0.0
时间复杂度:O(N), N 为要删除的域的数量。
删除哈希表 key
中的一个或多个指定域,不存在的域将被忽略。
Note
在Redis2.4以下的版本里, HDEL 每次只能删除单个域,如果你需要在一个原子时间内删除多个域,请将命令包含在 MULTI / EXEC 块内。
返回值
被成功移除的域的数量,不包括被忽略的域。
代码示例
1 | # 测试数据 |
HLEN
HLEN key
时间复杂度:O(1)
返回哈希表 key 中域的数量。
返回值
哈希表中域的数量。
当 key 不存在时,返回 0 。
代码示例
1 | redis> HSET db redis redis.com |
HSTRLEN
HSTRLEN key field
可用版本:>= 3.2.0
时间复杂度:O(1)
返回哈希表 key 中, 与给定域 field 相关联的值的字符串长度(string length)。
如果给定的键或者域不存在, 那么命令返回 0 。
返回值
一个整数。
代码示例
1 | redis> HMSET myhash f1 "HelloWorld" f2 "99" f3 "-256" |
HINCRBY
HINCRBY key field increment
可用版本:>= 2.0.0
时间复杂度:O(1)
为哈希表 key 中的域 field 的值加上增量 increment 。
增量也可以为负数,相当于对给定域进行减法操作。
如果 key 不存在,一个新的哈希表被创建并执行 HINCRBY 命令。
如果域 field 不存在,那么在执行命令前,域的值被初始化为 0 。
对一个储存字符串值的域 field 执行 HINCRBY 命令将造成一个错误。
本操作的值被限制在 64 位(bit)有符号数字表示之内。
返回值
执行 HINCRBY 命令之后,哈希表 key 中域 field 的值。
代码示例
1 | # increment 为正数 |
HINCRBYFLOAT
HINCRBYFLOAT key field increment
可用版本:>= 2.6.0
时间复杂度:O(1)
为哈希表 key 中的域 field 加上浮点数增量 increment 。
如果哈希表中没有域 field ,那么 HINCRBYFLOAT 会先将域 field 的值设为 0 ,然后再执行加法操作。
如果键 key 不存在,那么 HINCRBYFLOAT 会先创建一个哈希表,再创建域 field ,最后再执行加法操作。
当以下任意一个条件发生时,返回一个错误:
- 域 field 的值不是字符串类型(因为 redis 中的数字和浮点数都以字符串的形式保存,所以它们都属于字符串类型)
- 域 field 当前的值或给定的增量 increment 不能解释(parse)为双精度浮点数(double precision floating point number)
返回值
执行加法操作之后 field 域的值。
代码示例
1 | # 值和增量都是普通小数 |
HMSET
HMSET key field value [field value …]
可用版本:>= 2.0.0
时间复杂度:O(N), N 为 field-value 对的数量。
同时将多个 field-value (域-值)对设置到哈希表 key 中。
此命令会覆盖哈希表中已存在的域。
如果 key 不存在,一个空哈希表被创建并执行 HMSET 操作。
返回值
如果命令执行成功,返回 OK 。
当 key 不是哈希表(hash)类型时,返回一个错误。
代码示例
1 | redis> HMSET website google www.google.com yahoo www.yahoo.com |
HMGET
HMGET key field [field …]
可用版本:>= 2.0.0
时间复杂度:O(N), N 为给定域的数量。
返回哈希表 key 中,一个或多个给定域的值。
如果给定的域不存在于哈希表,那么返回一个 nil 值。
因为不存在的 key 被当作一个空哈希表来处理,所以对一个不存在的 key 进行 HMGET 操作将返回一个只带有 nil 值的表。
返回值
一个包含多个给定域的关联值的表,表值的排列顺序和给定域参数的请求顺序一样。
代码示例
1 | redis> HMSET pet dog "doudou" cat "nounou" # 一次设置多个域 |
HKEYS
HKEYS key
可用版本:>= 2.0.0
时间复杂度:O(N), N 为哈希表的大小。
返回哈希表 key 中的所有域。
返回值
一个包含哈希表中所有域的表。
当 key 不存在时,返回一个空表。
示例代码
1 | # 哈希表非空 |
HVALS
HVALS key
可用版本:>= 2.0.0
时间复杂度:O(N), N 为哈希表的大小。
返回哈希表 key 中所有域的值。
返回值
一个包含哈希表中所有值的表。
当 key 不存在时,返回一个空表。
代码示例
1 | # 非空哈希表 |
HGETALL
HGETALL key
可用版本:>= 2.0.0
时间复杂度:O(N), N 为哈希表的大小。
返回哈希表 key 中,所有的域和值。
在返回值里,紧跟每个域名(field name)之后是域的值(value),所以返回值的长度是哈希表大小的两倍。
返回值
以列表形式返回哈希表的域和域的值。
若 key 不存在,返回空列表。
代码示例
1 | redis> HSET people jack "Jack Sparrow" |
HSCAN
HSCAN key cursor [MATCH pattern] [COUNT count]
具体信息请参考 SCAN cursor [MATCH pattern] [COUNT count] 命令。
列表
LPUSH
LPUSH key value [value …]
可用版本: >= 1.0.0
时间复杂度: O(1)
将一个或多个值 value 插入到列表 key 的表头
如果有多个 value 值,那么各个 value 值按从左到右的顺序依次插入到表头: 比如说,对空列表 mylist 执行命令 LPUSH mylist a b c ,列表的值将是 c b a ,这等同于原子性地执行 LPUSH mylist a 、 LPUSH mylist b 和 LPUSH mylist c 三个命令。
如果 key 不存在,一个空列表会被创建并执行 LPUSH 操作。
当 key 存在但不是列表类型时,返回一个错误。
Note
在Redis 2.4版本以前的 LPUSH 命令,都只接受单个 value 值。
返回值
执行 LPUSH 命令后,列表的长度。
代码示例
1 | # 加入单个元素 |
LPUSHX
LPUSHX key value
可用版本: >= 2.2.0
时间复杂度: O(1)
将值 value 插入到列表 key 的表头,当且仅当 key 存在并且是一个列表。
和 LPUSH key value [value …] 命令相反,当 key 不存在时, LPUSHX 命令什么也不做。
返回值
LPUSHX 命令执行之后,表的长度。
代码示例
1 | # 对空列表执行 LPUSHX |
RPUSH
RPUSH key value [value …]
可用版本: >= 1.0.0
时间复杂度: O(1)
将一个或多个值 value 插入到列表 key 的表尾(最右边)。
如果有多个 value 值,那么各个 value 值按从左到右的顺序依次插入到表尾:比如对一个空列表 mylist 执行 RPUSH mylist a b c ,得出的结果列表为 a b c ,等同于执行命令 RPUSH mylist a 、 RPUSH mylist b 、 RPUSH mylist c 。
如果 key 不存在,一个空列表会被创建并执行 RPUSH 操作。
当 key 存在但不是列表类型时,返回一个错误。
Note
在 Redis 2.4 版本以前的 RPUSH 命令,都只接受单个 value 值。
返回值
执行 RPUSH 操作后,表的长度。
代码示例
1 | # 添加单个元素 |
RPUSHX
RPUSHX key value
可用版本: >= 2.2.0
时间复杂度: O(1)
将值 value 插入到列表 key 的表尾,当且仅当 key 存在并且是一个列表。
和 RPUSH key value [value …] 命令相反,当 key 不存在时, RPUSHX 命令什么也不做。
返回值
RPUSHX 命令执行之后,表的长度。
代码示例
1 | # key不存在 |
LPOP
LPOP key
可用版本: >= 1.0.0
时间复杂度: O(1)
移除并返回列表 key 的头元素。
返回值
列表的头元素。 当 key 不存在时,返回 nil 。
代码示例
1 | redis> LLEN course |
RPOP
RPOP key
可用版本: >= 1.0.0
时间复杂度: O(1)
移除并返回列表 key 的尾元素。
返回值
列表的尾元素。 当 key 不存在时,返回 nil 。
代码示例
1 | redis> RPUSH mylist "one" |
RPOPLPUSH
RPOPLPUSH source destination
可用版本: >= 1.2.0
时间复杂度: O(1)
命令 RPOPLPUSH 在一个原子时间内,执行以下两个动作:
将列表 source 中的最后一个元素(尾元素)弹出,并返回给客户端。
将 source 弹出的元素插入到列表 destination ,作为 destination 列表的的头元素。
举个例子,你有两个列表 source 和 destination , source 列表有元素 a, b, c , destination 列表有元素 x, y, z ,执行 RPOPLPUSH source destination 之后, source 列表包含元素 a, b , destination 列表包含元素 c, x, y, z ,并且元素 c 会被返回给客户端。
如果 source 不存在,值 nil 被返回,并且不执行其他动作。
如果 source 和 destination 相同,则列表中的表尾元素被移动到表头,并返回该元素,可以把这种特殊情况视作列表的旋转(rotation)操作。
返回值
被弹出的元素。
代码示例
1 | # source 和 destination 不同 |
模式: 安全的队列
Redis的列表经常被用作队列(queue),用于在不同程序之间有序地交换消息(message)。一个客户端通过 LPUSH key value [value …] 命令将消息放入队列中,而另一个客户端通过 RPOP key 或者 BRPOP key [key …] timeout 命令取出队列中等待时间最长的消息。
不幸的是,上面的队列方法是『不安全』的,因为在这个过程中,一个客户端可能在取出一个消息之后崩溃,而未处理完的消息也就因此丢失。
使用 RPOPLPUSH 命令(或者它的阻塞版本 BRPOPLPUSH source destination timeout )可以解决这个问题:因为它不仅返回一个消息,同时还将这个消息添加到另一个备份列表当中,如果一切正常的话,当一个客户端完成某个消息的处理之后,可以用 LREM key count value 命令将这个消息从备份表删除。
最后,还可以添加一个客户端专门用于监视备份表,它自动地将超过一定处理时限的消息重新放入队列中去(负责处理该消息的客户端可能已经崩溃),这样就不会丢失任何消息了。
模式:循环列表
通过使用相同的 key 作为 RPOPLPUSH 命令的两个参数,客户端可以用一个接一个地获取列表元素的方式,取得列表的所有元素,而不必像 LRANGE key start stop 命令那样一下子将所有列表元素都从服务器传送到客户端中(两种方式的总复杂度都是 O(N))。
以上的模式甚至在以下的两个情况下也能正常工作:
- 有多个客户端同时对同一个列表进行旋转(rotating),它们获取不同的元素,直到所有元素都被读取完,之后又从头开始。
- 有客户端在向列表尾部(右边)添加新元素。
这个模式使得我们可以很容易实现这样一类系统:有 N 个客户端,需要连续不断地对一些元素进行处理,而且处理的过程必须尽可能地快。一个典型的例子就是服务器的监控程序:它们需要在尽可能短的时间内,并行地检查一组网站,确保它们的可访问性。
注意,使用这个模式的客户端是易于扩展(scala)且安全(reliable)的,因为就算接收到元素的客户端失败,元素还是保存在列表里面,不会丢失,等到下个迭代来临的时候,别的客户端又可以继续处理这些元素了。
LREM
LREM key count value
可用版本: >= 1.0.0
时间复杂度: O(N), N 为列表的长度。
根据参数 count 的值,移除列表中与参数 value 相等的元素。
count 的值可以是以下几种:
- count > 0 : 从表头开始向表尾搜索,移除与 value 相等的元素,数量为 count 。
- count < 0 : 从表尾开始向表头搜索,移除与 value 相等的元素,数量为 count 的绝对值。
- count = 0 : 移除表中所有与 value 相等的值。
返回值
被移除元素的数量。 因为不存在的 key 被视作空表(empty list),所以当 key 不存在时, LREM 命令总是返回 0 。
代码示例
1 | # 先创建一个表,内容排列是 |
LLEN
LLEN key
可用版本: >= 1.0.0
时间复杂度: O(1)
返回列表 key 的长度。
如果 key 不存在,则 key 被解释为一个空列表,返回 0 .
如果 key 不是列表类型,返回一个错误。
返回值
列表 key 的长度。
代码示例
1 | # 空列表 |
LINDEX
LINDEX key index
可用版本: >= 1.0.0
时间复杂度:O(N), N 为到达下标 index 过程中经过的元素数量。因此,对列表的头元素和尾元素执行 LINDEX 命令,复杂度为O(1)。
返回列表 key 中,下标为 index 的元素。
下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
如果 key 不是列表类型,返回一个错误。
返回值
列表中下标为 index 的元素。 如果 index 参数的值不在列表的区间范围内(out of range),返回 nil 。
代码示例
1 | redis> LPUSH mylist "World" |
LINSERT
LINSERT key BEFORE|AFTER pivot value
可用版本: >= 2.2.0
时间复杂度: O(N), N 为寻找 pivot 过程中经过的元素数量。
将值 value 插入到列表 key 当中,位于值 pivot 之前或之后。
当 pivot 不存在于列表 key 时,不执行任何操作。
当 key 不存在时, key 被视为空列表,不执行任何操作。
如果 key 不是列表类型,返回一个错误。
返回值
如果命令执行成功,返回插入操作完成之后,列表的长度。 如果没有找到 pivot ,返回 -1 。 如果 key 不存在或为空列表,返回 0 。
代码示例
1 | redis> RPUSH mylist "Hello" |
LSET
LSET key index value
可用版本: >= 1.0.0
时间复杂度:对头元素或尾元素进行 LSET 操作,复杂度为 O(1)。其他情况下,为 O(N), N 为列表的长度。
将列表 key 下标为 index 的元素的值设置为 value 。
当 index 参数超出范围,或对一个空列表( key 不存在)进行 LSET 时,返回一个错误。
关于列表下标的更多信息,请参考 LINDEX key index 命令。
返回值
操作成功返回 ok ,否则返回错误信息。
代码示例
1 | # 对空列表(key 不存在)进行 LSET |
LRANGE
LRANGE key start stop
可用版本: >= 1.0.0
时间复杂度: O(S+N), S 为偏移量 start , N 为指定区间内元素的数量。
返回列表 key 中指定区间内的元素,区间以偏移量 start 和 stop 指定。
下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
注意LRANGE命令和编程语言区间函数的区别
假如你有一个包含一百个元素的列表,对该列表执行 LRANGE list 0 10 ,结果是一个包含11个元素的列表,这表明 stop 下标也在 LRANGE 命令的取值范围之内(闭区间),这和某些语言的区间函数可能不一致,比如Ruby的 Range.new 、 Array#slice 和Python的 range() 函数。
超出范围的下标
超出范围的下标值不会引起错误。
如果 start 下标比列表的最大下标 end ( LLEN list 减去 1 )还要大,那么 LRANGE 返回一个空列表。
如果 stop 下标比 end 下标还要大,Redis将 stop 的值设置为 end 。
返回值
一个列表,包含指定区间内的元素。
代码示例
1 | redis> RPUSH fp-language lisp |
LTRIM
LTRIM key start stop
可用版本: >= 1.0.0
时间复杂度: O(N), N 为被移除的元素的数量。
对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
举个例子,执行命令 LTRIM list 0 2 ,表示只保留列表 list 的前三个元素,其余元素全部删除。
下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
当 key 不是列表类型时,返回一个错误。
LTRIM 命令通常和 LPUSH key value [value …] 命令或 RPUSH key value [value …] 命令配合使用,举个例子:
1 | LPUSH log newest_log |
BLPOP
BLPOP key [key …] timeout
可用版本: >= 2.0.0
时间复杂度: O(1)
BLPOP 是列表的阻塞式(blocking)弹出原语。
它是 LPOP key 命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被 BLPOP 命令阻塞,直到等待超时或发现可弹出元素为止。
当给定多个 key 参数时,按参数 key 的先后顺序依次检查各个列表,弹出第一个非空列表的头元素。
非阻塞行为
当 BLPOP 被调用时,如果给定 key 内至少有一个非空列表,那么弹出遇到的第一个非空列表的头元素,并和被弹出元素所属的列表的名字一起,组成结果返回给调用者。
当存在多个给定 key 时, BLPOP 按给定 key 参数排列的先后顺序,依次检查各个列表。
假设现在有 job 、 command 和 request 三个列表,其中 job 不存在, command 和 request 都持有非空列表。考虑以下命令:
BLPOP job command request 0
BLPOP 保证返回的元素来自 command ,因为它是按”查找 job -> 查找 command -> 查找 request “这样的顺序,第一个找到的非空列表。
1 | redis> DEL job command request # 确保key都被删除 |
阻塞行为
如果所有给定 key 都不存在或包含空列表,那么 BLPOP 命令将阻塞连接,直到等待超时,或有另一个客户端对给定 key 的任意一个执行 LPUSH key value [value …] 或 RPUSH key value [value …] 命令为止。
超时参数 timeout 接受一个以秒为单位的数字作为值。超时参数设为 0 表示阻塞时间可以无限期延长(block indefinitely) 。
1 | redis> EXISTS job # 确保两个 key 都不存在 |
相同的key被多个客户端同时阻塞
相同的 key 可以被多个客户端同时阻塞。
不同的客户端被放进一个队列中,按『先阻塞先服务』(first-BLPOP,first-served)的顺序为 key 执行 BLPOP 命令。
在MULTI/EXEC事务中的BLPOP
BLPOP 可以用于流水线(pipline,批量地发送多个命令并读入多个回复),但把它用在 MULTI / EXEC 块当中没有意义。因为这要求整个服务器被阻塞以保证块执行时的原子性,该行为阻止了其他客户端执行 LPUSH key value [value …] 或 RPUSH key value [value …] 命令。
因此,一个被包裹在 MULTI / EXEC 块内的 BLPOP 命令,行为表现得就像 LPOP key 一样,对空列表返回 nil ,对非空列表弹出列表元素,不进行任何阻塞操作。
1 | # 对非空列表进行操作 |
返回值
如果列表为空,返回一个 nil 。 否则,返回一个含有两个元素的列表,第一个元素是被弹出元素所属的 key ,第二个元素是被弹出元素的值。
模式:事件提醒
有时候,为了等待一个新元素到达数据中,需要使用轮询的方式对数据进行探查。
另一种更好的方式是,使用系统提供的阻塞原语,在新元素到达时立即进行处理,而新元素还没到达时,就一直阻塞住,避免轮询占用资源。
对于 Redis ,我们似乎需要一个阻塞版的 SPOP key 命令,但实际上,使用 BLPOP 或者 BRPOP key [key …] timeout 就能很好地解决这个问题。
使用元素的客户端(消费者)可以执行类似以下的代码:
1 | LOOP forever |
添加元素的客户端(生产者)则执行以下代码:
1 | MULTI |
BRPOP
BRPOP key [key …] timeout
可用版本: >= 2.0.0
时间复杂度: O(1)
BRPOP 是列表的阻塞式(blocking)弹出原语。
它是 RPOP key 命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被 BRPOP 命令阻塞,直到等待超时或发现可弹出元素为止。
当给定多个 key 参数时,按参数 key 的先后顺序依次检查各个列表,弹出第一个非空列表的尾部元素。
关于阻塞操作的更多信息,请查看 BLPOP key [key …] timeout 命令, BRPOP 除了弹出元素的位置和 BLPOP key [key …] timeout 不同之外,其他表现一致。
返回值
假如在指定时间内没有任何元素被弹出,则返回一个 nil 和等待时长。 反之,返回一个含有两个元素的列表,第一个元素是被弹出元素所属的 key ,第二个元素是被弹出元素的值。
代码示例
1 | redis> LLEN course |
BRPOPLPUSH
BRPOPLPUSH source destination timeout
可用版本: >= 2.2.0
时间复杂度: O(1)
BRPOPLPUSH 是 RPOPLPUSH source destination 的阻塞版本,当给定列表 source 不为空时, BRPOPLPUSH 的表现和 RPOPLPUSH source destination 一样。
当列表 source 为空时, BRPOPLPUSH 命令将阻塞连接,直到等待超时,或有另一个客户端对 source 执行 LPUSH key value [value …] 或 RPUSH key value [value …] 命令为止。
超时参数 timeout 接受一个以秒为单位的数字作为值。超时参数设为 0 表示阻塞时间可以无限期延长(block indefinitely) 。
更多相关信息,请参考 RPOPLPUSH source destination 命令。
返回值
假如在指定时间内没有任何元素被弹出,则返回一个 nil 和等待时长。 反之,返回一个含有两个元素的列表,第一个元素是被弹出元素的值,第二个元素是等待时长。
代码示例
1 | # 非空列表 |
模式:安全队列
参考 RPOPLPUSH source destination 命令的《安全队列》一节。
模式:循环列表
参考 RPOPLPUSH source destination 命令的《循环列表》一节。
集合
SADD
SADD key member [member …]
可用版本: >= 1.0.0
时间复杂度: O(N), N 是被添加的元素的数量。
将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略。
假如 key 不存在,则创建一个只包含 member 元素作成员的集合。
当 key 不是集合类型时,返回一个错误。
Note
在Redis2.4版本以前, SADD 只接受单个 member 值。
返回值
被添加到集合中的新元素的数量,不包括被忽略的元素。
代码示例
1 | # 添加单个元素 |
SISMEMBER
SISMEMBER key member
可用版本: >= 1.0.0
时间复杂度: O(1)
判断 member 元素是否集合 key 的成员。
返回值
如果 member 元素是集合的成员,返回 1 。 如果 member 元素不是集合的成员,或 key 不存在,返回 0 。
代码示例
1 | redis> SMEMBERS joe's_movies |
SPOP
SPOP key
可用版本: >= 1.0.0
时间复杂度: O(1)
移除并返回集合中的一个随机元素。
如果只想获取一个随机元素,但不想该元素从集合中被移除的话,可以使用 SRANDMEMBER key [count] 命令。
返回值
被移除的随机元素。 当 key 不存在或 key 是空集时,返回 nil 。
代码示例
1 | redis> SMEMBERS db |
SRANDMEMBER
SRANDMEMBER key [count]
可用版本: >= 1.0.0
时间复杂度: 只提供 key 参数时为 O(1) 。如果提供了 count 参数,那么为 O(N) ,N 为返回数组的元素个数。
如果命令执行时,只提供了 key 参数,那么返回集合中的一个随机元素。
从 Redis 2.6 版本开始, SRANDMEMBER 命令接受可选的 count 参数:
如果 count 为正数,且小于集合基数,那么命令返回一个包含 count 个元素的数组,数组中的元素各不相同。如果 count 大于等于集合基数,那么返回整个集合。
如果 count 为负数,那么命令返回一个数组,数组中的元素可能会重复出现多次,而数组的长度为 count 的绝对值。
该操作和 SPOP key 相似,但 SPOP key 将随机元素从集合中移除并返回,而 SRANDMEMBER 则仅仅返回随机元素,而不对集合进行任何改动。
返回值
只提供 key 参数时,返回一个元素;如果集合为空,返回 nil 。 如果提供了 count 参数,那么返回一个数组;如果集合为空,返回空数组。
代码示例
1 | # 添加元素 |
SREM
SREM key member [member …]
可用版本: >= 1.0.0
时间复杂度: O(N), N 为给定 member 元素的数量。
移除集合 key 中的一个或多个 member 元素,不存在的 member 元素会被忽略。
当 key 不是集合类型,返回一个错误。
Note
在 Redis 2.4 版本以前, SREM 只接受单个 member 值。
返回值
被成功移除的元素的数量,不包括被忽略的元素。
代码示例
1 | # 测试数据 |
SMOVE
SMOVE source destination member
可用版本: >= 1.0.0
时间复杂度: O(1)
将 member 元素从 source 集合移动到 destination 集合。
SMOVE 是原子性操作。
如果 source 集合不存在或不包含指定的 member 元素,则 SMOVE 命令不执行任何操作,仅返回 0 。否则, member 元素从 source 集合中被移除,并添加到 destination 集合中去。
当 destination 集合已经包含 member 元素时, SMOVE 命令只是简单地将 source 集合中的 member 元素删除。
当 source 或 destination 不是集合类型时,返回一个错误。
返回值
如果 member 元素被成功移除,返回 1 。 如果 member 元素不是 source 集合的成员,并且没有任何操作对 destination 集合执行,那么返回 0 。
代码示例
1 | redis> SMEMBERS songs |
SCARD
SCARD key
可用版本: >= 1.0.0
时间复杂度: O(1)
返回集合 key 的基数(集合中元素的数量)。
返回值
集合的基数。 当 key 不存在时,返回 0 。
代码示例
1 | redis> SADD tool pc printer phone |
SMEMBERS
SMEMBERS key
可用版本: >= 1.0.0
时间复杂度: O(N), N 为集合的基数。
返回集合 key 中的所有成员。
不存在的 key 被视为空集合。
返回值
集合中的所有成员。
代码示例
1 | # key 不存在或集合为空 |
SSCAN
SSCAN key cursor [MATCH pattern] [COUNT count]
详细信息请参考 SCAN cursor [MATCH pattern] [COUNT count] 命令。
SINTER
SINTER key [key …]
可用版本: >= 1.0.0
时间复杂度: O(N * M), N 为给定集合当中基数最小的集合, M 为给定集合的个数。
返回一个集合的全部成员,该集合是所有给定集合的交集。
不存在的 key 被视为空集。
当给定集合当中有一个空集时,结果也为空集(根据集合运算定律)。
返回值
交集成员的列表。
代码示例
1 | redis> SMEMBERS group_1 |
SINTERSTORE
SINTERSTORE destination key [key …]
可用版本: >= 1.0.0
时间复杂度: O(N * M), N 为给定集合当中基数最小的集合, M 为给定集合的个数。
这个命令类似于 SINTER key [key …] 命令,但它将结果保存到 destination 集合,而不是简单地返回结果集。
如果 destination 集合已经存在,则将其覆盖。
destination 可以是 key 本身。
返回值
结果集中的成员数量。
代码示例
1 | redis> SMEMBERS songs |
SUNION
SUNION key [key …]
可用版本: >= 1.0.0
时间复杂度: O(N), N 是所有给定集合的成员数量之和。
返回一个集合的全部成员,该集合是所有给定集合的并集。
不存在的 key 被视为空集。
返回值
并集成员的列表。
代码示例
1 | redis> SMEMBERS songs |
SUNIONSTORE
SUNIONSTORE destination key [key …]
可用版本: >= 1.0.0
时间复杂度: O(N), N 是所有给定集合的成员数量之和。
这个命令类似于 SUNION key [key …] 命令,但它将结果保存到 destination 集合,而不是简单地返回结果集。
如果 destination 已经存在,则将其覆盖。
destination 可以是 key 本身。
返回值
结果集中的元素数量。
代码示例
1 | redis> SMEMBERS NoSQL |
SDIFF
SDIFF key [key …]
可用版本: >= 1.0.0
时间复杂度: O(N), N 是所有给定集合的成员数量之和。
返回一个集合的全部成员,该集合是所有给定集合之间的差集。
不存在的 key 被视为空集。
返回值
一个包含差集成员的列表。
代码示例
1 | redis> SMEMBERS peter's_movies |
SDIFFSTORE
SDIFFSTORE destination key [key …]
可用版本: >= 1.0.0
时间复杂度: O(N), N 是所有给定集合的成员数量之和。
这个命令的作用和 SDIFF key [key …] 类似,但它将结果保存到 destination 集合,而不是简单地返回结果集。
如果 destination 集合已经存在,则将其覆盖。
destination 可以是 key 本身。
返回值
结果集中的元素数量。
代码示例
1 | redis> SMEMBERS joe's_movies |
有序集合
ZADD
ZADD key score member [[score member] [score member] …]
可用版本: >= 1.2.0
时间复杂度: O(M*log(N)), N 是有序集的基数, M 为成功添加的新成员的数量。
将一个或多个 member 元素及其 score 值加入到有序集 key 当中。
如果某个 member 已经是有序集的成员,那么更新这个 member 的 score 值,并通过重新插入这个 member 元素,来保证该 member 在正确的位置上。
score 值可以是整数值或双精度浮点数。
如果 key 不存在,则创建一个空的有序集并执行 ZADD 操作。
当 key 存在但不是有序集类型时,返回一个错误。
Note
在 Redis 2.4 版本以前, ZADD 每次只能添加一个元素。
返回值
被成功添加的新成员的数量,不包括那些被更新的、已经存在的成员。
代码示例
1 | # 添加单个元素 |
ZSCORE
ZSCORE key member
可用版本: >= 1.2.0
时间复杂度: O(1)
返回有序集 key 中,成员 member 的 score 值。
如果 member 元素不是有序集 key 的成员,或 key 不存在,返回 nil 。
返回值
member 成员的 score 值,以字符串形式表示。
代码示例
1 | redis> ZRANGE salary 0 -1 WITHSCORES # 测试数据 |
ZINCRBY
ZINCRBY key increment member
可用版本: >= 1.2.0
时间复杂度: O(log(N))
为有序集 key 的成员 member 的 score 值加上增量 increment 。
可以通过传递一个负数值 increment ,让 score 减去相应的值,比如 ZINCRBY key -5 member ,就是让 member 的 score 值减去 5 。
当 key 不存在,或 member 不是 key 的成员时, ZINCRBY key increment member 等同于 ZADD key increment member 。
当 key 不是有序集类型时,返回一个错误。
score 值可以是整数值或双精度浮点数。
返回值
member 成员的新 score 值,以字符串形式表示。
代码示例
1 | redis> ZSCORE salary tom |
ZCARD
ZCARD key
可用版本: >= 1.2.0
时间复杂度: O(1)
返回有序集 key 的基数。
返回值
当 key 存在且是有序集类型时,返回有序集的基数。 当 key 不存在时,返回 0 。
代码示例
1 | redis > ZADD salary 2000 tom # 添加一个成员 |
ZCOUNT
ZCOUNT key min max
可用版本: >= 2.0.0
时间复杂度: O(log(N)), N 为有序集的基数。
返回有序集 key 中, score 值在 min 和 max 之间(默认包括 score 值等于 min 或 max )的成员的数量。
关于参数 min 和 max 的详细使用方法,请参考 ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] 命令。
返回值
score 值在 min 和 max 之间的成员的数量。
代码示例
1 | redis> ZRANGE salary 0 -1 WITHSCORES # 测试数据 |
ZRANGE
ZRANGE key start stop [WITHSCORES]
可用版本: >= 1.2.0
时间复杂度: O(log(N)+M), N 为有序集的基数,而 M 为结果集的基数。
返回有序集 key 中,指定区间内的成员。
其中成员的位置按 score 值递增(从小到大)来排序。
具有相同 score 值的成员按字典序(lexicographical order )来排列。
如果你需要成员按 score 值递减(从大到小)来排列,请使用 ZREVRANGE key start stop [WITHSCORES] 命令。
下标参数 start 和 stop 都以 0 为底,也就是说,以 0 表示有序集第一个成员,以 1 表示有序集第二个成员,以此类推。 你也可以使用负数下标,以 -1 表示最后一个成员, -2 表示倒数第二个成员,以此类推。
超出范围的下标并不会引起错误。 比如说,当 start 的值比有序集的最大下标还要大,或是 start > stop 时, ZRANGE 命令只是简单地返回一个空列表。 另一方面,假如 stop 参数的值比有序集的最大下标还要大,那么 Redis 将 stop 当作最大下标来处理。
可以通过使用 WITHSCORES 选项,来让成员和它的 score 值一并返回,返回列表以 value1,score1, …, valueN,scoreN 的格式表示。 客户端库可能会返回一些更复杂的数据类型,比如数组、元组等。
返回值
指定区间内,带有 score 值(可选)的有序集成员的列表。
代码示例
1 | redis > ZRANGE salary 0 -1 WITHSCORES # 显示整个有序集成员 |
ZREVRANGE
ZREVRANGE key start stop [WITHSCORES]
可用版本: >= 1.2.0
时间复杂度: O(log(N)+M), N 为有序集的基数,而 M 为结果集的基数。
返回有序集 key 中,指定区间内的成员。
其中成员的位置按 score 值递减(从大到小)来排列。 具有相同 score 值的成员按字典序的逆序(reverse lexicographical order)排列。
除了成员按 score 值递减的次序排列这一点外, ZREVRANGE 命令的其他方面和 ZRANGE key start stop [WITHSCORES] 命令一样。
返回值
指定区间内,带有 score 值(可选)的有序集成员的列表。
代码示例
1 | redis> ZRANGE salary 0 -1 WITHSCORES # 递增排列 |
ZRANGEBYSCORE
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
可用版本: >= 1.0.5
时间复杂度: O(log(N)+M), N 为有序集的基数, M 为被结果集的基数。
返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。
具有相同 score 值的成员按字典序(lexicographical order)来排列(该属性是有序集提供的,不需要额外的计算)。
可选的 LIMIT 参数指定返回结果的数量及区间(就像SQL中的 SELECT LIMIT offset, count ),注意当 offset 很大时,定位 offset 的操作可能需要遍历整个有序集,此过程最坏复杂度为 O(N) 时间。
可选的 WITHSCORES 参数决定结果集是单单返回有序集的成员,还是将有序集成员及其 score 值一起返回。 该选项自 Redis 2.0 版本起可用。
区间及无限
min 和 max 可以是 -inf 和 +inf ,这样一来,你就可以在不知道有序集的最低和最高 score 值的情况下,使用 ZRANGEBYSCORE 这类命令。
默认情况下,区间的取值使用闭区间 (小于等于或大于等于),你也可以通过给参数前增加 ( 符号来使用可选的开区间 (小于或大于)。
举个例子:
ZRANGEBYSCORE zset (1 5
返回所有符合条件 1 < score <= 5 的成员,而
ZRANGEBYSCORE zset (5 (10
则返回所有符合条件 5 < score < 10 的成员。
返回值
指定区间内,带有 score 值(可选)的有序集成员的列表。
代码示例
1 | redis> ZADD salary 2500 jack # 测试数据 |
ZREVRANGEBYSCORE
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
可用版本: >= 2.2.0
时间复杂度: O(log(N)+M), N 为有序集的基数, M 为结果集的基数。
返回有序集 key 中, score 值介于 max 和 min 之间(默认包括等于 max 或 min )的所有的成员。有序集成员按 score 值递减(从大到小)的次序排列。
具有相同 score 值的成员按字典序的逆序(reverse lexicographical order )排列。
除了成员按 score 值递减的次序排列这一点外, ZREVRANGEBYSCORE 命令的其他方面和 ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] 命令一样。
返回值
指定区间内,带有 score 值(可选)的有序集成员的列表。
代码示例
1 | redis > ZADD salary 10086 jack |
ZRANK
ZRANK key member
可用版本: >= 2.0.0
时间复杂度: O(log(N))
返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递增(从小到大)顺序排列。
排名以 0 为底,也就是说, score 值最小的成员排名为 0 。
使用 ZREVRANK key member 命令可以获得成员按 score 值递减(从大到小)排列的排名。
返回值
如果 member 是有序集 key 的成员,返回 member 的排名。 如果 member 不是有序集 key 的成员,返回 nil 。
代码示例
1 | redis> ZRANGE salary 0 -1 WITHSCORES # 显示所有成员及其 score 值 |
ZREVRANK
ZREVRANK key member
可用版本: >= 2.0.0
时间复杂度: O(log(N))
返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递减(从大到小)排序。
排名以 0 为底,也就是说, score 值最大的成员排名为 0 。
使用 ZRANK key member 命令可以获得成员按 score 值递增(从小到大)排列的排名。
返回值
如果 member 是有序集 key 的成员,返回 member 的排名。 如果 member 不是有序集 key 的成员,返回 nil 。
代码示例
1 | redis 127.0.0.1:6379> ZRANGE salary 0 -1 WITHSCORES # 测试数据 |
ZREM
ZREM key member [member …]
可用版本: >= 1.2.0
时间复杂度: O(M*log(N)), N 为有序集的基数, M 为被成功移除的成员的数量。
移除有序集 key 中的一个或多个成员,不存在的成员将被忽略。
当 key 存在但不是有序集类型时,返回一个错误。
Note
在 Redis 2.4 版本以前, ZREM 每次只能删除一个元素。
返回值
被成功移除的成员的数量,不包括被忽略的成员。
代码示例
1 | # 测试数据 |
ZREMRANGEBYRANK
ZREMRANGEBYRANK key start stop
可用版本: >= 2.0.0
时间复杂度: O(log(N)+M), N 为有序集的基数,而 M 为被移除成员的数量。
移除有序集 key 中,指定排名(rank)区间内的所有成员。
区间分别以下标参数 start 和 stop 指出,包含 start 和 stop 在内。
下标参数 start 和 stop 都以 0 为底,也就是说,以 0 表示有序集第一个成员,以 1 表示有序集第二个成员,以此类推。 你也可以使用负数下标,以 -1 表示最后一个成员, -2 表示倒数第二个成员,以此类推。
返回值
被移除成员的数量。
代码示例
1 | redis> ZADD salary 2000 jack |
ZREMRANGEBYSCORE
ZREMRANGEBYSCORE key min max
可用版本: >= 1.2.0
时间复杂度: O(log(N)+M), N 为有序集的基数,而 M 为被移除成员的数量。
移除有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。
自版本2.1.6开始, score 值等于 min 或 max 的成员也可以不包括在内,详情请参见 ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] 命令。
返回值
被移除成员的数量。
代码示例
1 | redis> ZRANGE salary 0 -1 WITHSCORES # 显示有序集内所有成员及其 score 值 |
ZRANGEBYLEX
ZRANGEBYLEX key min max [LIMIT offset count]
可用版本: >= 2.8.9
时间复杂度:O(log(N)+M), 其中 N 为有序集合的元素数量, 而 M 则是命令返回的元素数量。 如果 M 是一个常数(比如说,用户总是使用 LIMIT 参数来返回最先的 10 个元素), 那么命令的复杂度也可以看作是 O(log(N)) 。
当有序集合的所有成员都具有相同的分值时, 有序集合的元素会根据成员的字典序(lexicographical ordering)来进行排序, 而这个命令则可以返回给定的有序集合键 key 中, 值介于 min 和 max 之间的成员。
如果有序集合里面的成员带有不同的分值, 那么命令返回的结果是未指定的(unspecified)。
命令会使用 C 语言的 memcmp() 函数, 对集合中的每个成员进行逐个字节的对比(byte-by-byte compare), 并按照从低到高的顺序, 返回排序后的集合成员。 如果两个字符串有一部分内容是相同的话, 那么命令会认为较长的字符串比较短的字符串要大。
可选的 LIMIT offset count 参数用于获取指定范围内的匹配元素 (就像 SQL 中的 SELECT LIMIT offset count 语句)。 需要注意的一点是, 如果 offset 参数的值非常大的话, 那么命令在返回结果之前, 需要先遍历至 offset 所指定的位置, 这个操作会为命令加上最多 O(N) 复杂度。
如何指定范围区间
合法的 min 和 max 参数必须包含 ( 或者 [ , 其中 ( 表示开区间(指定的值不会被包含在范围之内), 而 [ 则表示闭区间(指定的值会被包含在范围之内)。
特殊值 + 和 - 在 min 参数以及 max 参数中具有特殊的意义, 其中 + 表示正无限, 而 - 表示负无限。 因此, 向一个所有成员的分值都相同的有序集合发送命令 ZRANGEBYLEX <zset> - + , 命令将返回有序集合中的所有元素。
返回值
数组回复:一个列表,列表里面包含了有序集合在指定范围内的成员。
代码示例
1 | redis> ZADD myzset 0 a 0 b 0 c 0 d 0 e 0 f 0 g |
ZLEXCOUNT
ZLEXCOUNT key min max
可用版本: >= 2.8.9
时间复杂度: O(log(N)),其中 N 为有序集合包含的元素数量。
对于一个所有成员的分值都相同的有序集合键 key 来说, 这个命令会返回该集合中, 成员介于 min 和 max 范围内的元素数量。
这个命令的 min 参数和 max 参数的意义和 ZRANGEBYLEX key min max [LIMIT offset count] 命令的 min 参数和 max 参数的意义一样。
返回值
整数回复:指定范围内的元素数量。
代码示例
1 | redis> ZADD myzset 0 a 0 b 0 c 0 d 0 e |
ZREMRANGEBYLEX
ZREMRANGEBYLEX key min max
可用版本: >= 2.8.9
时间复杂度: O(log(N)+M), 其中 N 为有序集合的元素数量, 而 M 则为被移除的元素数量。
对于一个所有成员的分值都相同的有序集合键 key 来说, 这个命令会移除该集合中, 成员介于 min 和 max 范围内的所有元素。
这个命令的 min 参数和 max 参数的意义和 ZRANGEBYLEX key min max [LIMIT offset count] 命令的 min 参数和 max 参数的意义一样。
返回值
整数回复:被移除的元素数量。
代码示例
1 | redis> ZADD myzset 0 aaaa 0 b 0 c 0 d 0 e |
ZSCAN
ZSCAN key cursor [MATCH pattern] [COUNT count]
详细信息请参考 SCAN cursor [MATCH pattern] [COUNT count] 命令。
ZUNIONSTORE
ZUNIONSTORE destination numkeys key [key …] [WEIGHTS weight [weight …]] [AGGREGATE SUM|MIN|MAX]
可用版本:>= 2.0.0
时间复杂度: O(N)+O(M log(M)), N 为给定有序集基数的总和, M 为结果集的基数。
计算给定的一个或多个有序集的并集,其中给定 key 的数量必须以 numkeys 参数指定,并将该并集(结果集)储存到 destination 。
默认情况下,结果集中某个成员的 score 值是所有给定集下该成员 score 值之 和 。
WEIGHTS
使用 WEIGHTS 选项,你可以为 每个 给定有序集 分别 指定一个乘法因子(multiplication factor),每个给定有序集的所有成员的 score 值在传递给聚合函数(aggregation function)之前都要先乘以该有序集的因子。
如果没有指定 WEIGHTS 选项,乘法因子默认设置为 1 。
AGGREGATE
使用 AGGREGATE 选项,你可以指定并集的结果集的聚合方式。
默认使用的参数 SUM ,可以将所有集合中某个成员的 score 值之 和 作为结果集中该成员的 score 值;使用参数 MIN ,可以将所有集合中某个成员的 最小 score 值作为结果集中该成员的 score 值;而参数 MAX 则是将所有集合中某个成员的 最大 score 值作为结果集中该成员的 score 值。
返回值
保存到 destination 的结果集的基数。
代码示例
1 | redis> ZRANGE programmer 0 -1 WITHSCORES |
ZINTERSTORE
ZINTERSTORE destination numkeys key [key …] [WEIGHTS weight [weight …]] [AGGREGATE SUM|MIN|MAX]
可用版本: >= 2.0.0
时间复杂度: O(N*K)+O(M*log(M)), N 为给定 key 中基数最小的有序集, K 为给定有序集的数量, M 为结果集的基数。
计算给定的一个或多个有序集的交集,其中给定 key 的数量必须以 numkeys 参数指定,并将该交集(结果集)储存到 destination 。
默认情况下,结果集中某个成员的 score 值是所有给定集下该成员 score 值之和.
关于 WEIGHTS 和 AGGREGATE 选项的描述,参见 ZUNIONSTORE destination numkeys key [key …] [WEIGHTS weight [weight …]] [AGGREGATE SUM|MIN|MAX] 命令。
返回值
保存到 destination 的结果集的基数。
代码示例
1 | redis > ZADD mid_test 70 "Li Lei" |
HyperLogLog
PFADD
PFADD key element [element …]
可用版本: >= 2.8.9
时间复杂度: 每添加一个元素的复杂度为 O(1) 。
将任意数量的元素添加到指定的 HyperLogLog 里面。
作为这个命令的副作用, HyperLogLog 内部可能会被更新, 以便反映一个不同的唯一元素估计数量(也即是集合的基数)。
如果 HyperLogLog 估计的近似基数(approximated cardinality)在命令执行之后出现了变化, 那么命令返回 1 , 否则返回 0 。 如果命令执行时给定的键不存在, 那么程序将先创建一个空的 HyperLogLog 结构, 然后再执行命令。
调用 PFADD key element [element …] 命令时可以只给定键名而不给定元素:
如果给定键已经是一个 HyperLogLog , 那么这种调用不会产生任何效果;
但如果给定的键不存在, 那么命令会创建一个空的 HyperLogLog , 并向客户端返回 1 。
要了解更多关于 HyperLogLog 数据结构的介绍知识, 请查阅 PFCOUNT key [key …] 命令的文档。
返回值
整数回复: 如果 HyperLogLog 的内部储存被修改了, 那么返回 1 , 否则返回 0 。
代码示例
1 | redis> PFADD databases "Redis" "MongoDB" "MySQL" |
PFCOUNT
PFCOUNT key [key …]
可用版本: >= 2.8.9
时间复杂度: 当命令作用于单个 HyperLogLog 时, 复杂度为 O(1) , 并且具有非常低的平均常数时间。 当命令作用于 N 个 HyperLogLog 时, 复杂度为 O(N) , 常数时间也比处理单个 HyperLogLog 时要大得多。
当 PFCOUNT key [key …] 命令作用于单个键时, 返回储存在给定键的 HyperLogLog 的近似基数, 如果键不存在, 那么返回 0 。
当 PFCOUNT key [key …] 命令作用于多个键时, 返回所有给定 HyperLogLog 的并集的近似基数, 这个近似基数是通过将所有给定 HyperLogLog 合并至一个临时 HyperLogLog 来计算得出的。
通过 HyperLogLog 数据结构, 用户可以使用少量固定大小的内存, 来储存集合中的唯一元素 (每个 HyperLogLog 只需使用 12k 字节内存,以及几个字节的内存来储存键本身)。
命令返回的可见集合(observed set)基数并不是精确值, 而是一个带有 0.81% 标准错误(standard error)的近似值。
举个例子, 为了记录一天会执行多少次各不相同的搜索查询, 一个程序可以在每次执行搜索查询时调用一次 PFADD key element [element …] , 并通过调用 PFCOUNT key [key …] 命令来获取这个记录的近似结果。
返回值
整数回复: 给定 HyperLogLog 包含的唯一元素的近似数量。
代码示例
1 | redis> PFADD databases "Redis" "MongoDB" "MySQL" |
PFMERGE
PFMERGE destkey sourcekey [sourcekey …]
可用版本: >= 2.8.9
时间复杂度: O(N) , 其中 N 为被合并的 HyperLogLog 数量, 不过这个命令的常数复杂度比较高。
将多个 HyperLogLog 合并(merge)为一个 HyperLogLog , 合并后的 HyperLogLog 的基数接近于所有输入 HyperLogLog 的可见集合(observed set)的并集。
合并得出的 HyperLogLog 会被储存在 destkey 键里面, 如果该键并不存在, 那么命令在执行之前, 会先为该键创建一个空的 HyperLogLog 。
返回值
字符串回复:返回 OK 。
代码示例
1 | redis> PFADD nosql "Redis" "MongoDB" "Memcached" |
地理位置
GEOADD
GEOADD key longitude latitude member [longitude latitude member …]
可用版本: >= 3.2.0
时间复杂度: 每添加一个元素的复杂度为 O(log(N)) , 其中 N 为键里面包含的位置元素数量。
将给定的空间元素(纬度、经度、名字)添加到指定的键里面。 这些数据会以有序集合的形式被储存在键里面, 从而使得像 GEORADIUS 和 GEORADIUSBYMEMBER 这样的命令可以在之后通过位置查询取得这些元素。
GEOADD 命令以标准的 x,y 格式接受参数, 所以用户必须先输入经度, 然后再输入纬度。 GEOADD 能够记录的坐标是有限的: 非常接近两极的区域是无法被索引的。 精确的坐标限制由 EPSG:900913 / EPSG:3785 / OSGEO:41001 等坐标系统定义, 具体如下:
有效的经度介于 -180 度至 180 度之间。
有效的纬度介于 -85.05112878 度至 85.05112878 度之间。
当用户尝试输入一个超出范围的经度或者纬度时, GEOADD 命令将返回一个错误。
返回值
新添加到键里面的空间元素数量, 不包括那些已经存在但是被更新的元素。
代码示例
1 | redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" |
GEOPOS
GEOPOS key member [member …]
可用版本: >= 3.2.0
时间复杂度: 获取每个位置元素的复杂度为 O(log(N)) , 其中 N 为键里面包含的位置元素数量。
从键里面返回所有给定位置元素的位置(经度和纬度)。
因为 GEOPOS 命令接受可变数量的位置元素作为输入, 所以即使用户只给定了一个位置元素, 命令也会返回数组回复。
返回值
GEOPOS 命令返回一个数组, 数组中的每个项都由两个元素组成: 第一个元素为给定位置元素的经度, 而第二个元素则为给定位置元素的纬度。 当给定的位置元素不存在时, 对应的数组项为空值。
代码示例
1 | redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" |
GEODIST
GEODIST key member1 member2 [unit]
可用版本: >= 3.2.0
复杂度: O(log(N))
返回两个给定位置之间的距离。
如果两个位置之间的其中一个不存在, 那么命令返回空值。
指定单位的参数 unit 必须是以下单位的其中一个:
m 表示单位为米。
km 表示单位为千米。
mi 表示单位为英里。
ft 表示单位为英尺。
如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位。
GEODIST 命令在计算距离时会假设地球为完美的球形, 在极限情况下, 这一假设最大会造成 0.5% 的误差。
返回值
计算出的距离会以双精度浮点数的形式被返回。 如果给定的位置元素不存在, 那么命令返回空值。
代码示例
1 | redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" |
GEORADIUS
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [COUNT count]
可用版本: >= 3.2.0
时间复杂度: O(N+log(M)), 其中 N 为指定半径范围内的位置元素数量, 而 M 则是被返回位置元素的数量。
以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
范围可以使用以下其中一个单位:
m 表示单位为米。
km 表示单位为千米。
mi 表示单位为英里。
ft 表示单位为英尺。
在给定以下可选项时, 命令会返回额外的信息:
WITHDIST : 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致。
WITHCOORD : 将位置元素的经度和维度也一并返回。
WITHHASH : 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。
命令默认返回未排序的位置元素。 通过以下两个参数, 用户可以指定被返回位置元素的排序方式:
ASC : 根据中心的位置, 按照从近到远的方式返回位置元素。
DESC : 根据中心的位置, 按照从远到近的方式返回位置元素。
在默认情况下, GEORADIUS 命令会返回所有匹配的位置元素。 虽然用户可以使用 COUNT <count> 选项去获取前 N 个匹配元素, 但是因为命令在内部可能会需要对所有被匹配的元素进行处理, 所以在对一个非常大的区域进行搜索时, 即使只使用 COUNT 选项去获取少量元素, 命令的执行速度也可能会非常慢。 但是从另一方面来说, 使用 COUNT 选项去减少需要返回的元素数量, 对于减少带宽来说仍然是非常有用的。
返回值
GEORADIUS 命令返回一个数组, 具体来说:
在没有给定任何 WITH 选项的情况下, 命令只会返回一个像 [“New York”,”Milan”,”Paris”] 这样的线性(linear)列表。
在指定了 WITHCOORD 、 WITHDIST 、 WITHHASH 等选项的情况下, 命令返回一个二层嵌套数组, 内层的每个子数组就表示一个元素。
在返回嵌套数组时, 子数组的第一个元素总是位置元素的名字。 至于额外的信息, 则会作为子数组的后续元素, 按照以下顺序被返回:
- 以浮点数格式返回的中心与位置元素之间的距离, 单位与用户指定范围时的单位一致。
- geohash 整数。
- 由两个元素组成的坐标,分别为经度和纬度。
举个例子, GEORADIUS Sicily 15 37 200 km withcoord withdist 这样的命令返回的每个子数组都是类似以下格式的:
[“Palermo”,”190.4424”,[“13.361389338970184”,”38.115556395496299”]]
代码示例
1 | redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" |
GEORADIUSBYMEMBER
GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [COUNT count]
可用版本: >= 3.2.0
时间复杂度: O(log(N)+M), 其中 N 为指定范围之内的元素数量, 而 M 则是被返回的元素数量。
这个命令和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点。
返回值
一个数组, 数组中的每个项表示一个范围之内的位置元素。
代码示例
1 | redis> GEOADD Sicily 13.583333 37.316667 "Agrigento" |
GEOHASH
GEOHASH key member [member …]
可用版本: >= 3.2.0
时间复杂度: 寻找每个位置元素的复杂度为 O(log(N)) , 其中 N 为给定键包含的位置元素数量。
返回一个或多个位置元素的 Geohash 表示。
返回值
一个数组, 数组的每个项都是一个 geohash 。 命令返回的 geohash 的位置与用户给定的位置元素的位置一一对应。
代码示例
1 | redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" |
位图
SETBIT
SETBIT key offset value
可用版本: >= 2.2.0
时间复杂度: O(1)
对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。
位的设置或清除取决于 value 参数,可以是 0 也可以是 1 。
当 key 不存在时,自动生成一个新的字符串值。
字符串会进行伸展(grown)以确保它可以将 value 保存在指定的偏移量上。当字符串值进行伸展时,空白位置以 0 填充。
offset 参数必须大于或等于 0 ,小于 2^32 (bit 映射被限制在 512 MB 之内)。
Warning
对使用大的 offset 的 SETBIT 操作来说,内存分配可能造成 Redis 服务器被阻塞。具体参考 SETRANGE key offset value 命令,warning(警告)部分。
返回值
指定偏移量原来储存的位。
代码示例
1 | redis> SETBIT bit 10086 1 |
GETBIT
GETBIT key offset
可用版本: >= 2.2.0
时间复杂度: O(1)
对 key 所储存的字符串值,获取指定偏移量上的位(bit)。
当 offset 比字符串值的长度大,或者 key 不存在时,返回 0 。
返回值
字符串值指定偏移量上的位(bit)。
代码示例
1 | # 对不存在的 key 或者不存在的 offset 进行 GETBIT, 返回 0 |
BITCOUNT
BITCOUNT key [start] [end]
可用版本: >= 2.6.0
时间复杂度: O(N)
计算给定字符串中,被设置为 1 的比特位的数量。
一般情况下,给定的整个字符串都会被进行计数,通过指定额外的 start 或 end 参数,可以让计数只在特定的位上进行。
start 和 end 参数的设置和 GETRANGE key start end 命令类似,都可以使用负数值: 比如 -1 表示最后一个字节, -2 表示倒数第二个字节,以此类推。
不存在的 key 被当成是空字符串来处理,因此对一个不存在的 key 进行 BITCOUNT 操作,结果为 0 。
返回值
被设置为 1 的位的数量。
代码示例
1 | redis> BITCOUNT bits |
模式:使用 bitmap 实现用户上线次数统计
Bitmap 对于一些特定类型的计算非常有效。
假设现在我们希望记录自己网站上的用户的上线频率,比如说,计算用户 A 上线了多少天,用户 B 上线了多少天,诸如此类,以此作为数据,从而决定让哪些用户参加 beta 测试等活动 —— 这个模式可以使用 SETBIT key offset value 和 BITCOUNT key [start] [end] 来实现。
比如说,每当用户在某一天上线的时候,我们就使用 SETBIT key offset value ,以用户名作为 key ,将那天所代表的网站的上线日作为 offset 参数,并将这个 offset 上的为设置为 1 。
举个例子,如果今天是网站上线的第 100 天,而用户 peter 在今天阅览过网站,那么执行命令 SETBIT peter 100 1 ;如果明天 peter 也继续阅览网站,那么执行命令 SETBIT peter 101 1 ,以此类推。
当要计算 peter 总共以来的上线次数时,就使用 BITCOUNT key [start] [end] 命令:执行 BITCOUNT peter ,得出的结果就是 peter 上线的总天数。
性能
前面的上线次数统计例子,即使运行 10 年,占用的空间也只是每个用户 10*365 比特位(bit),也即是每个用户 456 字节。对于这种大小的数据来说, BITCOUNT key [start] [end] 的处理速度就像 GET key 和 INCR key 这种 O(1) 复杂度的操作一样快。
如果你的 bitmap 数据非常大,那么可以考虑使用以下两种方法:
- 将一个大的 bitmap 分散到不同的 key 中,作为小的 bitmap 来处理。使用 Lua 脚本可以很方便地完成这一工作。
- 使用 BITCOUNT key [start] [end] 的 start 和 end 参数,每次只对所需的部分位进行计算,将位的累积工作(accumulating)放到客户端进行,并且对结果进行缓存 (caching)。
BITPOS
BITPOS key bit [start] [end]
可用版本: >= 2.8.7
时间复杂度: O(N),其中 N 为位图包含的二进制位数量
返回位图中第一个值为 bit 的二进制位的位置。
在默认情况下, 命令将检测整个位图, 但用户也可以通过可选的 start 参数和 end 参数指定要检测的范围。
返回值
整数回复。
代码示例
1 | 127.0.0.1:6379> SETBIT bits 3 1 # 1000 |
BITOP
BITOP operation destkey key [key …]
可用版本: >= 2.6.0
时间复杂度: O(N)
对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
operation 可以是 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种:
BITOP AND destkey key [key ...] ,对一个或多个 key 求逻辑并,并将结果保存到 destkey 。
BITOP OR destkey key [key ...] ,对一个或多个 key 求逻辑或,并将结果保存到 destkey 。
BITOP XOR destkey key [key ...] ,对一个或多个 key 求逻辑异或,并将结果保存到 destkey 。
BITOP NOT destkey key ,对给定 key 求逻辑非,并将结果保存到 destkey 。
除了 NOT 操作之外,其他操作都可以接受一个或多个 key 作为输入。
处理不同长度的字符串
当 BITOP 处理不同长度的字符串时,较短的那个字符串所缺少的部分会被看作 0 。
空的 key 也被看作是包含 0 的字符串序列。
返回值
保存到 destkey 的字符串的长度,和输入 key 中最长的字符串长度相等。
Note
BITOP 的复杂度为 O(N) ,当处理大型矩阵(matrix)或者进行大数据量的统计时,最好将任务指派到附属节点(slave)进行,避免阻塞主节点。
代码示例
1 | redis> SETBIT bits-1 0 1 # bits-1 = 1001 |
BITFIELD
BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]
可用版本: >= 3.2.0
时间复杂度: 每个子命令的复杂度为 O(1) 。
BITFIELD 命令可以将一个 Redis 字符串看作是一个由二进制位组成的数组, 并对这个数组中储存的长度不同的整数进行访问 (被储存的整数无需进行对齐)。 换句话说, 通过这个命令, 用户可以执行诸如 “对偏移量 1234 上的 5 位长有符号整数进行设置”、 “获取偏移量 4567 上的 31 位长无符号整数”等操作。 此外, BITFIELD 命令还可以对指定的整数执行加法操作和减法操作, 并且这些操作可以通过设置妥善地处理计算时出现的溢出情况。
BITFIELD 命令可以在一次调用中同时对多个位范围进行操作: 它接受一系列待执行的操作作为参数, 并返回一个数组作为回复, 数组中的每个元素就是对应操作的执行结果。
比如以下命令就展示了如何对位于偏移量 100 的 8 位长有符号整数执行加法操作, 并获取位于偏移量 0 上的 4 位长无符号整数:
1 | > BITFIELD mykey INCRBY i8 100 1 GET u4 0 |
注意:
使用 GET 子命令对超出字符串当前范围的二进制位进行访问(包括键不存在的情况), 超出部分的二进制位的值将被当做是 0 。
使用 SET 子命令或者 INCRBY 子命令对超出字符串当前范围的二进制位进行访问将导致字符串被扩大, 被扩大的部分会使用值为 0 的二进制位进行填充。 在对字符串进行扩展时, 命令会根据字符串目前已有的最远端二进制位, 计算出执行操作所需的最小长度。
支持的子命令以及数字类型
以下是 BITFIELD 命令支持的子命令:
GET <type> <offset> —— 返回指定的二进制位范围。
SET <type> <offset> <value> —— 对指定的二进制位范围进行设置,并返回它的旧值。
INCRBY <type> <offset> <increment> —— 对指定的二进制位范围执行加法操作,并返回它的旧值。用户可以通过向 increment 参数传入负值来实现相应的减法操作。
除了以上三个子命令之外, 还有一个子命令, 它可以改变之后执行的 INCRBY 子命令在发生溢出情况时的行为:
OVERFLOW [WRAP|SAT|FAIL]
当被设置的二进制位范围值为整数时, 用户可以在类型参数的前面添加 i 来表示有符号整数, 或者使用 u 来表示无符号整数。 比如说, 我们可以使用 u8 来表示 8 位长的无符号整数, 也可以使用 i16 来表示 16 位长的有符号整数。
BITFIELD 命令最大支持 64 位长的有符号整数以及 63 位长的无符号整数, 其中无符号整数的 63 位长度限制是由于 Redis 协议目前还无法返回 64 位长的无符号整数而导致的。
二进制位和位置偏移量
在二进制位范围命令中, 用户有两种方法来设置偏移量:
如果用户给定的是一个没有任何前缀的数字, 那么这个数字指示的就是字符串以零为开始(zero-base)的偏移量。
另一方面, 如果用户给定的是一个带有 # 前缀的偏移量, 那么命令将使用这个偏移量与被设置的数字类型的位长度相乘, 从而计算出真正的偏移量。
比如说, 对于以下这个命令来说:
BITFIELD mystring SET i8 #0 100 i8 #1 200
命令会把 mystring 键里面, 第一个 i8 长度的二进制位的值设置为 100 , 并把第二个 i8 长度的二进制位的值设置为 200 。 当我们把一个字符串键当成数组来使用, 并且数组中储存的都是同等长度的整数时, 使用 # 前缀可以让我们免去手动计算被设置二进制位所在位置的麻烦。
溢出控制
用户可以通过 OVERFLOW 命令以及以下展示的三个参数, 指定 BITFIELD 命令在执行自增或者自减操作时, 碰上向上溢出(overflow)或者向下溢出(underflow)情况时的行为:
WRAP : 使用回绕(wrap around)方法处理有符号整数和无符号整数的溢出情况。 对于无符号整数来说, 回绕就像使用数值本身与能够被储存的最大无符号整数执行取模计算, 这也是 C 语言的标准行为。 对于有符号整数来说, 上溢将导致数字重新从最小的负数开始计算, 而下溢将导致数字重新从最大的正数开始计算。 比如说, 如果我们对一个值为 127 的 i8 整数执行加一操作, 那么将得到结果 -128 。
SAT : 使用饱和计算(saturation arithmetic)方法处理溢出, 也即是说, 下溢计算的结果为最小的整数值, 而上溢计算的结果为最大的整数值。 举个例子, 如果我们对一个值为 120 的 i8 整数执行加 10 计算, 那么命令的结果将为 i8 类型所能储存的最大整数值 127 。 与此相反, 如果一个针对 i8 值的计算造成了下溢, 那么这个 i8 值将被设置为 -127 。
FAIL : 在这一模式下, 命令将拒绝执行那些会导致上溢或者下溢情况出现的计算, 并向用户返回空值表示计算未被执行。
需要注意的是, OVERFLOW 子命令只会对紧随着它之后被执行的 INCRBY 命令产生效果, 这一效果将一直持续到与它一同被执行的下一个 OVERFLOW 命令为止。 在默认情况下, INCRBY 命令使用 WRAP 方式来处理溢出计算。
以下是一个使用 OVERFLOW 子命令来控制溢出行为的例子:
1 | > BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1 |
而以下则是一个因为 OVERFLOW FAIL 行为而导致子命令返回空值的例子:
1 | > BITFIELD mykey OVERFLOW FAIL incrby u2 102 1 |
作用
BITFIELD 命令的作用在于它能够将很多小的整数储存到一个长度较大的位图中, 又或者将一个非常庞大的键分割为多个较小的键来进行储存, 从而非常高效地使用内存, 使得 Redis 能够得到更多不同的应用 —— 特别是在实时分析领域: BITFIELD 能够以指定的方式对计算溢出进行控制的能力, 使得它可以被应用于这一领域。
性能注意事项
BITFIELD 在一般情况下都是一个快速的命令, 需要注意的是, 访问一个长度较短的字符串的远端二进制位将引发一次内存分配操作, 这一操作花费的时间可能会比命令访问已有的字符串花费的时间要长。
二进制位的排列
BITFIELD 把位图第一个字节偏移量 0 上的二进制位看作是 most significant 位, 以此类推。 举个例子, 如果我们对一个已经预先被全部设置为 0 的位图进行设置, 将它在偏移量 7 的值设置为 5 位无符号整数值 23 (二进制位为 10111 ), 那么命令将生产出以下这个位图表示:
1 | +--------+--------+ |
当偏移量和整数长度与字节边界进行对齐时, BITFIELD 表示二进制位的方式跟大端表示法(big endian)一致, 但是在没有对齐的情况下, 理解这些二进制位是如何进行排列也是非常重要的。
返回值
BITFIELD 命令的返回值是一个数组, 数组中的每个元素对应一个被执行的子命令。 需要注意的是, OVERFLOW 子命令本身并不产生任何回复。
数据库
EXISTS
EXISTS key
可用版本: >= 1.0.0
时间复杂度: O(1)
检查给定 key 是否存在。
返回值
若 key 存在,返回 1 ,否则返回 0 。
代码示例
1 | redis> SET db "redis" |
TYPE
TYPE key
可用版本: >= 1.0.0
时间复杂度: O(1)
返回 key 所储存的值的类型。
返回值
none (key不存在)
string (字符串)
list (列表)
set (集合)
zset (有序集)
hash (哈希表)
stream (流)
代码示例
1 | # 字符串 |
RENAME
RENAME key newkey
可用版本: >= 1.0.0
时间复杂度: O(1)
将 key 改名为 newkey 。
当 key 和 newkey 相同,或者 key 不存在时,返回一个错误。
当 newkey 已经存在时, RENAME 命令将覆盖旧值。
返回值
改名成功时提示 OK ,失败时候返回一个错误。
代码示例
1 | # key 存在且 newkey 不存在 |
RENAMENX
RENAMENX key newkey
可用版本: >= 1.0.0
时间复杂度: O(1)
当且仅当 newkey 不存在时,将 key 改名为 newkey 。
当 key 不存在时,返回一个错误。
返回值
修改成功时,返回 1 ; 如果 newkey 已经存在,返回 0 。
代码示例
1 | # newkey 不存在,改名成功 |
MOVE
MOVE key db
可用版本: >= 1.0.0
时间复杂度: O(1)
将当前数据库的 key 移动到给定的数据库 db 当中。
如果当前数据库(源数据库)和给定数据库(目标数据库)有相同名字的给定 key ,或者 key 不存在于当前数据库,那么 MOVE 没有任何效果。
因此,也可以利用这一特性,将 MOVE 当作锁(locking)原语(primitive)。
返回值
移动成功返回 1 ,失败则返回 0 。
代码示例
1 | # key 存在于当前数据库 |
DEL
DEL key [key …]
可用版本: >= 1.0.0
时间复杂度:O(N), N 为被删除的 key 的数量,其中删除单个字符串类型的 key ,时间复杂度为O(1);删除单个列表、集合、有序集合或哈希表类型的 key ,时间复杂度为O(M), M 为以上数据结构内的元素数量。
删除给定的一个或多个 key 。
不存在的 key 会被忽略。
返回值
被删除 key 的数量。
代码示例
1 | # 删除单个 key |
RANDOMKEY
RANDOMKEY
可用版本: >= 1.0.0
时间复杂度: O(1)
从当前数据库中随机返回(不删除)一个 key 。
返回值
当数据库不为空时,返回一个 key 。 当数据库为空时,返回 nil 。
代码示例
1 | # 数据库不为空 |
DBSIZE
DBSIZE
可用版本: >= 1.0.0
时间复杂度: O(1)
返回当前数据库的 key 的数量。
返回值
当前数据库的 key 的数量。
代码示例
1 | redis> DBSIZE |
KEYS
KEYS pattern
可用版本: >= 1.0.0
时间复杂度: O(N), N 为数据库中 key 的数量。
查找所有符合给定模式 pattern 的 key , 比如说:
KEYS * 匹配数据库中所有 key 。
KEYS h?llo 匹配 hello , hallo 和 hxllo 等。
KEYS h*llo 匹配 hllo 和 heeeeello 等。
KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo 。
特殊符号用 \ 隔开。
Warning
KEYS 的速度非常快,但在一个大的数据库中使用它仍然可能造成性能问题,如果你需要从一个数据集中查找特定的 key ,你最好还是用 Redis 的集合结构(set)来代替。
返回值
符合给定模式的 key 列表。
代码示例
1 | redis> MSET one 1 two 2 three 3 four 4 # 一次设置 4 个 key |
SCAN
SCAN cursor [MATCH pattern] [COUNT count]
可用版本: >= 2.8.0
时间复杂度:增量式迭代命令每次执行的复杂度为 O(1) , 对数据集进行一次完整迭代的复杂度为 O(N) , 其中 N 为数据集中的元素数量。
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)。
因为 SCAN 、 SSCAN 、 HSCAN 和 ZSCAN 四个命令的工作方式都非常相似, 所以这个文档会一并介绍这四个命令, 但是要记住:
- SSCAN 命令、 HSCAN 命令和 ZSCAN 命令的第一个参数总是一个数据库键。
- 而 SCAN 命令则不需要在第一个参数提供任何数据库键 —— 因为它迭代的是当前数据库中的所有数据库键。
SCAN 命令的基本用法
SCAN 命令是一个基于游标的迭代器(cursor based iterator): SCAN 命令每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数, 以此来延续之前的迭代过程。
当 SCAN 命令的游标参数被设置为 0 时, 服务器将开始一次新的迭代, 而当服务器向用户返回值为 0 的游标时, 表示迭代已结束。
以下是一个 SCAN 命令的迭代过程示例:
1 | redis 127.0.0.1:6379> scan 0 |
在上面这个例子中, 第一次迭代使用 0 作为游标, 表示开始一次新的迭代。
第二次迭代使用的是第一次迭代时返回的游标, 也即是命令回复第一个元素的值 —— 17 。
从上面的示例可以看到, 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 选项指定的值, 在第一次迭代就将数据集包含的所有元素都返回给用户。
Note
并非每次迭代都要使用相同的 COUNT 值。
用户可以在每次迭代中按自己的需要随意改变 COUNT 值, 只要记得将上次迭代返回的游标用到下次迭代里面就可以了。
MATCH 选项
和 KEYS 命令一样, 增量式迭代命令也可以通过提供一个 glob 风格的模式参数, 让命令只返回和给定模式相匹配的元素, 这一点可以通过在执行增量式迭代命令时, 通过给定 MATCH <pattern> 参数来实现。
以下是一个使用 MATCH 选项进行迭代的示例:
1 | redis 127.0.0.1:6379> sadd myset 1 2 3 foo foobar feelsgood |
需要注意的是, 对元素的模式匹配工作是在命令从数据集中取出元素之后, 向客户端返回元素之前的这段时间内进行的, 所以如果被迭代的数据集中只有少量元素和模式相匹配, 那么迭代命令或许会在多次执行中都不返回任何元素。
以下是这种情况的一个例子:
1 | redis 127.0.0.1:6379> scan 0 MATCH *11* |
如你所见, 以上的大部分迭代都不返回任何元素。
在最后一次迭代, 我们通过将 COUNT 选项的参数设置为 1000 , 强制命令为本次迭代扫描更多元素, 从而使得命令返回的元素也变多了。
并发执行多个迭代
在同一时间, 可以有任意多个客户端对同一数据集进行迭代, 客户端每次执行迭代都需要传入一个游标, 并在迭代执行之后获得一个新的游标, 而这个游标就包含了迭代的所有状态, 因此, 服务器无须为迭代记录任何状态。
中途停止迭代
因为迭代的所有状态都保存在游标里面, 而服务器无须为迭代保存任何状态, 所以客户端可以在中途停止一个迭代, 而无须对服务器进行任何通知。
即使有任意数量的迭代在中途停止, 也不会产生任何问题。
使用错误的游标进行增量式迭代
使用间断的(broken)、负数、超出范围或者其他非正常的游标来执行增量式迭代并不会造成服务器崩溃, 但可能会让命令产生未定义的行为。
未定义行为指的是, 增量式命令对返回值所做的保证可能会不再为真。
只有两种游标是合法的:
- 在开始一个新的迭代时, 游标必须为 0 。
- 增量式迭代命令在执行之后返回的, 用于延续(continue)迭代过程的游标。
迭代终结的保证
增量式迭代命令所使用的算法只保证在数据集的大小有界(bounded)的情况下, 迭代才会停止, 换句话说, 如果被迭代数据集的大小不断地增长的话, 增量式迭代命令可能永远也无法完成一次完整迭代。
从直觉上可以看出, 当一个数据集不断地变大时, 想要访问这个数据集中的所有元素就需要做越来越多的工作, 能否结束一个迭代取决于用户执行迭代的速度是否比数据集增长的速度更快。
返回值
SCAN 命令、 SSCAN 命令、 HSCAN 命令和 ZSCAN 命令都返回一个包含两个元素的 multi-bulk 回复: 回复的第一个元素是字符串表示的无符号 64 位整数(游标), 回复的第二个元素是另一个 multi-bulk 回复, 这个 multi-bulk 回复包含了本次被迭代的元素。
SCAN 命令返回的每个元素都是一个数据库键。
SSCAN 命令返回的每个元素都是一个集合成员。
HSCAN 命令返回的每个元素都是一个键值对,一个键值对由一个键和一个值组成。
ZSCAN 命令返回的每个元素都是一个有序集合元素,一个有序集合元素由一个成员(member)和一个分值(score)组成。
SORT
SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern …]] [ASC | DESC] [ALPHA] [STORE destination]
可用版本: >= 1.0.0
时间复杂度: O(N+M*log(M)), N 为要排序的列表或集合内的元素数量, M 为要返回的元素数量。如果只是使用 SORT 命令的 GET 选项获取数据而没有进行排序,时间复杂度 O(N)。
返回或保存给定列表、集合、有序集合 key 中经过排序的元素。
排序默认以数字作为对象,值被解释为双精度浮点数,然后进行比较。
一般 SORT 用法
最简单的 SORT 使用方法是 SORT key 和 SORT key DESC :
- SORT key 返回键值从小到大排序的结果。
- SORT key DESC 返回键值从大到小排序的结果。
假设 today_cost 列表保存了今日的开销金额, 那么可以用 SORT 命令对它进行排序:
1 | # 开销金额列表 |
使用 ALPHA 修饰符对字符串进行排序
因为 SORT 命令默认排序对象为数字, 当需要对字符串进行排序时, 需要显式地在 SORT 命令之后添加 ALPHA 修饰符:
1 | # 网址 |
如果系统正确地设置了 LC_COLLATE 环境变量的话,Redis能识别 UTF-8 编码。
使用 LIMIT 修饰符限制返回结果
排序之后返回元素的数量可以通过 LIMIT 修饰符进行限制, 修饰符接受 offset 和 count 两个参数:
- offset 指定要跳过的元素数量。
- count 指定跳过 offset 个指定的元素之后,要返回多少个对象。
以下例子返回排序结果的前 5 个对象( offset 为 0 表示没有元素被跳过)。可以组合使用多个修饰符。以下例子返回从大到小排序的前 5 个对象。1
2
3
4
5
6
7
8
9
10
11
12
13
14# 添加测试数据,列表值为 1 指 10
redis 127.0.0.1:6379> RPUSH rank 1 3 5 7 9
(integer) 5
redis 127.0.0.1:6379> RPUSH rank 2 4 6 8 10
(integer) 10
# 返回列表中最小的 5 个值
redis 127.0.0.1:6379> SORT rank LIMIT 0 5
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"1
2
3
4
5
6redis 127.0.0.1:6379> SORT rank LIMIT 0 5 DESC
1) "10"
2) "9"
3) "8"
4) "7"
5) "6"使用外部 key 进行排序
可以使用外部 key 的数据作为权重,代替默认的直接对比键值的方式来进行排序。
假设现在有用户数据如下:
以下代码将数据输入到 Redis 中:
1 | # admin |
BY 选项
默认情况下, SORT uid 直接按 uid 中的值排序:
1 | redis 127.0.0.1:6379> SORT uid |
通过使用 BY 选项,可以让 uid 按其他键的元素来排序。
比如说, 以下代码让 uid 键按照 user_level_{uid} 的大小来排序:
1 | redis 127.0.0.1:6379> SORT uid BY user_level_* |
user_level_* 是一个占位符, 它先取出 uid 中的值, 然后再用这个值来查找相应的键。
比如在对 uid 列表进行排序时, 程序就会先取出 uid 的值 1 、 2 、 3 、 4 , 然后使用 user_level_1 、 user_level_2 、 user_level_3 和 user_level_4 的值作为排序 uid 的权重。
GET 选项
使用 GET 选项, 可以根据排序的结果来取出相应的键值。
比如说, 以下代码先排序 uid , 再取出键 user_name_{uid} 的值:
1 | redis 127.0.0.1:6379> SORT uid GET user_name_* |
组合使用 BY 和 GET
通过组合使用 BY 和 GET , 可以让排序结果以更直观的方式显示出来。
比如说, 以下代码先按 user_level_{uid} 来排序 uid 列表, 再取出相应的 user_name_{uid} 的值:
1 | redis 127.0.0.1:6379> SORT uid BY user_level_* GET user_name_* |
现在的排序结果要比只使用 SORT uid BY user_level_* 要直观得多。
获取多个外部键
可以同时使用多个 GET 选项, 获取多个外部键的值。
以下代码就按 uid 分别获取 user_level_{uid} 和 user_name_{uid} :
1 | redis 127.0.0.1:6379> SORT uid GET user_level_* GET user_name_* |
GET 有一个额外的参数规则,那就是 —— 可以用 # 获取被排序键的值。
以下代码就将 uid 的值、及其相应的 user_level_* 和 user_name_* 都返回为结果:
1 | redis 127.0.0.1:6379> SORT uid GET # GET user_level_* GET user_name_* |
获取外部键,但不进行排序
通过将一个不存在的键作为参数传给 BY 选项, 可以让 SORT 跳过排序操作, 直接返回结果:
1 | redis 127.0.0.1:6379> SORT uid BY not-exists-key |
这种用法在单独使用时,没什么实际用处。
不过,通过将这种用法和 GET 选项配合, 就可以在不排序的情况下, 获取多个外部键, 相当于执行一个整合的获取操作(类似于 SQL 数据库的 join 关键字)。
以下代码演示了,如何在不引起排序的情况下,使用 SORT 、 BY 和 GET 获取多个外部键:
1 | redis 127.0.0.1:6379> SORT uid BY not-exists-key GET # GET user_level_* GET user_name_* |
将哈希表作为 GET 或 BY 的参数
除了可以将字符串键之外, 哈希表也可以作为 GET 或 BY 选项的参数来使用。
比如说,对于前面给出的用户信息表:
我们可以不将用户的名字和级别保存在 user_name_{uid} 和 user_level_{uid} 两个字符串键中, 而是用一个带有 name 域和 level 域的哈希表 user_info_{uid} 来保存用户的名字和级别信息:
1 | redis 127.0.0.1:6379> HMSET user_info_1 name admin level 9999 |
之后, BY 和 GET 选项都可以用 key->field 的格式来获取哈希表中的域的值, 其中 key 表示哈希表键, 而 field 则表示哈希表的域:
1 | redis 127.0.0.1:6379> SORT uid BY user_info_*->level |
保存排序结果
默认情况下, SORT 操作只是简单地返回排序结果,并不进行任何保存操作。
通过给 STORE 选项指定一个 key 参数,可以将排序结果保存到给定的键上。
如果被指定的 key 已存在,那么原有的值将被排序结果覆盖。
1 | # 测试数据 |
可以通过将 SORT 命令的执行结果保存,并用 EXPIRE key seconds 为结果设置生存时间,以此来产生一个 SORT 操作的结果缓存。
这样就可以避免对 SORT 操作的频繁调用:只有当结果集过期时,才需要再调用一次 SORT 操作。
另外,为了正确实现这一用法,你可能需要加锁以避免多个客户端同时进行缓存重建(也就是多个客户端,同一时间进行 SORT 操作,并保存为结果集),具体参见 SETNX key value 命令。
返回值
没有使用 STORE 参数,返回列表形式的排序结果。 使用 STORE 参数,返回排序结果的元素数量。
FLUSHDB
FLUSHDB
可用版本: >= 1.0.0
时间复杂度: O(1)
清空当前数据库中的所有 key。
此命令从不失败。
返回值
总是返回 OK 。
代码示例
1 | redis> DBSIZE # 清空前的 key 数量 |
FLUSHALL
FLUSHALL
可用版本: >= 1.0.0
时间复杂度: O(N)
清空整个 Redis 服务器的数据(删除所有数据库的所有 key )。
此命令从不失败。
返回值
总是返回 OK 。
1 | redis> DBSIZE # 0 号数据库的 key 数量 |
SELECT
SELECT index
可用版本: >= 1.0.0
时间复杂度: O(1)
切换到指定的数据库,数据库索引号 index 用数字值指定,以 0 作为起始索引值。
默认使用 0 号数据库。
返回值
OK
代码示例
1 | redis> SET db_number 0 # 默认使用 0 号数据库 |
SWAPDB
SWAPDB db1 db2
版本要求: >= 4.0.0
时间复杂度: O(1)
对换指定的两个数据库, 使得两个数据库的数据立即互换。
返回值
OK
代码示例
1 | # 对换数据库 0 和数据库 1 |
自动过期
EXPIRE
EXPIRE key seconds
可用版本: >= 1.0.0
时间复杂度: O(1)
为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。
在 Redis 中,带有生存时间的 key 被称为『易失的』(volatile)。
生存时间可以通过使用 DEL 命令来删除整个 key 来移除,或者被 SET 和 GETSET 命令覆写(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 。
代码示例
1 | redis> SET cache_page "www.google.com" |
模式:导航会话
假设你有一项 web 服务,打算根据用户最近访问的 N 个页面来进行物品推荐,并且假设用户停止阅览超过 60 秒,那么就清空阅览记录(为了减少物品推荐的计算量,并且保持推荐物品的新鲜度)。
这些最近访问的页面记录,我们称之为『导航会话』(Navigation session),可以用 INCR 和 RPUSH 命令在 Redis 中实现它:每当用户阅览一个网页的时候,执行以下代码:
1 | MULTI |
如果用户停止阅览超过 60 秒,那么它的导航会话就会被清空,当用户重新开始阅览的时候,系统又会重新记录导航会话,继续进行物品推荐。
EXPIREAT
EXPIREAT key timestamp
可用版本: >= 1.2.0
时间复杂度: O(1)
EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置生存时间。
不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。
返回值
如果生存时间设置成功,返回 1 ; 当 key 不存在或没办法设置生存时间,返回 0 。
代码示例
1 | redis> SET cache www.google.com |
TTL
TTL key
可用版本: >= 1.0.0
时间复杂度: O(1)
以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。
返回值
当 key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1 。 否则,以秒为单位,返回 key 的剩余生存时间。
Note
在 Redis 2.8 以前,当 key 不存在,或者 key 没有设置剩余生存时间时,命令都返回 -1 。
代码示例
1 | # 不存在的 key |
PERSIST
PERSIST key
可用版本: >= 2.2.0
时间复杂度: O(1)
移除给定 key 的生存时间,将这个 key 从“易失的”(带生存时间 key )转换成“持久的”(一个不带生存时间、永不过期的 key )。
返回值
当生存时间移除成功时,返回 1 . 如果 key 不存在或 key 没有设置生存时间,返回 0 。
代码示例
1 | redis> SET mykey "Hello" |
PEXPIRE
PEXPIRE key milliseconds
可用版本: >= 2.6.0
时间复杂度: O(1)
这个命令和 EXPIRE 命令的作用类似,但是它以毫秒为单位设置 key 的生存时间,而不像 EXPIRE 命令那样,以秒为单位。
返回值
设置成功,返回 1 key 不存在或设置失败,返回 0
代码示例
1 | redis> SET mykey "Hello" |
PEXPIREAT
PEXPIREAT key milliseconds-timestamp
可用版本: >= 2.6.0
时间复杂度: O(1)
这个命令和 expireat 命令类似,但它以毫秒为单位设置 key 的过期 unix 时间戳,而不是像 expireat 那样,以秒为单位。
返回值
如果生存时间设置成功,返回 1 。 当 key 不存在或没办法设置生存时间时,返回 0 。(查看 EXPIRE key seconds 命令获取更多信息)
代码示例
1 | redis> SET mykey "Hello" |
PTTL
PTTL key
可用版本: >= 2.6.0
复杂度: O(1)
这个命令类似于 TTL 命令,但它以毫秒为单位返回 key 的剩余生存时间,而不是像 TTL 命令那样,以秒为单位。
返回值
在 Redis 2.8 以前,当 key 不存在,或者 key 没有设置剩余生存时间时,命令都返回 -1 。
代码示例
1 | # 不存在的 key |
事务
MULTI
MULTI
可用版本:>= 1.2.0
时间复杂度:O(1)。
标记一个事务块的开始。
事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由 EXEC 命令原子性(atomic)地执行。
返回值:
总是返回 OK 。
代码示例
1 | redis> MULTI # 标记事务开始 |
EXEC
EXEC
可用版本:>= 1.2.0
时间复杂度:事务块内所有命令的时间复杂度的总和。
执行所有事务块内的命令。
假如某个(或某些) key 正处于 WATCH 命令的监视之下,且事务块中有和这个(或这些) key 相关的命令,那么 EXEC 命令只在这个(或这些) key 没有被其他命令所改动的情况下执行并生效,否则该事务被打断(abort)。
返回值:
事务块内所有命令的返回值,按命令执行的先后顺序排列。
当操作被打断时,返回空值 nil 。
代码示例
1 | # 事务被成功执行 |
DISCARD
DISCARD
可用版本: >= 2.0.0
时间复杂度: O(1)。
取消事务,放弃执行事务块内的所有命令。
如果正在使用 WATCH 命令监视某个(或某些) key,那么取消所有监视,等同于执行命令 UNWATCH 。
返回值
总是返回 OK 。
代码示例
1 | redis> MULTI |
WATCH
WATCH key [key …]
可用版本:>= 2.2.0
时间复杂度:O(1)。
监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
返回值:
总是返回 OK 。
代码示例
1 | redis> WATCH lock lock_times |
UNWATCH
UNWATCH
可用版本:>= 2.2.0
时间复杂度:O(1)
取消 WATCH 命令对所有 key 的监视。
如果在执行 WATCH 命令之后, EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行 UNWATCH 了。
因为 EXEC 命令会执行事务,因此 WATCH 命令的效果已经产生了;而 DISCARD 命令在取消事务的同时也会取消所有对 key 的监视,因此这两个命令执行之后,就没有必要执行 UNWATCH 了。
返回值:
总是 OK 。
代码示例
1 | redis> WATCH lock lock_times |
Lua 脚本
EVAL
EVAL script numkeys key [key …] arg [arg …]
可用版本: >= 2.6.0
时间复杂度: EVAL 和 EVALSHA 可以在 O(1) 复杂度内找到要被执行的脚本,其余的复杂度取决于执行的脚本本身。
从 Redis 2.6.0 版本开始,通过内置的 Lua 解释器,可以使用 EVAL 命令对 Lua 脚本进行求值。
script 参数是一段 Lua 5.1 脚本程序,它会被运行在 Redis 服务器上下文中,这段脚本不必(也不应该)定义为一个 Lua 函数。
numkeys 参数用于指定键名参数的个数。
键名参数 key [key …] 从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。
在命令的最后,那些不是键名参数的附加参数 arg [arg …] ,可以在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)。
上面这几段长长的说明可以用一个简单的例子来概括:
1 | > eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second |
其中 “return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}” 是被求值的 Lua 脚本,数字 2 指定了键名参数的数量, key1 和 key2 是键名参数,分别使用 KEYS[1] 和 KEYS[2] 访问,而最后的 first 和 second 则是附加参数,可以通过 ARGV[1] 和 ARGV[2] 访问它们。
在 Lua 脚本中,可以使用两个不同函数来执行 Redis 命令,它们分别是:
- redis.call()
- redis.pcall()
这两个函数的唯一区别在于它们使用不同的方式处理执行命令所产生的错误,在后面的『错误处理』部分会讲到这一点。
redis.call() 和 redis.pcall() 两个函数的参数可以是任何格式良好(well formed)的 Redis 命令:
1 | > eval "return redis.call('set','foo','bar')" 0 |
需要注意的是,上面这段脚本的确实现了将键 foo 的值设为 bar 的目的,但是,它违反了 EVAL 命令的语义,因为脚本里使用的所有键都应该由 KEYS 数组来传递,就像这样:
1 | > eval "return redis.call('set',KEYS[1],'bar')" 1 foo |
要求使用正确的形式来传递键(key)是有原因的,因为不仅仅是 EVAL 这个命令,所有的 Redis 命令,在执行之前都会被分析,籍此来确定命令会对哪些键进行操作。
因此,对于 EVAL 命令来说,必须使用正确的形式来传递键,才能确保分析工作正确地执行。除此之外,使用正确的形式来传递键还有很多其他好处,它的一个特别重要的用途就是确保 Redis 集群可以将你的请求发送到正确的集群节点。(对 Redis 集群的工作还在进行当中,但是脚本功能被设计成可以与集群功能保持兼容。)不过,这条规矩并不是强制性的,从而使得用户有机会滥用(abuse) Redis 单实例配置(single instance configuration),代价是这样写出的脚本不能被 Redis 集群所兼容。
在 Lua 数据类型和 Redis 数据类型之间转换
当 Lua 通过 call() 或 pcall() 函数执行 Redis 命令的时候,命令的返回值会被转换成 Lua 数据结构。同样地,当 Lua 脚本在 Redis 内置的解释器里运行时,Lua 脚本的返回值也会被转换成 Redis 协议(protocol),然后由 EVAL 将值返回给客户端。
数据类型之间的转换遵循这样一个设计原则:如果将一个 Redis 值转换成 Lua 值,之后再将转换所得的 Lua 值转换回 Redis 值,那么这个转换所得的 Redis 值应该和最初时的 Redis 值一样。
换句话说, Lua 类型和 Redis 类型之间存在着一一对应的转换关系。
以下列出的是详细的转换规则:
从 Redis 转换到 Lua :
Redis integer reply -> Lua number / Redis 整数转换成 Lua 数字
Redis bulk reply -> Lua string / Redis bulk 回复转换成 Lua 字符串
Redis multi bulk reply -> Lua table (may have other Redis data types nested) / Redis 多条 bulk 回复转换成 Lua 表,表内可能有其他别的 Redis 数据类型
Redis status reply -> Lua table with a single ok field containing the status / Redis 状态回复转换成 Lua 表,表内的 ok 域包含了状态信息
Redis error reply -> Lua table with a single err field containing the error / Redis 错误回复转换成 Lua 表,表内的 err 域包含了错误信息
Redis Nil bulk reply and Nil multi bulk reply -> Lua false boolean type / Redis 的 Nil 回复和 Nil 多条回复转换成 Lua 的布尔值 false
从 Lua 转换到 Redis:Lua number -> Redis integer reply / Lua 数字转换成 Redis 整数
Lua string -> Redis bulk reply / Lua 字符串转换成 Redis bulk 回复
Lua table (array) -> Redis multi bulk reply / Lua 表(数组)转换成 Redis 多条 bulk 回复
Lua table with a single ok field -> Redis status reply / 一个带单个 ok 域的 Lua 表,转换成 Redis 状态回复
Lua table with a single err field -> Redis error reply / 一个带单个 err 域的 Lua 表,转换成 Redis 错误回复
Lua boolean false -> Redis Nil bulk reply / Lua 的布尔值 false 转换成 Redis 的 Nil bulk 回复
从 Lua 转换到 Redis 有一条额外的规则,这条规则没有和它对应的从 Redis 转换到 Lua 的规则:Lua boolean true -> Redis integer reply with value of 1 / Lua 布尔值 true 转换成 Redis 整数回复中的 1
以下是几个类型转换的例子:1
2
3
4
5
6
7
8
9
10
11> eval "return 10" 0
(integer) 10
> eval "return {1,2,{3,'Hello World!'}}" 0
1) (integer) 1
2) (integer) 2
3) 1) (integer) 3
2) "Hello World!"
> eval "return redis.call('get','foo')" 0
"bar"在上面的三个代码示例里,前两个演示了如何将 Lua 值转换成 Redis 值,最后一个例子更复杂一些,它演示了一个将 Redis 值转换成 Lua 值,然后再将 Lua 值转换成 Redis 值的类型转过程。
脚本的原子性
Redis 使用单个 Lua 解释器去运行所有脚本,并且, Redis 也保证脚本会以原子性(atomic)的方式执行:当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行。这和使用 MULTI / EXEC 包围的事务很类似。在其他别的客户端看来,脚本的效果(effect)要么是不可见的(not visible),要么就是已完成的(already completed)。
另一方面,这也意味着,执行一个运行缓慢的脚本并不是一个好主意。写一个跑得很快很顺溜的脚本并不难,因为脚本的运行开销(overhead)非常少,但是当你不得不使用一些跑得比较慢的脚本时,请小心,因为当这些蜗牛脚本在慢吞吞地运行的时候,其他客户端会因为服务器正忙而无法执行命令。
错误处理
前面的命令介绍部分说过, redis.call() 和 redis.pcall() 的唯一区别在于它们对错误处理的不同。
当 redis.call() 在执行命令的过程中发生错误时,脚本会停止执行,并返回一个脚本错误,错误的输出信息会说明错误造成的原因:
1 | redis> lpush foo a |
和 redis.call() 不同, redis.pcall() 出错时并不引发(raise)错误,而是返回一个带 err 域的 Lua 表(table),用于表示错误:
1 | redis 127.0.0.1:6379> EVAL "return redis.pcall('get', 'foo')" 0 |
带宽和 EVALSHA
EVAL 命令要求你在每次执行脚本的时候都发送一次脚本主体(script body)。Redis 有一个内部的缓存机制,因此它不会每次都重新编译脚本,不过在很多场合,付出无谓的带宽来传送脚本主体并不是最佳选择。
为了减少带宽的消耗, Redis 实现了 EVALSHA 命令,它的作用和 EVAL 一样,都用于对脚本求值,但它接受的第一个参数不是脚本,而是脚本的 SHA1 校验和(sum)。
EVALSHA 命令的表现如下:
- 如果服务器还记得给定的 SHA1 校验和所指定的脚本,那么执行这个脚本
- 如果服务器不记得给定的 SHA1 校验和所指定的脚本,那么它返回一个特殊的错误,提醒用户使用 EVAL 代替 EVALSHA
以下是示例:客户端库的底层实现可以一直乐观地使用 EVALSHA 来代替 EVAL ,并期望着要使用的脚本已经保存在服务器上了,只有当 NOSCRIPT 错误发生时,才使用 EVAL 命令重新发送脚本,这样就可以最大限度地节省带宽。1
2
3
4
5
6
7
8
9
10
11> set foo bar
OK
> eval "return redis.call('get','foo')" 0
"bar"
> evalsha 6b1bf486c81ceb7edf3c093f4c48582e38c0e791 0
"bar"
> evalsha ffffffffffffffffffffffffffffffffffffffff 0
(error) `NOSCRIPT` No matching script. Please use [EVAL](/commands/eval).
这也说明了执行 EVAL 命令时,使用正确的格式来传递键名参数和附加参数的重要性:因为如果将参数硬写在脚本中,那么每次当参数改变的时候,都要重新发送脚本,即使脚本的主体并没有改变,相反,通过使用正确的格式来传递键名参数和附加参数,就可以在脚本主体不变的情况下,直接使用 EVALSHA 命令对脚本进行复用,免去了无谓的带宽消耗。
脚本缓存
Redis 保证所有被运行过的脚本都会被永久保存在脚本缓存当中,这意味着,当 EVAL 命令在一个 Redis 实例上成功执行某个脚本之后,随后针对这个脚本的所有 EVALSHA 命令都会成功执行。
刷新脚本缓存的唯一办法是显式地调用 SCRIPT FLUSH 命令,这个命令会清空运行过的所有脚本的缓存。通常只有在云计算环境中,Redis 实例被改作其他客户或者别的应用程序的实例时,才会执行这个命令。
缓存可以长时间储存而不产生内存问题的原因是,它们的体积非常小,而且数量也非常少,即使脚本在概念上类似于实现一个新命令,即使在一个大规模的程序里有成百上千的脚本,即使这些脚本会经常修改,即便如此,储存这些脚本的内存仍然是微不足道的。
事实上,用户会发现 Redis 不移除缓存中的脚本实际上是一个好主意。比如说,对于一个和 Redis 保持持久化链接(persistent connection)的程序来说,它可以确信,执行过一次的脚本会一直保留在内存当中,因此它可以在流水线中使用 EVALSHA 命令而不必担心因为找不到所需的脚本而产生错误(稍候我们会看到在流水线中执行脚本的相关问题)。
SCRIPT 命令
Redis 提供了以下几个 SCRIPT 命令,用于对脚本子系统(scripting subsystem)进行控制:
- SCRIPT FLUSH :清除所有脚本缓存
- SCRIPT EXISTS sha1 [sha1 …] :根据给定的脚本校验和,检查指定的脚本是否存在于脚本缓存
- SCRIPT LOAD script :将一个脚本装入脚本缓存,但并不立即运行它
- SCRIPT KILL :杀死当前正在运行的脚本
纯函数脚本
在编写脚本方面,一个重要的要求就是,脚本应该被写成纯函数(pure function)。
也就是说,脚本应该具有以下属性:
- 对于同样的数据集输入,给定相同的参数,脚本执行的 Redis 写命令总是相同的。脚本执行的操作不能依赖于任何隐藏(非显式)数据,不能依赖于脚本在执行过程中、或脚本在不同执行时期之间可能变更的状态,并且它也不能依赖于任何来自 I/O 设备的外部输入。
使用系统时间(system time),调用像 RANDOMKEY 那样的随机命令,或者使用 Lua 的随机数生成器,类似以上的这些操作,都会造成脚本的求值无法每次都得出同样的结果。
为了确保脚本符合上面所说的属性, Redis 做了以下工作:
- Lua 没有访问系统时间或者其他内部状态的命令
- Redis 会返回一个错误,阻止这样的脚本运行: 这些脚本在执行随机命令之后(比如 RANDOMKEY 、 SRANDMEMBER key [count] 或 TIME 等),还会执行可以修改数据集的 Redis 命令。如果脚本只是执行只读操作,那么就没有这一限制。注意,随机命令并不一定就指那些带 RAND 字眼的命令,任何带有非确定性的命令都会被认为是随机命令,比如 TIME 命令就是这方面的一个很好的例子。
- 每当从 Lua 脚本中调用那些返回无序元素的命令时,执行命令所得的数据在返回给 Lua 之前会先执行一个静默(slient)的字典序排序(lexicographical sorting)。举个例子,因为 Redis 的 Set 保存的是无序的元素,所以在 Redis 命令行客户端中直接执行 SMEMBERS key ,返回的元素是无序的,但是,假如在脚本中执行 redis.call(“smembers”, KEYS[1]) ,那么返回的总是排过序的元素。
- 对 Lua 的伪随机数生成函数 math.random 和 math.randomseed 进行修改,使得每次在运行新脚本的时候,总是拥有同样的 seed 值。这意味着,每次运行脚本时,只要不使用 math.randomseed ,那么 math.random 产生的随机数序列总是相同的。
- 尽管有那么多的限制,但用户还是可以用一个简单的技巧写出带随机行为的脚本(如果他们需要的话)。
假设现在我们要编写一个 Redis 脚本,这个脚本从列表中弹出 N 个随机数。一个 Ruby 写的例子如下:
1 | require 'rubygems' |
这个程序每次运行都会生成带有以下元素的列表:
1 | > lrange mylist 0 -1 |
上面的 Ruby 程序每次都只生成同样的列表,用途并不是太大。那么,该怎样修改这个脚本,使得它仍然是一个纯函数(符合 Redis 的要求),但是每次调用都可以产生不同的随机元素呢?
一个简单的办法是,为脚本添加一个额外的参数,让这个参数作为 Lua 的随机数生成器的 seed 值,这样的话,只要给脚本传入不同的 seed ,脚本就会生成不同的列表元素。
以下是修改后的脚本:
1 | RandomPushScript = <<EOF |
尽管对于同样的 seed ,上面的脚本产生的列表元素是一样的(因为它是一个纯函数),但是只要每次在执行脚本的时候传入不同的 seed ,我们就可以得到带有不同随机元素的列表。
Seed 会在复制(replication link)和写 AOF 文件时作为一个参数来传播,保证在载入 AOF 文件或附属节点(slave)处理脚本时, seed 仍然可以及时得到更新。
注意,Redis 实现保证 math.random 和 math.randomseed 的输出和运行 Redis 的系统架构无关,无论是 32 位还是 64 位系统,无论是小端(little endian)还是大端(big endian)系统,这两个函数的输出总是相同的。
全局变量保护
为了防止不必要的数据泄漏进 Lua 环境, Redis 脚本不允许创建全局变量。如果一个脚本需要在多次执行之间维持某种状态,它应该使用 Redis key 来进行状态保存。
企图在脚本中访问一个全局变量(不论这个变量是否存在)将引起脚本停止, EVAL 命令会返回一个错误:
1 | redis 127.0.0.1:6379> eval 'a=10' 0 |
Lua 的 debug 工具,或者其他设施,比如打印(alter)用于实现全局保护的 meta table ,都可以用于实现全局变量保护。
实现全局变量保护并不难,不过有时候还是会不小心而为之。一旦用户在脚本中混入了 Lua 全局状态,那么 AOF 持久化和复制(replication)都会无法保证,所以,请不要使用全局变量。
避免引入全局变量的一个诀窍是:将脚本中用到的所有变量都使用 local 关键字定义为局部变量。
库
Redis 内置的 Lua 解释器加载了以下 Lua 库:
- base
- table
- string
- math
- debug
- cjson
- cmsgpack
其中 cjson 库可以让 Lua 以非常快的速度处理 JSON 数据,除此之外,其他别的都是 Lua 的标准库。
每个 Redis 实例都保证会加载上面列举的库,从而确保每个 Redis 脚本的运行环境都是相同的。
使用脚本散发 Redis 日志
在 Lua 脚本中,可以通过调用 redis.log 函数来写 Redis 日志(log):
1 | redis.log(loglevel, message) |
其中, message 参数是一个字符串,而 loglevel 参数可以是以下任意一个值:
- redis.LOG_DEBUG
- redis.LOG_VERBOSE
- redis.LOG_NOTICE
- redis.LOG_WARNING
上面的这些等级(level)和标准 Redis 日志的等级相对应。
对于脚本散发(emit)的日志,只有那些和当前 Redis 实例所设置的日志等级相同或更高级的日志才会被散发。
以下是一个日志示例:
1 | redis.log(redis.LOG_WARNING, "Something is wrong with this script.") |
执行上面的函数会产生这样的信息:
1 | [32343] 22 Mar 15:21:39 # Something is wrong with this script. |
沙箱(sandbox)和最大执行时间
脚本应该仅仅用于传递参数和对 Redis 数据进行处理,它不应该尝试去访问外部系统(比如文件系统),或者执行任何系统调用。
除此之外,脚本还有一个最大执行时间限制,它的默认值是 5 秒钟,一般正常运作的脚本通常可以在几分之几毫秒之内完成,花不了那么多时间,这个限制主要是为了防止因编程错误而造成的无限循环而设置的。
最大执行时间的长短由 lua-time-limit 选项来控制(以毫秒为单位),可以通过编辑 redis.conf 文件或者使用 CONFIG GET parameter 和 CONFIG SET parameter value 命令来修改它。
当一个脚本达到最大执行时间的时候,它并不会自动被 Redis 结束,因为 Redis 必须保证脚本执行的原子性,而中途停止脚本的运行意味着可能会留下未处理完的数据在数据集(data set)里面。
因此,当脚本运行的时间超过最大执行时间后,以下动作会被执行:
- Redis 记录一个脚本正在超时运行
- Redis 开始重新接受其他客户端的命令请求,但是只有 SCRIPT KILL 和 SHUTDOWN NOSAVE 两个命令会被处理,对于其他命令请求, Redis 服务器只是简单地返回 BUSY 错误。
- 可以使用 SCRIPT KILL 命令将一个仅执行只读命令的脚本杀死,因为只读命令并不修改数据,因此杀死这个脚本并不破坏数据的完整性
- 如果脚本已经执行过写命令,那么唯一允许执行的操作就是 SHUTDOWN NOSAVE ,它通过停止服务器来阻止当前数据集写入磁盘
流水线(pipeline)上下文(context)中的 EVALSHA
在流水线请求的上下文中使用 EVALSHA 命令时,要特别小心,因为在流水线中,必须保证命令的执行顺序。
一旦在流水线中因为 EVALSHA 命令而发生 NOSCRIPT 错误,那么这个流水线就再也没有办法重新执行了,否则的话,命令的执行顺序就会被打乱。
为了防止出现以上所说的问题,客户端库实现应该实施以下的其中一项措施:
- 总是在流水线中使用 EVAL 命令
- 检查流水线中要用到的所有命令,找到其中的 EVAL 命令,并使用 SCRIPT EXISTS sha1 [sha1 …] 命令检查要用到的脚本是不是全都已经保存在缓存里面了。如果所需的全部脚本都可以在缓存里找到,那么就可以放心地将所有 EVAL 命令改成 EVALSHA 命令,否则的话,就要在流水线的顶端(top)将缺少的脚本用 SCRIPT LOAD script 命令加上去。
EVALSHA
EVALSHA sha1 numkeys key [key …] arg [arg …]
可用版本: >= 2.6.0
时间复杂度: 根据脚本的复杂度而定。
根据给定的 sha1 校验码,对缓存在服务器中的脚本进行求值。
将脚本缓存到服务器的操作可以通过 SCRIPT LOAD script 命令进行。
这个命令的其他地方,比如参数的传入方式,都和 EVAL script numkeys key [key …] arg [arg …] 命令一样。
1 | redis> SCRIPT LOAD "return 'hello moto'" |
SCRIPT LOAD
SCRIPT LOAD script
可用版本: >= 2.6.0
时间复杂度: O(N) , N 为脚本的长度(以字节为单位)。
将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本。
EVAL script numkeys key [key …] arg [arg …] 命令也会将脚本添加到脚本缓存中,但是它会立即对输入的脚本进行求值。
如果给定的脚本已经在缓存里面了,那么不做动作。
在脚本被加入到缓存之后,通过 EVALSHA 命令,可以使用脚本的 SHA1 校验和来调用这个脚本。
脚本可以在缓存中保留无限长的时间,直到执行 SCRIPT FLUSH 为止。
关于使用 Redis 对 Lua 脚本进行求值的更多信息,请参见 EVAL script numkeys key [key …] arg [arg …] 命令。
返回值
给定 script 的 SHA1 校验和。
代码示例
1 | redis> SCRIPT LOAD "return 'hello moto'" |
SCRIPT EXISTS
SCRIPT EXISTS sha1 [sha1 …]
可用版本: >= 2.6.0
时间复杂度: O(N) , N 为给定的 SHA1 校验和的数量。
给定一个或多个脚本的 SHA1 校验和,返回一个包含 0 和 1 的列表,表示校验和所指定的脚本是否已经被保存在缓存当中。
关于使用 Redis 对 Lua 脚本进行求值的更多信息,请参见 EVAL script numkeys key [key …] arg [arg …] 命令。
返回值
一个列表,包含 0 和 1 ,前者表示脚本不存在于缓存,后者表示脚本已经在缓存里面了。 列表中的元素和给定的 SHA1 校验和保持对应关系,比如列表的第三个元素的值就表示第三个 SHA1 校验和所指定的脚本在缓存中的状态。
代码示例
1 | redis> SCRIPT LOAD "return 'hello moto'" # 载入一个脚本 |
SCRIPT FLUSH
SCRIPT FLUSH
可用版本: >= 2.6.0
复杂度: O(N) , N 为缓存中脚本的数量。
清除所有 Lua 脚本缓存。
关于使用 Redis 对 Lua 脚本进行求值的更多信息,请参见 EVAL script numkeys key [key …] arg [arg …] 命令。
返回值
总是返回 OK
代码示例
1 | redis> SCRIPT FLUSH |
SCRIPT KILL
SCRIPT KILL
可用版本: >= 2.6.0
时间复杂度: O(1)
杀死当前正在运行的 Lua 脚本,当且仅当这个脚本没有执行过任何写操作时,这个命令才生效。
这个命令主要用于终止运行时间过长的脚本,比如一个因为 BUG 而发生无限 loop 的脚本,诸如此类。
SCRIPT KILL 执行之后,当前正在运行的脚本会被杀死,执行这个脚本的客户端会从 EVAL script numkeys key [key …] arg [arg …] 命令的阻塞当中退出,并收到一个错误作为返回值。
另一方面,假如当前正在运行的脚本已经执行过写操作,那么即使执行 SCRIPT KILL ,也无法将它杀死,因为这是违反 Lua 脚本的原子性执行原则的。在这种情况下,唯一可行的办法是使用 SHUTDOWN NOSAVE 命令,通过停止整个 Redis 进程来停止脚本的运行,并防止不完整(half-written)的信息被写入数据库中。
关于使用 Redis 对 Lua 脚本进行求值的更多信息,请参见 EVAL script numkeys key [key …] arg [arg …] 命令。
返回值
执行成功返回 OK ,否则返回一个错误。
代码示例
1 | # 没有脚本在执行时 |
以下是脚本被杀死之后,返回给执行脚本的客户端的错误:
1 | redis> EVAL "while true do end" 0 |
持久化
SAVE
SAVE
可用版本: >= 1.0.0
时间复杂度: O(N), N 为要保存到数据库中的 key 的数量。
SAVE 命令执行一个同步保存操作,将当前 Redis 实例的所有数据快照(snapshot)以 RDB 文件的形式保存到硬盘。
一般来说,在生产环境很少执行 SAVE 操作,因为它会阻塞所有客户端,保存数据库的任务通常由 BGSAVE 命令异步地执行。然而,如果负责保存数据的后台子进程不幸出现问题时, SAVE 可以作为保存数据的最后手段来使用。
返回值
保存成功时返回 OK 。
代码示例
1 | redis> SAVE |
BGSAVE
BGSAVE
可用版本: >= 1.0.0
时间复杂度: O(N), N 为要保存到数据库中的 key 的数量。
在后台异步(Asynchronously)保存当前数据库的数据到磁盘。
BGSAVE 命令执行之后立即返回 OK ,然后 Redis fork 出一个新子进程,原来的 Redis 进程(父进程)继续处理客户端请求,而子进程则负责将数据保存到磁盘,然后退出。
客户端可以通过 LASTSAVE 命令查看相关信息,判断 BGSAVE 命令是否执行成功。
请移步 持久化文档 查看更多相关细节。
返回值
反馈信息。
代码示例
1 | redis> BGSAVE |
BGREWRITEAOF
BGREWRITEAOF
可用版本: >= 1.0.0
时间复杂度: O(N), N 为要追加到 AOF 文件中的数据数量。
执行一个 AOF文件 重写操作。重写会创建一个当前 AOF 文件的体积优化版本。
即使 BGREWRITEAOF 执行失败,也不会有任何数据丢失,因为旧的 AOF 文件在 BGREWRITEAOF 成功之前不会被修改。
重写操作只会在没有其他持久化工作在后台执行时被触发,也就是说:
如果 Redis 的子进程正在执行快照的保存工作,那么 AOF 重写的操作会被预定(scheduled),等到保存工作完成之后再执行 AOF 重写。在这种情况下, BGREWRITEAOF 的返回值仍然是 OK ,但还会加上一条额外的信息,说明 BGREWRITEAOF 要等到保存操作完成之后才能执行。在 Redis 2.6 或以上的版本,可以使用 INFO [section] 命令查看 BGREWRITEAOF 是否被预定。
如果已经有别的 AOF 文件重写在执行,那么 BGREWRITEAOF 返回一个错误,并且这个新的 BGREWRITEAOF 请求也不会被预定到下次执行。
从 Redis 2.4 开始, AOF 重写由 Redis 自行触发, BGREWRITEAOF 仅仅用于手动触发重写操作。
请移步 持久化文档(英文) 查看更多相关细节。
返回值
反馈信息。
代码示例
1 | redis> BGREWRITEAOF |
LASTSAVE
LASTSAVE
可用版本: >= 1.0.0
时间复杂度: O(1)
返回最近一次 Redis 成功将数据保存到磁盘上的时间,以 UNIX 时间戳格式表示。
返回值
一个 UNIX 时间戳。
代码示例
1 | redis> LASTSAVE |
发布与订阅
PUBLISH
PUBLISH channel message
可用版本: >= 2.0.0
时间复杂度: O(N+M),其中 N 是频道 channel 的订阅者数量,而 M 则是使用模式订阅(subscribed patterns)的客户端的数量。
将信息 message 发送到指定的频道 channel 。
返回值
接收到信息 message 的订阅者数量。
代码示例
1 | # 对没有订阅者的频道发送信息 |
SUBSCRIBE
SUBSCRIBE channel [channel …]
可用版本: >= 2.0.0
时间复杂度: O(N),其中 N 是订阅的频道的数量。
订阅给定的一个或多个频道的信息。
返回值
接收到的信息(请参见下面的代码说明)。
代码示例
1 | # 订阅 msg 和 chat_room 两个频道 |
PSUBSCRIBE
PSUBSCRIBE pattern [pattern …]
可用版本: >= 2.0.0
时间复杂度: O(N), N 是订阅的模式的数量。
订阅一个或多个符合给定模式的频道。
每个模式以 * 作为匹配符,比如 it* 匹配所有以 it 开头的频道( it.news 、 it.blog 、 it.tweets 等等), news.* 匹配所有以 news. 开头的频道( news.it 、 news.global.today 等等),诸如此类。
返回值
接收到的信息(请参见下面的代码说明)。
代码示例
1 | # 订阅 news.* 和 tweet.* 两个模式 |
UNSUBSCRIBE
UNSUBSCRIBE [channel [channel …]]
可用版本: >= 2.0.0
时间复杂度: O(N) , N 是客户端已订阅的频道的数量。
指示客户端退订给定的频道。
如果没有频道被指定,也即是,一个无参数的 UNSUBSCRIBE 调用被执行,那么客户端使用 SUBSCRIBE 命令订阅的所有频道都会被退订。在这种情况下,命令会返回一个信息,告知客户端所有被退订的频道。
返回值
这个命令在不同的客户端中有不同的表现。
PUNSUBSCRIBE
PUNSUBSCRIBE [pattern [pattern …]]
可用版本: >= 2.0.0
时间复杂度: O(N+M) ,其中 N 是客户端已订阅的模式的数量, M 则是系统中所有客户端订阅的模式的数量。
指示客户端退订所有给定模式。
如果没有模式被指定,也即是,一个无参数的 PUNSUBSCRIBE 调用被执行,那么客户端使用 PSUBSCRIBE pattern [pattern …] 命令订阅的所有模式都会被退订。在这种情况下,命令会返回一个信息,告知客户端所有被退订的模式。
返回值
这个命令在不同的客户端中有不同的表现。
PUBSUB
PUBSUB[argument [argument …]]
可用版本:>= 2.8.0
PUBSUB 是一个查看订阅与发布系统状态的内省命令, 它由数个不同格式的子命令组成, 以下将分别对这些子命令进行介绍。
PUBSUB CHANNELS [pattern]
复杂度: O(N) , N 为活跃频道的数量(对于长度较短的频道和模式来说,将进行模式匹配的复杂度视为常数)。
列出当前的活跃频道。
活跃频道指的是那些至少有一个订阅者的频道, 订阅模式的客户端不计算在内。
pattern 参数是可选的:
代码示例
1 | # client-1 订阅 news.it 和 news.sport 两个频道 |
PUBSUB NUMSUB [channel-1 … channel-N]
复杂度: O(N) , N 为给定频道的数量。
返回给定频道的订阅者数量, 订阅模式的客户端不计算在内。
返回值
一个多条批量回复(Multi-bulk reply),回复中包含给定的频道,以及频道的订阅者数量。 格式为:频道 channel-1 , channel-1 的订阅者数量,频道 channel-2 , channel-2 的订阅者数量,诸如此类。 回复中频道的排列顺序和执行命令时给定频道的排列顺序一致。 不给定任何频道而直接调用这个命令也是可以的, 在这种情况下, 命令只返回一个空列表。
代码示例
1 | # client-1 订阅 news.it 和 news.sport 两个频道 |
PUBSUB NUMPAT
复杂度: O(1) 。
返回订阅模式的数量。
注意, 这个命令返回的不是订阅模式的客户端的数量, 而是客户端订阅的所有模式的数量总和。
返回值
一个整数回复(Integer reply)。
代码示例
1 | # client-1 订阅 news.* 和 discount.* 两个模式 |
复制
SLAVEOF
SLAVEOF host port
可用版本: >= 1.0.0
时间复杂度: SLAVEOF host port 的复杂度为 O(N),其中 N 为要同步的数据数量; SLAVEOF NO ONE 命令的复杂度为 O(1) 。
SLAVEOF 命令用于在 Redis 运行时动态地修改复制(replication)功能的行为。
通过执行 SLAVEOF host port 命令,可以将当前服务器转变为指定服务器的从属服务器(slave server)。
如果当前服务器已经是某个主服务器(master server)的从属服务器,那么执行 SLAVEOF host port 将使当前服务器停止对旧主服务器的同步,丢弃旧数据集,转而开始对新主服务器进行同步。
另外,对一个从属服务器执行命令 SLAVEOF NO ONE 将使得这个从属服务器关闭复制功能,并从从属服务器转变回主服务器,原来同步所得的数据集不会被丢弃。
利用“SLAVEOF NO ONE 不会丢弃同步所得数据集”这个特性,可以在主服务器失败的时候,将从属服务器用作新的主服务器,从而实现无间断运行。
返回值
总是返回 OK 。
代码示例
1 | redis> SLAVEOF 127.0.0.1 6379 |
ROLE
ROLE
可用版本: >= 2.8.12
时间复杂度: O(1)
返回实例在复制中担任的角色, 这个角色可以是 master 、 slave 或者 sentinel 。 除了角色之外, 命令还会返回与该角色相关的其他信息, 其中:
- 主服务器将返回属下从服务器的 IP 地址和端口。
- 从服务器将返回自己正在复制的主服务器的 IP 地址、端口、连接状态以及复制偏移量。
- Sentinel 将返回自己正在监视的主服务器列表。
返回值
ROLE 命令将返回一个数组。
代码示例
主服务器
1 | 1) "master" |
从服务器
1 | 1) "slave" |
Sentinel
1 | 1) "sentinel" |
客户端与服务器
AUTH
AUTH password
可用版本: >= 1.0.0
时间复杂度: O(1)
通过设置配置文件中 requirepass 项的值(使用命令 CONFIG SET requirepass password ),可以使用密码来保护 Redis 服务器。
如果开启了密码保护的话,在每次连接 Redis 服务器之后,就要使用 AUTH 命令解锁,解锁之后才能使用其他 Redis 命令。
如果 AUTH 命令给定的密码 password 和配置文件中的密码相符的话,服务器会返回 OK 并开始接受命令输入。
另一方面,假如密码不匹配的话,服务器将返回一个错误,并要求客户端需重新输入密码。
Warning
因为 Redis 高性能的特点,在很短时间内尝试猜测非常多个密码是有可能的,因此请确保使用的密码足够复杂和足够长,以免遭受密码猜测攻击。
返回值
密码匹配时返回 OK ,否则返回一个错误。
代码示例
1 | # 设置密码 |
QUIT
QUIT
可用版本: >= 1.0.0
时间复杂度: O(1)
请求服务器关闭与当前客户端的连接。
一旦所有等待中的回复(如果有的话)顺利写入到客户端,连接就会被关闭。
返回值
总是返回 OK (但是不会被打印显示,因为当时 Redis-cli 已经退出)。
代码示例
1 | $ redis |
INFO
INFO [section]
可用版本: >= 1.0.0
时间复杂度: O(1)
以一种易于解释(parse)且易于阅读的格式,返回关于 Redis 服务器的各种信息和统计数值。
通过给定可选的参数 section ,可以让命令只返回某一部分的信息:
server 部分记录了 Redis 服务器的信息,它包含以下域:
- redis_version : Redis 服务器版本
- redis_git_sha1 : Git SHA1
- redis_git_dirty : Git dirty flag
- os : Redis 服务器的宿主操作系统
- arch_bits : 架构(32 或 64 位)
- multiplexing_api : Redis 所使用的事件处理机制
- gcc_version : 编译 Redis 时所使用的 GCC 版本
- process_id : 服务器进程的 PID
- run_id : Redis 服务器的随机标识符(用于 Sentinel 和集群)
- tcp_port : TCP/IP 监听端口
- uptime_in_seconds : 自 Redis 服务器启动以来,经过的秒数
- uptime_in_days : 自 Redis 服务器启动以来,经过的天数
- lru_clock : 以分钟为单位进行自增的时钟,用于 LRU 管理
clients 部分记录了已连接客户端的信息,它包含以下域:
- connected_clients : 已连接客户端的数量(不包括通过从属服务器连接的客户端)
- client_longest_output_list : 当前连接的客户端当中,最长的输出列表
- client_longest_input_buf : 当前连接的客户端当中,最大输入缓存
- blocked_clients : 正在等待阻塞命令(BLPOP、BRPOP、BRPOPLPUSH)的客户端的数量
memory 部分记录了服务器的内存信息,它包含以下域:
used_memory : 由 Redis 分配器分配的内存总量,以字节(byte)为单位
used_memory_human : 以人类可读的格式返回 Redis 分配的内存总量
used_memory_rss : 从操作系统的角度,返回 Redis 已分配的内存总量(俗称常驻集大小)。这个值和 top 、 ps 等命令的输出一致。
used_memory_peak : Redis 的内存消耗峰值(以字节为单位)
used_memory_peak_human : 以人类可读的格式返回 Redis 的内存消耗峰值
used_memory_lua : Lua 引擎所使用的内存大小(以字节为单位)
mem_fragmentation_ratio : used_memory_rss 和 used_memory 之间的比率
mem_allocator : 在编译时指定的, Redis 所使用的内存分配器。可以是 libc 、 jemalloc 或者 tcmalloc 。
在理想情况下, used_memory_rss 的值应该只比 used_memory 稍微高一点儿。
当 rss > used ,且两者的值相差较大时,表示存在(内部或外部的)内存碎片。
内存碎片的比率可以通过 mem_fragmentation_ratio 的值看出。
当 used > rss 时,表示 Redis 的部分内存被操作系统换出到交换空间了,在这种情况下,操作可能会产生明显的延迟。
1
Because Redis does not have control over how its allocations are mapped to memory pages, high used_memory_rss is often the result of a spike in memory usage.
当 Redis 释放内存时,分配器可能会,也可能不会,将内存返还给操作系统。
如果 Redis 释放了内存,却没有将内存返还给操作系统,那么 used_memory 的值可能和操作系统显示的 Redis 内存占用并不一致。
查看 used_memory_peak 的值可以验证这种情况是否发生。
persistence 部分记录了跟 RDB 持久化和 AOF 持久化有关的信息,它包含以下域:
loading : 一个标志值,记录了服务器是否正在载入持久化文件。
rdb_changes_since_last_save : 距离最近一次成功创建持久化文件之后,经过了多少秒。
rdb_bgsave_in_progress : 一个标志值,记录了服务器是否正在创建 RDB 文件。
rdb_last_save_time : 最近一次成功创建 RDB 文件的 UNIX 时间戳。
rdb_last_bgsave_status : 一个标志值,记录了最近一次创建 RDB 文件的结果是成功还是失败。
rdb_last_bgsave_time_sec : 记录了最近一次创建 RDB 文件耗费的秒数。
rdb_current_bgsave_time_sec : 如果服务器正在创建 RDB 文件,那么这个域记录的就是当前的创建操作已经耗费的秒数。
aof_enabled : 一个标志值,记录了 AOF 是否处于打开状态。
aof_rewrite_in_progress : 一个标志值,记录了服务器是否正在创建 AOF 文件。
aof_rewrite_scheduled : 一个标志值,记录了在 RDB 文件创建完毕之后,是否需要执行预约的 AOF 重写操作。
aof_last_rewrite_time_sec : 最近一次创建 AOF 文件耗费的时长。
aof_current_rewrite_time_sec : 如果服务器正在创建 AOF 文件,那么这个域记录的就是当前的创建操作已经耗费的秒数。
aof_last_bgrewrite_status : 一个标志值,记录了最近一次创建 AOF 文件的结果是成功还是失败。
如果 AOF 持久化功能处于开启状态,那么这个部分还会加上以下域:
aof_current_size : AOF 文件目前的大小。
aof_base_size : 服务器启动时或者 AOF 重写最近一次执行之后,AOF 文件的大小。
aof_pending_rewrite : 一个标志值,记录了是否有 AOF 重写操作在等待 RDB 文件创建完毕之后执行。
aof_buffer_length : AOF 缓冲区的大小。
aof_rewrite_buffer_length : AOF 重写缓冲区的大小。
aof_pending_bio_fsync : 后台 I/O 队列里面,等待执行的 fsync 调用数量。
aof_delayed_fsync : 被延迟的 fsync 调用数量。
stats 部分记录了一般统计信息,它包含以下域:
- total_connections_received : 服务器已接受的连接请求数量。
- total_commands_processed : 服务器已执行的命令数量。
- instantaneous_ops_per_sec : 服务器每秒钟执行的命令数量。
- rejected_connections : 因为最大客户端数量限制而被拒绝的连接请求数量。
- expired_keys : 因为过期而被自动删除的数据库键数量。
- evicted_keys : 因为最大内存容量限制而被驱逐(evict)的键数量。
- keyspace_hits : 查找数据库键成功的次数。
- keyspace_misses : 查找数据库键失败的次数。
- pubsub_channels : 目前被订阅的频道数量。
- pubsub_patterns : 目前被订阅的模式数量。
- latest_fork_usec : 最近一次 fork() 操作耗费的毫秒数。
- replication : 主/从复制信息
role : 如果当前服务器没有在复制任何其他服务器,那么这个域的值就是 master ;否则的话,这个域的值就是 slave 。注意,在创建复制链的时候,一个从服务器也可能是另一个服务器的主服务器。
如果当前服务器是一个从服务器的话,那么这个部分还会加上以下域:
master_host : 主服务器的 IP 地址。
master_port : 主服务器的 TCP 监听端口号。
master_link_status : 复制连接当前的状态, up 表示连接正常, down 表示连接断开。
master_last_io_seconds_ago : 距离最近一次与主服务器进行通信已经过去了多少秒钟。
master_sync_in_progress : 一个标志值,记录了主服务器是否正在与这个从服务器进行同步。
如果同步操作正在进行,那么这个部分还会加上以下域:
master_sync_left_bytes : 距离同步完成还缺少多少字节数据。
master_sync_last_io_seconds_ago : 距离最近一次因为 SYNC 操作而进行 I/O 已经过去了多少秒。
如果主从服务器之间的连接处于断线状态,那么这个部分还会加上以下域:
master_link_down_since_seconds : 主从服务器连接断开了多少秒。
以下是一些总会出现的域:
connected_slaves : 已连接的从服务器数量。
对于每个从服务器,都会添加以下一行信息:
slaveXXX : ID、IP 地址、端口号、连接状态
cpu 部分记录了 CPU 的计算量统计信息,它包含以下域:
- used_cpu_sys : Redis 服务器耗费的系统 CPU 。
- used_cpu_user : Redis 服务器耗费的用户 CPU 。
- used_cpu_sys_children : 后台进程耗费的系统 CPU 。
- used_cpu_user_children : 后台进程耗费的用户 CPU 。
commandstats 部分记录了各种不同类型的命令的执行统计信息,比如命令执行的次数、命令耗费的 CPU 时间、执行每个命令耗费的平均 CPU 时间等等。对于每种类型的命令,这个部分都会添加一行以下格式的信息:
cmdstat_XXX:calls=XXX,usec=XXX,usecpercall=XXX
cluster 部分记录了和集群有关的信息,它包含以下域:
cluster_enabled : 一个标志值,记录集群功能是否已经开启。
keyspace 部分记录了数据库相关的统计信息,比如数据库的键数量、数据库已经被删除的过期键数量等。对于每个数据库,这个部分都会添加一行以下格式的信息:
- dbXXX:keys=XXX,expires=XXX
除上面给出的这些值以外, section 参数的值还可以是下面这两个:
- all : 返回所有信息
- default : 返回默认选择的信息
当不带参数直接调用 INFO 命令时,使用 default 作为默认参数。
Note
不同版本的 Redis 可能对返回的一些域进行了增加或删减。
因此,一个健壮的客户端程序在对 INFO [section] 命令的输出进行分析时,应该能够跳过不认识的域,并且妥善地处理丢失不见的域。
返回值
具体请参见下面的测试代码。
代码示例
1 | redis> INFO |
SHUTDOWN
SHUTDOWN [SAVE|NOSAVE]
可用版本: >= 1.0.0
时间复杂度: O(N),其中 N 为关机时需要保存的数据库键数量。
SHUTDOWN 命令执行以下操作:
停止所有客户端
- 如果有至少一个保存点在等待,执行 SAVE 命令
- 如果 AOF 选项被打开,更新 AOF 文件
- 关闭 redis 服务器(server)
- 如果持久化被打开的话, SHUTDOWN 命令会保证服务器正常关闭而不丢失任何数据。
另一方面,假如只是单纯地执行 SAVE 命令,然后再执行 QUIT 命令,则没有这一保证 —— 因为在执行 SAVE 之后、执行 QUIT 之前的这段时间中间,其他客户端可能正在和服务器进行通讯,这时如果执行 QUIT 就会造成数据丢失。
SAVE 和 NOSAVE 修饰符
通过使用可选的修饰符,可以修改 SHUTDOWN 命令的表现。比如说:
- 执行 SHUTDOWN SAVE 会强制让数据库执行保存操作,即使没有设定(configure)保存点
- 执行 SHUTDOWN NOSAVE 会阻止数据库执行保存操作,即使已经设定有一个或多个保存点(你可以将这一用法看作是强制停止服务器的一个假想的 ABORT 命令)
返回值
执行失败时返回错误。 执行成功时不返回任何信息,服务器和客户端的连接断开,客户端自动退出。
代码示例
1 | redis> PING |
TIME
TIME
可用版本: >= 2.6.0
时间复杂度: O(1)
返回当前服务器时间。
返回值
一个包含两个字符串的列表: 第一个字符串是当前时间(以 UNIX 时间戳格式表示),而第二个字符串是当前这一秒钟已经逝去的微秒数。
代码示例
1 | redis> TIME |
CLIENT
CLIENT GETNAME
可用版本: >= 2.6.9
时间复杂度: O(1)
返回 CLIENT SETNAME 命令为连接设置的名字。
因为新创建的连接默认是没有名字的, 对于没有名字的连接, CLIENT GETNAME 返回空白回复。
返回值
如果连接没有设置名字,那么返回空白回复; 如果有设置名字,那么返回名字。
代码示例
1 | # 新连接默认没有名字 |
CLIENT KILL ip:port
可用版本: >= 2.4.0
时间复杂度: O(N) , N 为已连接的客户端数量。
关闭地址为 ip:port 的客户端。
ip:port 应该和 CLIENT LIST 命令输出的其中一行匹配。
因为 Redis 使用单线程设计,所以当 Redis 正在执行命令的时候,不会有客户端被断开连接。
如果要被断开连接的客户端正在执行命令,那么当这个命令执行之后,在发送下一个命令的时候,它就会收到一个网络错误,告知它自身的连接已被关闭。
返回值
当指定的客户端存在,且被成功关闭时,返回 OK 。
代码示例
1 | # 列出所有已连接客户端 |
CLIENT LIST
可用版本: >= 2.4.0
时间复杂度: O(N) , N 为连接到服务器的客户端数量。
以人类可读的格式,返回所有连接到服务器的客户端信息和统计数据。
1 | redis> CLIENT LIST |
返回值
命令返回多行字符串,这些字符串按以下形式被格式化:
每个已连接客户端对应一行(以 LF 分割)
每行字符串由一系列 属性=值 形式的域组成,每个域之间以空格分开
以下是域的含义:
- addr : 客户端的地址和端口
- fd : 套接字所使用的文件描述符
- age : 以秒计算的已连接时长
- idle : 以秒计算的空闲时长
- flags : 客户端 flag (见下文)
- db : 该客户端正在使用的数据库 ID
- sub : 已订阅频道的数量
- psub : 已订阅模式的数量
- multi : 在事务中被执行的命令数量
- qbuf : 查询缓冲区的长度(字节为单位, 0 表示没有分配查询缓冲区)
- qbuf-free : 查询缓冲区剩余空间的长度(字节为单位, 0 表示没有剩余空间)
- obl : 输出缓冲区的长度(字节为单位, 0 表示没有分配输出缓冲区)
- oll : 输出列表包含的对象数量(当输出缓冲区没有剩余空间时,命令回复会以字符串对象的形式被入队到这个队列里)
- omem : 输出缓冲区和输出列表占用的内存总量
- events : 文件描述符事件(见下文)
- cmd : 最近一次执行的命令
客户端 flag 可以由以下部分组成:
- O : 客户端是 MONITOR 模式下的附属节点(slave)
- S : 客户端是一般模式下(normal)的附属节点
- M : 客户端是主节点(master)
- x : 客户端正在执行事务
- b : 客户端正在等待阻塞事件
- i : 客户端正在等待 VM I/O 操作(已废弃)
- d : 一个受监视(watched)的键已被修改, EXEC 命令将失败
- c : 在将回复完整地写出之后,关闭链接
- u : 客户端未被阻塞(unblocked)
- A : 尽可能快地关闭连接
- N : 未设置任何 flag
文件描述符事件可以是:
为了 debug 的需要,经常会对域进行添加和删除,一个安全的 Redis 客户端应该可以对 CLIENT LIST 的输出进行相应的处理(parse),比如忽略不存在的域,跳过未知域,诸如此类。
CLIENT SETNAME connection-name
可用版本: >= 2.6.9
时间复杂度: O(1)
为当前连接分配一个名字。
这个名字会显示在 CLIENT LIST 命令的结果中, 用于识别当前正在与服务器进行连接的客户端。
举个例子, 在使用 Redis 构建队列(queue)时, 可以根据连接负责的任务(role), 为信息生产者(producer)和信息消费者(consumer)分别设置不同的名字。
名字使用 Redis 的字符串类型来保存, 最大可以占用 512 MB 。 另外, 为了避免和 CLIENT LIST 命令的输出格式发生冲突, 名字里不允许使用空格。
要移除一个连接的名字, 可以将连接的名字设为空字符串 “” 。
使用 CLIENT GETNAME 命令可以取出连接的名字。
新创建的连接默认是没有名字的。
Tip
在 Redis 应用程序发生连接泄漏时,为连接设置名字是一种很好的 debug 手段。
返回值
设置成功时返回 OK 。
代码示例
1 | # 新连接默认没有名字 |
配置选项
CONFIG SET
CONFIG SET parameter value
可用版本: >= 2.0.0
时间复杂度:O(1)
CONFIG SET 命令可以动态地调整 Redis 服务器的配置(configuration)而无须重启。
你可以使用它修改配置参数,或者改变 Redis 的持久化(Persistence)方式。
CONFIG SET 可以修改的配置参数可以使用命令 CONFIG GET * 来列出,所有被 CONFIG SET 修改的配置参数都会立即生效。
关于 CONFIG SET 命令的更多消息,请参见命令 CONFIG GET 的说明。
关于如何使用 CONFIG SET 命令修改 Redis 持久化方式,请参见 Redis Persistence 。
返回值
当设置成功时返回 OK ,否则返回一个错误。
代码示例
1 | redis> CONFIG GET slowlog-max-len |
CONFIG GET
CONFIG GET parameter
可用版本: >= 2.0.0
时间复杂度: O(N),其中 N 为命令返回的配置选项数量。
CONFIG GET 命令用于取得运行中的 Redis 服务器的配置参数(configuration parameters),在 Redis 2.4 版本中, 有部分参数没有办法用 CONFIG GET 访问,但是在最新的 Redis 2.6 版本中,所有配置参数都已经可以用 CONFIG GET 访问了。
CONFIG GET 接受单个参数 parameter 作为搜索关键字,查找所有匹配的配置参数,其中参数和值以“键-值对”(key-value pairs)的方式排列。
比如执行 CONFIG GET s* 命令,服务器就会返回所有以 s 开头的配置参数及参数的值:
1 | redis> CONFIG GET s* |
如果你只是寻找特定的某个参数的话,你当然也可以直接指定参数的名字:
1 | redis> CONFIG GET slowlog-max-len |
使用命令 CONFIG GET * ,可以列出 CONFIG GET 命令支持的所有参数:
1 | redis> CONFIG GET * |
所有被 CONFIG SET 所支持的配置参数都可以在配置文件 redis.conf 中找到,不过 CONFIG GET 和 CONFIG SET 使用的格式和 redis.conf 文件所使用的格式有以下两点不同:
10kb 、 2gb 这些在配置文件中所使用的储存单位缩写,不可以用在 CONFIG 命令中, CONFIG SET 的值只能通过数字值显式地设定。
像 CONFIG SET xxx 1k 这样的命令是错误的,正确的格式是 CONFIG SET xxx 1000 。
save 选项在 redis.conf 中是用多行文字储存的,但在 CONFIG GET 命令中,它只打印一行文字。
以下是 save 选项在 redis.conf 文件中的表示:
1 | save 900 1 |
但是 CONFIG GET 命令的输出只有一行:
1 | redis> CONFIG GET save |
上面 save 参数的三个值表示:在 900 秒内最少有 1 个 key 被改动,或者 300 秒内最少有 10 个 key 被改动,又或者 60 秒内最少有 1000 个 key 被改动,以上三个条件随便满足一个,就触发一次保存操作。
返回值
给定配置参数的值。
CONFIG RESETSTAT
CONFIG RESETSTAT
可用版本: >= 2.0.0
时间复杂度: O(1)
重置 INFO 命令中的某些统计数据,包括:
- Keyspace hits (键空间命中次数)
- Keyspace misses (键空间不命中次数)
- Number of commands processed (执行命令的次数)
- Number of connections received (连接服务器的次数)
- Number of expired keys (过期key的数量)
- Number of rejected connections (被拒绝的连接数量)
- Latest fork(2) time(最后执行 fork(2) 的时间)
- The aof_delayed_fsync counter(aof_delayed_fsync 计数器的值)
返回值
总是返回 OK 。
代码示例
重置前
1 | redis 127.0.0.1:6379> INFO |
重置
1 | redis 127.0.0.1:6379> CONFIG RESETSTAT |
重置后
1 | redis 127.0.0.1:6379> INFO |
CONFIG REWRITE
CONFIG REWRITE
可用版本: >= 2.8.0
时间复杂度:O(N),其中 N 为被重写的配置选项数量。
CONFIG REWRITE 命令对启动 Redis 服务器时所指定的 redis.conf 文件进行改写: 因为 CONFIG_SET 命令可以对服务器的当前配置进行修改, 而修改后的配置可能和 redis.conf 文件中所描述的配置不一样, CONFIG REWRITE 的作用就是通过尽可能少的修改, 将服务器当前所使用的配置记录到 redis.conf 文件中。
重写会以非常保守的方式进行:
- 原有 redis.conf 文件的整体结构和注释会被尽可能地保留。
- 如果一个选项已经存在于原有 redis.conf 文件中 , 那么对该选项的重写会在选项原本所在的位置(行号)上进行。
- 如果一个选项不存在于原有 redis.conf 文件中, 并且该选项被设置为默认值, 那么重写程序不会将这个选项添加到重写后的 redis.conf 文件中。
- 如果一个选项不存在于原有 redis.conf 文件中, 并且该选项被设置为非默认值, 那么这个选项将被添加到重写后的 redis.conf 文件的末尾。
- 未使用的行会被留白。 比如说, 如果你在原有 redis.conf 文件上设置了数个关于 save 选项的参数, 但现在你将这些 save 参数的一个或全部都关闭了, 那么这些不再使用的参数原本所在的行就会变成空白的。
即使启动服务器时所指定的 redis.conf 文件已经不再存在, CONFIG REWRITE 命令也可以重新构建并生成出一个新的 redis.conf 文件。
另一方面, 如果启动服务器时没有载入 redis.conf 文件, 那么执行 CONFIG REWRITE 命令将引发一个错误。
原子性重写
对 redis.conf 文件的重写是原子性的, 并且是一致的: 如果重写出错或重写期间服务器崩溃, 那么重写失败, 原有 redis.conf 文件不会被修改。 如果重写成功, 那么 redis.conf 文件为重写后的新文件。
返回值
一个状态值:如果配置重写成功则返回 OK ,失败则返回一个错误。
代码示例
以下是执行 CONFIG REWRITE 前, 被载入到 Redis 服务器的 redis.conf 文件中关于 appendonly 选项的设置:
1 | # ... 其他选项 |
调试
PING
PING
可用版本: >= 1.0.0
时间复杂度: O(1)
使用客户端向 Redis 服务器发送一个 PING ,如果服务器运作正常的话,会返回一个 PONG 。
通常用于测试与服务器的连接是否仍然生效,或者用于测量延迟值。
返回值
如果连接正常就返回一个 PONG ,否则返回一个连接错误。
代码示例
1 | # 客户端和服务器连接正常 |
ECHO
ECHO message
可用版本: >= 1.0.0
时间复杂度: O(1)
打印一个特定的信息 message ,测试时使用。
返回值
message 自身。
代码示例
1 | redis> ECHO "Hello Moto" |
OBJECT
OBJECT subcommand [arguments [arguments]]
可用版本: >= 2.2.3
时间复杂度: O(1)
OBJECT 命令允许从内部察看给定 key 的 Redis 对象, 它通常用在除错(debugging)或者了解为了节省空间而对 key 使用特殊编码的情况。 当将Redis用作缓存程序时,你也可以通过 OBJECT 命令中的信息,决定 key 的驱逐策略(eviction policies)。
OBJECT 命令有多个子命令:
OBJECT REFCOUNT <key> 返回给定 key 引用所储存的值的次数。此命令主要用于除错。
OBJECT ENCODING <key> 返回给定 key 锁储存的值所使用的内部表示(representation)。
OBJECT IDLETIME <key> 返回给定 key 自储存以来的空闲时间(idle, 没有被读取也没有被写入),以秒为单位。
对象可以以多种方式编码:字符串可以被编码为 raw (一般字符串)或 int (为了节约内存,Redis 会将字符串表示的 64 位有符号整数编码为整数来进行储存)。
列表可以被编码为 ziplist 或 linkedlist 。 ziplist 是为节约大小较小的列表空间而作的特殊表示。
集合可以被编码为 intset 或者 hashtable 。 intset 是只储存数字的小集合的特殊表示。
哈希表可以编码为 zipmap 或者 hashtable 。 zipmap 是小哈希表的特殊表示。
有序集合可以被编码为 ziplist 或者 skiplist 格式。 ziplist 用于表示小的有序集合,而 skiplist 则用于表示任何大小的有序集合。
假如你做了什么让 Redis 没办法再使用节省空间的编码时(比如将一个只有 1 个元素的集合扩展为一个有 100 万个元素的集合),特殊编码类型(specially encoded types)会自动转换成通用类型(general type)。
返回值
REFCOUNT 和 IDLETIME 返回数字。 ENCODING 返回相应的编码类型。
代码示例
1 | redis> SET game "COD" # 设置一个字符串 |
SLOWLOG
SLOWLOG subcommand [argument]
可用版本: >= 2.2.12
时间复杂度: O(1)
什么是 SLOWLOG
Slow log 是 Redis 用来记录查询执行时间的日志系统。
查询执行时间指的是不包括像客户端响应(talking)、发送回复等 IO 操作,而单单是执行一个查询命令所耗费的时间。
另外,slow log 保存在内存里面,读写速度非常快,因此你可以放心地使用它,不必担心因为开启 slow log 而损害 Redis 的速度。
设置 SLOWLOG
Slow log 的行为由两个配置参数(configuration parameter)指定,可以通过改写 redis.conf 文件或者用 CONFIG GET 和 CONFIG SET 命令对它们动态地进行修改。
第一个选项是 slowlog-log-slower-than ,它决定要对执行时间大于多少微秒(microsecond,1秒 = 1,000,000 微秒)的查询进行记录。
比如执行以下命令将让 slow log 记录所有查询时间大于等于 100 微秒的查询:
1 | CONFIG SET slowlog-log-slower-than 100 |
而以下命令记录所有查询时间大于 1000 微秒的查询:
1 | CONFIG SET slowlog-log-slower-than 1000 |
另一个选项是 slowlog-max-len ,它决定 slow log 最多能保存多少条日志, slow log 本身是一个 FIFO 队列,当队列大小超过 slowlog-max-len 时,最旧的一条日志将被删除,而最新的一条日志加入到 slow log ,以此类推。
以下命令让 slow log 最多保存 1000 条日志:
1 | CONFIG SET slowlog-max-len 1000 |
使用 CONFIG GET 命令可以查询两个选项的当前值:
1 | redis> CONFIG GET slowlog-log-slower-than |
查看 slow log
要查看 slow log ,可以使用 SLOWLOG GET 或者 SLOWLOG GET number 命令,前者打印所有 slow log ,最大长度取决于 slowlog-max-len 选项的值,而 SLOWLOG GET number 则只打印指定数量的日志。
最新的日志会最先被打印:
1 | # 为测试需要,将 slowlog-log-slower-than 设成了 10 微秒 |
日志的唯一 id 只有在 Redis 服务器重启的时候才会重置,这样可以避免对日志的重复处理(比如你可能会想在每次发现新的慢查询时发邮件通知你)。
查看当前日志的数量
使用命令 SLOWLOG LEN 可以查看当前日志的数量。
请注意这个值和 slower-max-len 的区别,它们一个是当前日志的数量,一个是允许记录的最大日志的数量。
1 | redis> SLOWLOG LEN |
清空日志
使用命令 SLOWLOG RESET 可以清空 slow log 。
1 | redis> SLOWLOG LEN |
返回值
取决于不同命令,返回不同的值。
MONITOR
MONITOR
可用版本: >= 1.0.0
时间复杂度: O(N)
实时打印出 Redis 服务器接收到的命令,调试用。
返回值
总是返回 OK 。
代码示例
1 | 127.0.0.1:6379> MONITOR |
DEBUG OBJECT
DEBUG OBJECT key
可用版本: >= 1.0.0
时间复杂度: O(1)
DEBUG OBJECT 是一个调试命令,它不应被客户端所使用。
查看 OBJECT 命令获取更多信息。
返回值
当 key 存在时,返回有关信息。 当 key 不存在时,返回一个错误。
代码示例
1 | redis> DEBUG OBJECT my_pc |
DEBUG SEGFAULT
DEBUG SEGFAULT
可用版本: >= 1.0.0
时间复杂度: O(1)
执行一个不合法的内存访问从而让 Redis 崩溃,仅在开发时用于 BUG 模拟。
返回值
无
代码示例
1 | redis> DEBUG SEGFAULT |
内部命令
MIGRATE
MIGRATE host port key destination-db timeout [COPY] [REPLACE]
可用版本: >= 2.6.0
时间复杂度: 这个命令在源实例上实际执行 DUMP 命令和 DEL 命令,在目标实例执行 RESTORE 命令,查看以上命令的文档可以看到详细的复杂度说明。 key 数据在两个实例之间传输的复杂度为 O(N) 。
将 key 原子性地从当前实例传送到目标实例的指定数据库上,一旦传送成功, key 保证会出现在目标实例上,而当前实例上的 key 会被删除。
这个命令是一个原子操作,它在执行的时候会阻塞进行迁移的两个实例,直到以下任意结果发生:迁移成功,迁移失败,等待超时。
命令的内部实现是这样的:它在当前实例对给定 key 执行 DUMP 命令 ,将它序列化,然后传送到目标实例,目标实例再使用 RESTORE 对数据进行反序列化,并将反序列化所得的数据添加到数据库中;当前实例就像目标实例的客户端那样,只要看到 RESTORE 命令返回 OK ,它就会调用 DEL 删除自己数据库上的 key 。
timeout 参数以毫秒为格式,指定当前实例和目标实例进行沟通的最大间隔时间。这说明操作并不一定要在 timeout 毫秒内完成,只是说数据传送的时间不能超过这个 timeout 数。
MIGRATE 命令需要在给定的时间规定内完成 IO 操作。如果在传送数据时发生 IO 错误,或者达到了超时时间,那么命令会停止执行,并返回一个特殊的错误: IOERR 。
当 IOERR 出现时,有以下两种可能:
- key 可能存在于两个实例
- key 可能只存在于当前实例
唯一不可能发生的情况就是丢失 key ,因此,如果一个客户端执行 MIGRATE 命令,并且不幸遇上 IOERR 错误,那么这个客户端唯一要做的就是检查自己数据库上的 key 是否已经被正确地删除。
如果有其他错误发生,那么 MIGRATE 保证 key 只会出现在当前实例中。(当然,目标实例的给定数据库上可能有和 key 同名的键,不过这和 MIGRATE 命令没有关系)。
可选项
COPY :不移除源实例上的 key 。
REPLACE :替换目标实例上已存在的 key 。
返回值
迁移成功时返回 OK ,否则返回相应的错误。
代码示例
先启动两个 Redis 实例,一个使用默认的 6379 端口,一个使用 7777 端口。
1 | $ ./redis-server & |
然后用客户端连上 6379 端口的实例,设置一个键,然后将它迁移到 7777 端口的实例上:
1 | $ ./redis-cli |
使用另一个客户端,查看 7777 端口上的实例:
1 | $ ./redis-cli -p 7777 |
DUMP
DUMP key
可用版本: >= 2.6.0
时间复杂度:查找给定键的复杂度为 O(1) ,对键进行序列化的复杂度为 O(N*M) ,其中 N 是构成 key 的 Redis 对象的数量,而 M 则是这些对象的平均大小。如果序列化的对象是比较小的字符串,那么复杂度为 O(1) 。
序列化给定 key ,并返回被序列化的值,使用 RESTORE 命令可以将这个值反序列化为 Redis 键。
序列化生成的值有以下几个特点:
- 它带有 64 位的校验和,用于检测错误, RESTORE 在进行反序列化之前会先检查校验和。
- 值的编码格式和 RDB 文件保持一致。
- RDB 版本会被编码在序列化值当中,如果因为 Redis 的版本不同造成 RDB 格式不兼容,那么 Redis 会拒绝对这个值进行反序列化操作。
序列化的值不包括任何生存时间信息。
返回值
如果 key 不存在,那么返回 nil 。 否则,返回序列化之后的值。
代码示例
1 | redis> SET greeting "hello, dumping world!" |
RESTORE
RESTORE key ttl serialized-value [REPLACE]
可用版本: >= 2.6.0
时间复杂度: 查找给定键的复杂度为 O(1) ,对键进行反序列化的复杂度为 O(N*M) ,其中 N 是构成 key 的 Redis 对象的数量,而 M 则是这些对象的平均大小。 有序集合(sorted set)的反序列化复杂度为 O(N*M*log(N)) ,因为有序集合每次插入的复杂度为 O(log(N)) 。 如果反序列化的对象是比较小的字符串,那么复杂度为 O(1) 。
反序列化给定的序列化值,并将它和给定的 key 关联。
参数 ttl 以毫秒为单位为 key 设置生存时间;如果 ttl 为 0 ,那么不设置生存时间。
RESTORE 在执行反序列化之前会先对序列化值的 RDB 版本和数据校验和进行检查,如果 RDB 版本不相同或者数据不完整的话,那么 RESTORE 会拒绝进行反序列化,并返回一个错误。
如果键 key 已经存在, 并且给定了 REPLACE 选项, 那么使用反序列化得出的值来代替键 key 原有的值; 相反地, 如果键 key 已经存在, 但是没有给定 REPLACE 选项, 那么命令返回一个错误。
更多信息可以参考 DUMP 命令。
返回值
如果反序列化成功那么返回 OK ,否则返回一个错误。
代码示例
1 | # 创建一个键,作为 DUMP 命令的输入 |
SYNC
SYNC
可用版本: >= 1.0.0
时间复杂度: O(N)
用于复制功能(replication)的内部命令。
更多信息请参考 Redis 官网的 Replication 章节 。
返回值
序列化数据。
代码示例
1 | redis> SYNC |
PSYNC
PSYNC master_run_id offset
可用版本: >= 2.8.0
时间复杂度: 不明确
用于复制功能(replication)的内部命令。
更多信息请参考 复制(Replication) 文档。
返回值
序列化数据。
代码示例
1 | 127.0.0.1:6379> PSYNC ? -1 |