Appearance
Redis
Redis是一个开源(BSD许可)的内存数据结构存储,用作数据库、缓存、消息代理和流引擎等。
连接管理
SELECT:切换至指定的数据库
- 语法:
SELECT index
选择具有指定的从零开始的数字索引的Redis逻辑数据库。新的连接总是使用数据库0。
可选择的Redis数据库是命名空间的一种形式:所有的数据库仍然保存在同一个RDB / AOF文件中。然而,不同的数据库可以有相同名称的键,像FLUSHDB、SWAPDB或RANDOMKEY这样的命令可以在特定的数据库上工作。
实际上,Redis数据库应该用于分离属于同一个应用程序的不同键(如果需要的话),而不是将一个Redis实例用于多个不相关的应用程序。
当使用Redis集群时,不能使用SELECT命令,因为Redis集群只支持数据库0。在Redis集群的情况下,拥有多个数据库将是无用的和不必要的复杂性的来源。对于Redis集群的设计和目标来说,在单个数据库上原子地运行命令是不可能的。
由于当前选择的数据库是连接的一个属性,因此客户端应该跟踪当前选择的数据库,并在重新连接时重新选择它。虽然没有命令用于查询当前连接中所选的数据库,但CLIENT LIST输出为每个客户机显示当前所选的数据库。
返回
简单字符串回复
redis
> SELECT 1
OK
[1]> SELECT 0
OK
> SELECT 16
(error) ERR DB index is out of range
> SELECT 15
OK
[15]> SELECT 0
OK
服务管理
SHUTDOWN:关闭服务器
- 语法:
SHUTDOWN [NOSAVE | SAVE] [NOW] [FORCE] [ABORT]
关闭服务器。
通用
KEYS:获取所有与给定匹配符相匹配的键
- 语法:
KEYS pattern
返回所有匹配模式的键。
虽然该操作的时间复杂度为O(N),但常数时间相当低。例如,在入门级笔记本电脑上运行的Redis可以在40毫秒内扫描100万个密钥的数据库。
警告:将KEYS视为只应在生产环境中极其小心地使用的命令。当它针对大型数据库执行时,可能会破坏性能。此命令用于调试和特殊操作,例如更改密钥空间布局。不要在常规应用程序代码中使用key。如果正在寻找在键空间子集中查找键的方法,请考虑使用SCAN或sets。
支持的全局样式模式:
h?llo 匹配 hello, hallo 和 hxllo
h*llo 匹配 hllo 和 heeeello
h[ae]llo 可以匹配 hello 和 hallo, 但不能匹配 hillo
h[^e]llo 可以匹配 hallo, hbllo, ... 但不能匹配 hello
h[a-b]llo 匹配 hallo 和 hbllo
如果你想逐字匹配特殊字符,使用\
来转义。
返回值
数组回复:匹配模式的键列表。
示例
redis
> MSET firstname Jack lastname Stuntman age 35
"OK"
> KEYS *name*
1) "lastname"
2) "firstname"
> KEYS a??
1) "age"
> KEYS *
1) "age"
2) "lastname"
3) "firstname"
SCAN:以增量方式迭代数据库中的键
- 语法:
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
使用SCAN命令和密切相关的命令SSCAN、HSCAN和ZSCAN对元素集合进行增量迭代。
- SCAN迭代当前选择的Redis数据库中的键集。
- SSCAN迭代集合类型的元素。
- HSCAN迭代哈希类型的字段及其相关值。
- ZSCAN迭代排序集类型的元素及其相关的分数。
由于这些命令允许增量迭代,每次调用只返回少量的元素,因此可以在生产中使用它们,而不需要像KEYS或members这样的命令,这些命令在针对大量键或元素集合调用时可能会阻塞服务器很长时间(甚至几秒钟)。
然而,虽然像members这样的阻塞命令能够在给定时刻提供作为Set一部分的所有元素,但SCAN命令家族只能对返回的元素提供有限的保证,因为我们增量迭代的集合可能在迭代过程中发生变化。
请注意,SCAN、SCAN、HSCAN和ZSCAN的工作原理非常相似,因此本文档涵盖了所有四个命令。然而,一个明显的区别是,在SSCAN, HSCAN和ZSCAN的情况下,第一个参数是持有Set, Hash或Sorted Set值的键的名称。SCAN命令在迭代当前数据库中的键时不需要任何键名参数,因此被迭代的对象是数据库本身。
SCAN基本用法
SCAN是一个基于游标的迭代器。这意味着在每次调用该命令时,服务器都会返回一个更新后的游标,用户需要在下次调用中使用它作为游标参数。
当游标设置为0时,迭代开始,当服务器返回的游标为0时,迭代结束。下面是一个SCAN迭代的例子:
redis
> MSET 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10 11 11 12 12 13 13 14 14 15 15 16 16 17 17 18 18 19 19
OK
> SCAN 0
1) "25"
2) 1) "19"
2) "9"
3) "13"
4) "6"
5) "4"
6) "12"
7) "10"
8) "1"
9) "8"
10) "15"
11) "16"
12) "17"
> SCAN 25
1) "0"
2) 1) "11"
2) "7"
3) "2"
4) "3"
5) "14"
6) "18"
7) "5"
正如您所看到的,SCAN返回值是一个包含两个值的数组:第一个值是在下一次调用中使用的新游标,第二个值是一个元素数组。
由于在第二个调用中返回的游标为0,所以服务器向调用者发出迭代结束的信号,集合已被完全探索。以游标值为0开始迭代,并调用SCAN直到返回的游标再次为0,这称为完整迭代。
迭代保证
针对数据库的一次完整迭代(full iteration)以用户给定游标0调用SCAN 命令开始,直到SCAN命令返回游标0结束。SCAN命令为完整迭代提供 以下保证:
- 从迭代开始到迭代结束的整个过程中,一直存在于数据库中的键总会 被返回。
- 如果一个键在迭代的过程中被添加到数据库中,那么这个键是否会被 返回是不确定的。
- 如果一个键在迭代的过程中被移除了,那么SCAN命令在它被移除之后 将不再返回这个键,但是这个键在被移除之前仍然有可能被SCAN命令 返回。
- 无论数据库如何变化,迭代总是有始有终的,不会出现循环迭代或者 其他无法终止迭代的情况。
返回值
SCAN、SSCAN、HSCAN和ZSCAN返回一个包含两个元素的多批量应答,其中第一个元素是一个表示无符号64位数字(游标)的字符串,第二个元素是一个包含元素数组的多批量应答。
- SCAN的元素数组是键的列表。
- SSCAN的元素数组是Set成员的列表。
- HSCAN的元素数组包含两个元素,一个字段和一个值,用于Hash的每个返回元素。
- 对于排序集的每个返回元素,ZSCAN元素数组包含两个元素,一个成员及其关联的分数。
示例
SCAN
redis
> MSET 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10 11 11 12 12 13 13 14 14 15 15 16 16 17 17 18 18 19 19
OK
> SCAN 0 match 1* count 5
1) "26"
2) 1) "14"
2) "16"
3) "10"
4) "19"
> SCAN 26 match 1* count 5
1) "21"
2) 1) "13"
2) "18"
> SCAN 21 match 1* count 5
1) "27"
2) 1) "11"
2) "15"
3) "17"
4) "1"
5) "12"
> SCAN 27 match 1* count 5
1) "0"
2) (empty array)
SSCAN
redis
> SADD myset 1 2 3 4 5 6 7 8 9
(integer) 9
> SSCAN myset 0
1) "0"
2) 1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
8) "8"
9) "9"
HSCAN
redis
> HSET myhash a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9
(integer) 9
> HSCAN myhash 0
1) "0"
2) 1) "a"
2) "1"
3) "b"
4) "2"
5) "c"
6) "3"
7) "d"
8) "4"
9) "e"
10) "5"
11) "f"
12) "6"
13) "g"
14) "7"
15) "h"
16) "8"
17) "i"
18) "9"
ZSCAN
redis
> ZADD myzset 1 one 2 two 3 three 4 four 5 five 6 six 7 seven 8 eight 9 nine
(integer) 9
> ZSCAN myzset 0
1) "0"
2) 1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
7) "four"
8) "4"
9) "five"
10) "5"
11) "six"
12) "6"
13) "seven"
14) "7"
15) "eight"
16) "8"
17) "nine"
18) "9"
SORT:对键的值进行排序
- 语法:
SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC | DESC] [ALPHA] [STORE destination]
返回或存储list、set或sorted set中包含的元素。
默认情况下,排序是数值的,元素的比较是根据它们的值解释为双精度浮点数。这是SORT最简单的形式:
redis
SORT mylist
假设mylist是一个数字列表,该命令将返回元素从小到大排序的相同列表。为了将数字从大到小排序,使用DESC修饰符:
redis
SORT mylist DESC
当mylist包含字符串值并希望按字典顺序排序时,请使用ALPHA修饰符:
redis
SORT mylist ALPHA
可以使用LIMIT修饰符限制返回元素的数量。此修饰符接受offset参数(指定要跳过的元素数量)和count参数(指定从offset开始返回的元素数量)。下面的例子将返回10个mylist的排序版本的元素,从元素0开始(偏移量从零开始):
redis
SORT mylist LIMIT 0 10
几乎所有的修饰语都可以一起使用。下面的示例将返回前5个元素,按字典顺序降序排序:
redis
SORT mylist LIMIT 0 5 ALPHA DESC
示例
redis
> SADD myset xiaomi huawei apple vivo oppo
(integer) 5
> SMEMBERS myset
1) "vivo"
2) "apple"
3) "huawei"
4) "xiaomi"
5) "oppo"
> SORT myset
(error) ERR One or more scores can't be converted into double
> SORT myset alpha
1) "apple"
2) "huawei"
3) "oppo"
4) "vivo"
5) "xiaomi"
数据类型
Strings(字符串)
Redis strings存储字节序列,包括文本、序列化对象和二进制数组。因此,strings是最基本的Redis数据类型。
SET:设置string
redis
> SET mykey somevalue
OK
SET
默认会覆盖已有键的值:
redis
> SET mykey somevalue
OK
> SET mykey newvalue
OK
从Redis 2.6.12版本开始,用户可以通过向SET
命令提供可选的NX
选项 或者XX
选项来指示SET
命令是否要覆盖一个已经存在的值:
NX — 只在键不存在的情况下设置键
redis> SET mykey somevalue OK > SET mykey newvalue NX (nil) > SET newkey somevalue NX OK
XX — 只设置已经存在的键
redis> SET mykey somevalue OK > SET mykey newvalue XX OK > SET mykey1 somevalue XX (nil)
GET:获取string
redis
> SET mykey somevalue
OK
> GET mykey
"somevalue"
> GET mykey1
(nil)
GETSET:获取旧string并设置新string
redis
> SET mykey somevalue
OK
> GET mykey
"somevalue"
> GETSET mykey newvalue
"somevalue"
> GET mykey
"newvalue"
如果被设置的键并不存在于数据库,那么GETSET
命令将返回空值作为键的旧值:
redis
> GETSET mykey1 somevalue
(nil)
> GET mykey1
"somevalue"
MSET:一次为多个键设置string
redis
> MSET a 10 b 20 c 30
OK
MGET:一次获取多个键的string
redis
> MGET a b c
1) "10"
2) "20"
3) "30"
MSETNX:只在键都不存在的情况下,一次为多个键设置string
将给定的键设置为它们各自的值。即使只有一个键已经存在,MSETNX
也不会执行任何操作。
redis
> MSET a 10 b 20 c 30
OK
> MSETNX c 10 d 20
(integer) 0
> MSETNX d 10 e 20 f 30
(integer) 1
STRLEN:获取string的长度
redis
> SET mykey somevalue
OK
> GET mykey
"somevalue"
> STRLEN mykey
(integer) 9
> GET mykey1
(nil)
> STRLEN mykey1
(integer) 0
GETRANGE:获取指定索引范围的string
返回存储在key处的字符串值的子字符串,由偏移量start和end确定(两者都包含在内)。可以使用负偏移量来提供从字符串末尾开始的偏移量。-1表示最后一个字符,-2表示倒数第二个字符,以此类推。
redis
> SET mykey "helloworld.study"
OK
> GETRANGE mykey 0 4
"hello"
> GETRANGE mykey -5 -1
"study"
> GETRANGE mykey 0 -1
"helloworld.study"
> GETRANGE mykey 10 100
".study"
SETRANGE:设置指定索引范围的string
覆盖存储在key处的字符串的一部分,从指定的偏移量开始,覆盖value的整个长度。如果偏移量大于键处字符串的当前长度,则用零字节填充字符串以使偏移量适合。不存在的键被认为是空字符串,因此该命令将确保它持有一个足够大的字符串,以便能够在offset处设置value。
redis
> SET mykey "hello world"
OK
> SETRANGE mykey 6 redis
(integer) 11
> GET mykey
"hello redis"
偏移量大于字符串的当前长度,则用零字节填充字符串:
redis
> GET mykey
(nil)
> SETRANGE mykey 5 hello
(integer) 10
> GET mykey
"\x00\x00\x00\x00\x00hello"
> SETRANGE mykey 15 redis
(integer) 20
> GET mykey
"\x00\x00\x00\x00\x00hello\x00\x00\x00\x00\x00redis"
APPEND:追加新内容到string的末尾
如果key已经存在并且是一个字符串,该命令将在字符串的末尾追加该值。如果key不存在,则创建它并将其设置为空字符串,因此在此特殊情况下APPEND
将类似于SET
。
APPEND
命令从Redis 2.0.0开始可用。
redis
> GET mykey
(nil)
> APPEND mykey hello
(integer) 5
> APPEND mykey " world"
(integer) 11
> GET mykey
"hello world"
INCR、DECR:对整数值执行加1操作和减1操作
INCR
将键上存储的数字增加1。如果该密钥不存在,则在执行该操作前将其设置为0。如果键包含错误类型的值或包含不能用整数表示的字符串,则返回错误。此操作仅限于64位有符号整数。
redis
> SET mykey 10
OK
> INCR mykey
(integer) 11
> GET mykey
"11"
> SET mykey 10.5
OK
> INCR mykey
(error) ERR value is not an integer or out of range
> SET mykey -10
OK
> INCR mykey
(integer) -9
> GET mykey
"-9"
DECR
将键上存储的数字减1。如果该密钥不存在,则在执行该操作前将其设置为0。如果键包含错误类型的值或包含不能用整数表示的字符串,则返回错误。此操作仅限于64位有符号整数。
redis
> SET mykey 10
OK
> DECR mykey
(integer) 9
> GET mykey
"9"
> SET mykey 10.5
OK
> DECR mykey
(error) ERR value is not an integer or out of range
> SET mykey -10
OK
> DECR mykey
(integer) -11
> GET mykey
"-11"
INCRBY、DECRBY:对整数值执行加法操作和减法操作
INCRBY
将键上存储的数字按增量递增。如果该密钥不存在,则在执行该操作前将其设置为0。如果键包含错误类型的值或包含不能用整数表示的字符串,则返回错误。此操作仅限于64位有符号整数。
redis
> SET mykey 10
OK
> INCRBY mykey 5
(integer) 15
> GET mykey
"15"
> SET mykey 10.5
OK
> INCRBY mykey 5
(error) ERR value is not an integer or out of range
> SET mykey -10
OK
> INCRBY mykey 5
(integer) -5
> GET mykey
"-5"
DECRBY
将键上存储的数字递减。如果该密钥不存在,则在执行该操作前将其设置为0。如果键包含错误类型的值或包含不能用整数表示的字符串,则返回错误。此操作仅限于64位有符号整数。
redis
> SET mykey 10
OK
> DECRBY mykey 5
(integer) 5
> GET mykey
"5"
> SET mykey 10.5
OK
> DECRBY mykey 5
(error) ERR value is not an integer or out of range
> SET mykey -10
OK
> DECRBY mykey 5
(integer) -15
> GET mykey
"-15"
INCRBYFLOAT:对数字值执行浮点数加法操作
对表示存储在键上的浮点数的字符串增加指定的增量。通过使用负增量值,结果是存储在键上的值被减(通过明显的加法属性)。如果该密钥不存在,则在执行该操作前将其设置为0。
输出的精度固定在小数点后的17位,而不考虑计算的实际内部精度。
redis
> SET mykey 10
OK
> INCRBYFLOAT mykey 5.5
"15.5"
> GET mykey
"15.5"
> INCRBYFLOAT mykey -5.5
"10"
> GET mykey
"10"
Lists(列表)
Redis lists是按插入顺序排序的字符串列表。
LPUSH、RPUSH:将元素推入列表左端、右端
LPUSH
将所有指定的值插入存储在key的列表头部。如果key不存在,则在执行push操作之前将其创建为空列表。当键保存的值不是列表时,将返回错误。
只需在命令末尾指定多个参数,就可以使用单个命令调用推入多个元素。元素依次插入到列表的头部,从最左边的元素到最右边的元素。例如,命令LPUSH mylist a b c
将生成一个包含c
作为第一个元素,b
作为第二个元素,a
作为第三个元素的列表。
redis
> LPUSH mylist a b c
(integer) 3
> LRANGE mylist 0 -1
1) "c"
2) "b"
3) "a"
RPUSH
将所有指定的值插入存储在key的列表尾部。如果key不存在,则在执行push操作之前将其创建为空列表。当键保存的值不是列表时,将返回错误。
只需在命令末尾指定多个参数,就可以使用单个命令调用推入多个元素。元素依次插入到列表的尾部,从最左边的元素到最右边的元素。例如,命令RPUSH mylist a b c
将生成一个包含a
作为第一个元素,b
作为第二个元素,c
作为第三个元素的列表。
redis
> RPUSH mylist a b c
(integer) 3
> LRANGE mylist 0 -1
1) "a"
2) "b"
3) "c"
LPUSHX、RPUSHX:只对已存在的列表执行推入操作
LPUSHX
仅当key已经存在并保存列表时,才在存储在key的列表头部插入指定的值。与LPUSH
相反,当key还不存在时,将不执行任何操作。
redis
> LPUSHX mylist b
(integer) 0
> LPUSH mylist a
(integer) 1
> LPUSHX mylist b
(integer) 2
> LRANGE mylist 0 -1
1) "b"
2) "a"
RPUSHX
仅当key已经存在并保存列表时,才在存储在key的列表尾部插入指定的值。与RPUSH
相反,当key不存在时,将不执行任何操作。
redis
> RPUSHX mylist b
(integer) 0
> RPUSH mylist a
(integer) 1
> RPUSHX mylist b
(integer) 2
> LRANGE mylist 0 -1
1) "a"
2) "b"
LPOP、RPOP:弹出列表最左端、最右端的元素
LPOP
删除并返回存储在key处的列表的第一个元素。
默认情况下,该命令从列表开始弹出单个元素。当提供可选的count
参数时,根据列表的长度,响应将由最多count
个元素组成。
redis
> RPUSH mylist a b c d e
(integer) 5
> LPOP mylist
"a"
> LPOP mylist 2
1) "b"
2) "c"
> LPOP mylist 2
1) "d"
2) "e"
> LPOP mylist
(nil)
RPOP
删除并返回存储在键处的列表的最后一个元素。 默认情况下,该命令从列表末尾弹出单个元素。当提供可选的count
参数时,根据列表的长度,应答将由最多count
个元素组成。
redis
> LPUSH mylist a b c d e
(integer) 5
> RPOP mylist
"a"
> RPOP mylist 2
1) "b"
2) "c"
> RPOP mylist 2
1) "d"
2) "e"
> RPOP mylist
(nil)
LMOVE:弹出并推入另一个列表
原子地返回并删除存储在源列表中的第一个/最后一个元素(头/尾取决于wherefrom参数),并将存储在目标列表中的第一个/最后一个元素(头/尾取决于whereto参数)的元素推入。
例如:考虑source列表a、b、c,而destination列表x、y、z。执行LMOVE source destination RIGHT LEFT
会导致source保存a、b, destination保存c、x、y、z。
如果source不存在,则返回nil,不执行任何操作。如果source和destination相同,该操作相当于从列表中删除第一个/最后一个元素,并将其作为列表的第一个/最后一个元素推入,因此可以将其视为列表旋转命令(或者如果wherefrom与whereto相同则为无操作)。
redis
> RPUSH mylist a b c d e
(integer) 5
> LMOVE mylist myanotherlist right left
"e"
> LMOVE mylist myanotherlist left right
"a"
> LMOVE mylist myanotherlist right left
"d"
> LRANGE mylist 0 -1
1) "b"
2) "c"
> LRANGE myanotherlist 0 -1
1) "d"
2) "e"
3) "a"
LLEN:获取列表的长度
返回存储在key的列表的长度。如果key不存在,则将其解释为空列表并返回0。当存储在key的值不是一个列表时,将返回一个错误。
redis
> LPUSH mylist hello world
(integer) 2
> LLEN mylist
(integer) 2
> LLEN myanotherlist
(integer) 0
LINDEX:获取指定索引上的元素
返回存储在key的列表中索引index处的元素。索引是从零开始的,因此0表示第一个元素,1表示第二个元素,以此类推。可以使用负索引指定从列表尾部开始的元素。这里-1表示最后一个元素,-2表示倒数第二个元素,以此类推。
当存储在key的值不是一个列表时,将返回一个错误。
redis
> RPUSH mylist hello world
(integer) 2
> LINDEX mylist 0
"hello"
> LINDEX mylist 1
"world"
> LINDEX mylist 2
(nil)
> LINDEX mylist -1
"world"
> LINDEX mylist -2
"hello"
> LINDEX mylist -3
(nil)
LRANGE:获取指定索引范围上的元素
返回存储在key处的列表的指定元素。偏移量开始(start)和结束(stop)是从零开始的索引,0是列表的第一个元素(列表的头),1是下一个元素,以此类推。
这些偏移量也可以是负数,表示从列表末尾开始的偏移量。例如,-1是列表的最后一个元素,-2是倒数第二个元素,依此类推。
索引超出范围
超出范围的索引不会产生错误。如果start大于列表的stop,则返回空列表。如果stop大于列表的实际结束,Redis会将其视为列表的最后一个元素。
redis
> RPUSH mylist one two three
(integer) 3
> LRANGE mylist 0 -1
1) "one"
2) "two"
3) "three"
> LRANGE mylist 0 0
1) "one"
> LRANGE mylist -3 2
1) "one"
2) "two"
3) "three"
LSET:为指定索引设置新元素
将列表元素的index设置为element。有关index参数的更多信息,请参见LINDEX。
redis
> RPUSH mylist hello world
(integer) 2
> LSET mylist 1 redis
OK
> LRANGE mylist 0 -1
1) "hello"
2) "redis"
LINSERT:将元素插入列表
将存储在key的元素插入到指定值之前(before)或之后(after)的列表中。当key不存在时,它被认为是一个空列表,不执行任何操作。
当存储在key的值不是一个列表时,将返回一个错误。
redis
> RPUSH mylist hello world
(integer) 2
> LINSERT mylist before world ,
(integer) 3
> LRANGE mylist 0 -1
1) "hello"
2) ","
3) "world"
> LINSERT mylist after redis !
(integer) -1
> LRANGE mylist 0 -1
1) "hello"
2) ","
3) "world"
LTRIM:修剪列表
修剪现有列表,使其只包含指定的元素的指定范围。start和stop都是从零开始的索引,其中0是列表(头)的第一个元素,1是下一个元素,依此类推。
例如:LTRIM foobar 0 2
将修改存储在foobar中的列表,以便只保留列表的前三个元素。
start和stop也可以是负数,表示与列表末尾的偏移量,其中-1是列表的最后一个元素,-2是倒数第二个元素,以此类推。
超出范围索引不会产生错误:如果start大于列表的结束,或者start > end,结果将是一个空列表(这会导致key被删除)。如果end大于列表的末尾,Redis将把它视为列表的最后一个元素。
redis
> RPUSH mylist one two three
(integer) 3
> LTRIM mylist 0 1
OK
> LRANGE mylist 0 -1
1) "one"
2) "two"
> LTRIM mylist -1 -1
OK
> LRANGE mylist 0 -1
1) "two"
LREM:从列表中移除指定元素
从存储在key处的列表中删除等于element的元素的第一次出现次数。count参数通过以下方式影响操作:
- count = 0:删除等于element的所有元素。
- count > 0:删除从头部到尾部移动的元素。
- count < 0:删除从尾部移动到头部的元素。
例如,LREM list -2 hello
将删除存储在list中的列表中最后两次出现的“hello”。
注意,不存在的键被视为空列表,因此当键不存在时,该命令将始终返回0。
redis
> RPUSH mylist hello hello world redis hello
(integer) 5
> LREM mylist -1 hello
(integer) 1
> LRANGE mylist 0 -1
1) "hello"
2) "hello"
3) "world"
4) "redis"
> LREM mylist 0 hello
(integer) 2
> LRANGE mylist 0 -1
1) "world"
2) "redis"
BLPOP:阻塞式左端弹出操作
BLPOP
是一个阻塞列表弹出操作。它是LPOP
的阻塞版本,因为当任何给定的列表中都没有要弹出的元素时,它会阻塞连接。从第一个非空列表的头部弹出一个元素,并按给定的顺序检查给定的键。
redis
> RPUSH mylist1 a b c
(integer) 3
> RPUSH mylist2 1 2 3
(integer) 3
> BLPOP mylist2 mylist1 0
1) "mylist2"
2) "1"
> LRANGE mylist2 0 -1
1) "2"
2) "3"
非阻塞行为
在调用BLPOP
时,如果指定的键中至少有一个包含非空列表,则从列表头部弹出一个元素,并将元素与弹出它的键一起返回给调用者。
键按给定的顺序进行检查。例如,BLPOP list1 list2 list3 0
命令,假设键list1不存在,list2和list3包含非空列表。BLPOP
保证从存储在list2的列表中返回一个元素(因为在按顺序检查list1、list2和list3时,它是第一个非空列表)。
阻塞行为
如果指定的键都不存在,BLPOP
将阻塞连接,直到另一个客户端对其中一个键执行LPUSH
或RPUSH
操作。
一旦新数据出现在其中一个列表中,客户端返回解除阻塞的键的名称和弹出的值。
当BLPOP
导致客户端阻塞且指定了非零超时时,当指定的超时已过期而没有对指定键中的至少一个进行push操作时,客户端将解除阻塞,返回一个nil多批量值。
timeout参数被解释为指定要阻塞的最大秒数的值。为0可以用来无限期阻塞。
BRPOP:阻塞式右端弹出操作
BRPOP
是一个阻塞列表弹出操作。它是RPOP
的阻塞版本,因为当任何给定的列表中都没有要弹出的元素时,它会阻塞连接。从第一个非空列表的尾部弹出一个元素,并按给定的顺序检查给定的键。
查看BLPOP文档了解确切的语义,因为BRPOP
与BLPOP
相同,唯一的区别是它从列表尾部弹出元素,而不是从列表头部弹出元素。
redis
> RPUSH mylist1 a b c
(integer) 3
> RPUSH mylist2 1 2 3
(integer) 3
> BRPOP mylist2 mylist1 0
1) "mylist2"
2) "3"
> LRANGE mylist2 0 -1
1) "1"
2) "2"
BLMOVE:阻塞式弹出并推入另一个列表
BLMOVE
是LMOVE的阻塞变体。当source包含元素时,此命令的行为与LMOVE
完全相同。当在MULTI/EXEC
块中使用时,该命令的行为与LMOVE
完全相同。当source为空时,Redis将阻塞连接,直到另一个客户端推送到它,或直到超时(指定阻塞的最大秒数的双重值)到达。timeout为0可以用来无限期阻塞。
redis
> RPUSH mylist a b c d e
(integer) 5
> BLMOVE mylist myanotherlist right left 0
"e"
> BLMOVE mylist myanotherlist left right 0
"a"
> BLMOVE mylist myanotherlist right left 0
"d"
> LRANGE mylist 0 -1
1) "b"
2) "c"
> LRANGE myanotherlist 0 -1
1) "d"
2) "e"
3) "a"
Sets(集合)
Redis sets是唯一字符串(成员)的无序集合,其行为类似于您最喜欢的编程语言(例如,Java HashSet等)中的集合。
SADD:将元素添加到集合
- 语法:
SADD key member [member ...]
将指定的成员添加到存储在key的集合中。已经是该集合成员的指定成员将被忽略。如果key不存在,则在添加指定成员之前创建一个新的集合。
当存储在key上的值不是一个集合时,将返回一个错误。
从Redis 2.4.0版本开始:接受多个成员参数。
redis
> SADD myset hello world world
(integer) 2
> SMEMBERS myset
1) "world"
2) "hello"
SREM:从集合中移除元素
- 语法:
SREM key member [member ...]
从存储在key处的集合中删除指定的成员。不属于此集合的指定成员将被忽略。如果key不存在,则将其视为空集,该命令返回0。
当存储在key上的值不是一个集合时,将返回一个错误。
从Redis 2.4.0版本开始:接受多个成员参数。
redis
> SADD myset one two three
(integer) 3
> SREM myset one four
(integer) 1
> SMEMBERS myset
1) "three"
2) "two"
SMOVE:将元素从一个集合移动到另一个集合
- 语法:
SMOVE source destination member
将member从source集合移动到destination集合。这个操作是原子的。在每一个给定的时刻,元素都是其他客户机的source或destination的成员。
如果source集合不存在或不包含指定的元素,则不执行任何操作,返回0。否则,该元素将从source集合中删除并添加到destination集合中。如果指定的元素已经存在于destination集合中,则只从source集合中删除它。
如果source或destination没有设置值,则返回错误。
redis
> SADD myset one two three
(integer) 3
> SMOVE myset myotherset two
(integer) 1
> SMEMBERS myset
1) "one"
2) "three"
> SMEMBERS myotherset
1) "two"
SMEMBERS:获取集合包含的所有元素
- 语法:
SMEMBERS key
返回存储在key处的设置值的所有成员。
redis
> SADD myset hello world
(integer) 2
> SMEMBERS myset
1) "world"
2) "hello"
SCARD:获取集合包含的元素数量
- 语法:
SCARD key
返回存储在key的集合的基数(元素个数)。
redis
> SADD myset hello world
(integer) 2
> SCARD myset
(integer) 2
SISMEMBER:检查给定元素是否存在于集合
- 语法:
SISMEMBER key member
如果member是存储在key上的集合的成员则返回:如果元素是集合的成员,则为1。如果元素不是set的成员,或者key不存在,则为0。
redis
> SADD myset one
(integer) 1
> SISMEMBER myset one
(integer) 1
> SISMEMBER myset two
(integer) 0
SRANDMEMBER:随机获取集合中的元素
- 语法:
SRANDMEMBER key [count]
当只使用key参数调用时,从存储在key处的set值中返回一个随机元素。
如果提供的count参数为正,则返回由不同元素组成的数组。数组的长度要么是count,要么是集合的基数(SCARD
),取两者中的较低者。
如果以负数计数调用,则行为会发生改变,命令允许多次返回相同的元素。在本例中,返回元素的数量是指定计数的绝对值。
redis
> SADD myset one two three
(integer) 3
> SRANDMEMBER myset
"one"
> SRANDMEMBER myset
"two"
> SRANDMEMBER myset 2
1) "one"
2) "two"
> SRANDMEMBER myset -2
1) "one"
2) "one"
> SRANDMEMBER myset 4
1) "one"
2) "three"
3) "two"
> SRANDMEMBER myset -4
1) "one"
2) "two"
3) "one"
4) "three"
> SMEMBERS myset
1) "one"
2) "three"
3) "two"
SPOP:随机地从集合中移除指定数量的元素
- 语法:
SPOP key [count]
当只使用key参数调用时,从存储在key处的set值中删除并返回一个随机元素。这个操作类似于SRANDMEMBER,不同的是SRANDMEMBER
从集合中返回一个或多个随机元素,但不删除它。
默认情况下,该命令从集合中弹出单个成员。当提供可选的count参数时,答复将由多达count个成员组成,这取决于集合的基数。
redis
> SADD myset one two three
(integer) 3
> SPOP myset
"three"
> SMEMBERS myset
1) "one"
2) "two"
> SPOP myset -2
(error) ERR value is out of range, must be positive
> SPOP myset 2
1) "one"
2) "two"
> SMEMBERS myset
(empty array)
> SPOP myset 2
(empty array)
SINTER:对集合执行交集计算
- 语法:
SINTER key [key ...]
返回由所有给定集合的交集产生的集合的成员。
redis
> SADD myset1 a b c
(integer) 3
> SADD myset2 c d e
(integer) 3
> SINTER myset1 myset2
1) "c"
SINTERSTORE:对集合执行交集计算并将结果保存到另外一个集合
- 语法:
SINTERSTORE destination key [key ...]
这个命令等于SINTER,但是它不是返回结果集,而是存储在destination中。
如果destination已经存在,则会覆盖它。
redis
> SADD myset1 a b c
(integer) 3
> SADD myset2 c d e
(integer) 3
> SINTERSTORE myset3 myset1 myset2
(integer) 1
> SMEMBERS myset3
1) "c"
> SADD myset3 d
(integer) 1
> SINTERSTORE myset3 myset1 myset2
(integer) 1
> SMEMBERS myset3
1) "c"
SUNION:对集合执行并集计算
- 语法:
SUNION key [key ...]
返回由所有给定集合的并集产生的集合的成员。
redis
> SADD myset1 a b c
(integer) 3
> SADD myset2 c d e
(integer) 3
> SUNION myset1 myset2
1) "c"
2) "a"
3) "b"
4) "e"
5) "d"
SUNIONSTORE:对集合执行并集计算并将结果保存到另外一个集合
- 语法:
SUNIONSTORE destination key [key ...]
这个命令等于SUNION,但是它不是返回结果集,而是存储在destination中。
如果destination已经存在,则会覆盖它。
redis
> SADD myset1 a b c
(integer) 3
> SADD myset2 c d e
(integer) 3
> SUNIONSTORE myset3 myset1 myset2
(integer) 5
> SMEMBERS myset3
1) "c"
2) "a"
3) "b"
4) "e"
5) "d"
> SADD myset3 f
(integer) 1
> SUNIONSTORE myset3 myset1 myset2
(integer) 5
> SMEMBERS myset3
1) "c"
2) "a"
3) "b"
4) "e"
5) "d"
SDIFF:对集合执行差集计算
- 语法:
SDIFF key [key ...]
返回由第一个集合与所有后续集合之间的差集产生的集合的成员。
redis
> SADD myset1 a b c
(integer) 3
> SADD myset2 c d e
(integer) 3
> SDIFF myset1 myset2
1) "a"
2) "b"
SDIFFSTORE:对集合执行差集计算并将结果保存到另外一个集合
- 语法:
SDIFFSTORE destination key [key ...]
这个命令等于SDIFF,但是它不是返回结果集,而是存储在destination中。
如果destination已经存在,则会覆盖它。
redis
> SADD myset1 a b c
(integer) 3
> SADD myset2 c d e
(integer) 3
> SDIFFSTORE myset3 myset1 myset2
(integer) 2
> SMEMBERS myset3
1) "a"
2) "b"
> SADD myset3 f
(integer) 1
> SDIFFSTORE myset3 myset1 myset2
(integer) 2
> SMEMBERS myset3
1) "a"
2) "b"
Hashes(散列)
Redis hashs是结构为字段值对集合的记录类型。同样,Redis hash类似于Java HashMap等。
HSET:设置hash上的一个或多个字段的值
设置散列中存储在key到value处的字段。如果key不存在,则创建一个包含散列的新key。如果字段在散列中已经存在,则会覆盖它。
从Redis版本4.0.0开始,接受多个字段和值的参数。
redis
> HSET myhash field1 hello
(integer) 1
> HSET myhash field1 hello field2 world
(integer) 1
HSET
默认会覆盖已有字段的值:
redis
> HSET myhash field1 hello
(integer) 1
> HSET myhash field1 hi
(integer) 0
HGET:获取hash上的一个字段的值
redis
> HSET myhash field1 hello
(integer) 1
> HGET myhash field1
"hello"
> HSET myhash field1 hi
(integer) 0
> HGET myhash field1
"hi"
HMGET:获取hash上的一个或多个字段的值
redis
> HSET myhash field1 hello field2 world
(integer) 2
> HMGET myhash field1 field2
1) "hello"
2) "world"
HSETNX:只在字段不存在的情况下为它设置值
仅当字段还不存在时,才将存储在键处的散列中的字段设置值。如果key不存在,则创建一个包含散列的新key。如果字段已经存在,则此操作无效。
redis
> HSET myhash field1 hello
(integer) 1
> HSETNX myhash field1 hi
(integer) 0
> HGET myhash field1
"hello"
> HSETNX myhash field2 world
(integer) 1
> HGET myhash field2
"world"
HSTRLEN:获取字段值的字节长度
返回与存储在key的散列中的字段相关联的值的字符串长度。如果该键或该字段不存在,则返回0。
redis
> HSET myhash f1 hello f2 99 f3 -256
(integer) 3
> HSTRLEN myhash f1
(integer) 5
> HSTRLEN myhash f2
(integer) 2
> HSTRLEN myhash f3
(integer) 4
HLEN:获取散列包含的字段数量
返回存储在key的散列中包含的字段的数量。
redis
> HSET myhash field1 hello field2 world
(integer) 2
> HLEN myhash
(integer) 2
HEXISTS:检查字段是否存在
如果散列包含字段,则为1。如果散列不包含字段或键不存在,则为0。
redis
> HSET myhash field1 hello
(integer) 1
> HEXISTS myhash field1
(integer) 1
> HEXISTS myhash field2
(integer) 0
HINCRBY:对字段存储的整数值执行加法或减法操作
将存储在键处的散列中存储在字段处的数字按增量递增。如果key不存在,则创建一个包含散列的新key。如果字段不存在,则在执行操作前将该值设置为0。
redis
> HSET myhash field 5
(integer) 1
> HINCRBY myhash field 1
(integer) 6
> HINCRBY myhash field -1
(integer) 5
> HINCRBY myhash field -10
(integer) -5
> HINCRBY myhash field 5.5
(error) ERR value is not an integer or out of range
> HSET myhash field 5.5
(integer) 0
> HINCRBY myhash field -10
(error) ERR hash value is not an integer
HINCRBYFLOAT:对字段存储的数字值执行浮点数加法或减法操作
将存储在键上并表示浮点数的散列的指定字段按指定的增量递增。如果增量值为负,则结果是哈希字段值减少而不是增加。如果该字段不存在,则先设置为0,再执行操作。
redis
> HSET myhash field 10.5
(integer) 0
> HINCRBYFLOAT myhash field 0.1
"10.6"
> HINCRBYFLOAT myhash field -5
"5.6"
> HSET myhash field 10
(integer) 0
> HINCRBYFLOAT myhash field 0.1
"10.1"
> HINCRBYFLOAT myhash field -5
"5.1"
HDEL:删除字段
从存储在键上的散列中删除指定字段。在此散列中不存在的指定字段将被忽略。如果key不存在,它将被视为空散列,该命令返回0。
redis
> HSET myhash field1 hello
(integer) 1
> HDEL myhash field1
(integer) 1
> HDEL myhash field2
(integer) 0
HKEYS、HVALS、HGETALL:获取所有字段、所有值、所有字段和值
HKEYS
返回存储在key的散列中的所有字段名。
redis
> HSET myhash field1 hello field2 world
(integer) 0
> HKEYS myhash
1) "field1"
2) "field2"
HVALS
返回存储在key的散列中的所有值。
redis
> HSET myhash field1 hello field2 world
(integer) 0
> HVALS myhash
1) "hello"
2) "world"
HGETALL
返回存储在key的散列的所有字段和值。在返回值中,每个字段名后面都跟着它的值,因此响应的长度是散列大小的两倍。
redis
> HSET myhash field1 hello field2 world
(integer) 0
> HGETALL myhash
1) "field1"
2) "hello"
3) "field2"
4) "world"
Sorted sets(有序集合)
Redis排序集是按相关分数排序的唯一字符串(成员)的集合。当多个字符串具有相同的分数时,字符串将按字典顺序排列。
ZADD:添加或更新成员
- 语法:
ZADD key [NX | XX] [GT | LT] [CH] [INCR] score member [score member...]
将具有指定分数的所有指定成员添加到存储在key处的排序集。可以指定多个评分/成员对。如果指定的成员已经是排序集的成员,则更新评分并将元素重新插入到正确的位置,以确保正确的排序。
如果key不存在,则创建一个新的排序集,其中指定的成员为唯一的成员,就像排序集为空一样。如果key存在但不包含已排序的集合,则返回错误。
得分值应该是双精度浮点数的字符串表示形式。+inf
和-inf
值也是有效值。
选项
注意:
GT
,LT
和NX
选项是互斥的。XX
:只更新已经存在的元素。不要添加新元素。NX
:只添加新元素。不要更新已经存在的元素。LT
:只在新分数小于当前分数的情况下更新现有元素。此标志不阻止添加新元素。GT
:只有在新分数大于当前分数时才更新现有的元素。此标志不阻止添加新元素。CH
:从添加的新元素数量修改返回值,到更改的元素总数(CH是changed的缩写)。更改的元素是新添加的元素和已经存在的元素,这些元素的分数已经更新。因此,在命令行中指定的具有与过去相同分数的元素不会被计数。注意:通常ZADD的返回值只计算新添加元素的数量。INCR
:当指定此选项时,ZADD
的作用类似于ZINCRBY
。在这种模式下只能指定一个分数元素对。
返回
返回整数:
- 当不带可选参数时,添加到排序集的元素数量(不包括分数更新)。
- 如果指定了
CH
选项,则表示更改(添加或更新)的元素数量。
返回字符串:
- 如果指定了
INCR
选项,返回值将是字符串。成员(双精度浮点数)的新分数表示为字符串,如果操作被中止(当使用XX
或NX
选项调用时)则为nil。
- 如果指定了
历史
- 从Redis 2.4.0版本开始:接受多个元素。
- 从Redis 3.0.2版本开始:添加了
XX
,NX
,CH
和INCR
选项。 - 从Redis 6.2.0版本开始:添加
GT
和LT
选项。
redis
> ZADD myzset 1 one 1 neo 2 two 3 three
(integer) 4
> ZRANGE myzset 0 -1 withscores
1) "neo"
2) "1"
3) "one"
4) "1"
5) "two"
6) "2"
7) "three"
8) "3"
ZREM:移除指定的成员
- 语法:
ZREM key member [member ...]
从存储在键处的已排序集合中删除指定成员。不存在的成员被忽略。
如果key存在但不包含已排序的集合,则返回错误。
redis
> ZADD myzset 1 one 2 two 3 three
(integer) 3
> ZREM myzset two
(integer) 1
> ZRANGE myzset 0 -1 withscores
1) "one"
2) "1"
3) "three"
4) "3"
ZSCORE:获取成员的分值
- 语法:
ZSCORE key member
返回位于key的排序集合中成员的分数。
如果成员在排序集合中不存在,或者key不存在,则返回nil。
redis
> ZADD myzset 1 one
(integer) 1
> ZSCORE myzset one
"1"
> ZSCORE myzset two
(nil)
ZINCRBY:对成员的分值执行自增或自减操作
- 语法:
ZINCRBY key increment member
按增量递增存储在键上的排序集中成员的得分。如果成员在排序集中不存在,则以增量作为其得分(就像其先前得分为0.0)。如果key不存在,则创建一个以指定成员为唯一成员的新排序集。
如果成员在排序集合中不存在,或者key不存在,则返回nil。
如果key存在但不包含已排序的集合,则返回错误。
得分值应该是数值的字符串表示形式,并接受双精度浮点数。可以提供一个负值来降低分数。
redis
> ZADD myzset 1 one
(integer) 1
> ZINCRBY myzset 1.5 one
"2.5"
> ZINCRBY myzset 2 two
"2"
> ZINCRBY myzset -2.5 two
"-0.5"
> ZRANGE myzset 0 -1 withscores
1) "two"
2) "-0.5"
3) "one"
4) "2.5"
ZCARD:获取有序集合的大小
- 语法:
ZCARD key
返回存储在键的已排序集合的已排序集合基数(元素个数)。
redis
> ZADD myzset 1 one 2 two
(integer) 2
> ZCARD myzset
(integer) 2
ZRANK、ZREVRANK:获取成员在有序集合中的排名
- 语法:
ZRANK key member
或ZREVRANK key member
返回存储在key的排序集中成员的秩,分数从低到高排序。排名(或索引)以0为基础,这意味着得分最低的成员排名为0。
使用ZREVRANK
获取一个元素的等级,其分数从高到低排序。
返回
- 如果成员在排序集中存在,则Integer reply:成员的秩。
- 如果成员在排序集合中不存在或键不存在,则Bulk string reply:nil。
redis
> ZADD myzset 1 one 2 two 3 three
(integer) 3
> ZRANK myzset one
(integer) 0
> ZRANK myzset three
(integer) 2
> ZREVRANK myzset one
(integer) 2
> ZREVRANK myzset three
(integer) 0
> ZRANK myzset four
(nil)
> ZREVRANK myzset four
(nil)
ZRANGE:获取指定索引范围内的成员
- 语法:
ZRANGE key start stop [BYSCORE | BYLEX] [REV] [LIMIT offset count] [WITHSCORES]
返回存储在<键>的已排序集合中指定范围的元素。
ZRANGE
可以执行不同类型的范围查询:按索引(秩)、按分数或按字典顺序。
从Redis 6.2.0开始,该命令可以替换以下命令:ZREVRANGE
, ZRANGEBYSCORE
, ZREVRANGEBYSCORE
, ZRANGEBYLEX
和ZREVRANGEBYLEX
。
redis
> ZADD myzset 100 one 200 two 300 three
(integer) 3
> ZRANGE myzset 1 2
1) "two"
2) "three"
> ZRANGE myzset -2 -1
1) "two"
2) "three"
> ZRANGE myzset 0 -1
1) "one"
2) "two"
3) "three"
> ZRANGE myzset 0 -1 withscores
1) "one"
2) "100"
3) "two"
4) "200"
5) "three"
6) "300"
> ZRANGE myzset 0 -1 rev withscores
1) "three"
2) "300"
3) "two"
4) "200"
5) "one"
6) "100"
> ZRANGE myzset 100 200 byscore withscores
1) "one"
2) "100"
3) "two"
4) "200"
> ZRANGE myzset 300 200 byscore rev withscores
1) "three"
2) "300"
3) "two"
4) "200"
> ZRANGE myzset 100 300 byscore limit 0 2 withscores
1) "one"
2) "100"
3) "two"
4) "200"
> ZRANGE myzset (100 (300 byscore withscores
1) "two"
2) "200"
> ZRANGE myzset -inf +inf byscore withscores
1) "one"
2) "100"
3) "two"
4) "200"
5) "three"
6) "300"
> FLUSHDB
OK
> ZADD myzset 0 a 0 b 0 c 0 d 0 e 0 f
(integer) 6
> ZRANGE myzset - + bylex
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
6) "f"
> ZRANGE myzset - (d bylex
1) "a"
2) "b"
3) "c"
> ZRANGE myzset - [d bylex
1) "a"
2) "b"
3) "c"
4) "d"
> ZRANGE myzset + [d bylex rev
1) "f"
2) "e"
3) "d"
> ZRANGE myzset + [d bylex rev limit 1 2
1) "e"
2) "d"
ZCOUNT:统计指定分值范围内的成员数量
- 语法:
ZCOUNT key min max
返回在key处的排序集合中的元素个数,其值介于最小值和最大值之间。
min和max参数具有与ZRANGEBYSCORE所描述的相同的语义。
注意:该命令的复杂度仅为O(log(N)),因为它使用元素秩(参见ZRANK)来了解范围。因此,不需要做与范围大小成正比的功。
redis
> ZADD myzset 100 one 200 two 300 three
(integer) 3
> ZCOUNT myzset -inf +inf
(integer) 3
> ZCOUNT myzset (100 300
(integer) 2
ZLEXCOUNT:统计位于字典序指定范围内的成员数量
- 语法:
ZLEXCOUNT key min max
当以相同的分数插入排序集中的所有元素时,为了强制按字典顺序排序,该命令返回在key处的排序集中的元素数量,其值介于min和max之间。
redis
> ZADD myzset 0 a 0 b 0 c 0 d 0 e 0 f
(integer) 6
> ZLEXCOUNT myzset - +
(integer) 6
> ZLEXCOUNT myzset [b (f
(integer) 4
ZREMRANGEBYRANK:移除指定排名范围内的成员
- 语法:
ZREMRANGEBYRANK key start stop
移除存储在键上的排序集中的所有元素,其秩介于start和stop之间。start和stop都是基于0的索引,0是得分最低的元素。这些索引可以是负数,它们表示从得分最高的元素开始的偏移量。例如:-1是得分最高的元素,-2是得分第二高的元素,依此类推。
redis
> ZADD myzset 100 one 200 two 300 three
(integer) 3
> ZREMRANGEBYRANK myzset -2 -1
(integer) 2
> ZRANGE myzset 0 -1
1) "one"
ZREMRANGEBYSCORE:移除指定分值范围内的成员
- 语法:
ZREMRANGEBYSCORE key min max
移除存储在key处且得分介于min和max(包括)之间的排序集中的所有元素。
redis
> ZADD myzset 100 one 200 two 300 three
(integer) 3
> ZREMRANGEBYSCORE myzset -inf (300
(integer) 2
> ZRANGE myzset 0 -1
1) "three"
ZREMRANGEBYLEX:移除位于字典序指定范围内的成员
- 语法:
ZREMRANGEBYLEX key min max
当以相同的分数插入排序集中的所有元素时,为了强制按字典顺序排序,该命令删除存储在key处、位于min和max指定的字典顺序范围之间的排序集中的所有元素。
redis
> ZADD myzset 0 a 0 b 0 c 0 d 0 e 0 f
(integer) 6
> ZREMRANGEBYLEX myzset [b (f
(integer) 4
> ZRANGE myzset 0 -1
1) "a"
2) "f"
ZUNIONSTORE:有序集合的并集运算
- 语法:
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE <SUM | MIN | MAX>]
计算由指定键给出的numkeys排序集的并集,并将结果存储在目标中。在传递输入键和其他(可选)参数之前,必须提供输入键的数量(numkeys)。
默认情况下,一个元素的结果得分是它在其存在的排序集中得分的总和。
使用WEIGHTS选项,可以为每个输入排序集指定乘法因子。这意味着,在传递给聚合函数之前,每个输入排序集中的每个元素的得分都要乘以这个因子。当没有给出WEIGHTS时,乘法因子默认为1。
通过AGGREGATE选项,可以指定如何聚合联合的结果。该选项默认为SUM,其中元素的得分在其存在的输入中求和。当将该选项设置为MIN或MAX时,结果集将包含元素在输入中存在的最小或最大分数。
如果目标已经存在,则会覆盖它。
redis
> ZADD zset1 1 one 2 two 3 three
(integer) 3
> ZADD zset2 1 one 2 two
(integer) 2
> ZUNIONSTORE zset3 2 zset1 zset2
(integer) 3
> ZRANGE zset3 0 -1 withscores
1) "one"
2) "2"
3) "three"
4) "3"
5) "two"
6) "4"
> ZUNIONSTORE zset3 2 zset1 zset2 weights 2 3
(integer) 3
> ZRANGE zset3 0 -1 withscores
1) "one"
2) "5"
3) "three"
4) "6"
5) "two"
6) "10"
> ZUNIONSTORE zset3 2 zset1 zset2 aggregate min
(integer) 3
> ZRANGE zset3 0 -1 withscores
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
ZINTERSTORE:有序集合的交集运算
- 语法:
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE <SUM | MIN | MAX>]
计算由指定键给出的numkeys排序集的交集,并将结果存储在目标中。在传递输入键和其他(可选)参数之前,必须提供输入键的数量(numkeys)。
默认情况下,一个元素的结果得分是它在其存在的排序集中得分的总和。因为交集要求一个元素是每个给定排序集的成员,这导致结果排序集中每个元素的得分等于输入排序集的数量。
使用WEIGHTS选项,可以为每个输入排序集指定乘法因子。这意味着,在传递给聚合函数之前,每个输入排序集中的每个元素的得分都要乘以这个因子。当没有给出WEIGHTS时,乘法因子默认为1。
通过AGGREGATE选项,可以指定如何聚合联合的结果。该选项默认为SUM,其中元素的得分在其存在的输入中求和。当将该选项设置为MIN或MAX时,结果集将包含元素在输入中存在的最小或最大分数。
如果目标已经存在,则会覆盖它。
redis
> ZADD zset1 1 one 2 two 3 three
(integer) 3
> ZADD zset2 1 one 2 two
(integer) 2
> ZINTERSTORE zset3 2 zset1 zset2
(integer) 2
> ZRANGE zset3 0 -1 withscores
1) "one"
2) "2"
3) "two"
4) "4"
> ZINTERSTORE zset3 2 zset1 zset2 weights 2 3
(integer) 2
> ZRANGE zset3 0 -1 withscores
1) "one"
2) "5"
3) "two"
4) "10"
> ZINTERSTORE zset3 2 zset1 zset2 aggregate min
(integer) 2
> ZRANGE zset3 0 -1 withscores
1) "one"
2) "1"
3) "two"
4) "2"
ZPOPMAX、ZPOPMIN:弹出分值最高和最低的成员
- 语法:
ZPOPMAX key [count]
或ZPOPMIN key [count]
删除并返回存储在key的排序集中得分最高(或最低)的成员,最多不超过count个。
如果未指定,count的默认值为1。指定高于排序集基数的计数值将不会产生错误。当返回多个元素时,得分最高(或最低)的元素将是第一个,其次是得分较低(或较高)的元素。
redis
> ZADD myzset 1 a 2 b 3 c 4 e 5 f 6 g
(integer) 6
> ZPOPMAX myzset 2
1) "g"
2) "6"
3) "f"
4) "5"
> ZPOPMIN myzset
1) "a"
2) "1"
> ZRANGE myzset 0 -1 withscores
1) "b"
2) "2"
3) "c"
4) "3"
5) "e"
6) "4"
BZPOPMAX、BZPOPMIN:阻塞式最大/最小元素弹出操作
- 语法:
BZPOPMAX key [key ...] timeout
或BZPOPMIN key [count]
它是阻塞版本,因为当没有成员要从任何给定的排序集中弹出时,它会阻塞连接。从第一个非空排序集弹出得分最高(或最低)的成员,并按给定的顺序检查给定的键。
timeout参数被解释为指定要阻塞的最大秒数的双值。超时为零可以用来无限期阻塞。
redis
> ZADD zset1 1 a
(integer) 1
> ZADD zset2 1 aa 2 bb
(integer) 2
> BZPOPMAX zset1 zset2 3
1) "zset1"
2) "a"
3) "1"
> BZPOPMIN zset1 zset2 3
1) "zset2"
2) "aa"
3) "1"
> BZPOPMIN zset1 zset2 3
1) "zset2"
2) "bb"
3) "2"
> BZPOPMIN zset1 zset2 3
(nil)
(3.03s)
Streams(流)
Redis stream是一种数据结构,其作用类似于追加日志。您可以使用流来实时记录和同时聚合事件。
Redis为每个流条目生成一个唯一的ID。您可以使用这些id稍后检索它们关联的条目,或者读取和处理流中的所有后续条目。
Redis流支持多种精简策略(防止流无界增长)和多个消费策略(参见XREAD, XREADGROUP和XRANGE)。
XADD:追加新元素到流的末尾
语法:
redisXADD key [NOMKSTREAM] [<MAXLEN | MINID> [= | ~] threshold [LIMIT count]] <* | id> field value [field value ...]
将指定的流项附加到流的指定键处。如果该键不存在,作为运行此命令的副作用,将使用流值创建该键。可以使用NOMKSTREAM选项禁用流键的创建。
条目由字段值对列表组成。字段值对按照用户给出的顺序存储。读取流的命令,如XRANGE或XREAD,保证以XADD添加字段和值时的相同顺序返回它们。
XADD是唯一可以向流中添加数据的Redis命令,但还有其他命令,如XDEL和XTRIM,能够从流中删除数据。
指定流ID作为参数
流条目ID标识流中给定的条目。
如果指定的ID参数是*字符(星号ASCII字符),XADD命令将自动为您生成唯一的ID。然而,尽管只在极少数情况下有用,但可以指定格式良好的ID,这样新条目将完全使用指定的ID添加。
id由两个由-
字符分隔的数字指定,如1526919030474-55
。这两个量都是64位的数字。当自动生成ID时,第一部分是生成ID的Redis实例的Unix时间(以毫秒为单位)。第二部分只是一个序列号,用于区分在同一毫秒内生成的id。
您还可以指定一个不完整的ID,它只包含毫秒部分,对于序列部分,它被解释为零值。如果只自动生成序列部分,请指定毫秒部分,后面跟着-分隔符和*字符:
redis
> XADD mystream 1526919030474-55 message "Hello,"
"1526919030474-55"
> XADD mystream 1526919030474-* message " World!"
"1526919030474-56"
ID保证始终是增量的:如果比较刚刚插入的条目的ID,它将大于任何其他过去的ID,因此条目在流中完全有序。为了保证该属性,如果流中的当前顶部ID的时间大于实例的当前本地时间,则将使用顶部进入时间,并且ID的序列部分增加。例如,当本地时钟向后跳时,或者在故障转移之后,新的主服务器具有不同的绝对时间时,可能会发生这种情况。
当用户向XADD指定显式ID时,最小有效ID为0-1,并且用户必须指定一个大于当前流中的任何其他ID的ID,否则命令将失败并返回错误。通常求助于特定的id是有用的,只有当你有另一个系统生成唯一的id(例如SQL表),你真的想要Redis流id匹配另一个系统。
限制流
XADD包含了与XTRIM命令相同的语义——有关更多信息,请参阅其文档页面。这允许添加新条目并通过对XADD的单个调用来检查流的大小,从而有效地用任意阈值限制流。虽然精确的修剪是可能的,而且是默认的,但由于流的内部表示,使用几乎精确的修剪(~参数)用XADD添加条目和修剪流会更有效。
例如,以以下形式调用XADD:
redis
XADD mystream MAXLEN ~ 1000 * ... entry fields here ...
将添加一个新条目,但也将删除旧条目,这样流将只包含1000个条目,或者最多包含几十个条目。
返回值
批量字符串回复,具体为:
该命令返回添加的条目的ID。如果将*作为ID参数传递,则该ID是自动生成的ID,否则该命令只返回用户在插入时指定的相同ID。
当与NOMKSTREAM选项一起使用时,该命令返回Null回复,并且键不存在。
示例
redis
> XADD mystream 100000-0 k1 v1
"100000-0"
> XADD mystream 100000-1 k1 v1 k2 v2
"100000-1"
> XADD mystream 100000-* k1 v1
"100000-2"
> XADD mystream * k1 v1
"1675495165942-0"
> XADD mystream maxlen 3 * k1 v1
"1675495241389-0"
> XLEN mystream
(integer) 3
> XADD mystream maxlen ~ 3 * k1 v1
"1675495451126-0"
> XLEN mystream
(integer) 4
XTRIM:对流进行修剪
- 语法:
XTRIM key <MAXLEN | MINID> [= | ~] threshold [LIMIT count]
XTRIM在需要时通过清除较旧的条目(id较低的条目)来修剪流。
修剪流可以使用以下策略之一:
MAXLEN:只要流的长度超过指定的长度,就会清除条目
threshold:其中阈值为正整数。
MINID:清除ID小于阈值的表项,其中阈值是一个流ID。
例如,这将把流修剪到最近的1000项:XTRIM mystream MAXLEN 1000
。
而在本例中,ID小于649085820-0的所有条目将被清除:XTRIM mystream MINID 649085820
。
默认情况下,或者当提供可选的=参数时,该命令执行精确的调整。
根据策略的不同,精确修剪意味着:
MAXLEN:修剪后的流的长度将恰好是其原始长度和指定阈值之间的最小值。
MINID:流中最老的ID将恰好是其原始最老ID与指定阈值之间的最大值。
近乎精确的修剪
因为精确的修剪可能需要Redis服务器付出额外的努力,所以可以提供可选的~参数来提高效率。
例如:XTRIM mystream MAXLEN ~ 1000
。
MAXLEN策略和阈值之间的~参数意味着用户请求修剪流,使其长度至少等于阈值,但可能略大于阈值。在这种情况下,Redis会在性能可以提高时(例如,当数据结构中的整个宏节点不能被删除时)提前停止修剪。这使得修剪更加有效,这通常是您想要的,尽管在修剪之后,流可能会有几十个超过阈值的额外条目。
在使用~时控制该命令的工作量的另一种方法是使用LIMIT子句。使用时,它指定将被清除的条目的最大计数。当LIMIT和count未指定时,默认值100 *宏节点中的条目数将隐式用作计数。将值0指定为count将完全禁用限制机制。
示例
redis
> XADD mystream * k1 v1
"1675648181613-0"
> XADD mystream * k1 v1
"1675648188223-0"
> XADD mystream * k1 v1
"1675648188941-0"
> XADD mystream * k1 v1
"1675648189544-0"
> XADD mystream * k1 v1
"1675648190582-0"
> XTRIM mystream maxlen 3
(integer) 2
> XLEN mystream
(integer) 3
XDEL:移除指定元素
- 语法:
XDEL key id [id ...]
从流中删除指定的项,并返回已删除的项数。如果流中不存在某些指定的id,则此数目可能小于传递给命令的id数目。
通常情况下,你可能认为Redis流是一个只能追加的数据结构,但是Redis流是在内存中表示的,所以我们也可以删除条目。这可能是有用的,例如,为了遵守某些隐私政策。
返回
整数回复:实际删除的条目数。
redis
> XADD mystream * k1 v1
"1675648704833-0"
> XADD mystream * k1 v1
"1675648706009-0"
> XADD mystream * k1 v1
"1675648709915-0"
> XADD mystream 10000000000000 k1 v1
"10000000000000-0"
> XDEL mystream 10000000000000-0 1675648189544-0
(integer) 1
> XLEN mystream
(integer) 3
XLEN:获取流包含的元素数量
- 语法:
XLEN key
返回流中的条目数。如果指定的键不存在,该命令返回零,就像流为空一样。但是请注意,不像其他Redis类型,零长度流是可能的,所以你应该调用TYPE或EXISTS来检查一个键是否存在。
一旦流内部没有条目(例如在XDEL调用之后),流就不会自动删除,因为流可能有与之关联的消费者组。
返回
整数回复:该流在键处的条目数。
redis
> XADD mystream * k1 v1
"1675649321401-0"
> XADD mystream * k1 v1
"1675649322259-0"
> XADD mystream * k1 v1
"1675649322771-0"
> XLEN mystream
(integer) 3
XRANGE:正序访问流中元素
- 语法:
XRANGE key start end [COUNT count]
该命令返回匹配给定id范围的流项。范围由最小ID和最大ID指定。返回ID在两个指定的ID之间或恰好是两个指定ID之一的所有条目(封闭间隔)。
XRANGE命令有很多应用:
返回特定时间范围内的项目。这是可能的,因为流id与时间相关。
增量迭代流,每次迭代只返回几个项。然而,它在语义上比SCAN函数家族健壮得多。
从流中获取单个条目,提供两次要获取的条目ID:作为查询间隔的开始和结束。
该命令还有一个以相反顺序返回项的对等命令,称为XREVRANGE,在其他方面是相同的。
特殊id-和+
特殊ID -和+分别表示流中可能的最小ID和最大ID,因此下面的命令将只返回流中的每个条目:
redis
> XRANGE somestream - +
1) 1) 1526985054069-0
2) 1) "duration"
2) "72"
3) "event-id"
4) "9"
5) "user-id"
6) "839248"
2) 1) 1526985069902-0
2) 1) "duration"
2) "415"
3) "event-id"
4) "2"
5) "user-id"
6) "772213"
... other entries here ...
特殊id -和+分别表示最小范围id和最大范围id,但是它们更便于输入。
不完整的id
流id由两部分组成,Unix毫秒时间戳和插入同一毫秒的条目的序列号。可以使用XRANGE仅指定ID的第一部分,即毫秒时间,如下例所示:
redis
> XRANGE somestream 1526985054069 1526985055069
在本例中,XRANGE将自动用-0补全开始间隔,用-18446744073709551615补全结束间隔,以便返回在给定毫秒和另一个指定毫秒结束之间生成的所有条目。这也意味着重复相同的毫秒两次,我们将获得该毫秒内的所有条目,因为序列号范围将从0到最大值。
在这种情况下,XRANGE作为一个范围查询命令,在指定的时间内获取条目。这对于访问流中过去事件的历史非常方便。
独占范围
默认情况下,范围很近(包括),这意味着应答可以包括id与查询的开始和结束间隔匹配的条目。可以通过在ID前面加上字符(来指定一个开放间隔(独占)。这对于迭代流非常有用,如下所述。
返回最大条目数
使用COUNT选项可以减少报告的条目数量。这是一个非常重要的特性,即使它看起来很边缘,因为它允许,例如,建模操作,例如给我大于或等于以下项:
redis
> XRANGE somestream 1526985054069-0 + COUNT 1
1) 1) 1526985054069-0
2) 1) "duration"
2) "72"
3) "event-id"
4) "9"
5) "user-id"
6) "839248"
在上面的例子中,条目1526985054069-0存在,否则服务器会发送给我们下一个条目。使用COUNT也是使用XRANGE作为迭代器的基础。
迭代流
为了迭代流,我们可以按照以下步骤进行。让我们假设每次迭代需要两个元素。我们开始获取前两个元素,这很简单:
redis
> XRANGE writers - + COUNT 2
1) 1) 1526985676425-0
2) 1) "name"
2) "Virginia"
3) "surname"
4) "Woolf"
2) 1) 1526985685298-0
2) 1) "name"
2) "Jane"
3) "surname"
4) "Austen"
然后,我们不再从-开始迭代,而是使用前一次XRANGE调用返回的最后一个条目的条目ID作为独占间隔。
最后一个条目的ID是1526985685298-0,所以我们只需在它前面加上一个'(',然后继续我们的迭代:
redis
> XRANGE writers (1526985685298-0 + COUNT 2
1) 1) 1526985691746-0
2) 1) "name"
2) "Toni"
3) "surname"
4) "Morrison"
2) 1) 1526985712947-0
2) 1) "name"
2) "Agatha"
3) "surname"
4) "Christie"
等等。最终,这将允许访问流中的所有条目。显然,我们可以通过提供给定的不完整的开始ID,从任何ID开始迭代,甚至从特定的时间开始迭代。此外,我们可以通过提供结束ID或不完整ID而不是+来限制迭代到给定的ID或时间。
获取单个项目
如果您寻找XGET命令,您将会失望,因为XRANGE是有效地从流中获取单个条目的方法。你所要做的就是在XRANGE的参数中指定ID两次:
redis
> XRANGE mystream 1526984818136-0 1526984818136-0
1) 1) 1526984818136-0
2) 1) "duration"
2) "1532"
3) "event-id"
4) "5"
5) "user-id"
6) "7782813"
返回值
数组回复,具体来说:
该命令返回id与指定范围匹配的表项。返回的条目是完整的,这意味着返回ID和由它们组成的所有字段。而且,返回的条目的字段和值与XADD添加它们的顺序完全相同。
示例
redis
> XADD mystream * k1 v1
"1675670959398-0"
> XADD mystream * k1 v1
"1675670959877-0"
> XADD mystream * k1 v1
"1675670960280-0"
> XADD mystream * k1 v1
"1675670960799-0"
> XADD mystream * k1 v1
"1675670962558-0"
> XLEN mystream
(integer) 5
> XRANGE mystream - + count 3
1) 1) "1675670959398-0"
2) 1) "k1"
2) "v1"
2) 1) "1675670959877-0"
2) 1) "k1"
2) "v1"
3) 1) "1675670960280-0"
2) 1) "k1"
2) "v1"
XREVRANGE:倒序访问流中元素
- 语法:
XREVRANGE key end start [COUNT count]
该命令与XRANGE完全相似,但有一个显著的区别,即以相反的顺序返回条目,并且也以相反的顺序获取起始-结束范围:在XREVRANGE中,您需要先声明结束ID,然后声明开始ID,该命令将从结束端开始生成两个ID之间(或完全相同)的所有元素。
例如,要获取从高ID到低ID的所有元素,可以使用:
redis
XREVRANGE somestream + -
类似地,只需要将最后一个元素添加到流中就足够了:
redis
XREVRANGE somestream + - COUNT 1
返回
数组回复,具体来说:
该命令返回ID匹配指定范围的表项,从高ID到低ID匹配。返回的条目是完整的,这意味着返回ID和由它们组成的所有字段。此外,返回的条目的字段和值与XADD添加它们的顺序完全相同。
redis
> XADD mystream * k1 v1
"1675670959398-0"
> XADD mystream * k1 v1
"1675670959877-0"
> XADD mystream * k1 v1
"1675670960280-0"
> XADD mystream * k1 v1
"1675670960799-0"
> XADD mystream * k1 v1
"1675670962558-0"
> XLEN mystream
(integer) 5
> XREVRANGE mystream + - count 3
1) 1) "1675670962558-0"
2) 1) "k1"
2) "v1"
2) 1) "1675670960799-0"
2) 1) "k1"
2) "v1"
3) 1) "1675670960280-0"
2) 1) "k1"
2) "v1"
XREAD:以阻塞或非阻塞方式获取流元素
- 语法:
XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] id [id ...]
从一个或多个流读取数据,只返回ID大于调用者报告的最后接收ID的条目。该命令有一个选项,可以在项目不可用时阻塞,类似于BRPOP或BZPOPMIN等。
非阻塞使用
如果没有使用BLOCK选项,则该命令是同步的,可以认为它与XRANGE有点关联:它将在流中返回一系列项,然而,即使我们只考虑同步使用,它与XRANGE有两个基本区别:
如果我们希望同时从多个键读取,则可以使用多个流调用此命令。这是XREAD的一个关键特性,因为特别是在使用BLOCK进行阻塞时,能够侦听到多个键的单个连接是一个重要特性。
XRANGE返回一个id范围内的项,而XREAD更适合于从第一个条目开始消费流,这个条目比我们目前看到的任何其他条目都要大。所以我们传递给XREAD的是,对于每个流,我们从该流接收到的最后一个元素的ID。
例如,如果我有两个流mystream和writer,我想从两个流中包含的第一个元素开始读取数据,我可以像下面的例子中那样调用XREAD。
注意:我们在示例中使用COUNT选项,因此对于每个流,调用将最多返回两个元素。
redis
> XREAD COUNT 2 STREAMS mystream writers 0-0 0-0
1) 1) "mystream"
2) 1) 1) 1526984818136-0
2) 1) "duration"
2) "1532"
3) "event-id"
4) "5"
5) "user-id"
6) "7782813"
2) 1) 1526999352406-0
2) 1) "duration"
2) "812"
3) "event-id"
4) "9"
5) "user-id"
6) "388234"
2) 1) "writers"
2) 1) 1) 1526985676425-0
2) 1) "name"
2) "Virginia"
3) "surname"
4) "Woolf"
2) 1) 1526985685298-0
2) 1) "name"
2) "Jane"
3) "surname"
4) "Austen"
STREAMS选项是强制的,必须是最后一个选项,因为这个选项的参数长度是可变的,格式如下:
redis
STREAMS key_1 key_2 key_3 ... key_N ID_1 ID_2 ID_3 ... ID_N
因此,我们从一个键列表开始,然后继续使用所有相关的ID,表示我们为该流接收到的最后一个ID,因此调用将只为我们提供来自同一流的较大ID。
例如,在上面的例子中,流mystream接收到的最后一个项的ID为1526999352406-0,而流writer的ID为1526985685298-0。
为了继续迭代这两个流,我将调用:
redis
> XREAD COUNT 2 STREAMS mystream writers 1526999352406-0 1526985685298-0
1) 1) "mystream"
2) 1) 1) 1526999626221-0
2) 1) "duration"
2) "911"
3) "event-id"
4) "7"
5) "user-id"
6) "9488232"
2) 1) "writers"
2) 1) 1) 1526985691746-0
2) 1) "name"
2) "Toni"
3) "surname"
4) "Morrison"
2) 1) 1526985712947-0
2) 1) "name"
2) "Agatha"
3) "surname"
4) "Christie"
等等。最终,调用将不会返回任何项,而只是一个空数组,然后我们知道没有更多的东西可以从我们的流中获取(并且我们将不得不重试操作,因此该命令也支持阻塞模式)。
不完整的id
使用不完整的id是有效的,就像使用XRANGE一样。然而,在这里,ID的序列部分,如果缺失,总是被解释为零,因此命令: > XREAD COUNT 2 STREAMS mystream writers 0 0
完全等价于> XREAD COUNT 2 STREAMS mystream writers 0-0 0-0
。
阻塞数据
在其同步形式中,只要有更多可用项,该命令就可以获得新数据。但是,在某些时候,我们必须等待数据生产者使用XADD将新条目推入我们正在使用的流中。为了避免在固定或自适应的时间间隔内轮询,该命令可以根据指定的流和id在无法返回任何数据时阻塞,并在请求的键之一接受数据时自动取消阻塞。
重要的是要理解这个命令扇形到等待相同范围id的所有客户端,因此每个消费者都将获得数据的从服务器,这与使用阻塞列表弹出操作时发生的情况不同。
为了阻塞,我们使用了block选项,以及在超时前我们想要阻塞的毫秒数。通常情况下,Redis阻塞命令的超时时间以秒为单位,但是这个命令的超时时间为毫秒,即使正常情况下服务器的超时决议接近0.1秒。这一次,在某些用例中阻塞的时间可能会更短,如果服务器内部结构随着时间的推移而改进,则超时的决议可能会提高。
当传递BLOCK命令时,但在传递的流中至少有一个要返回的数据,该命令将同步执行,就像缺少BLOCK选项一样。
这是一个阻塞调用的例子,命令稍后返回一个空应答,因为超时已经过去,没有新的数据到达:
redis
> XREAD BLOCK 1000 STREAMS mystream 1526999626221-0
(nil)
特殊的$ ID
阻塞时,有时我们希望只接收从阻塞时开始通过XADD添加到流中的条目。在这种情况下,我们对已经添加的条目的历史不感兴趣。对于这个用例,我们必须检查流顶部元素ID,并在XREAD命令行中使用该ID。这并不干净,需要调用其他命令,因此可以使用特殊的$ ID向流发出信号,表明我们只想要新东西。
很重要的一点是,您应该只在第一次调用XREAD时使用$ ID。稍后,ID应该是流中最后报告的项之一,否则您可能会错过在这中间添加的所有条目。
这是一个典型的XREAD调用在消费者只消费新条目的第一次迭代中的样子:
redis
> XREAD BLOCK 5000 COUNT 100 STREAMS mystream $
一旦我们得到一些回复,下一个电话将是这样的:
redis
> XREAD BLOCK 5000 COUNT 100 STREAMS mystream 1526999644174-3
等等。
如何为单个流上阻塞的多个客户端提供服务
列表或排序集上的阻塞列表操作具有弹出行为。基本上,元素从列表或排序集中删除,以便返回给客户端。在这个场景中,您希望以公平的方式使用项,这取决于客户机阻塞给定密钥到达的时间。通常Redis在这个用例中使用FIFO语义。
但是请注意,对于流,这不是一个问题:当客户端被服务时,流条目不会从流中删除,因此只要XADD命令向流提供数据,每个等待的客户端都将被服务。
返回值
数组回复,具体来说:
该命令返回一个结果数组:返回数组的每个元素都是由两个元素组成的数组,其中包含键名和为该键报告的条目。报告的条目是完整的流条目,具有id和所有字段和值的列表。字段和值的报告顺序保证与XADD添加时相同。
当使用BLOCK时,超时时返回空应答。
示例
redis
> XADD mystream * k1 v1
"1675737504264-0"
> XREAD streams mystream 0
1) 1) "mystream"
2) 1) 1) "1675737504264-0"
2) 1) "k1"
2) "v1"
> XREAD streams mystream $
(nil)
> XADD mystream * k1 v1
"1675737551381-0"
> XADD mystream * k1 v1
"1675737553717-0"
> XREAD count 2 streams mystream 0
1) 1) "mystream"
2) 1) 1) "1675737504264-0"
2) 1) "k1"
2) "v1"
2) 1) "1675737551381-0"
2) 1) "k1"
2) "v1"
> XREAD block 1000 streams mystream $
(nil)
(1.09s)
> XREAD block 5000 streams mystream $
1) 1) "mystream"
2) 1) 1) "1675738127844-0"
2) 1) "k2"
2) "v2"
(3.09s)
other> XADD mystream * k2 v2
"1675738127844-0"
other> XADD mystream * k2 v2
"1675738129139-0"
> XADD mystream1 * k1 v1
"1675739914536-0"
> XREAD count 2 streams mystream mystream1 0 0
1) 1) "mystream"
2) 1) 1) "1675737504264-0"
2) 1) "k1"
2) "v1"
2) 1) "1675737551381-0"
2) 1) "k1"
2) "v1"
2) 1) "mystream1"
2) 1) 1) "1675739914536-0"
2) 1) "k1"
2) "v1"
> XREAD block 10000 streams mystream mystream1 $ $
1) 1) "mystream1"
2) 1) 1) "1675740032559-0"
2) 1) "k1"
2) "v1"
(4.85s)
other> XADD mystream1 * k1 v1
"1675740032559-0"
other> XADD mystream * k1 v1
"1675740036194-0"
消费者组
当手头的任务是使用来自不同客户端的相同流时,XREAD已经提供了一种向外扇到N个客户端的方法,可能还使用从服务器以提供更多的读取可伸缩性。然而,在某些问题中,我们想做的不是向许多客户端提供相同的消息流,而是向许多客户端提供来自同一流的不同消息子集。一个明显的有用的例子是处理速度较慢的消息:有N个不同的工作人员接收流的不同部分的能力允许我们扩展消息处理,通过将不同的消息路由到准备做更多工作的不同工作人员。
基本的消费组命令如下:
XGROUP用于创建、销毁和管理用户组。
XREADGROUP用于通过消费组从流中读取。
XACK是允许使用者将挂起的消息标记为正确处理的命令。
XGROUP CREATE:创建消费者组
- 语法:
XGROUP CREATE key groupname <id | $> [MKSTREAM] [ENTRIESREAD entries_read]
为存储在<key
>的流创建一个由<groupname
>唯一标识的新消费者组。
在给定的流中,每个组都有一个唯一的名称。如果已经存在同名的用户组,则该命令返回-BUSYGROUP错误。
命令的<id
>参数指定从新组的透视图来看流中最后交付的条目。特殊ID $是流中最后一个条目的ID,但是您可以用任何有效的ID替换它。
例如,如果你想让组的消费者从开始获取整个流,使用0作为消费者组的起始ID:XGROUP CREATE mystream mygroup 0
默认情况下,XGROUP CREATE
命令期望目标流存在,如果不存在则返回错误。如果一个流不存在,你可以使用可选的MKSTREAM子命令作为<id
>后的最后一个参数自动创建长度为0的流:XGROUP CREATE mystream mygroup $ MKSTREAM
要启用消费者组延迟跟踪,请使用任意ID指定可选的entries_read参数。任意ID是流的第一个条目、最后一个条目或零(“0-0”)ID以外的任何ID。使用它来找出任意ID(不包括它)和流的最后一个条目之间有多少条目。设置entries_read流的entries_added减去条目数。
返回
简单的字符串回答:成功就OK。
redis
> XGROUP CREATE mystream mygroup $ mkstream
OK
XGROUP SETID:修改消费者组的最后递送消息ID
- 语法:
XGROUP SETID key groupname <id | $> [ENTRIESREAD entries_read]
设置消费者组的最后一个交付ID。
通常,使用XGROUP CREATE创建组时设置消费者组的最后一个交付ID。XGROUP SETID命令允许修改组的最后一个交付ID,而不必删除和重新创建组。例如,如果你想让消费者组中的消费者重新处理流中的所有消息,你可能想将其下一个ID设置为0:XGROUP SETID mystream mygroup 0
可选的entries_read参数可以指定为任意ID启用消费者组延迟跟踪。任意ID是流的第一个条目、最后一个条目或零(“0-0”)ID以外的任何ID。这很有用,因为您可以准确地知道在任意ID(不包括它)和流的最后一个条目之间有多少条目。在这种情况下,可以将entries_read设置为流的entries_added减去条目数。
返回
简单的字符串回答:成功就OK。
redis
> XGROUP CREATE mystream mygroup $ mkstream
OK
> XGROUP SETID mystream mygroup 0
OK
XGROUP CREATECONSUMER:创建消费者
- 语法:
XGROUP CREATECONSUMER key groupname consumername
在存储在<key
>的流的消费者组<groupname
>中创建一个名为<consumername
>的消费者。
当一个操作(比如XREADGROUP)引用一个不存在的消费者时,也会自动创建消费者。只有当流中有数据时,这才对XREADGROUP有效。
返回
整数回复:已创建的消费者数量(0或1)
redis
> XGROUP CREATECONSUMER mystream mygroup myconsumer
(integer) 1
XREADGROUP GROUP:读取消费者组
- 语法:
XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] id [id ...]
XREADGROUP命令是支持使用者组的XREAD命令的特殊版本。在阅读本页之前,您可能必须先理解XREAD命令。
此命令与普通XREAD之间的区别在于,该命令支持使用者组。
如果没有消费者组,只使用XREAD,则所有客户端都将使用流中的所有条目进行服务。而使用XREADGROUP的消费者组,可以创建客户端组,这些客户端组使用到达给定流的消息的不同部分。例如,如果流获得新条目A、B和C,并且有两个消费者通过消费者组读取,那么一个客户端将获得消息A和C,另一个客户端将获得消息B,依此类推。
在消费者组中,给定的消费者(即从流中消费消息的客户端)必须使用唯一的消费者名称进行标识。它只是一个字符串。
消费者组的保证之一是,给定的消费者只能看到传递给它的消息的历史,因此消息只有一个所有者。但是,有一个称为消息声明的特殊功能,允许其他消费者在某些消费者出现不可恢复的故障时声明消息。为了实现这样的语义,使用者组需要通过XACK命令显式地确认使用者成功处理的消息。这是必需的,因为对于每个消费者组,流将跟踪谁正在处理什么消息。
下面是如何理解你是否想要使用一个消费者组:
如果您有一个流和多个客户端,并且希望所有客户端获得所有消息,则不需要消费者组。
如果您有一个流和多个客户端,并且希望跨客户端对流进行分区或分片,以便每个客户端将获得流中到达的消息的一个子集,那么您需要一个消费者组。
XREAD和XREADGROUP之间的区别
从语法的角度来看,这些命令几乎是相同的,但是XREADGROUP需要一个特殊的强制性选项:GROUP <group-name> <consumer-name>
组名只是与流关联的使用者组的名称。组是使用XGROUP命令创建的。使用者名称是客户端用来在组中标识自身的字符串。消费者是在消费者组中第一次看到时自动创建的。不同的客户端应该选择不同的使用者名。
当您使用XREADGROUP进行读取时,服务器将记住已传递给您的给定消息:该消息将存储在消费者组中所谓的Pending Entries List (PEL)中,这是一个已传递但尚未确认的消息id列表。
客户端必须使用XACK确认消息处理,以便从PEL中删除挂起的条目。可以使用XPENDING命令检查PEL。
可以使用NOACK子命令来避免在不需要可靠性且偶尔消息丢失是可以接受的情况下向PEL添加消息。这相当于在读取消息时确认消息。
使用XREADGROUP时在STREAMS选项中指定的ID可以是以下两个之一:
特殊的> ID,这意味着使用者只想接收从未传递给任何其他使用者的消息。意思是,给我新的信息。
任何其他ID,即0或任何其他有效ID或不完整ID(仅为毫秒时间部分),将具有为发送命令的消费者返回ID大于所提供ID的待处理条目的效果。因此,基本上如果ID不是>,那么该命令将只允许客户端访问其挂起的条目:传递给它但尚未确认的消息。注意,在这种情况下,BLOCK和NOACK都被忽略。
像XREAD一样,XREADGROUP命令可以以阻塞的方式使用。在这方面没有区别。
当消息传递给消费者时会发生什么?
如果消息从未交付给任何人,也就是说,如果我们谈论的是一条新消息,那么将创建一个PEL (Pending Entries List)。
如果消息已经交付给该使用者,并且它只是再次重新获取相同的消息,那么最后一个交付计数器将更新为当前时间,并且交付的数量将增加1。您可以使用XPENDING命令访问这些消息属性。
删除挂起的消息时会发生什么?
在任何时候,由于修改或显式调用XDEL,条目都可能从流中删除。根据设计,Redis不会阻止删除流的bpel中存在的条目。当发生这种情况时,bpel保留已删除条目的id,但实际的条目有效负载不再可用。因此,当读取这样的PEL条目时,Redis将返回一个空值来代替它们各自的数据。
例如:
redis
> XADD mystream 1 myfield mydata
"1-0"
> XGROUP CREATE mystream mygroup 0
OK
> XREADGROUP GROUP mygroup myconsumer STREAMS mystream >
1) 1) "mystream"
2) 1) 1) "1-0"
2) 1) "myfield"
2) "mydata"
> XDEL mystream 1-0
(integer) 1
> XREADGROUP GROUP mygroup myconsumer STREAMS mystream 0
1) 1) "mystream"
2) 1) 1) "1-0"
2) (nil)
返回值
数组回复,具体来说:
该命令返回一个结果数组:返回数组的每个元素都是由两个元素组成的数组,其中包含键名和为该键报告的条目。报告的条目是完整的流条目,具有id和所有字段和值的列表。字段和值的报告顺序保证与XADD添加时相同。
当使用BLOCK时,超时时返回空应答。
示例
redis
> XGROUP CREATE mystream mygroup $ mkstream
OK
> XREADGROUP group mygroup myconsumer streams mystream >
(nil)
> XADD mystream * k1 v1
"1675932530320-0"
> XREADGROUP group mygroup myconsumer streams mystream >
1) 1) "mystream"
2) 1) 1) "1675932530320-0"
2) 1) "k1"
2) "v1"
> XREADGROUP group mygroup myconsumer streams mystream >
(nil)
> XREADGROUP group mygroup myconsumer streams mystream 0
1) 1) "mystream"
2) 1) 1) "1675932530320-0"
2) 1) "k1"
2) "v1"
XACK:消息的状态转换
- 语法:
XACK key group id [id ...]
XACK命令从流使用者组的Pending Entries List (PEL)中删除一条或多条消息。当消息被传递给某个消费者时(通常作为调用XREADGROUP的副作用),或者当消费者获得调用XCLAIM的消息的所有权时,消息处于挂起状态,因此存储在PEL中。挂起的消息已传递给某个使用者,但服务器还不能确定它至少被处理了一次。因此,对XREADGROUP的新调用以获取消费者的消息历史记录(例如使用ID为0)将返回这样的消息。类似地,检查PEL的XPENDING命令将列出挂起的消息。
一旦使用者成功处理了一条消息,它应该调用XACK,这样该消息就不会再次被处理,作为副作用,关于该消息的PEL条目也会被清除,从而从Redis服务器释放内存。
返回
整数回复,具体为:
该命令返回成功确认的消息数。某些消息id可能不再是PEL的一部分(例如,因为它们已经被确认),XACK将不会将它们算作已成功确认。
redis
> XGROUP CREATE mystream mygroup $ mkstream
OK
> XADD mystream * k1 v1
"1675932530320-0"
> XREADGROUP group mygroup myconsumer streams mystream >
1) 1) "mystream"
2) 1) 1) "1675932530320-0"
2) 1) "k1"
2) "v1"
> XACK mystream mygroup 1675932530320-0
(integer) 1
> XREADGROUP group mygroup myconsumer streams mystream 0
1) 1) "mystream"
2) (empty array)
XPENDING:显示待处理消息的相关信息
- 语法:
XPENDING key group [[IDLE min-idle-time] start end count [consumer]]
通过消费者组从流中获取数据,而不确认这样的数据,会产生创建挂起条目的效果。这在XREADGROUP命令中有很好的解释,在我们对Redis Streams的介绍中有更好的解释。XACK命令将立即从pending Entries List (PEL)中删除挂起的条目,因为一旦消息被成功处理,消费者组就不再需要跟踪它并记住消息的当前所有者。
XPENDING命令是用于检查挂起消息列表的接口,因此它是一个非常重要的命令,可以观察和理解流消费者组中发生了什么:哪些客户端处于活动状态,哪些消息挂起等待消费,或者查看是否有空闲消息。
此外,此命令与XCLAIM一起用于实现长时间出现故障的消费者的恢复,从而导致某些消息得不到处理:不同的消费者可以声明该消息并继续。这在流介绍和XCLAIM命令页中有更好的解释,这里不做介绍。
XPENDING总结
当只使用键名和使用者组名调用XPENDING时,它只输出给定使用者组中挂起消息的摘要。在下面的示例中,我们创建了一个消费者组,并通过使用XREADGROUP从该组读取消息来立即创建一条待处理消息。
redis
> XGROUP CREATE mystream group55 0-0
OK
> XREADGROUP GROUP group55 consumer-123 COUNT 1 STREAMS mystream >
1) 1) "mystream"
2) 1) 1) 1526984818136-0
2) 1) "duration"
2) "1532"
3) "event-id"
4) "5"
5) "user-id"
6) "7782813"
我们期望消费者组group55的待处理条目列表现在有一条消息:名为consumer-123的消费者获取了消息,但没有确认其处理。简单的XPENDING表单将为我们提供以下信息:
redis
> XPENDING mystream group55
1) (integer) 1
2) 1526984818136-0
3) 1526984818136-0
4) 1) 1) "consumer-123"
2) "1"
在这种形式中,该命令输出此消费者组的待定消息总数(为1),后面是待定消息中最小和最大的ID,然后列出至少有一条待定消息的消费者组中的每个消费者及其拥有的待定消息的数量。
XPENDING延伸
摘要提供了一个很好的概述,但有时我们对细节感兴趣。为了查看所有带有更多相关信息的挂起消息,我们还需要传递一个id范围,以类似的方式,我们使用XRANGE和一个不可选的count参数来限制每次调用返回的消息数量:
redis
> XPENDING mystream group55 - + 10
1) 1) 1526984818136-0
2) "consumer-123"
3) (integer) 196415
4) (integer) 1
在扩展表单中,我们不再看到摘要信息,取而代之的是挂起条目列表中每个消息的详细信息。对于每条消息返回四个属性:
消息的ID。
获取消息且仍需确认的使用者的名称。我们称它为消息的当前所有者。
自此消息最后一次交付给此使用者以来所经过的毫秒数。
传递此消息的次数。
传递计数器是数组中的第四个元素,当其他一些消费者使用XCLAIM声明消息时,或者当访问消费者组中的消费者的历史记录时,通过XREADGROUP再次传递消息时(有关详细信息,请参阅XREADGROUP页面),传递计数器会递增。
可以向命令传递一个额外的参数,以查看具有特定所有者的消息:> XPENDING mystream group55 - + 10 consumer-123
但在上面的情况下,输出将是相同的,因为我们只有单个消费者的挂起消息。然而,重要的是要记住,即使有来自多个消费者的许多待处理消息,这种由特定消费者筛选的操作也不是低效的:我们有一个全局和每个消费者的待处理条目列表数据结构,因此我们可以非常有效地仅显示单个消费者的待处理消息。
空闲时间过滤器
也可以通过它们的空闲时间来过滤挂起的流条目,以毫秒为单位(对于没有处理过一段时间的xclaim条目很有用):
redis
> XPENDING mystream group55 IDLE 9000 - + 10
> XPENDING mystream group55 IDLE 9000 - + 10 consumer-123
第一种情况将返回整个组中空闲超过9秒的前10个(或更少)PEL条目,而第二种情况只返回consumer-123的PEL条目。
独占范围和迭代PEL
XPENDING命令允许迭代挂起的条目,就像XRANGE和XREVRANGE允许流的条目一样。为此,您可以在最后一次读取挂起条目的ID前加上(字符,表示一个开放(独占)范围,并在随后的命令调用中证明它。
返回值
数组回复,具体来说:
该命令根据调用的方式以不同的格式返回数据,如本页前面所述。然而,回复总是一个数组的项目。
示例
redis
> XGROUP CREATE mystream mygroup $ mkstream
OK
> XADD mystream * k1 v1
"1675995126989-0"
> XREADGROUP group mygroup myconsumer streams mystream >
1) 1) "mystream"
2) 1) 1) "1675995126989-0"
2) 1) "k1"
2) "v1"
> XPENDING mystream mygroup
1) (integer) 1
2) "1675995126989-0"
3) "1675995126989-0"
4) 1) 1) "myconsumer"
2) "1"
> XPENDING mystream mygroup - + 1
1) 1) "1675995126989-0"
2) "myconsumer"
3) (integer) 152871
4) (integer) 1
> XACK mystream mygroup 1675995126989-0
(integer) 1
> XPENDING mystream mygroup
1) (integer) 0
2) (nil)
3) (nil)
4) (nil)
> XPENDING mystream mygroup - + 1
(empty array)
XCLAIM:转移消息的归属权
语法:
redisXCLAIM key group consumer min-idle-time id [id ...] [IDLE ms] [TIME unix-time-milliseconds] [RETRYCOUNT count] [FORCE] [JUSTID] [LASTID id]
在流使用者组的上下文中,此命令更改挂起消息的所有权,以便新的所有者是作为命令参数指定的使用者。通常情况是这样的:
有一个与消费者组相关联的流。
某个消费者A在该消费者组的上下文中通过XREADGROUP从流中读取消息。
作为副作用,在消费者组的pending Entries List (PEL)中创建了一个pending消息条目:这意味着消息已交付给给定的消费者,但尚未通过XACK进行确认。
然后突然之间,消费者永远地失败了。
其他使用者可能会使用XPENDING命令检查挂起的消息列表,这些消息已经过期很长时间了。为了继续处理此类消息,它们使用XCLAIM获取消息的所有权并继续。使用者还可以使用XAUTOCLAIM命令自动扫描和声明过期的待处理消息。
注意,只有当消息的空闲时间大于我们在调用XCLAIM时指定的最小空闲时间时,消息才会被声明。作为副作用,XCLAIM还将重置空闲时间(因为这是处理消息的新尝试),两个试图同时声明消息的消费者永远不会同时成功:只有一个会成功声明消息。这避免了我们以简单的方式多次处理给定的消息(然而在一般情况下,多次处理是可能的,也是不可避免的)。
此外,作为副作用,XCLAIM将增加尝试传递消息的计数,除非指定了JUSTID选项(仅传递消息ID,而不传递消息本身)。通过这种方式,由于某种原因(例如由于消费者试图处理它们时崩溃)而无法处理的消息将开始具有更大的计数器,并可以在系统内部检测到。
XCLAIM在以下情况下不会声明消息:
消息在组PEL中不存在(即它从未被任何消费者读取)
消息存在于组PEL中,但不存在于流本身(即消息被读取但从未被确认,然后通过修剪或XDEL从流中删除)
在这两种情况下,回复都不会包含与该消息对应的条目(即,回复数组的长度可能小于提供给XCLAIM的id的数量)。在后一种情况下,消息也将从发现它的PEL中删除。该特性是在Redis 7.0中引入的。
命令选项
该命令有多个选项,但大多数主要用于内部使用,以便将XCLAIM或其他命令的效果传输到AOF文件,并将相同的效果传播到从服务器,并且不太可能对普通用户有用:
IDLE <
ms
>:设置消息的空闲时间(最后一次发送)。如果没有指定IDLE,则假设IDLE为0,也就是说,时间计数将被重置,因为消息现在有一个新的所有者试图处理它。TIME <
ms-unix-time
>:这与IDLE相同,但不是相对的毫秒数,它将空闲时间设置为特定的Unix时间(以毫秒为单位)。这对于重写生成XCLAIM命令的AOF文件非常有用。RETRYCOUNT <
count
>:设置重试次数为指定值。每当再次传递消息时,此计数器都会增加。通常XCLAIM不会改变这个计数器,它只在调用XPENDING命令时提供给客户端:这样客户端就可以检测到异常情况,比如在尝试大量传递后由于某种原因从未处理过的消息。FORCE:在PEL中创建挂起的消息条目,即使某些指定的id还没有在分配给不同客户端的PEL中。但是消息必须在流中存在,否则不存在的消息id将被忽略。
JUSTID:只返回成功声明的消息的id数组,而不返回实际的消息。使用此选项意味着重试计数器不增加。
返回值
数组回复,具体来说:
该命令以与XRANGE相同的格式返回成功声明的所有消息。但是,如果指定了JUSTID选项,则只报告消息id,而不包括实际的消息。
示例
redis
> XGROUP CREATE mystream mygroup $ mkstream
OK
> XADD mystream * k1 v1
"1675996748819-0"
> XREADGROUP group mygroup myconsumer streams mystream >
1) 1) "mystream"
2) 1) 1) "1675996748819-0"
2) 1) "k1"
2) "v1"
> XPENDING mystream mygroup - + 1
1) 1) "1675996748819-0"
2) "myconsumer"
3) (integer) 41631
4) (integer) 1
> XCLAIM mystream mygroup myconsumer1 40000 1675996748819-0
1) 1) "1675996748819-0"
2) 1) "k1"
2) "v1"
> XPENDING mystream mygroup - + 1
1) 1) "1675996748819-0"
2) "myconsumer1"
3) (integer) 3376
4) (integer) 2
XAUTOCLAIM:自动转移消息的归属权
- 语法:
XAUTOCLAIM key group consumer min-idle-time start [COUNT count] [JUSTID]
此命令转移匹配指定条件的挂起流条目的所有权。从概念上讲,XAUTOCLAIM等同于调用XPENDING,然后调用XCLAIM,但是通过类似scan的语义提供了一种更直接的方法来处理消息传递失败。
与XCLAIM类似,该命令对<键>处的流项和所提供的<组>上下文中的流项进行操作。它将待处理消息的所有权转移到<消费者>,时间超过<min-idle-time
>毫秒,且ID等于或大于<start
>。
可选的<count
>参数默认为100,是命令试图声明的条目数量的上限。在内部,该命令从<start
>开始扫描消费者组的Pending Entries List (PEL),并过滤出空闲时间小于或等于<min-idle-time
>的条目。命令扫描的挂起条目的最大数量是<count
>的值乘以10(硬编码)的乘积。因此,声明的条目数量可能小于指定的值。
可选的JUSTID参数将回复更改为仅返回成功声明的消息的id数组,而不返回实际的消息。使用此选项意味着重试计数器不增加。
该命令将声明的条目作为数组返回。它还返回一个流ID,用于类似游标的用途,作为后续调用的<start
>参数。当没有剩余的PEL条目时,该命令返回特殊的0-0 ID以表示完成。但是,请注意,您可能希望在扫描完成后继续调用XAUTOCLAIM,并将0-0作为<start
> ID,因为已经经过了足够的时间,所以旧的待处理条目现在可能符合索赔条件。
注意,只有空闲时间大于<min-idle-time
>的消息才会被声明,并且声明消息将重置其空闲时间。这确保了只有一个消费者可以在特定时刻成功地声明给定的挂起消息,并大大降低了多次处理相同消息的可能性。
在迭代PEL时,如果XAUTOCLAIM偶然发现流中不再存在的消息(被XDEL修剪或删除),它不会声明它,而是从发现它的PEL中删除它。该特性是在Redis 7.0中引入的。这些消息id作为XAUTOCLAIMs应答的一部分返回给调用者。
最后,使用XAUTOCLAIM声明消息还会增加该消息的尝试传递计数,除非指定了JUSTID选项(仅传递消息ID,而不传递消息本身)。由于某种原因无法处理的消息(例如,由于消费者在处理它们时系统崩溃)将显示可以通过监控检测到的高尝试传递计数。
返回
数组回复,具体来说:
数组:有三个元素的数组:
- 用于下一次调用XAUTOCLAIM时作为<
start
>参数的流ID。 - 一个数组,包含所有成功认领的消息,格式与XRANGE相同。
- 一个数组,其中包含流中不再存在的消息id,并且已从发现它们的PEL中删除。
- 用于下一次调用XAUTOCLAIM时作为<
redis
> XGROUP CREATE mystream mygroup $ mkstream
OK
> XADD mystream * k1 v1
"1676010102849-0"
> XREADGROUP group mygroup myconsumer streams mystream >
1) 1) "mystream"
2) 1) 1) "1676010102849-0"
2) 1) "k1"
2) "v1"
> XAUTOCLAIM mystream mygroup consumer1 10000 0
1) "0-0"
2) 1) 1) "1676010102849-0"
2) 1) "k1"
2) "v1"
3) (empty array)
> XPENDING mystream mygroup
1) (integer) 1
2) "1676010102849-0"
3) "1676010102849-0"
4) 1) 1) "consumer1"
2) "1"
XGROUP DELCONSUMER:删除消费者
- 语法:
XGROUP DELCONSUMER key groupname consumername
XGROUP DELCONSUMER命令用来从消费者组中删除一个消费者。
有时,删除旧的消费者可能是有用的,因为它们不再使用。
但是请注意,消费者拥有的任何挂起的消息在被删除后都将变得不可声明。因此,强烈建议在从组中删除使用者之前声明或确认任何挂起的消息。
返回
整数回复:在删除使用者之前的挂起消息的数量
redis
> XGROUP CREATE mystream mygroup $ mkstream
OK
> XADD mystream * k1 v1
"1676011635947-0"
> XREADGROUP group mygroup myconsumer streams mystream >
1) 1) "mystream"
2) 1) 1) "1676011635947-0"
2) 1) "k1"
2) "v1"
> XGROUP DELCONSUMER mystream mygroup myconsumer
(integer) 1
> XPENDING mystream mygroup - + 1 myconsumer
(empty array)
XGROUP DESTROY:删除消费者组
- 语法:
XGROUP DESTROY key groupname
XGROUP DESTROY命令完全销毁一个消费者组。
即使存在活动的消费者和挂起的消息,消费者组也将被销毁,因此请确保仅在真正需要时调用此命令。
返回
整数回复:被销毁的消费组个数(0或1)
redis
> XGROUP CREATE mystream mygroup $ mkstream
OK
> XADD mystream * k1 v1
"1676012030910-0"
> XREADGROUP group mygroup myconsumer streams mystream >
1) 1) "mystream"
2) 1) 1) "1676012030910-0"
2) 1) "k1"
2) "v1"
> XGROUP DESTROY mystream mygroup
(integer) 1
> XPENDING mystream mygroup
(error) NOGROUP No such key 'mystream' or consumer group 'mygroup'
XINFO STREAM:查看流信息
- 语法:
XINFO STREAM key [FULL [COUNT count]]
该命令返回存储在<key
>的流的信息。
该命令提供的详细信息如下:
length:流中条目的数量(参见XLEN)
radix-tree-keys:底层基数据结构中的键数
radix-tree-nodes:底层基数据结构中的节点数量
groups:为流定义的消费者组的数量
last-generated-id:最近添加到流的条目ID
max-deleted-entry-id:从流中删除的最大条目ID
entries-added:在流的生命周期内添加到流的所有条目的计数
first-entry:流中第一个条目的ID和字段值元组
last-entry:流中最后一个条目的ID和字段值元组
可选的FULL修饰符提供了更详细的回复。当提供FULL应答时,包含一个条目数组,该数组由流条目(ID和字段值元组)按升序组成。此外,groups也是一个数组,对于每个消费者组,它由XINFO groups和XINFO CONSUMERS报告的信息组成。
COUNT选项可用于限制返回的流和PEL条目的数量(返回第一个< COUNT >条目)。默认的COUNT是10,COUNT为0意味着将返回所有的条目(如果流有很多条目,执行时间可能会很长)。
返回值
数组回复:信息位的列表
示例
redis
> XGROUP CREATE mystream mygroup $ mkstream
OK
> XINFO STREAM mystream
1) "length"
2) (integer) 0
3) "radix-tree-keys"
4) (integer) 0
5) "radix-tree-nodes"
6) (integer) 1
7) "last-generated-id"
8) "0-0"
9) "max-deleted-entry-id"
10) "0-0"
11) "entries-added"
12) (integer) 0
13) "recorded-first-entry-id"
14) "0-0"
15) "groups"
16) (integer) 1
17) "first-entry"
18) (nil)
19) "last-entry"
20) (nil)
> XINFO STREAM mystream full
1) "length"
2) (integer) 0
3) "radix-tree-keys"
4) (integer) 0
5) "radix-tree-nodes"
6) (integer) 1
7) "last-generated-id"
8) "0-0"
9) "max-deleted-entry-id"
10) "0-0"
11) "entries-added"
12) (integer) 0
13) "recorded-first-entry-id"
14) "0-0"
15) "entries"
16) (empty array)
17) "groups"
18) 1) 1) "name"
2) "mygroup"
3) "last-delivered-id"
4) "0-0"
5) "entries-read"
6) (nil)
7) "lag"
8) (integer) 0
9) "pel-count"
10) (integer) 0
11) "pending"
12) (empty array)
13) "consumers"
14) (empty array)
XINFO GROUPS:查看消费者组信息
- 语法:
XINFO GROUPS key
该命令返回存储在<key
>的流的所有消费者组的列表。
默认情况下,每个组只提供以下信息:
name:消费者组的名称
consumers:组中消费者的数量
pending:组的pending条目列表(PEL)的长度,它是已交付但尚未被确认的消息
last-delivered-id:最后传递该组消费者的条目的ID
entries-read:发送给组消费者的最后一个条目的逻辑“读取计数器”
lag:流中仍在等待交付给组的消费者的条目的数量,或者当这个数量无法确定时为NULL。
返回值
数组回复:消费者组列表。
示例
redis
> XGROUP CREATE mystream mygroup $ mkstream
OK
> XINFO GROUPS mystream
1) 1) "name"
2) "mygroup"
3) "consumers"
4) (integer) 0
5) "pending"
6) (integer) 0
7) "last-delivered-id"
8) "0-0"
9) "entries-read"
10) (nil)
11) "lag"
12) (integer) 0
XINFO CONSUMERS:查看消费者信息
- 语法:
XINFO CONSUMERS key groupname
该命令返回属于存储在<key
>的流的<groupname
>消费者组的消费者列表。
为组中的每个消费者提供以下信息:
name:消费者名称
pending:客户端挂起的消息数量,这些消息是已交付但尚未被确认的消息
idle:自消费者最后一次与服务器交互以来所经过的毫秒数
返回值
数组回复:消费者列表。
示例
redis
> XGROUP CREATE mystream mygroup $ mkstream
OK
> XINFO CONSUMERS mystream mygroup
(empty array)
> XADD mystream * k1 v1
"1676015264787-0"
> XREADGROUP group mygroup myconsumer streams mystream >
1) 1) "mystream"
2) 1) 1) "1676015264787-0"
2) 1) "k1"
2) "v1"
> XINFO CONSUMERS mystream mygroup
1) 1) "name"
2) "myconsumer"
3) "pending"
4) (integer) 1
5) "idle"
6) (integer) 3776
> XACK mystream mygroup 1676015264787-0
(integer) 1
> XINFO CONSUMERS mystream mygroup
1) 1) "name"
2) "myconsumer"
3) "pending"
4) (integer) 0
5) "idle"
6) (integer) 69518
Geospatial(地理空间)
Redis geospatial indexes可以让你存储坐标并搜索它们。此数据结构用于在给定半径或包围框内查找附近点。
GEOADD:存储坐标
- 语法:
GEOADD key [NX | XX] [CH] longitude latitude member [longitude latitude member ...]
将指定的地理空间项(经度、纬度、名称)添加到指定的键。数据以排序集的形式存储到键中,这样就可以使用GEOSEARCH
命令查询项。
该命令接受标准格式的参数x,y,因此经度必须在纬度之前指定。可以索引的坐标是有限制的:非常接近极点的区域是不可索引的。
当用户试图索引指定范围之外的坐标时,该命令将报告一个错误。
注意:没有GEODEL
命令,因为您可以使用ZREM
删除元素。Geo索引结构只是一个排序集。
GEOADD
选项XX:只更新已经存在的元素。不要添加元素。 NX:不要更新已经存在的元素。总是添加新元素。 CH:将返回值从添加的新元素数量修改为更改的元素总数(CH是changed的缩写)。已更改的元素是添加的新元素和已存在的元素,这些元素的坐标已更新。因此,在命令行中指定的与过去得分相同的元素不会被计算在内。注意:通常,GEOADD的返回值只计算添加的新元素的数量。
注意:XX和NX选项互斥。
返回
整数回复,具体为:
当不带可选参数时,添加到排序集的元素数(不包括分数更新)。
如果指定了CH选项,则表示更改(添加或更新)的元素数量。
redis
> GEOADD guangdong 113.280637 23.125178 guangzhou
(integer) 1
> GEOADD guangdong 114.085947 22.547 shenzhen 116.708463 23.37102 shantou
(integer) 2
GEOPOS:获取指定位置的坐标
- 语法:
GEOPOS key member [member ...]
返回由设置在键的排序集表示的地理空间索引的所有指定成员的位置(经度、纬度)。
给定一个表示地理空间索引的排序集(使用GEOADD
命令填充),获取指定成员的坐标通常很有用。当通过GEOADD
填充地理空间索引时,坐标被转换为52位geohash,因此返回的坐标可能与添加元素时使用的坐标不完全相同,但可能会引入一些小错误。
该命令可以接受可变数量的参数,因此即使指定了单个元素,它也总是返回一个位置数组。
返回
数组回复,具体来说:
该命令返回一个数组,其中每个元素都是一个包含两个元素的数组,表示作为参数传递给该命令的每个成员名的经度和纬度(x,y)。
不存在的元素被报告为数组的NULL元素。
redis
> GEOADD guangdong 113.280637 23.125178 guangzhou 114.085947 22.547 shenzhen 116.708463 23.37102 shantou
(integer) 3
> GEOPOS guangdong guangzhou shenzhen zhuhai shantou
1) 1) "113.28063815832138062"
2) "23.12517743834835215"
2) 1) "114.08594459295272827"
2) "22.54699993773966327"
3) (nil)
4) 1) "116.70846372842788696"
2) "23.37102004359263674"
GEODIST:计算两个位置之间的直线距离
- 语法:
GEODIST key member1 member2 [M | KM | FT | MI]
返回由已排序集合表示的地理空间索引中两个成员之间的距离。
给定一个使用GEOADD
命令填充表示地理空间索引的排序集,该命令返回指定单元中两个指定成员之间的距离。
如果缺少一个或两个成员,该命令返回NULL。
这个距离是在假设地球是一个完美的球体的情况下计算的,所以在边缘情况下误差可能高达0.5%。
选项
单位必须为以下值之一,默认为米:
- m表示米。
- km表示千米。
- mi表示英里。
- ft表示英尺。
返回
批量字符串回复,具体为:
该命令以指定单位的双精度值(以字符串形式表示)返回距离,如果缺少一个或两个元素,则返回NULL。
redis
> GEOADD guangdong 113.280637 23.125178 guangzhou 114.085947 22.547 shenzhen 116.708463 23.37102 shantou
(integer) 3
> GEODIST guangdong guangzhou shenzhen
"104642.6221"
> GEODIST guangdong guangzhou shenzhen km
"104.6426"
> GEODIST guangdong guangzhou zhuhai
(nil)
GEOSEARCH:查找指定坐标给定形状指定的区域内的其他成员
语法:
redisGEOSEARCH key <FROMMEMBER member | FROMLONLAT longitude latitude> <BYRADIUS radius <M | KM | FT | MI> | BYBOX width height <M | KM | FT | MI>> [ASC | DESC] [COUNT count [ANY]] [WITHCOORD] [WITHDIST] [WITHHASH]
返回使用GEOADD
填充地理空间信息的已排序集合的成员,这些成员位于给定形状指定的区域的边界内。这个命令扩展了GEORADIUS
命令,因此除了在圆形区域内搜索外,它还支持在矩形区域内搜索。
这个命令应该用来代替已弃用的GEORADIUS
和GEORADIUSBYMEMBER
命令。
查询的中心点由以下强制选项之一提供:
FROMMEMBER:使用给定的<成员>在排序集中的位置。
FROMLONLAT:使用给定的<经度>和<纬度>位置。
查询的形状由以下强制选项之一提供:
BYRADIUS:类似于
GEORADIUS
,根据给定的<半径>搜索圆形区域内。BYBOX:在轴对齐的矩形内搜索,由<宽度>和<高度>决定。
该命令使用以下选项可选地返回附加信息:
WITHDIST:还返回返回项到指定中心点的距离。距离以与radius或height和width参数指定的相同单位返回。
WITHCOORD:还返回匹配项的经度和纬度。
WITHHASH:还返回该项的原始地理哈希编码排序集得分,以52位无符号整数的形式。这只对低级黑客或调试有用,对普通用户没有什么兴趣。
默认情况下,匹配项未排序返回。要对它们进行排序,请使用以下两个选项之一:
ASC:相对于中心点,从最近到最远对返回项进行排序。
DESC:相对于中心点,从最远到最近对返回项进行排序。
默认情况下返回所有匹配的项。若要将结果限制为前N个匹配项,请使用
COUNT <数量>
选项。当使用ANY选项时,只要找到足够的匹配项,该命令就返回。这意味着返回的结果可能不是最接近指定点的结果,但是服务器为生成这些结果所投入的精力会大大减少。当没有提供ANY时,该命令将执行与匹配指定区域的项目数量成比例的工作并对它们进行排序,因此使用非常小的COUNT选项查询非常大的区域可能会很慢,即使只返回一些结果。返回
数组回复,具体来说:
如果没有指定WITH选项,该命令只返回一个线性数组,如["New York","Milan","Paris"]。
如果指定了WITHCOORD、WITHDIST或WITHHASH选项,该命令将返回一个数组的数组,其中每个子数组代表一个项。
当附加信息作为每个项的数组数组返回时,子数组中的第一项始终是返回项的名称。其他信息将按照以下顺序作为子数组的连续元素返回。
- 到中心的距离,以浮点数表示,与形状中指定的单位相同。
- geohash整数。
- 将坐标作为两项x、y数组(经度、纬度)。
redis
> GEOADD guangdong 113.280637 23.125178 guangzhou 114.085947 22.547 shenzhen 116.708463 23.37102 shantou
(integer) 3
> GEOSEARCH guangdong frommember shantou byradius 300 km asc
1) "shantou"
2) "shenzhen"
> GEOSEARCH guangdong fromlonlat 116.71 23.38 bybox 1000 800 km desc count 2 withdist withcoord
1) 1) "guangzhou"
2) "351.5882"
3) 1) "113.28063815832138062"
2) "23.12517743834835215"
2) 1) "shenzhen"
2) "284.2513"
3) 1) "114.08594459295272827"
2) "22.54699993773966327"
GEOSEARCHSTORE:查找指定坐标给定形状指定的区域内的其他成员并将结果保存到目标键
语法:
redisGEOSEARCHSTORE destination source <FROMMEMBER member | FROMLONLAT longitude latitude> <BYRADIUS radius <M | KM | FT | MI> | BYBOX width height <M | KM | FT | MI>> [ASC | DESC] [COUNT count [ANY]] [STOREDIST]
此命令类似于GEOSEARCH,但将结果存储在目标键中。
该命令替换现在已弃用的GEORADIUS
和GEORADIUSBYMEMBER
。
默认情况下,它将结果存储在目的地排序集合中,其中包含地理空间信息。
当使用STOREDIST选项时,该命令将项目存储在一个已排序的集中,其中填充了它们到圆圈或框的中心的距离,作为浮点数,存储在为该形状指定的同一单位中。
返回
整数回复:结果集中的元素数量。
redis
> GEOADD guangdong 113.280637 23.125178 guangzhou 114.085947 22.547 shenzhen 116.708463 23.37102 shantou
(integer) 3
> GEOSEARCHSTORE key1 guangdong frommember shantou byradius 300 km asc
(integer) 2
> GEOSEARCH key1 fromlonlat 116.71 23.38 bybox 600 800 km desc count 2 withdist withcoord
1) 1) "shenzhen"
2) "284.2513"
3) 1) "114.08594459295272827"
2) "22.54699993773966327"
2) 1) "shantou"
2) "1.0110"
3) 1) "116.70846372842788696"
2) "23.37102004359263674"
> GEOSEARCHSTORE key2 guangdong frommember shantou byradius 300 km asc storedist
(integer) 2
> ZRANGE key2 0 -1 withscores
1) "shantou"
2) "0"
3) "shenzhen"
4) "283.78695287202783"
Bitmaps(位图)
Redis bitmaps是字符串数据类型的扩展,它可以让您像对待位向量一样对待字符串。还可以对一个或多个字符串执行按位操作。
SETBIT:设置二进制位的值
- 语法:
SETBIT key offset value
设置或清除存储在key上的字符串值中的偏移位。
根据值设置或清除位,该值可以是0或1。
当key不存在时,将创建一个新的字符串值。字符串的生长是为了确保它能在偏移处容纳一点。offset参数必须大于等于0,小于2^32(这将位图限制为512MB)。当key的字符串增长时,添加的位被设置为0。
返回
整型回复:存储在offset位置的原始位值。
redis
> SETBIT mybitmap 0 1
(integer) 0
> SETBIT mybitmap 0 0
(integer) 1
> SETBIT mybitmap 5 1
(integer) 0
> SETBIT mybitmap 2 1
(integer) 0
GETBIT:获取二进制位的值
- 语法:
GETBIT key offset
返回存储在key的字符串值中偏移处的位值。
当偏移量超过字符串长度时,假定字符串是一个0位的连续空格。当key不存在时,它被假定为空字符串,因此offset总是超出范围,值也被假定为一个0位的连续空格。
返回
整型回复:存储在offset位置的位值。
redis
> SETBIT mybitmap 5 1
(integer) 0
> SETBIT mybitmap 2 1
(integer) 0
> GETBIT mybitmap 2
(integer) 1
> GETBIT mybitmap 5
(integer) 1
> GETBIT mybitmap 0
(integer) 0
BITCOUNT:统计被设置的二进制位数量
- 语法:
BITCOUNT key [start end [BYTE | BIT]]
计算字符串中设置位的数量(总计数)。
默认情况下,检查字符串中包含的所有字节。可以只在传递附加参数start和end的间隔内指定计数操作。
与GETRANGE
命令类似,start和end可以包含负值,以便索引从字符串末尾开始的字节,其中-1是最后一个字节,-2是倒数第二个字节,依此类推。
不存在的键被视为空字符串,因此该命令将返回零。
默认情况下,附加参数start和end指定字节索引。我们可以使用额外的参数BIT来指定位索引。0是第1位,1是第2位,以此类推。对于负值,-1是最后一位,-2是倒数第二位,依此类推。
返回
整型回复:设置为1的比特数量。
历史
从Redis 7.0.0版本开始:添加了BYTE|BIT选项。
redis
> SETBIT mybitmap 0 1
(integer) 0
> SETBIT mybitmap 5 1
(integer) 0
> SETBIT mybitmap 10 1
(integer) 0
> SETBIT mybitmap 12 1
(integer) 0
> SETBIT mybitmap 13 1
(integer) 0
> BITCOUNT mybitmap 0 -1
(integer) 5
> BITCOUNT mybitmap 0 0
(integer) 2
> BITCOUNT mybitmap 0 0 bit
(integer) 1
> BITCOUNT mybitmap -1 -1
(integer) 3
> BITCOUNT mybitmap -3 -1 bit
(integer) 1
BITPOS:查找第一个指定的二进制位值
- 语法:
BITPOS key bit [start [end [BYTE | BIT]]]
返回字符串中第一个位设置为1或0的位置。
返回位置,将字符串视为一个从左到右的位数组,其中第一个字节的最高位在位置0,第二个字节的最高位在位置8,依此类推。
GETBIT
和SETBIT
遵循相同的位位置约定。
默认情况下,检查字符串中包含的所有字节。它可以只在指定的间隔内寻找位,传递额外的参数start和end(也可以只传递start,该操作将假设end是字符串的最后一个字节)。但是语义上有差异,后面会解释)。默认情况下,该范围被解释为一个字节范围,而不是一个比特范围,因此start=0和end=2意味着查看前三个字节。
请注意,即使使用start和end指定一个范围,位位置也总是作为从位0开始的绝对值返回。
与GETRANGE
命令类似,start和end可以包含负值,以便索引从字符串末尾开始的字节,其中-1是最后一个字节,-2是倒数第二个字节,依此类推。当指定BIT时,-1是最后一位,-2是倒数第二位,依此类推。
不存在的键被视为空字符串。
返回
整型回复:
该命令根据请求返回第一个位设置为1或0的位置。
如果我们查找set bits (bit参数为1),而字符串为空或仅由零字节组成,则返回-1。
如果我们寻找明确的位(bit参数为0),而字符串只包含设置为1的位,函数将返回第一个不属于右侧字符串的位。因此,如果字符串是3个字节,设置为值0xff,命令BITPOS密钥0将返回24,因为直到第23位所有的位都是1。
基本上,如果您寻找明确的位,并且没有指定范围或只指定start参数,则该函数将字符串的右侧视为用零填充。
但是,如果您正在寻找清晰的位,并同时指定起始和结束范围,则此行为将发生变化。如果在指定的范围内没有找到明确的位,该函数将返回-1,因为用户指定了一个明确的范围,并且在该范围内没有0位。
redis
> SETBIT mybitmap 5 1
(integer) 0
> SETBIT mybitmap 10 1
(integer) 0
> SETBIT mybitmap 12 1
(integer) 0
> SETBIT mybitmap 13 1
(integer) 0
> BITPOS mybitmap 0
(integer) 0
> BITPOS mybitmap 1
(integer) 5
> BITPOS mybitmap 1 0 0
(integer) 5
> BITPOS mybitmap 1 1 1
(integer) 10
> BITPOS mybitmap 1 1 1 bit
(integer) -1
> BITPOS mybitmap 1 1 -1 bit
(integer) 5
> BITPOS mybitmap 0 13 -1 bit
(integer) 14
> BITPOS mybitmap 0 14 -1 bit
(integer) 14
BITOP:执行二进制位运算
- 语法:
BITOP operation destkey key [key ...]
在多个键(包含字符串值)之间执行位操作,并将结果存储在目标键中。
BITOP命令支持四种位操作:AND, OR, XOR和NOT,因此调用该命令的有效形式是:
redis
BITOP AND destkey srckey1 srckey2 srckey3 ... srckeyN
BITOP OR destkey srckey1 srckey2 srckey3 ... srckeyN
BITOP XOR destkey srckey1 srckey2 srckey3 ... srckeyN
BITOP NOT destkey srckey
正如你所看到的,NOT是特殊的,因为它只接受一个输入键,因为它执行位的反转,所以它只作为一个一元运算符有意义。
操作的结果总是存储在destkey。
当在具有不同长度的字符串之间执行操作时,集合中所有比最长字符串短的字符串都将被视为从零填充到最长字符串的长度。
这同样适用于不存在的键,它们被视为长度不超过最长字符串的零字节流。
返回
整型回复:
存储在目标键中的字符串的大小,等于最长的输入字符串的大小。
redis
> SETBIT bitmap1 1 1
(integer) 0
> SETBIT bitmap1 3 1
(integer) 0
> SETBIT bitmap2 0 1
(integer) 0
> SETBIT bitmap2 2 1
(integer) 0
> SETBIT bitmap2 4 1
(integer) 0
> BITOP and and-bitmap bitmap1 bitmap2
(integer) 1
> BITCOUNT and-bitmap
(integer) 0
> BITOP or or-bitmap bitmap1 bitmap2
(integer) 1
> BITCOUNT or-bitmap
(integer) 5
> BITOP xor xor-bitmap bitmap1 bitmap2
(integer) 1
> BITCOUNT xor-bitmap
(integer) 5
> BITOP not not-bitmap bitmap1
(integer) 1
> BITCOUNT not-bitmap
(integer) 6
Bitfields(位域)
Redis bitfields允许您设置、递增和获取任意位长度的整数值。例如,您可以对从无符号1位整数到有符号63位整数的任何数字进行操作。
这些值使用二进制编码的Redis字符串存储。位字段支持原子读、写和递增操作,这使它们成为管理计数器和类似数值的好选择。
BITFIELD:在位图中存储整数值
语法:
redisBITFIELD key <GET encoding offset | [OVERFLOW <WRAP | SAT | FAIL>] <SET encoding offset value | INCRBY encoding offset increment> [GET encoding offset | [OVERFLOW <WRAP | SAT | FAIL>] <SET encoding offset value | INCRBY encoding offset increment> ...]>
该命令将Redis字符串视为一个比特数组,并能够处理特定的整数字段的变化位宽和任意非(必要的)对齐偏移。在实际应用中,您可以使用此命令将一个带符号的5位整数(位偏移1234)设置为特定值,从偏移4567检索一个31位无符号整数。类似地,该命令处理指定整数的递增和递减,提供用户可以配置的有保证且指定良好的溢出和下溢行为。
BITFIELD能够在同一个命令调用中操作多个位字段。它接受一个要执行的操作列表,并返回一个答复数组,其中每个数组都与参数列表中的相应操作相匹配。
例如,下面的命令将一个5位无符号整数的位偏移量为100的值加1,并得到一个4位无符号整数的位偏移量为0的值:
redis
> BITFIELD mykey INCRBY i5 100 1 GET u4 0
1) (integer) 1
2) (integer) 0
注意:
使用当前字符串长度之外的GET位寻址(包括键根本不存在的情况),结果执行的操作就像缺失的部分全部由设置为0的位组成。
在当前字符串长度之外使用SET或INCRBY位寻址将扩大字符串,根据需要,根据触及的最远位,对其进行零填充以达到所需的最小长度。
支持子命令和整数编码
下面是支持的命令列表。
GET <encoding> <offset>
—— 返回指定的位字段。SET <encoding> <offset> <value>
—— 设置指定的位字段并返回其旧值。INCRBY <encoding> <offset> <increment>
—— 递增或递减指定的位字段(如果给出负的增量)并返回新值。
还有一个子命令只通过设置溢出行为来改变连续INCRBY和SET子命令调用的行为:
OVERFLOW [WRAP|SAT|FAIL]
在期望使用整数编码的情况下,可以通过在整数编码的位数前加上i和u来组成整数编码。例如u8是一个8位的无符号整数,i16是一个16位的有符号整数。
对于有符号整数,支持的编码最高为64位,对于无符号整数,支持的编码最高为63位。无符号整数的这种限制是由于目前Redis协议无法返回64位无符号整数作为应答。
位和位置偏移量
有两种方法可以在位字段命令中指定偏移量。
如果指定了一个没有任何前缀的数字,则它仅用作字符串中以零为基础的位偏移量。
然而,如果偏移量以#字符作为前缀,则指定的偏移量将乘以整数编码的宽度。例如:
redis
> BITFIELD mystring SET i8 #0 100 SET i8 #1 200
1) (integer) 0
2) (integer) 0
将第一个i8整数设置为偏移量0,第二个i8整数设置为偏移量8。这样,如果您想要的是给定大小的普通整数数组,您就不必在客户端内部亲自进行计算。
溢出控制
使用OVERFLOW命令,用户可以通过指定以下行为之一来微调增量或递减溢出(或下溢)的行为:
WRAP:对有符号整数和无符号整数进行环绕。在无符号整数的情况下,包装就像对整数可以包含的最大值进行模运算(C标准行为)。而对于有符号整数,自动换行意味着溢出会朝着最负的值重新开始,而下溢出则朝着最正的值重新开始,例如,如果将一个i8整数设置为127,则将其加1将得到-128。
SAT:采用饱和算法,即流量不足时取最小整数值,溢出时取最大整数值。例如,一个i8整数从值120开始加10,将得到值127,再加10将始终保持值127。同样的情况也发生在下流量上,但在最负的值处被阻塞。
FAIL:对检测到的溢出或下溢不做任何操作。相应的返回值被设置为NULL以向调用者发出条件信号。
注意,每个OVERFLOW语句只影响子命令列表中紧随其后的INCRBY和SET命令,直到下一个OVERFLOW语句。
缺省情况下,如果没有指定,则使用WRAP。
redis
> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 1
2) (integer) 1
> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 2
2) (integer) 2
> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 3
2) (integer) 3
> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 0
2) (integer) 3
返回值
该命令返回一个数组,其中每个条目都是在相同位置给定的子命令的相应结果。OVERFLOW子命令不算作生成应答。
下面是OVERFLOW FAIL返回NULL的示例。
redis
> BITFIELD mykey OVERFLOW FAIL incrby u2 102 1
1) (nil)
示例
BITFIELD SET和GET
redis> BITFIELD mybitmap set u8 0 123 set u16 8 456 1) (integer) 0 2) (integer) 0 > BITFIELD mybitmap get u8 0 get u16 8 1) (integer) 123 2) (integer) 456 > BITFIELD mybitmap set u8 #3 100 set u8 #4 50 1) (integer) 0 2) (integer) 0 > BITFIELD mybitmap get u8 0 get u16 8 get u8 16 get u8 24 1) (integer) 123 2) (integer) 456 3) (integer) 200 4) (integer) 100 > BITFIELD mybitmap get u8 0 get u16 8 get u8 24 get u8 32 1) (integer) 123 2) (integer) 456 3) (integer) 100 4) (integer) 50
BITFIELD INCRBY
redis> BITFIELD mybitmap incrby u8 0 128 1) (integer) 128 > BITFIELD mybitmap incrby u8 0 100 1) (integer) 228 > BITFIELD mybitmap incrby u8 0 27 1) (integer) 255 > BITFIELD mybitmap incrby u8 0 1 1) (integer) 0 > BITFIELD mybitmap get u8 0 1) (integer) 0
BITFIELD OVERFLOW
redis> BITFIELD mybitmap incrby u8 0 255 1) (integer) 255 > BITFIELD mybitmap incrby u8 0 2 1) (integer) 1 > BITFIELD mybitmap get u8 0 1) (integer) 1 > BITFIELD mybitmap overflow wrap incrby u8 0 256 1) (integer) 1 > BITFIELD mybitmap overflow sat incrby u8 0 256 1) (integer) 255 > BITFIELD mybitmap overflow fail incrby u8 0 2 1) (nil) > BITFIELD mybitmap get u8 0 1) (integer) 255
HyperLogLog(基数统计)
HyperLogLog是一种估计集合基数的数据结构。作为一种概率数据结构,HyperLogLog以完美的准确性换取了高效的空间利用。
Redis HyperLogLog实现最多使用12 KB,提供0.81%的标准误差。
PFADD:对集合元素进行计数
- 语法:
PFADD key [element [element ...]]
将所有元素参数添加到存储在作为第一个参数指定的变量名处的HyperLogLog数据结构中。
这个命令的一个副作用是,HyperLogLog内部可能会被更新,以反映到目前为止添加的唯一项数量的不同估计(集合的基数)。
如果执行命令后HyperLogLog估计的近似基数发生变化,PFADD返回1,否则返回0。如果指定的键不存在,该命令会自动创建一个空的HyperLogLog结构(即具有指定长度和给定编码的Redis String)。
如果不使用元素而只使用变量名来调用命令,如果变量已经存在,则不执行任何操作;如果键不存在,则只创建数据结构(在后一种情况下返回1)。
返回
如果至少有一个HyperLogLog内部寄存器被更改,则为1。否则为0。
redis
> PFADD myhll a b c
(integer) 1
> PFADD myhll c d e
(integer) 1
> PFADD myhll a
(integer) 0
PFCOUNT:返回集合的近似基数
- 语法:
PFCOUNT key [key ...]
当使用单个键调用时,返回由存储在指定变量中的HyperLogLog数据结构计算的近似基数,如果变量不存在,则该基数为0。
当使用多个键调用时,通过在内部将存储在提供的键中的HyperLogLog合并为临时HyperLogLog,返回传递的HyperLogLog联合的近似基数。
HyperLogLog数据结构可以用来计算一个集合中的唯一元素,只需要少量的固定内存,特别是每个HyperLogLog使用12k字节(加上密钥本身的几个字节)。
观测集的返回基数并不精确,但近似于0.81%的标准误差。
例如,为了计算一天中执行的所有惟一搜索查询的计数,程序需要在每次处理查询时调用PFADD。可以在任何时候用PFCOUNT检索惟一查询的估计数量。
注意:作为调用此函数的一个副作用,HyperLogLog可能会被修改,因为最后8个字节编码了用于缓存的最新计算基数。PFCOUNT在技术上是一个写命令。
返回
通过PFADD观察到的唯一元素的近似数量。
redis
> PFADD hll1 a b c
(integer) 1
> PFCOUNT hll1
(integer) 3
> PFADD hll1 c d
(integer) 1
> PFCOUNT hll1
(integer) 4
> PFADD hll2 1 2 3
(integer) 1
> PFCOUNT hll1 hll2
(integer) 7
PFMERGE:计算多个HyperLogLog的并集
- 语法:
PFMERGE destkey sourcekey [sourcekey ...]
将多个HyperLogLog值合并为一个唯一值,该值将近似于源HyperLogLog结构的观测集的联合的基数。
计算合并的HyperLogLog设置为目标变量,如果不存在则创建目标变量(默认为空HyperLogLog)。
如果目标变量存在,则将其视为源集之一,其基数将包含在计算的HyperLogLog基数中。
返回
命令只返回OK。
redis
> PFADD hll1 a b c
(integer) 1
> PFADD hll2 1 2 3
(integer) 1
> PFMERGE hll3 hll1 hll2
OK
> PFCOUNT hll3
(integer) 6
> PFADD hll3 hello
(integer) 1
> PFMERGE hll3 hll1 hll2
OK
> PFCOUNT hll3
(integer) 7
事务
Redis事务允许在一个步骤中执行一组命令,它们以命令MULTI, EXEC, DISCARD和WATCH为中心。Redis Transactions做了两个重要的保证:
事务中的所有命令都被序列化并按顺序执行。另一个客户端发送的请求永远不会在执行Redis事务的过程中被服务。这保证了命令作为单个隔离操作执行。
EXEC命令触发事务中所有命令的执行,因此,如果客户端在调用EXEC命令之前失去了与事务上下文中服务器的连接,则不会执行任何操作,相反,如果调用EXEC命令,则执行所有操作。当使用append only文件时,Redis确保使用一个write(2)系统调用将事务写入磁盘。但是,如果Redis服务器崩溃或被系统管理员以某种困难的方式杀死,则可能只注册了部分操作。Redis会在重启时检测到这种情况,并退出并报错。使用redis-check-aof工具可以修复append only文件,删除部分事务,以便服务器可以重新启动。
从2.2版本开始,Redis以乐观锁定的形式,以一种非常类似于CAS (check and set)操作的方式,为上述两个提供了额外的保证。这将在本页后面进行说明。
使用
使用MULTI命令输入Redis事务。该命令总是返回OK。
此时,用户可以发出多个命令。Redis不会执行这些命令,而是将它们排队。
一旦调用EXEC,就会执行所有命令。
而调用DISCARD将刷新事务队列并退出事务。
redis
> MULTI
OK
(TX)> SET key1 value1
QUEUED
(TX)> SET key2 value2
QUEUED
(TX)> MGET key1 key2
QUEUED
(TX)> EXEC
1) OK
2) OK
3) 1) "value1"
2) "value2"
> MULTI
OK
(TX)> SET key1 hello
QUEUED
(TX)> SET key2 world
QUEUED
(TX)> DISCARD
OK
> MGET key1 key2
1) "value1"
2) "value2"
从上面的会话中可以清楚地看到,EXEC返回一个应答数组,其中每个元素都是事务中单个命令的应答,顺序与发出命令的顺序相同。
当一个Redis连接处于MULTI请求的上下文中时,所有的命令都会用字符串QUEUED(从Redis协议的角度来看,作为状态回复发送)来回复。排队命令只是在调用EXEC时安排执行。
使用check-and-set的乐观锁(WATCH)
WATCH用于为Redis事务提供检查和设置(CAS)行为。
监视键是为了检测针对它们的更改。如果在执行EXEC命令之前至少修改了一个被监视的键,那么整个事务将终止,EXEC将返回一个Null应答来通知事务失败。
redis
> SET mykey 0
OK
> WATCH mykey
OK
> INCR mykey
(integer) 1
> MULTI
OK
(TX)> INCR mykey
QUEUED
(TX)> EXEC
(nil)
> GET mykey
"1"
那么WATCH到底是关于什么的呢?这是一个使EXEC有条件的命令:我们要求Redis只在没有被修改的情况下执行事务。这包括客户端所做的修改,比如写命令,以及Redis本身所做的修改,比如过期或驱逐。如果在监视键和接收EXEC之间修改了键,则整个事务将被中止。
当调用EXEC时,无论事务是否被终止,所有键都是UNWATCH的。此外,当客户端连接关闭时,所有内容也都是UNWATCH的。
也可以使用UNWATCH命令(不带参数)来刷新所有被监视的键。有时,当我们乐观地锁定几个键时,这是有用的,因为我们可能需要执行一个事务来更改这些键,但在读取了键的当前内容后,我们不想继续。当这种情况发生时,我们只需调用UNWATCH,这样连接就可以自由地用于新的事务。
redis
> SET mykey 0
OK
> WATCH mykey
OK
> INCR mykey
(integer) 1
> UNWATCH
OK
> MULTI
OK
(TX)> INCR mykey
QUEUED
(TX)> EXEC
1) (integer) 2
> GET mykey
"2"
发布与订阅
SUBSCRIBE, UNSUBSCRIBE和PUBLISH实现了PUBLISH / SUBSCRIBE消息传递范例,其中(引用Wikipedia)发送者(发布者)没有被编程为将其消息发送给特定的接收者(订阅者)。相反,发布的消息被特征化到通道中,而不知道可能有哪些订阅者(如果有的话)。订阅者表示对一个或多个通道感兴趣,并且只接收感兴趣的消息,而不知道有什么发布者(如果有的话)。发布者和订阅者的这种解耦允许更大的可伸缩性和更动态的网络拓扑。
请注意,在使用redis-cli时,不能使用订阅模式下的命令,如UNSUBSCRIBE和PUNSUBSCRIBE,因为redis-cli不接受任何命令,只能使用Ctrl-C退出模式。
消息传递语义
Redis的Pub/Sub展示了最多一次的消息传递语义。顾名思义,它意味着消息将被传递一次。一旦消息被Redis服务器发送,就没有机会再次发送。如果订阅者无法处理消息(例如,由于错误或网络断开),则消息将永远丢失。
如果你的应用需要更强的交付保证,你可能想了解Redis Streams。流中的消息是持久化的,并且支持最多一次和至少一次的传递语义。
PUBLISH:向频道发送消息
- 语法:
PUBLISH channel message
将消息发布到给定的频道。
在Redis集群中,客户端可以发布到每个节点。集群确保根据需要转发已发布的消息,因此客户机可以通过连接到任何一个节点来订阅任何通道。
- 返回 整型回复:收到消息的客户端数量。注意,在Redis集群中,只有与发布客户端连接到同一节点的客户端才会被计算在内。
redis
> PUBLISH channel-1 hello
(integer) 0
SUBSCRIBE:订阅频道
- 语法:
SUBSCRIBE channel [channel ...]
将客户端订阅到指定的通道。
一旦客户端进入订阅状态,它就不应该发出任何其他命令,除了额外的SUBSCRIBE、SSUBSCRIBE、PSUBSCRIBE、UNSUBSCRIBE、SUNSUBSCRIBE、PUNSUBSCRIBE、PING、RESET和QUIT命令。但是,如果使用了RESP3(参见HELLO),则客户机可以在订阅状态下发出任何命令。
- 返回 当成功时,该命令不返回任何东西。相反,对于每个通道,推送一个第一个元素为字符串“subscribe”的消息,作为命令成功的确认。
redis
> SUBSCRIBE channel-1 channel-2
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel-1"
3) (integer) 1
1) "subscribe"
2) "channel-2"
3) (integer) 2
1) "message"
2) "channel-1"
3) "hello"
1) "message"
2) "channel-2"
3) "world"
other> PUBLISH channel-1 hello
(integer) 1
other> PUBLISH channel-2 world
(integer) 1
UNSUBSCRIBE:退订频道
- 语法:
UNSUBSCRIBE [channel [channel ...]]
取消对指定通道的订阅,如果没有指定通道,则取消对所有通道的订阅。
当没有指定通道时,客户端将从以前订阅的所有通道中取消订阅。在这种情况下,将向客户端发送每个未订阅通道的消息。
- 返回 当成功时,该命令不返回任何东西。相反,对于每个通道,推送一个第一个元素为字符串“unsubscribe”的消息,作为命令成功的确认。
redis
> SUBSCRIBE channel-1 channel-2 channel-3
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel-1"
3) (integer) 1
1) "subscribe"
2) "channel-2"
3) (integer) 2
1) "subscribe"
2) "channel-3"
3) (integer) 3
(1.09s)
> UNSUBSCRIBE channel-1
1) "unsubscribe"
2) "channel-1"
3) (integer) 0
> UNSUBSCRIBE
1) "unsubscribe"
2) (nil)
3) (integer) 0
模式匹配订阅
Redis Pub/Sub实现支持模式匹配。客户端可以订阅全局样式的模式,以接收发送到与给定模式匹配的通道名的所有消息。
PSUBSCRIBE:订阅模式
- 语法:
PSUBSCRIBE pattern [pattern ...]
为客户端订阅给定的模式。
支持的全局样式模式:
- h?llo表示订阅hello, hallo and hxllo
- h*llo表示订阅hllo and heeeello
- h[ae]llo表示订阅hello and hallo, 但不订阅hillo
如果要逐字匹配特殊字符,请使用\来转义它们。
一旦客户端进入订阅状态,它就不应该发出任何其他命令,除了额外的SUBSCRIBE、SSUBSCRIBE、PSUBSCRIBE、UNSUBSCRIBE、SUNSUBSCRIBE、PUNSUBSCRIBE、PING、RESET和QUIT命令。但是,如果使用了RESP3(参见HELLO),则客户机可以在订阅状态下发出任何命令。
- 返回 当成功时,该命令不返回任何东西。相反,对于每个模式,推送一个第一个元素为字符串“psubscribe”的消息,作为命令成功的确认。
redis
> PSUBSCRIBE channel*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "channel*"
3) (integer) 1
1) "pmessage"
2) "channel*"
3) "channel-1"
4) "hello"
1) "pmessage"
2) "channel*"
3) "channel-2"
4) "world"
other> PUBLISH channel-1 hello
(integer) 1
other> PUBLISH channel-2 world
(integer) 1
同时匹配模式和通道订阅的消息
如果客户端订阅了与已发布消息匹配的多个模式,或者同时订阅了与该消息匹配的模式和通道,则客户端可能会多次接收单个消息。下面的例子显示了这一点:
redis
SUBSCRIBE foo
PSUBSCRIBE f*
在上面的例子中,如果一条消息被发送到通道foo,客户端将收到两条消息:一条是message类型,另一条是pmessage类型。
PUNSUBSCRIBE:退订模式
- 语法:
PUNSUBSCRIBE [pattern [pattern ...]]
取消客户端对给定模式的订阅,如果没有提供,则取消对所有模式的订阅。
当没有指定模式时,客户端将取消订阅以前订阅的所有模式。在这种情况下,将向客户端发送每个未订阅模式的消息。
- 返回 当成功时,该命令不返回任何东西。相反,对于每个模式,推送一个第一个元素为字符串“punsubscribe”的消息,作为命令成功的确认。
redis
> PSUBSCRIBE channel-1* channel-2* channel-3*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "channel-1*"
3) (integer) 1
1) "psubscribe"
2) "channel-2*"
3) (integer) 2
1) "psubscribe"
2) "channel-3*"
3) (integer) 3
(1.28s)
> PUNSUBSCRIBE channel-1*
1) "punsubscribe"
2) "channel-1*"
3) (integer) 0
> PUNSUBSCRIBE
1) "punsubscribe"
2) (nil)
3) (integer) 0
PUBSUB:查看发布与订阅的相关信息
这是一个用于Pub/Sub自省命令的容器命令。要查看可用命令的列表,可以调用PUBSUB HELP。
redis
> PUBSUB HELP
1) PUBSUB <subcommand> [<arg> [value] [opt] ...]. Subcommands are:
2) CHANNELS [<pattern>]
3) Return the currently active channels matching a <pattern> (default: '*').
4) NUMPAT
5) Return number of subscriptions to patterns.
6) NUMSUB [<channel> ...]
7) Return the number of subscribers for the specified channels, excluding
8) pattern subscriptions(default: no channels).
9) SHARDCHANNELS [<pattern>]
10) Return the currently active shard level channels matching a <pattern> (default: '*').
11) SHARDNUMSUB [<shardchannel> ...]
12) Return the number of subscribers for the specified shard level channel(s)
13) HELP
14) Prints this help.
PUBSUB NUMSUB:查看频道的订阅者数量
- 语法:
PUBSUB NUMSUB [channel [channel ...]]
返回指定通道的订阅者数量(不包括订阅模式的客户端)。
请注意,在没有通道的情况下调用该命令是有效的。在本例中,它将返回一个空列表。
集群注意事项:在Redis集群中,客户端可以订阅每个节点,也可以向每个其他节点发布。集群将确保根据需要转发已发布的消息。也就是说,集群中的PUBSUB应答仅报告来自节点的Pub/Sub上下文的信息,而不是整个集群。
- 返回 数组回复:通道列表和每个通道的订阅者数量。
格式是channel, count, channel, count,…,所以列表是平的。列出通道的顺序与命令调用中指定的通道的顺序相同。
redis
> SUBSCRIBE channel-1 channel-2
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel-1"
3) (integer) 1
1) "subscribe"
2) "channel-2"
3) (integer) 2
other> PUBSUB NUMSUB channel-1 channel-2 channel-3
1) "channel-1"
2) (integer) 1
3) "channel-2"
4) (integer) 1
5) "channel-3"
6) (integer) 0
PUBSUB NUMPAT:查看被订阅模式的总数量
- 语法:
PUBSUB NUMPAT
返回客户端订阅的唯一模式的数量(使用PSUBSCRIBE命令执行)。
注意,这不是订阅模式的客户机数量,而是所有客户机订阅的唯一模式的总数。
集群注意事项:在Redis集群中,客户端可以订阅每个节点,也可以向每个其他节点发布。集群将确保根据需要转发已发布的消息。也就是说,集群中的PUBSUB应答仅报告来自节点的Pub/Sub上下文的信息,而不是整个集群。
- 返回 整数回复:所有客户端订阅的模式数。
redis
> PSUBSCRIBE channel-1* channel-2* channel-3*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "channel-1*"
3) (integer) 1
1) "psubscribe"
2) "channel-2*"
3) (integer) 2
1) "psubscribe"
2) "channel-3*"
3) (integer) 3
other> PUBSUB NUMPAT
(integer) 3
PUBSUB CHANNELS:查看被订阅的频道
- 语法:
PUBSUB CHANNELS [pattern]
列出当前活动的通道。
活动通道是具有一个或多个订阅者(不包括订阅模式的客户端)的Pub/Sub通道。
如果没有指定模式,则列出所有通道,否则,如果指定了pattern,则只列出与指定的全局样式模式匹配的通道。
集群注意事项:在Redis集群中,客户端可以订阅每个节点,也可以向每个其他节点发布。集群将确保根据需要转发已发布的消息。也就是说,集群中的PUBSUB应答仅报告来自节点的Pub/Sub上下文的信息,而不是整个集群。
- 返回 数组回复:活动通道的列表,可选地匹配指定的模式。
redis
> SUBSCRIBE channel-1 channel-2
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel-1"
3) (integer) 1
1) "subscribe"
2) "channel-2"
3) (integer) 2
other> PUBSUB CHANNELS
1) "channel-2"
2) "channel-1"
> PUBSUB CHANNELS channel*
1) "channel-2"
2) "channel-1"
持久化
持久性是指将数据写入持久存储,例如固态磁盘(SSD)。Redis提供了一系列的持久化选项。这些包括:
RDB (Redis Database):RDB持久化以指定的时间间隔执行数据集的时间点快照。
AOF (Append Only File):AOF持久化记录服务器接收到的每个写操作。然后,这些操作可以在服务器启动时再次重播,重建原始数据集。命令使用与Redis协议本身相同的格式进行记录。
RDB + AOF:您还可以在同一个实例中组合使用AOF和RDB。
No persistence:您可以完全禁用持久性。这有时在缓存时使用。
RDB
SAVE:阻塞服务器并创建RDB文件
- 语法:
SAVE
SAVE命令执行数据集的同步保存,以RDB文件的形式生成Redis实例中所有数据的时间点快照。
您几乎不希望在生产环境中调用SAVE,因为它会阻塞所有其他客户机。相反,通常使用BGSAVE。但是,如果出现阻止Redis创建后台保存子进程的问题(例如fork(2)系统调用中的错误),SAVE命令可以是执行最新数据集转储的最后一种好方法。
redis
> SAVE
OK
BGSAVE:以非阻塞方式创建RDB文件
- 语法:
BGSAVE [SCHEDULE]
在后台保存DB。
通常会立即返回OK代码。Redis分叉,父进程继续为客户端服务,子进程将DB保存在磁盘上,然后退出。
如果已经有一个后台保存正在运行,或者有另一个非后台保存进程正在运行,特别是正在进行的AOF重写,则返回错误。
如果使用了BGSAVE SCHEDULE,当AOF重写正在进行时,该命令将立即返回OK,并安排在下一次机会时运行后台保存。
客户端可以使用LASTSAVE命令检查操作是否成功。
redis
> BGSAVE
Background saving started
通过配置选项自动创建RDB文件
用户除了可以使用SAVE命令和BGSAVE命令手动创建RDB文件之外,还可以通过设置save选项,让Redis服务器在满足指定条件时自动执行BGSAVE命令:
conf
save <seconds> <changes> [<seconds> <changes> ...]
- 默认设置 RDB持久化是Redis默认使用的持久化方式,如果用户在启动Redis服务器时,既没有显式地关闭RDB持久化功能,也没有启用AOF持久化功能,那么Redis默认将使用以下save选项进行RDB持久化:
conf
save 3600 1 300 100 60 10000
RDB的优点
RDB是Redis数据的一个非常紧凑的单文件时间点表示。RDB文件是完美的备份。例如,您可能希望在最近24小时内每小时归档RDB文件,并在30天内每天保存RDB快照。这允许您在发生灾难时轻松恢复数据集的不同版本。
RDB非常适合灾难恢复,它是一个单一的压缩文件,可以传输到远端的数据中心,也可以传输到Amazon S3(可能是加密的)。
RDB最大限度地提高了Redis的性能,因为Redis父进程为了持久化所需要做的唯一工作就是分支一个子进程来完成其余的工作。父进程永远不会执行磁盘I/O或类似的操作。
与AOF相比,RDB允许更快地重启大数据集。
在从服务器上,RDB支持重启和故障转移后的部分重新同步。
RDB的缺点
如果你需要在Redis停止工作(例如停电后)时最小化数据丢失的机会,RDB不是很好。您可以在生成RDB的地方配置不同的保存点(例如,在对数据集进行至少5分钟和100次写操作之后,您可以有多个保存点)。然而,你通常会每五分钟或更长时间创建一个RDB快照,所以如果Redis在没有正确关机的情况下停止工作,你应该准备好丢失最近几分钟的数据。
为了使用子进程在磁盘上持久化,RDB需要经常fork()。如果数据集很大,fork()可能会很耗时,如果数据集很大,CPU性能不是很好,可能会导致Redis停止为客户端服务几毫秒甚至一秒钟。AOF也需要fork(),但频率较低,您可以调整重写日志的频率,而不会影响持久性。
AOF
打开AOF持久化功能
用户可以通过服务器的appendonly选项来决定是否打开AOF持久化功能:
conf
appendonly <value>
- 开启AOF持久化功能
conf
appendonly yes
- 关闭AOF持久化功能
conf
appendonly no
当AOF持久化功能处于打开状态时,Redis服务器在默认情况下将创建一个名为appendonly.aof的文件作为AOF文件。
设置AOF文件的冲洗频率
Redis向用户提供了appendfsync选项,以此来控制系统冲洗AOF文件的频率:
conf
appendfsync<value>
appendfsync选项拥有always、everysec和no这3个值可选,它们代表的意义分别为:
always——每执行一个写命令,就对AOF文件执行一次冲洗操作。
everysec——每隔1s,就对AOF文件执行一次冲洗操作。
no——不主动对AOF文件执行冲洗操作,由操作系统决定何时对AOF进行冲洗。
建议的(和默认的)策略是everysec。它既快又相对安全。always策略在实践中很慢,但它支持组提交,所以如果有多个并行写,Redis将尝试执行单个fsync操作。
AOF重写
用户可以通过执行BGREWRITEAOF命令或者设置相关的配置选项来触发AOF重写操作。
BGREWRITEAOF命令
用户可以通过执行BGREWRITEAOF命令显式地触发AOF重写操作,该命令是一个无参数命令:
redis
> BGREWRITEAOF
Background append only file rewriting started
AOF重写配置选项
用户除了可以手动执行BGREWRITEAOF命令创建新的AOF文件之外,还可以通过设置以下两个配置选项让Redis自动触发BGREWRITEAOF命令:
conf
auto-aof-rewrite-min-size <value>
auto-aof-rewrite-percentage <value>
其中auto-aof-rewrite-min-size选项用于设置触发自动AOF文件重写所需的最小AOF文件体积,当AOF文件的体积小于给定值时,服务器将不会自动执行BGREWRITEAOF命令。在默认情况下,该选项的值为:
conf
auto-aof-rewrite-min-size 64mb
也就是说,如果AOF文件的体积小于64MB,那么Redis将不会自动执行 BGREWRI-TEAOF命令。
至于另一个选项,它控制的是触发自动AOF文件重写所需的文件体积增大比例。举个例子,对于该选项的默认值:
conf
auto-aof-rewrite-percentage 100
表示如果当前AOF文件的体积比最后一次AOF文件重写之后的体积增大了一倍(100%),那么将自动执行一次BGREWRITEAOF命令。如果Redis服务器刚刚启动,还没有执行过AOF文件重写操作,那么启动服务器时使用的AOF文件的体积将被用作最后一次AOF文件重写的体积。
举个例子,如果服务器启动时AOF文件的体积为200MB,而auto-aof-rewrite-percentage选项的值为100,那么当AOF文件的体积增大至超过400MB时,服务器就会自动进行一次AOF重写。与此类似,在同样设置下,如果AOF文件的体积从最后一次重写之后的300MB增大至超过600MB,那么服务器将再次执行AOF重写操作。
AOF的优点
使用AOF Redis更持久:你可以有不同的fsync策略:根本不fsync,每秒fsync,每次查询fsync。在默认的每秒fsync策略下,写性能仍然很好。Fsync是使用后台线程执行的,主线程在没有Fsync的情况下会努力执行写操作,所以你只会损失一秒钟的写时间。
AOF日志是一个只能追加的日志,因此在停电时不会出现查找和损坏问题。即使日志由于某种原因(磁盘已满或其他原因)以未写完的命令结束,redis-check-aof工具也能够轻松修复它。
Redis能够在后台自动重写AOF,当它变得太大。重写是完全安全的,因为当Redis继续附加到旧文件时,一个全新的文件将产生,并且使用创建当前数据集所需的最小操作集,一旦第二个文件准备好,Redis切换两个文件并开始附加到新文件。
AOF以易于理解和解析的格式包含所有操作的一个接一个的日志。您甚至可以轻松地导出AOF文件。例如,即使您使用FLUSHALL命令意外刷新了所有内容,只要在此期间没有执行日志重写,您仍然可以通过停止服务器,删除最新命令并重新启动Redis来保存数据集。
AOF的缺点
对于相同的数据集,AOF文件通常比同等的RDB文件大。
AOF可能比RDB慢,这取决于确切的fsync策略。一般来说,将fsync设置为每秒的性能仍然非常高,并且在禁用fsync的情况下,即使在高负载下,它也应该与RDB一样快。尽管如此,即使在写负载很大的情况下,RDB也能够提供更多关于最大延迟的保证。
Redis < 7.0
如果在重写过程中有对数据库的写操作,AOF可能会使用大量内存(这些操作在内存中进行缓冲,并在最后写入新的AOF)。
在重写期间到达的所有写命令都被写入磁盘两次。
Redis可以冻结写,并在重写结束时将这些写命令同步到新的AOF文件。
RDB-AOF混合持久化
由于RDB持久化和AOF持久化都有各自的优缺点,因此在很长一段时间里,如何选择合适的持久化方式成了很多Redis用户面临的一个难题。为了解决这个问题,Redis从4.0版本开始引入RDB-AOF混合持久化模式,这种模式是基于AOF持久化模式构建而来的——如果用户打开了服务器的AOF持久化功能,并且将aof-use-rdb-preamble <value>
选项的值设置成了yes,那么Redis服务器在执行AOF重写操作时,就会像执行BGSAVE命令那样,根据数据库当前的状态生成出相应的RDB数据,并将这些数据写入新建的AOF文件中,至于那些在AOF重写开始之后执行的Redis命令,则会继续以协议文本的方式追加到新AOF文件的 末尾,即已有的RDB数据的后面。
无持久化
即使用户没有显式地开启RDB持久化功能和AOF持久化功能,Redis服务器也会默认使用以下配置进行RDB持久化:
conf
save 3600 1 300 100 60 10000
如果用户想要彻底关闭这一默认的RDB持久化行为,让Redis服务器处于完全的无持久化状态,那么可以在服务器启动时向它提供以下配置选项:
conf
save ""
这样一来,服务器将不会再进行默认的RDB持久化,从而使得服务器处于完全的无持久化状态中。处于这一状态的服务器在关机之后将丢失关机之前存储的所有数据,这种服务器可以用作单纯的内存缓存服务器。
主从复制
REPLICAOF:将服务器设置为从服务器
- 语法:
REPLICAOF host port
REPLICAOF命令可以动态地更改从服务器的复制设置。
如果一个Redis服务器已经作为从服务器,命令REPLICAOF NO ONE将关闭复制,将Redis服务器变为MASTER。以适当的形式,REPLICAOF主机名端口将使服务器成为侦听指定主机名和端口的另一台服务器的从服务器。
如果服务器已经是某个主服务器的从服务器,REPLICAOF host port将停止对旧服务器的复制,并开始对新服务器的同步,丢弃旧数据集。
使用REPLICAOF NO ONE将停止复制,将服务器变为MASTER,但不会丢弃复制。因此,如果旧的主服务器停止工作,可以将从服务器转换为主服务器,并设置应用程序在读/写时使用这个新的主服务器。当其他Redis服务器被修复后,它可以被重新配置为一个从服务器。
- 返回 简单字符串回复
redis
127.0.0.1:6380> REPLICAOF 127.0.0.1 6379
OK
127.0.0.1:6379> SET mykey hello
OK
127.0.0.1:6380> GET mykey
"hello"
通过配置选项设置从服务器
conf
replicaof <masterip> <masterport>
取消复制
使用REPLICAOF NO ONE将停止复制,将服务器变为MASTER,但不会丢弃复制。
redis
127.0.0.1:6380> REPLICAOF NO ONE
OK
127.0.0.1:6380> GET mykey
"hello"
ROLE:查看服务器的角色
- 语法:
ROLE
通过返回当前Redis实例是主、从还是哨兵(master, 从服务器, or sentinel),提供Redis实例在复制上下文中所扮演角色的信息。该命令还返回关于复制状态的附加信息(如果角色是master或从服务器)或被监视的主名称列表(如果角色是sentinel)。
返回值
数组回复:其中第一个元素是master, 从服务器, sentinel中的一个,其他元素是角色特定的,如下所示。
master
redis
1) "master"
2) (integer) 3129659
3) 1) 1) "127.0.0.1"
2) "9001"
3) "3129242"
2) 1) "127.0.0.1"
2) "9002"
3) "3129543"
master输出由以下几个部分组成:
- 字符串master。
- 在部分重同步中,当前主复制偏移量是主复制和从服务器共享以理解的偏移量,是从服务器需要获取以继续的复制流的一部分。
- 由三个元素组成的数组,表示连接的从服务器。每个子阵列都包含从服务器IP、端口和最后确认的复制偏移量。
从服务器
redis
1) "从服务器"
2) "127.0.0.1"
3) (integer) 9000
4) "connected"
5) (integer) 3167038
从服务器输出由以下几个部分组成:
- 字符串从服务器。
- master的IP。
- master的端口号。
- 从主节点的角度来看,复制的状态可以是connect(实例需要连接到它的主节点)、connecting(主-从服务器连接正在进行中)、sync(主节点和从服务器正在尝试执行同步)、connected(从服务器在线)。
- 迄今为止以主复制偏移量表示的从从服务器接收的数据量。
sentinel
redis
1) "sentinel"
2) 1) "resque-master"
2) "html-fragments-master"
3) "stats-master"
4) "metadata-master"
sentinel 输出由以下几个部分组成:
- 字符串sentinel 。
- 这个Sentinel实例监视的主名称数组。
示例
redis
127.0.0.1:6380> ROLE
1) "master"
2) (integer) 1661
3) (empty array)
127.0.0.1:6380> REPLICAOF 127.0.0.1 6379
OK
127.0.0.1:6380> ROLE
1) "从服务器"
2) "127.0.0.1"
3) (integer) 6379
4) "handshake"
5) (integer) -1
127.0.0.1:6380> ROLE
1) "从服务器"
2) "127.0.0.1"
3) (integer) 6379
4) "connected"
5) (integer) 1703
127.0.0.1:6379> ROLE
1) "master"
2) (integer) 1899
3) 1) 1) "127.0.0.1"
2) "6380"
3) "1899"
哨兵
Redis Sentinel在不使用Redis Cluster的情况下为Redis提供高可用性。
Redis Sentinel还提供其他附属任务,如监控、通知,并作为客户端的配置提供者。
以下是Sentinel在宏观层面上的完整功能列表(即大局):
监控。Sentinel不断检查主实例和从服务器实例是否按预期工作。
通知。Sentinel可以通过API通知系统管理员或其他计算机程序,被监视的Redis实例之一出现了问题。
自动故障转移。如果一个主服务器没有像预期的那样工作,Sentinel可以启动一个故障转移过程,其中一个从服务器被提升为主服务器,其他额外的从服务器被重新配置为使用新的主服务器,并且使用Redis服务器的应用程序在连接时被告知使用的新地址。
配置提供者。哨兵作为客户端服务发现的权威来源:客户端连接到哨兵以请求当前负责给定服务的Redis主服务器的地址。如果发生故障转移,哨兵将报告新的地址。
哨兵作为一个分布式系统
Redis Sentinel是一个分布式系统:
Sentinel本身被设计为在多个Sentinel进程协同工作的配置中运行。多个哨兵进程协作的优势如下:
故障检测是在多个哨兵一致认为某个主服务器不再可用时执行的。这降低了误报的概率。
即使不是所有的Sentinel进程都在工作,Sentinel也能工作,使系统对故障具有健壮性。毕竟,拥有一个本身就是单点故障的故障转移系统是没有乐趣的。
哨兵、Redis实例(主和从服务器)以及连接到哨兵和Redis的客户端的总和,也是一个具有特定属性的更大的分布式系统。在本文档中,概念将逐步介绍,从基本信息开始,以便了解Sentinel的基本属性,到更复杂的信息(可选的),以便了解Sentinel的确切工作原理。
部署Sentinel之前需要了解的基本事项
- 要进行健壮的部署,至少需要三个Sentinel实例。
- 这三个Sentinel实例应该被放置在被认为会以独立的方式失败的计算机或虚拟机中。例如,不同的物理服务器或虚拟机在不同的可用性区域上执行。
- Sentinel + Redis分布式系统不保证在故障期间保留已确认的写,因为Redis使用异步复制。然而,有一些方法可以部署Sentinel,使窗口只在特定时刻丢失写,同时还有其他不太安全的部署方法。
- 你的客户需要哨兵的支持。流行的客户端库支持Sentinel,但不是全部。
- 如果不经常在开发环境中进行测试,那么就没有安全的HA设置,如果可以的话,在生产环境中进行测试就更好了。你可能有一个错误的配置,只有在太晚的时候才会变得明显(在凌晨3点,你的主机停止工作)。
- Sentinel、Docker或其他形式的网络地址转换或端口映射应该小心混合:Docker执行端口重新映射,破坏Sentinel自动发现其他Sentinel进程和master的从服务器列表。更多信息,请查看本文档后面关于Sentinel和Docker的部分。
启动Sentinel
如果你正在使用redis-sentinel可执行文件(或者你有一个与redis-server可执行文件同名的符号链接),你可以用下面的命令行运行Sentinel:
sh
$ redis-sentinel sentinel.conf
或者也可以直接使用redis-server可执行文件,以Sentinel模式启动它:
sh
$ redis-server sentinel.conf --sentinel
但是,在运行Sentinel时必须使用配置文件,因为系统将使用该文件来保存在重新启动时将重新加载的当前状态。如果没有提供配置文件或者配置文件路径不可写,Sentinel将拒绝启动。
默认情况下,Sentinels会监听到TCP端口26379的连接,因此要使Sentinels工作,服务器的端口26379必须打开以接收来自其他Sentinel实例的IP地址的连接。否则哨兵就无法交谈,也无法就该做什么达成一致,所以故障转移永远不会执行。
- 默认sentinel.conf内容
conf
...
# port <sentinel-port>
# The port that this sentinel instance will run on
port 26379
...
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
#
# Tells Sentinel to monitor this master, and to consider it in O_DOWN
# (Objectively Down) state only if at least <quorum> sentinels agree.
#
# Note that whatever is the ODOWN quorum, a Sentinel will require to
# be elected by the majority of the known Sentinels in order to
# start a failover, so no failover can be performed in minority.
#
# Replicas are auto-discovered, so you don't need to specify replicas in
# any way. Sentinel itself will rewrite this configuration file adding
# the replicas using additional configuration options.
# Also note that the configuration file is rewritten when a
# replica is promoted to master.
#
# Note: master name should not include special characters or spaces.
# The valid charset is A-z 0-9 and the three characters ".-_".
sentinel monitor mymaster 127.0.0.1 6379 2
...
配置Sentinel
Redis源代码发行版包含一个名为Sentinel .conf的文件,这是一个自定义的示例配置文件,你可以使用它来配置Sentinel,但是一个典型的最小配置文件看起来像下面这样:
conf
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 60000
sentinel failover-timeout mymaster 180000
sentinel parallel-syncs mymaster 1
sentinel monitor resque 192.168.1.3 6380 4
sentinel down-after-milliseconds resque 10000
sentinel failover-timeout resque 180000
sentinel parallel-syncs resque 5
您只需要指定要监视的主服务器,为每个分离的主服务器(可能有任意数量的从服务器)指定不同的名称。不需要指定从服务器,从服务器是自动发现的。Sentinel将使用关于从服务器的其他信息自动更新配置(以便在重新启动时保留这些信息)。每次在故障转移期间将从服务器提升为主从服务器以及每次发现新的Sentinel时,都会重写配置。
上面的示例配置基本上监视两组Redis实例,每个实例由一个主实例和一个未定义数量的从服务器组成。一组实例称为mymaster,另一组实例称为resque。
sentinel monitor语句的参数含义如下:
conf
sentinel monitor <master-name> <ip> <port> <quorum>
第一行用于告诉Redis监控一个名为mymaster的主机,该主机的地址为127.0.0.1,端口为6379,quorum为2。一切都很明显,但法定人数的论点:
quorum是需要就主服务器不可达这一事实达成一致的哨兵数量,以便真正将主服务器标记为故障,并在可能的情况下最终启动故障转移过程。
然而,仲裁仅用于检测故障。为了实际执行故障转移,一个哨兵需要被选为故障转移的领导者,并被授权继续进行。这只发生在大多数哨兵进程投票的情况下。
例如,如果您有5个哨兵进程,并且给定主服务器的仲裁设置为2,则会发生以下情况:
如果两个哨兵同时同意主节点不可达,其中一个将尝试启动故障转移。
如果总共至少有三个哨兵可达,则将授权并实际启动故障转移。
实际上,这意味着在故障期间,如果大多数Sentinel进程无法通信(也就是在少数分区中没有故障转移),则Sentinel永远不会启动故障转移。
其他Sentinel选项
其他选项几乎总是在表单中:
conf
sentinel <option_name> <master_name> <option_value>
并用于以下目的:
down-after-milliseconds是指一个实例在Sentinel开始认为它已经关闭时无法访问的时间,以毫秒为单位(要么没有回复我们的ping,要么正在回复错误)。
parallel-syncs设置可在故障转移后同时重新配置以使用新主服务器的从服务器数量。这个数字越低,完成故障转移过程所需的时间就越长,但是,如果将从服务器配置为提供旧数据,则可能不希望所有从服务器同时与主服务器重新同步。虽然复制过程对于从服务器来说大部分是无阻塞的,但它会停止从主服务器加载批量数据。您可能希望通过将该选项设置为1来确保一次只有一个从服务器不可访问。
其他选项将在本文档的其余部分进行描述,并在Redis发行版附带的示例sentinel.conf文件中进行记录。
配置参数可以在运行时修改:
使用SENTINEL SET修改特定于主的配置参数。
使用SENTINEL CONFIG SET修改全局配置参数。
有关更多信息,请参阅在运行时重新配置Sentinel部分。
快速教程
在本文的下一节中,将逐步介绍有关Sentinel API、配置和语义的所有细节。然而,对于那些想要尽快使用系统的人来说,这部分是一个教程,展示了如何配置和与3个哨兵实例交互。
这里我们假设实例在端口5000、5001、5002上执行。我们还假设您在端口6379上运行Redis master,在端口6380上运行从服务器。在本教程中,我们将在任何地方使用IPv4环回地址127.0.0.1,假设您在个人计算机上运行模拟。
三个Sentinel配置文件如下所示:
conf
port 5000
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
其他两个配置文件将是相同的,但使用5001和5002作为端口号。
关于上述配置,有几点需要注意:
这个主集叫做mymaster。它能识别主人和从服务器。由于每个主集都有不同的名称,因此Sentinel可以同时监视不同的主集和从服务器集。
将quorum值设置为2(哨兵监视器配置指令的最后一个参数)。
down-after-milliseconds的值是5000毫秒,也就是5秒,所以只要我们在这段时间内没有收到来自ping的任何回复,就会检测到主服务器失败。
一旦你启动了三个哨兵,你会看到他们记录的一些消息,比如:
log
+monitor master mymaster 127.0.0.1 6379 quorum 2
这是一个哨兵事件,如果您订阅后面在Pub/Sub消息部分中指定的事件名称,则可以通过Pub/Sub接收此类事件。
Sentinel在故障检测和故障转移期间生成并记录不同的事件。
向哨兵询问主人的状态
对于Sentinel来说,最明显的事情就是检查它所监控的主机是否运行良好:
redis
$ redis-cli -p 5000
127.0.0.1:5000> sentinel master mymaster
1) "name"
2) "mymaster"
3) "ip"
4) "127.0.0.1"
5) "port"
6) "6379"
7) "runid"
8) "953ae6a589449c13ddefaee3538d356d287f509b"
9) "flags"
10) "master"
11) "link-pending-commands"
12) "0"
13) "link-refcount"
14) "1"
15) "last-ping-sent"
16) "0"
17) "last-ok-ping-reply"
18) "735"
19) "last-ping-reply"
20) "735"
21) "down-after-milliseconds"
22) "5000"
23) "info-refresh"
24) "126"
25) "role-reported"
26) "master"
27) "role-reported-time"
28) "532439"
29) "config-epoch"
30) "1"
31) "num-从服务器s"
32) "1"
33) "num-other-sentinels"
34) "2"
35) "quorum"
36) "2"
37) "failover-timeout"
38) "60000"
39) "parallel-syncs"
40) "1"
正如您所看到的,它打印了许多关于主机的信息。其中有一些是我们特别感兴趣的:
- num-other-sentinels是2,所以我们知道哨兵已经为这个主人发现了另外两个哨兵。如果您检查日志,您将看到生成的+sentinel事件。
- flags只是master。如果主服务器关闭,我们也可以看到s_down或o_down标志。
- num-从服务器s被正确地设置为1,所以Sentinel也检测到有一个附加的从服务器到我们的主服务器。
为了更深入地了解这个实例,您可能想尝试以下两个命令:
redis
SENTINEL replicas mymaster
SENTINEL sentinels mymaster
第一个将提供有关连接到主的从服务器的类似信息,第二个将提供有关其他哨兵的信息。
获取当前主机的地址
正如我们已经指定的,Sentinel还充当想要连接到一组主从服务器和从服务器的客户机的配置提供者。由于可能的故障转移或重新配置,客户端不知道谁是给定实例集的当前活动主,因此Sentinel导出一个API来询问这个问题:
redis
127.0.0.1:5000> SENTINEL get-master-addr-by-name mymaster
1) "127.0.0.1"
2) "6379"
测试故障转移
此时,我们的玩具哨兵部署已经准备好进行测试了。我们可以干掉主程序,看看配置是否改变了。要做到这一点,我们可以这样做:
sh
$ redis-cli -p 6379 DEBUG sleep 30
这个命令将使我们的主人无法访问,睡眠30秒。它基本上是出于某种原因模拟了主人的绞刑。
如果你检查哨兵日志,你应该能够看到很多动作:
- 每个Sentinel检测到主服务器宕机并触发+sdown事件。
- 此事件随后升级为+odown,这意味着多个哨兵同意无法到达主节点的事实。
- 哨兵投票给哨兵,哨兵将开始第一次故障转移尝试。
- 发生故障转移。
如果你再次询问mymaster当前的主地址是什么,最终我们这次应该会得到不同的回复:
到目前为止一切都好……此时,您可以跳转到创建Sentinel部署,也可以阅读更多内容以了解Sentinel的所有命令和内部原理。
Sentinel API
Sentinel提供了一个API,用于检查其状态、检查被监视的主服务器和从服务器的运行状况、订阅以接收特定通知,以及在运行时更改Sentinel配置。
默认情况下,Sentinel使用TCP端口26379运行(注意6379是正常的Redis端口)。Sentinels接受使用Redis协议的命令,所以你可以使用redis-cli或任何其他未修改的Redis客户端来与Sentinel交谈。
可以直接查询一个哨兵,从它的角度检查被监控的Redis实例的状态,看看它知道的其他哨兵,等等。或者,使用Pub/Sub,可以在每次发生某些事件(如故障转移或实例进入错误条件等)时从Sentinels接收推送样式的通知。
Sentinel命令
SENTINEL命令是SENTINEL的主要API。以下是它的子命令列表(在适用的地方注明了最小版本):
SENTINEL CONFIG GET <name>
(>= 6.2)获取全局SENTINEL配置参数的当前值。指定的名称可以是通配符,类似于Redis CONFIG GET命令。SENTINEL CONFIG SET <name> <value>
(>= 6.2)设置全局SENTINEL配置参数的值。SENTINEL CKQUORUM <master name>
检查当前SENTINEL配置是否能够达到主服务器故障转移所需的仲裁数量,以及授权故障转移所需的多数数量。该命令应该用于监视系统,以检查Sentinel部署是否正常。SENTINEL FLUSHCONFIG
强制SENTINEL重写磁盘上的配置,包括当前的SENTINEL状态。通常情况下,每当状态发生变化时(在重新启动时保留在磁盘上的状态子集的上下文中),Sentinel都会重写配置。但是,有时可能由于操作错误、磁盘故障、包升级脚本或配置管理器而丢失配置文件。在这些情况下,强制Sentinel重写配置文件的方法很方便。即使以前的配置文件完全丢失,该命令也可以工作。SENTINEL FAILOVER <master name>
强制故障转移,如果主不可达,而不要求其他哨兵的同意(然而,一个新版本的配置将被发布,以便其他哨兵将更新他们的配置)。SENTINEL GET-MASTER-ADDR-BY-NAME <master name>
返回该名称的master的ip和端口号。如果这个主机的故障转移正在进行或成功终止,它将返回提升从服务器的地址和端口。SENTINEL INFO-CACHE
(>= 3.2)返回主从服务器和从服务器的缓存信息输出。SENTINEL IS-MASTER-DOWN-BY-ADDR
从当前SENTINEL的角度检查ip:port指定的master是否down。该命令主要用于内部使用。SENTINEL MASTER <master name>
显示指定MASTER的状态和信息。SENTINEL MASTERS
显示被监视的主机及其状态的列表。SENTINEL MONITOR
启动SENTINEL监控。有关更多信息,请参阅运行时重新配置哨兵一节。SENTINEL MYID
(>= 6.2)返回SENTINEL实例的ID。SENTINEL PENDING-SCRIPTS
该命令返回挂起脚本的信息。SENTINEL REMOVE
停止哨兵的监控。有关更多信息,请参阅运行时重新配置哨兵一节。SENTINEL REPLICAS <master name>
(>= 5.0)显示该主机的从服务器列表及其状态。SENTINEL SENTINELS <master name>
显示该主机的哨兵实例列表及其状态。SENTINEL SET
设置SENTINEL的监控配置。有关更多信息,请参阅运行时重新配置哨兵一节。SENTINEL SIMULATE-FAILURE (crash-after-election|crash-after-promotion|help)
(>= 3.2)这个命令模拟不同的SENTINEL崩溃场景。SENTINEL RESET <pattern>
该命令将重置所有具有匹配名称的master。pattern参数是一个全局样式的模式。重置过程清除主服务器中的任何先前状态(包括正在进行的故障转移),并删除已发现并与主服务器关联的所有从服务器和哨兵。
出于连接管理和管理的目的,Sentinel支持以下Redis命令的子集:
ACL
(>= 6.2)该命令用于管理哨兵访问控制列表。有关更多信息,请参阅ACL文档页面和哨兵访问控制列表身份验证。AUTH
(>= 5.0.1)验证客户端连接。有关更多信息,请参阅AUTH命令和配置带有身份验证的哨兵实例一节。CLIENT
该命令管理客户端连接。有关更多信息,请参阅其子命令的页面。COMMAND
(>= 6.2)该命令返回命令的相关信息。有关更多信息,请参阅COMMAND命令及其各个子命令。HELLO
(>= 6.0)切换连接协议。更多信息请参见HELLO命令。INFO
返回Sentinel服务器的信息和统计信息。有关更多信息,请参阅INFO命令。PING
这个命令只是返回PONG。ROLE
该命令返回字符串“sentinel”和被监控的主机列表。更多信息请参见ROLE命令。SHUTDOWN
关闭Sentinel实例。
最后,Sentinel还支持SUBSCRIBE、UNSUBSCRIBE、PSUBSCRIBE和PUNSUBSCRIBE命令。有关详细信息,请参阅Pub/Sub消息部分。
在运行时重新配置Sentinel
从Redis 2.8.4版本开始,Sentinel提供了一个API来添加、删除或更改给定主服务器的配置。注意,如果你有多个哨兵,你应该把这些变化应用到你的所有实例中,以便Redis哨兵正常工作。这意味着更改单个哨兵的配置不会自动将更改传播到网络中的其他哨兵。
下面是用于更新SENTINEL实例配置的SENTINEL子命令列表。
SENTINEL MONITOR <name> <ip> <port> <quorum>
该命令告诉SENTINEL开始监视具有指定name、ip、port和quorum的新主机。它与sentinel.conf配置文件中的sentinel monitor配置指令相同,不同之处在于您不能在ip中使用主机名,但您需要提供IPv4或IPv6地址。SENTINEL REMOVE <name>
用于删除指定的主机:该主机将不再被监视,并且将完全从SENTINEL的内部状态中删除,因此它将不再被SENTINEL主机列出,等等。SENTINEL SET <name> [<option> <value> …]
SET命令与Redis的CONFIG SET命令非常相似,用于更改特定主机的配置参数。可以指定多个选项/值对(或者根本不指定)。可以通过sentinel.conf配置的所有配置参数也可以使用SET命令进行配置。
下面是SENTINEL SET命令的示例,用于修改名为objects-cache的主服务器的down-after-milliseconds配置:
redis
> SENTINEL SET objects-cache-master down-after-milliseconds 1000
如前所述,SENTINEL SET可用于设置启动配置文件中可设置的所有配置参数。此外,可以仅更改主仲裁配置,而无需使用SENTINEL REMOVE和SENTINEL MONITOR删除和重新添加主仲裁,只需使用:
redis
> SENTINEL SET objects-cache-master quorum 5
注意,没有等价的GET命令,因为SENTINEL MASTER以易于解析的格式(作为字段/值对数组)提供了所有配置参数。
从Redis 6.2版本开始,Sentinel还允许获取和设置全局配置参数,而在此之前,这些参数只能在配置文件中得到支持。
SENTINEL CONFIG GET <name>
获取全局SENTINEL配置参数的当前值。指定的名称可以是通配符,类似于Redis CONFIG GET命令。SENTINEL CONFIG SET <name> <value>
设置全局SENTINEL配置参数的值。
可以操作的全局参数包括:
- resolve-hostnames, announce-hostnames。请参见IP地址和DNS名称。
- announce-ip, announce-port。请参阅Sentinel、Docker、NAT和可能的问题。
- sentinel-user, sentinel-pass。请参阅配置带有身份验证的哨兵实例。
增加或移除Sentinel
由于Sentinel实现了自动发现机制,因此向部署中添加新Sentinel是一个简单的过程。您所需要做的就是启动新的Sentinel,它被配置为监视当前活动的主机。在10秒内,哨兵将获得其他哨兵的名单和附在主人身上的从服务器。
如果你需要一次添加多个哨兵,建议一个接一个地添加,等待所有其他哨兵已经知道了第一个,然后再添加下一个。这对于保证只能在分区的一侧获得多数玩家是很有用的,因为在添加新哨兵的过程中可能会发生失败。
这可以通过在没有网络分区的情况下以30秒的延迟添加每个新的Sentinel来轻松实现。
在这个过程的最后,可以使用命令SENTINEL MASTER mastername来检查是否所有的哨兵都同意监视主的哨兵总数。
移除哨兵有点复杂:哨兵永远不会忘记已经看到的哨兵,即使它们很长一段时间无法访问,因为我们不想动态更改授权故障转移和创建新配置号所需的大多数。因此,为了删除Sentinel,应该在没有网络分区的情况下执行以下步骤:
- 停止待移除的Sentinel的Sentinel进程。
- 发送一个SENTINEL RESET 命令到所有其他的SENTINEL实例(而不是,你可以使用确切的主名称,如果你只想重置一个主)。一个接一个,实例之间至少等待30秒。
- 通过检查每个哨兵的SENTINEL MASTER mastername的输出,检查所有哨兵当前活动的哨兵数量是否一致。
移除旧的master或无法到达的从服务器
哨兵永远不会忘记一个给定主人的复制品,即使他们很长一段时间都无法到达。这很有用,因为sentinel应该能够在网络分区或故障事件发生后正确地重新配置返回的从服务器。
此外,在故障转移之后,故障转移主服务器实际上是作为新主服务器的从服务器添加的,通过这种方式,它将被重新配置,以便在新主服务器再次可用时使用它进行复制。
然而,有时你想从哨兵监视的从服务器列表中永远删除从服务器(可能是旧master)。
为了做到这一点,你需要向所有哨兵发送一个SENTINEL RESET mastername命令:他们将在接下来的10秒内刷新从服务器列表,只添加那些从当前主INFO输出中正确复制的列表。
从服务器优先级
Redis实例有一个名为replica-priority的配置参数。这些信息是由Redis从服务器实例在其INFO输出中暴露的,Sentinel使用它来从可以用于故障转移的从服务器中选择一个从服务器。
- 如果将从服务器优先级设置为0,则该从服务器永远不会提升为master。
- 具有较低优先级号的从服务器被Sentinel优先选择。
例如,如果在当前主服务器的同一数据中心有一个从服务器S1,在另一个数据中心有另一个从服务器S2,则可以将S1的优先级设置为10,将S2的优先级设置为100,这样,如果主服务器故障,并且S1和S2都可用,则优先选择S1。
有关选择副本方式的更多信息,请查看本文档的副本选择和优先级部分。
Sentinel实战
启动三台Sentinel
sentinel.conf配置中,除了端口号分别是26379、26380和26381以外,其他内容都一样。具体如下:
- sentinel-26379.conf
conf
# Sentinel运行实例端口。
port 26379
# 告诉Sentinel监视这个主机,并在O_DOWN中考虑它(客观上说)只有在至少达到法定人数时才处于哨兵同意状态。
sentinel monitor mymaster 127.0.0.1 6379 2
# 主服务器(或任何附加的副本或哨兵)应该使用的毫秒数不可达(例如,不能接受PING的连续应答)(主观上),以考虑其处于S_DOWN状态。
sentinel down-after-milliseconds mymaster 5000
# 指定故障转移超时(以毫秒为单位)。
sentinel failover-timeout mymaster 60000
# 我们可以重新配置多少个副本来同时指向新的副本在故障转移期间。如果使用副本提供查询,为了避免所有副本在同一时间无法访问与主服务器执行同步时的时间则使用较低的数字。
sentinel parallel-syncs mymaster 1
- sentinel-26380.conf
conf
# Sentinel运行实例端口。
port 26380
# 告诉Sentinel监视这个主机,并在O_DOWN中考虑它(客观上说)只有在至少达到法定人数时才处于哨兵同意状态。
sentinel monitor mymaster 127.0.0.1 6379 2
# 主服务器(或任何附加的副本或哨兵)应该使用的毫秒数不可达(例如,不能接受PING的连续应答)(主观上),以考虑其处于S_DOWN状态。
sentinel down-after-milliseconds mymaster 5000
# 指定故障转移超时(以毫秒为单位)。
sentinel failover-timeout mymaster 60000
# 我们可以重新配置多少个副本来同时指向新的副本在故障转移期间。如果使用副本提供查询,为了避免所有副本在同一时间无法访问与主服务器执行同步时的时间则使用较低的数字。
sentinel parallel-syncs mymaster 1
- sentinel-26381.conf
conf
# Sentinel运行实例端口。
port 26381
# 告诉Sentinel监视这个主机,并在O_DOWN中考虑它(客观上说)只有在至少达到法定人数时才处于哨兵同意状态。
sentinel monitor mymaster 127.0.0.1 6379 2
# 主服务器(或任何附加的副本或哨兵)应该使用的毫秒数不可达(例如,不能接受PING的连续应答)(主观上),以考虑其处于S_DOWN状态。
sentinel down-after-milliseconds mymaster 5000
# 指定故障转移超时(以毫秒为单位)。
sentinel failover-timeout mymaster 60000
# 我们可以重新配置多少个副本来同时指向新的副本在故障转移期间。如果使用副本提供查询,为了避免所有副本在同一时间无法访问与主服务器执行同步时的时间则使用较低的数字。
sentinel parallel-syncs mymaster 1
启动命令分别如下:
sh
$ redis-server sentinel-26379.conf --sentinel
1541:X 26 Aug 2023 10:34:28.060 # Sentinel ID is 10d649a97421cb46c749c4b58286c135882e173a
1541:X 26 Aug 2023 10:34:28.060 # +monitor master mymaster 127.0.0.1 6379 quorum 2
1541:X 26 Aug 2023 10:34:33.026 # +sdown master mymaster 127.0.0.1 6379
$ redis-server sentinel-26380.conf --sentinel
1542:X 26 Aug 2023 10:42:45.016 # Sentinel ID is a2b2b6b89975ed77be7eda6ad89e2a1da8057fda
1542:X 26 Aug 2023 10:42:45.020 # +monitor master mymaster 127.0.0.1 6379 quorum 2
1542:X 26 Aug 2023 10:42:49.946 # +sdown master mymaster 127.0.0.1 6379
$ redis-server sentinel-26381.conf --sentinel
1543:X 26 Aug 2023 10:42:57.801 # Sentinel ID is 452d2e3f91b752592624e5ed1855b6d9e594ee43
1543:X 26 Aug 2023 10:42:57.813 # +monitor master mymaster 127.0.0.1 6379 quorum 2
1543:X 26 Aug 2023 10:43:02.744 # +sdown master mymaster 127.0.0.1 6379
由于还没有启动主服务器,所以可以看到三台Sentinel都认为此时主服务器是下线状态。
启动主服务器和从服务器
启动命令分别如下:
sh
$ redis-server.exe --port 6379
$ redis-server.exe --port 6380 --replicaof 127.0.0.1 6379
$ redis-server.exe --port 6381 --replicaof 127.0.0.1 6379
主从服务器分别启动后,此时可以看到三台Sentinel的日志都显示检测到了主从服务器上线,内容如下:
log
1541:X 26 Aug 2023 11:15:30.984 # -sdown master mymaster 127.0.0.1 6379
1541:X 26 Aug 2023 11:16:31.120 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
1541:X 26 Aug 2023 11:17:11.203 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379
测试故障转移
此时关闭主服务器,会发现Sentinel的日志出现以下内容:
- 每个Sentinel检测到主服务器宕机并触发+sdown事件。
- 此事件随后升级为+odown,这意味着多个哨兵同意无法到达主节点的事实。
- 哨兵投票给哨兵,哨兵将开始第一次故障转移尝试。
- 发生故障转移。
log
1542:X 26 Aug 2023 12:00:37.700 # +sdown master mymaster 127.0.0.1 6379
1542:X 26 Aug 2023 12:00:37.760 # +odown master mymaster 127.0.0.1 6379 #quorum 2/2
1542:X 26 Aug 2023 12:00:37.762 # +new-epoch 1
1542:X 26 Aug 2023 12:00:37.763 # +try-failover master mymaster 127.0.0.1 6379
1542:X 26 Aug 2023 12:00:37.773 # +vote-for-leader a2b2b6b89975ed77be7eda6ad89e2a1da8057fda 1
1542:X 26 Aug 2023 12:00:37.797 # 10d649a97421cb46c749c4b58286c135882e173a voted for a2b2b6b89975ed77be7eda6ad89e2a1da8057fda 1
1542:X 26 Aug 2023 12:00:37.799 # 452d2e3f91b752592624e5ed1855b6d9e594ee43 voted for a2b2b6b89975ed77be7eda6ad89e2a1da8057fda 1
1542:X 26 Aug 2023 12:00:37.864 # +elected-leader master mymaster 127.0.0.1 6379
1542:X 26 Aug 2023 12:00:37.865 # +failover-state-select-slave master mymaster 127.0.0.1 6379
1542:X 26 Aug 2023 12:00:37.967 # +selected-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379
1542:X 26 Aug 2023 12:00:37.969 * +failover-state-send-slaveof-noone slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379
1542:X 26 Aug 2023 12:00:38.071 * +failover-state-wait-promotion slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379
1542:X 26 Aug 2023 12:00:38.833 # +promoted-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379
1542:X 26 Aug 2023 12:00:38.837 # +failover-state-reconf-slaves master mymaster 127.0.0.1 6379
1542:X 26 Aug 2023 12:00:38.905 * +slave-reconf-sent slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
1542:X 26 Aug 2023 12:00:39.864 * +slave-reconf-inprog slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
1542:X 26 Aug 2023 12:00:39.864 * +slave-reconf-done slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
1542:X 26 Aug 2023 12:00:39.933 # -odown master mymaster 127.0.0.1 6379
1542:X 26 Aug 2023 12:00:39.933 # +failover-end master mymaster 127.0.0.1 6379
1542:X 26 Aug 2023 12:00:39.933 # +switch-master mymaster 127.0.0.1 6379 127.0.0.1 6381
1542:X 26 Aug 2023 12:00:39.934 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381
1542:X 26 Aug 2023 12:00:39.935 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6381
1542:X 26 Aug 2023 12:00:44.950 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6381
可以看到6379主服务器下线后,三台Sentinel选举了6381作为新的主服务器,并将6379作为从服务器。
下面是6379重新上线后的Sentinel日志:
log
1542:X 26 Aug 2023 12:07:11.139 # -sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6381
集群
Redis集群TCP端口
每个Redis集群节点需要两个开放的TCP连接:一个Redis TCP端口用于服务客户端,例如6379,第二个端口称为集群总线端口。默认情况下,集群总线端口通过在数据端口上添加10000来设置(例如,16379);但是,您可以在集群端口配置中覆盖这一点。
Redis集群数据分片
Redis集群不使用一致哈希,而是使用一种不同形式的分片,其中每个键在概念上都是我们称之为哈希槽的一部分。
Redis集群中有16384个哈希槽,为了计算给定键的哈希槽,我们只需将键的CRC16模取16384。
Redis集群中的每个节点负责哈希槽的一个子集,因此,例如,你可能有一个有3个节点的集群,其中:
- 节点A包含0 ~ 5500个哈希槽。
- 节点B包含从5501到11000的哈希槽。
- 节点C包含从11001到16383的哈希槽。
这使得添加和删除集群节点变得很容易。例如,如果我想添加一个新的节点D,我需要将节点a, B, C的一些哈希槽移动到D。类似地,如果我想从集群中删除节点a,我只需将a服务的哈希槽移动到B和C。一旦节点a为空,我可以将其完全从集群中删除。
将哈希槽从一个节点移动到另一个节点不需要停止任何操作;因此,添加和删除节点,或更改节点持有的哈希槽百分比,都不需要停机。
Redis Cluster支持多键操作,只要在单个命令执行(或整个事务,或Lua脚本执行)中涉及的所有键都属于相同的哈希槽。用户可以通过使用称为散列标签的功能强制多个密钥成为同一散列槽的一部分。
散列标签在Redis集群规范中被记录,但要点是,如果在关键字的{}括号之间有子字符串,则只有字符串内部的内容被散列。例如,密钥user:{123}:profile和user:{123}:account因为共享相同的哈希标签而保证在相同的哈希槽中。因此,您可以在同一个多键操作中操作这两个键。
Redis集群主-副本模型
为了在主节点子集出现故障或无法与大多数节点通信时保持可用,Redis Cluster使用主-复制模型,其中每个哈希槽有从1(主节点本身)到N个副本(N-1个额外的副本节点)。
在我们的节点A、B、C的示例集群中,如果节点B失败,集群将无法继续,因为我们不再有办法为5501-11000范围内的哈希槽提供服务。
但是,当创建集群时(或稍后),我们向每个主节点添加一个复制节点,这样最终的集群由主节点a、B、C和复制节点A1、B1、C1组成。这样,如果节点B出现故障,系统可以继续运行。
节点B1复制B,如果B失败,集群将提升节点B1为新的主节点,继续正常运行。
但是,请注意,如果节点B和节点B1同时故障,Redis Cluster将无法继续运行。
Redis集群一致性保证
Redis集群不保证强一致性。实际上,这意味着在某些情况下,Redis集群可能会丢失系统向客户端确认的写操作。
Redis集群丢失写的第一个原因是因为它使用异步复制。这意味着在写入过程中会发生以下事情:
- 您的客户端写入主B。
- 主B向你的客户端回复OK。
- 主B将写操作传播到它的副本B1、B2和B3。
正如你所看到的,B在回复客户端之前不会等待来自B1, B2, B3的确认,因为这对Redis来说是一个令人望而却步的延迟惩罚,所以如果你的客户端写了一些东西,B承认写,但在能够将写发送到副本之前崩溃,其中一个副本(没有收到写)可以提升为主,永远失去写。
这与配置为每秒将数据刷新到磁盘的大多数数据库所发生的情况非常相似,因此,由于过去使用不涉及分布式系统的传统数据库系统的经验,您已经能够推断出这种情况。类似地,您可以通过强制数据库在回复客户机之前将数据刷新到磁盘来提高一致性,但这通常会导致非常低的性能。这相当于Redis集群中的同步复制。
基本上,在性能和一致性之间需要做出权衡。
Redis集群在绝对需要的时候支持同步写,通过WAIT命令实现。这大大降低了写操作失败的可能性。但是,请注意,即使使用同步复制,Redis Cluster也不会实现强一致性:在更复杂的故障场景下,无法接收写操作的副本总是有可能被选为主副本。
还有一个值得注意的场景,Redis集群会丢失写,发生在网络分区期间,客户端与少数实例隔离,其中至少包括一个主节点。
以A、B、C、A1、B1、C1组成的6节点集群为例,集群中有3个主节点和3个副本。还有一个客户端,我们称之为Z1。
在分区发生后,可能在分区的一侧有A, C, A1, B1, C1,而在另一侧有B和Z1。
Z1仍然可以写入B, B会接受它的写入。如果分区在很短的时间内愈合,那么集群将继续正常运行。但是,如果分区持续的时间足够长,使得B1在分区的多数侧提升为master,则Z1在此期间发送给B的写操作将丢失。
WARNING
对于Z1能够发送给B的写操作数量,存在一个最大窗口:如果分区的多数侧已经经过了足够的时间来选择一个副本作为主节点,那么少数侧的每个主节点都将停止接受写操作。
这个时间是Redis Cluster的一个非常重要的配置指令,被称为node timeout。
在节点超时之后,主节点被认为是失败的,并且可以由它的一个副本替换。类似地,在节点超时之后,如果没有一个主节点能够感知大多数其他主节点,它将进入错误状态并停止接受写操作。
创建和使用一个Redis集群
创建Redis集群的要求
要创建一个集群,你需要做的第一件事就是在集群模式下运行一些空的Redis实例。
至少,在redis.conf文件中设置以下指令:
conf
# Redis运行实例端口。
port 7000
# 正常的Redis实例不能成为Redis集群的一部分;只有节点启动集群节点可以。
cluster-enabled yes
# 每个集群节点都有一个集群配置文件。这个文件不是打算手工编辑。它由Redis节点创建和更新。每个Redis集群节点需要一个不同的集群配置文件。
cluster-config-file nodes-7000.conf
# 集群节点超时是节点不可达的毫秒数表示它被认为处于故障状态。大多数其他内部时间限制是节点超时的倍数。
cluster-node-timeout 5000
# 开启AOF持久化功能
appendonly yes
要启用集群模式,请将cluster-enabled指令设置为yes。每个实例还包含存储该节点配置的文件的路径,默认情况下是nodes.conf。这个文件从未被人类碰过;它只是由Redis集群实例在启动时生成,并在每次需要时更新。
注意,按预期工作的最小集群必须包含至少三个主节点。对于部署,我们强烈建议使用六个节点的集群,其中包含三个主节点和三个副本。
您可以在本地测试这一点,方法是以您将在任何给定目录中运行的实例的端口号来创建以下目录。例如:
sh
mkdir cluster-test
cd cluster-test
mkdir 7000 7001 7002 7003 7004 7005
在从7000到7005的每个目录中创建一个redis.conf文件。只需使用上面的小示例作为配置文件的模板,但要确保根据目录名将端口号7000替换为正确的端口号。
你可以像下面这样启动每个实例,每个实例运行在一个单独的终端选项卡中:
sh
cd 7000
redis-server ./redis.conf
您将从日志中看到,每个节点为自己分配了一个新ID:
log
889:M 28 Aug 2023 12:02:26.699 * No cluster configuration found, I'm 872fc625a3728afdb42d67abf7137f5df572fdf8
此ID将由该特定实例永久使用,以便实例在集群上下文中具有唯一的名称。每个节点都使用这些id来记住其他节点,而不是通过IP或端口。IP地址和端口可能会改变,但唯一的节点标识符在节点的整个生命周期内永远不会改变。我们将此标识符简称为节点ID。
创建Redis集群
现在我们已经运行了许多实例,您需要通过向节点写入一些有意义的配置来创建集群。
您可以手动配置和执行单个实例,也可以使用create-cluster脚本。让我们来看看如何手动操作。
创建集群,使用命令:
redis
redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 \
127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \
--cluster-replicas 1
这里使用的命令是create,因为我们想要创建一个新的集群。选项——cluster-replicas 1表示我们希望为每个创建的主节点创建一个副本。
其他参数是我想用来创建新集群的实例的地址列表。
redis-cli将提出一个配置。输入yes接受建议的配置。集群将被配置和加入,这意味着实例将被引导成相互通信。最后,如果一切正常,您将看到这样的消息:
log
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
.
>>> Performing Cluster Check (using node 127.0.0.1:7000)
M: 55b5059cb680686c924a6b5792c9c7bdf35bf4ac 127.0.0.1:7000
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: a30922d69d1537994ca0658e4d13250ea324d816 127.0.0.1:7003
slots: (0 slots) slave
replicates 55b5059cb680686c924a6b5792c9c7bdf35bf4ac
M: 54c088d731422ab730ffc1d65c5c396163f32e05 127.0.0.1:7001
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
M: c639a26858ab99d5356e70aef7d951147e0f2f14 127.0.0.1:7002
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
S: b1aa690b03b8da3aedeea6ded5135e5161b6f4b8 127.0.0.1:7005
slots: (0 slots) slave
replicates c639a26858ab99d5356e70aef7d951147e0f2f14
S: 88aa5c76702e7e32f1b466f48d02d797e58af3d8 127.0.0.1:7004
slots: (0 slots) slave
replicates 54c088d731422ab730ffc1d65c5c396163f32e05
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
这意味着至少有一个主实例为16384个可用插槽中的每个插槽提供服务。
与集群交互
要连接到Redis Cluster,你需要一个集群感知的Redis客户端。请参阅所选客户机的文档,以确定其集群支持。
你也可以使用redis-cli命令行工具来测试你的Redis集群:
redis
$ redis-cli -c -p 7000
127.0.0.1:7000> SET mykey hello
-> Redirected to slot [14687] located at 127.0.0.1:7002
OK
127.0.0.1:7002> SET mykey1 world
-> Redirected to slot [1860] located at 127.0.0.1:7000
OK
127.0.0.1:7000> GET mykey
-> Redirected to slot [14687] located at 127.0.0.1:7002
"hello"
127.0.0.1:7002> GET mykey1
-> Redirected to slot [1860] located at 127.0.0.1:7000
"world"
redis-cli集群支持是非常基本的,所以它总是使用Redis集群节点能够将客户端重定向到正确的节点这一事实。一个严肃的客户端能够做得更好,并缓存散列槽和节点地址之间的映射,以便直接使用到正确节点的正确连接。映射仅在集群配置发生更改时才刷新,例如在故障转移之后,或者在系统管理员通过添加或删除节点更改集群布局之后。