前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >redis简单使用

redis简单使用

原创
作者头像
墨紫羽墨
修改2022-12-02 17:49:10
1.9K0
修改2022-12-02 17:49:10
举报
文章被收录于专栏:FutureTester

一、redis入门

1、redis简介

Redis是一个主要由Salvatore Sanfilippo(Antirez)开发的开源内存数据结构存储器,经常用作数据库、缓存以及消息代理等。

Redis 是完全开源免费的,遵守 BSD 协议,是一个灵活的高性能 key-value 数据结构存储,可以用来作为数据库、缓存和消息队列。

Redis 比其他 key-value 缓存产品有以下三个特点:

  • Redis 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载到内存使用。
  • Redis 不仅支持简单的 key-value 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。
  • Redis 支持主从复制,即 master-slave 模式的数据备份。
redis特色.png
redis特色.png

1.1、预备工作

1.1.1、下载

下载地址:https://redis.com.cn/download.html

可以下载安装版的或者redis服务压缩包

1.1.2、启动

服务端:打开压缩包目录所在的命令行,执行redis-server

redis-server-start.png
redis-server-start.png

客户端:重新在打开一个压缩包目录所在的命令行,执行redis-cli

redis-cli-start.png
redis-cli-start.png

测试客户端和服务器之间的连接是否正常,在客户端执行命令:ping,那么服务器在连接正常的情况下,将向客户端返回PONG作为回复

代码语言:shell
复制
127.0.0.1:6379> PING
PONG

如果用户给定了可选的消息,那么服务器将原封不动地向客户端返回该消息:

代码语言:shell
复制
127.0.0.1:6379> PING "hello world"
"hello world"

另外,如果服务器与客户端的连接不正常,那么客户端将返回一个错误:

客户端未能连接服务器,返回一个连接错误

代码语言:shell
复制
127.0.0.1:6379> PING
Could not connect to Redis at 127.0.0.1:6379: Connection refused

或者:

代码语言:shell
复制
127.0.0.1:6379> ping
Could not connect to Redis at 127.0.0.1:6379: 由于目标计算机积极拒绝,无法连接。
(2.05s)
not connected>

2、数据结构与应用

2.1、字符串

字符串(string)键是Redis最基本的键值对类型,这种类型的键值对会在数据库中把单独的一个键和单独的一个值关联起来,被关联的键和值既可以是普通的文字数据,也可以是图片、视频、音频、压缩文件等更为复杂的二进制数据。

2.1.1、SET:为字符串键设置值

创建字符串键最常用的方法就是使用SET命令,这个命令可以为一个字符串键设置相应的值。SET key value

代码语言:shell
复制
redis> SET number "10086"
OK

用户可以通过向SET命令提供可选的NX选项或者XX选项来指示SET命令是否要覆盖一个已经存在的值:SET key value [NX|XX],如果用户在执行SET命令时给定了NX选项,那么SET命令只会在键没有值的情况下执行设置操作,并返回OK表示设置成功;如果键已经存在,那么SET命令将放弃执行设置操作,并返回空值nil表示设置失败。

以下代码展示了带有NX选项的SET命令的行为:

代码语言:shell
复制
redis> SET password "123456" NX
OK -- 对尚未有值的password键进行设置,成功

redis> SET password "999999" NX
(nil) -- password键已经有了值,设置失败

如果我们对一个没有值的键mongodb-homepage执行以下SET命令,那么命令将因为XX选项的作用而放弃执行设置操作,相反,如果我们对一个已经有值的键执行带有XX选项的SET命令,那么命令将使用新值去覆盖已有的旧值.

代码语言:shell
复制
redis> SET mongodb-homepage "mongodb.com" XX
(nil)

redis> SET mysql-homepage "mysql.org"
OK -- 为键mysql-homepage设置一个值

redis> SET mysql-homepage "mysql.com" XX
OK -- 对键的值进行更新
2.1.2、GET:获取字符串键的值

用户可以使用GET命令从数据库中获取指定字符串键的值:GET key

代码语言:shell
复制
redis> GET number
"10086"
2.1.3、GETSET:获取旧值并设置新值

GETSET命令就像GET命令和SET命令的组合版本,GETSET首先获取字符串键目前已有的值,接着为键设置新值,最后把之前获取到的旧值返回给用户:GETSET key new_value

代码语言:shell
复制
redis> GET number -- number键现在的值为"10086"
"10086"

redis> GETSET number "12345"
"10086" -- 返回旧值

redis> GET number -- number键的值已被更新为"12345"
"12345"
2.1.4、MSET:一次为多个字符串键设置值

除了SET命令和GETSET命令之外,Redis还提供了MSET命令用于对字符串键进行设置。与SET命令和GETSET命令只能设置单个字符串键不同,MSET命令可以一次为多个字符串键设置值:MSET key value [key value ...]

代码语言:shell
复制
redis> MSET message "hello world" number "10086" homepage "redis.io"
OK

redis> GET message
"hello world"

redis> GET number
"10086"

redis> GET homepage
"redis.io"
2.1.5、MGET:一次获取多个字符串键的值

MGET命令就是一个多键版本的GET命令,MGET接受一个或多个字符串键作为参数,并返回这些字符串键的值:MGET key [key ...]

代码语言:shell
复制
redis> MGET message number homepage
1) "hello world" -- message键的值
2) "10086" -- number键的值
3) "redis.io" -- homepage键的值
2.1.6、MSETNX:只在键不存在的情况下,一次为多个字符串键设置值

MSETNX命令与MSET命令一样,都可以对多个字符串键进行设置:MSETNX key value [key value ...]

代码语言:shell
复制
redis> mget num1 num2 num3 msg
1) (nil)
2) (nil)
3) (nil)
4) "hello world" -- 键msg已存在

redis> msetnx num1 1 num2 2 num3 3 msg "hello"
(integer) 0  -- 因为键msg已存在,所以MSETNX未能执行设置操作

redis> MGET k1 k2 k3 -- 各个键的值没有变化
1) (nil)
2) (nil)
3) (nil)

redis> MSETNX k1 "one" k2 "two" k3 "three"
(integer) 1 -- 所有给定键都不存在,成功执行设置操作
2.1.7、STRLEN:获取字符串值的字节长度

通过对字符串键执行STRLEN命令,用户可以取得字符串键存储的值的字节长度:STRLEN key

代码语言:shell
复制
redis> GET number
"10086"

redis> STRLEN number -- number键的值长5字节
(integer) 5

redis> GET msg
"hello world"

redis> STRLEN msg -- msg键的值长11字节
(integer) 11
2.1.8、GETRANGE:获取字符串值指定索引范围上的内容

通过使用GETRANGE命令,用户可以获取字符串值从start索引开始,直到end索引为止的所有内容:GETRANGE key start end

代码语言:shell
复制
redis> GETRANGE msg 0 4 -- 获取字符串值索引0至索引4上的内容
"hello"
2.1.9、SETRANGE:对字符串值的指定索引范围进行设置

通过使用SETRANGE命令,用户可以将字符串键的值从索引index开始的部分替换为指定的新内容,被替换内容的长度取决于新内容的长度:SETRANGE key index substitute

代码语言:shell
复制
redis> GET message
"hello world"

redis> SETRANGE message 6 "Redis"
(integer) 11 -- 字符串值当前的长度为11字节

redis> GET message
"hello Redis"

redis> SETRANGE message 5 ", this is a message send from peter."
(integer) 41 -- 替换部分比原字符串长

redis> GET message
"hello, this is a message send from peter."

redis> GET greeting
"hello"

redis> SETRANGE greeting 10 "world"
(integer) 15 -- 替换开始的索引超出原字符串的长度

redis> GET greeting
"hello\x00\x00\x00\x00\x00world"
2.1.10、APPEND:追加新内容到值的末尾

通过调用APPEND命令,用户可以将给定的内容追加到字符串键已有值的末尾:APPEND key suffix

代码语言:shell
复制
redis> GET description
"Redis"

redis> APPEND description " is a database"
(integer) 19 -- 追加操作执行完毕之后,值的长度

redis> GET description
"Redis is a database"
2.1.11、使用字符串键存储数字值

每当用户将一个值存储到字符串键里面的时候,Redis都会对这个值进行检测,如果这个值能够被解释为以下两种类型的其中一种,那么Redis就会把这个值当作数字来处理:

  • 第一种类型是能够使用C语言的long long int类型存储的整数,在大多数系统中,这种类型存储的都是64位长度的有符号整数,取值范围介于-9223372036854775808和9223372036854775807之间。
  • 第二种类型是能够使用C语言的long double类型存储的浮点数,在大多数系统中,这种类型存储的都是128位长度的有符号浮点数,取值范围介于3.36210314311209350626e-4932和1.18973149535723176502e+4932L之间。
2.1.12、INCRBY、DECRBY:对整数值执行加法操作和减法操作

当字符串键存储的值能够被Redis解释为整数时,用户就可以通过INCRBY命令和DECRBY命令对被存储的整数值执行加法或减法操作。INCRBY命令用于为整数值加上指定的整数增量,并返回键在执行加法操作之后的值:INCRBY key increment

代码语言:shell
复制
redis> SET number 100
OK

redis> INCRBY number 300 -- 将键的值加上300
(integer) 400

redis> GET number
"400"

INCRBY命令的作用正好相反,DECRBY命令用于为整数值减去指定的整数减量,并返回键在执行减法操作之后的值:DECRBY key increment

代码语言:shell
复制
redis> SET number 10086
OK

redis> DECRBY number 300 -- 将键的值减去300
(integer) 9786

redis> GET number
"9786"
2.1.13、INCR、DECR:对整数值执行加1操作和减1操作

因为对整数值执行加1操作或减1操作的场景经常会出现,所以为了能够更方便地执行这两个操作,Redis分别提供了用于执行加1操作的INCR命令以及用于执行减1操作的DECR命令。INCR key,DECR key

代码语言:shell
复制
redis> SET counter 100
OK

redis> INCR counter -- 对整数值执行加1操作
(integer) 101

redis> DECR counter -- 对整数值执行减1操作
(integer) 100
2.1.14、INCRBYFLOAT:对数字值执行浮点数加法操作

除了用于执行整数加法操作的INCR命令以及INCRBY命令之外,Redis还提供了用于执行浮点数加法操作的INCRBYFLOAT命令:INCRBYFLOAT key increment

代码语言:shell
复制
redis> SET decimal 3.14 -- 一个存储着浮点数值的键
OK

redis> GET decimal
"3.14"

redis> INCRBYFLOAT decimal 2.55 -- 将键decimal的值加上2.55
"5.69"

redis> GET decimal
"5.69"
  • 只能通过给INCRBYFLOAT命令传入负数增量来执行浮点数减法操作。
  • 在使用INCRBYFLOAT命令处理浮点数的时候,命令最多只会保留计算结果小数点后的17位数字,超过这个范围的小数将被截断.

2.2、散列

Redis的散列键会将一个键和一个散列在数据库里关联起来,用户可以在散列中为任意多个字段(field)设置值。与字符串键一样,散列的字段和值既可以是文本数据,也可以是二进制数据。

2.2.1、HSET:为字段设置值

用户可以通过执行HSET命令为散列中的指定字段设置值:HSET hash field value

代码语言:shell
复制
redis> HSET article::10086 title "greeting"
(integer) 1
2.2.2、HSETNX:只在字段不存在的情况下为它设置值

HSETNX命令的作用和HSET命令的作用非常相似,它们之间的区别在于,HSETNX命令只会在指定字段不存在的情况下执行设置操作:HSETNX hash field value

代码语言:shell
复制
redis> HSETNX article::10086 view_count 100
(integer) 1
2.2.3、HGET:获取字段的值

HGET命令可以根据用户给定的字段,从散列中获取该字段的值:HGET hash field

代码语言:shell
复制
redis> HGET article::10086 author
"peter"
2.2.4、HINCRBY:对字段存储的整数值执行加法或减法操作

与字符串键的INCRBY命令一样,如果散列的字段里面存储着能够被Redis解释为整数的数字,那么用户就可以使用HINCRBY命令为该字段的值加上指定的整数增量:HINCRBY hash field increment

代码语言:shell
复制
redis> HINCRBY article::10086 view_count 1
(integer) 101
  • 需要将一个负数增量传给HINCRBY命令,从而达到对值执行减法计算的目的。
2.2.5、HINCRBYFLOAT:对字段存储的数字值执行浮点数加法或减法操作

HINCRBYFLOAT命令的作用和HINCRBY命令的作用类似,它们之间的主要区别在于HINCRBYFLOAT命令不仅可以使用整数作为增量,还可以使用浮点数作为增量:HINCRBYFLOAT hash field increment

代码语言:shell
复制
redis> HGET geo::peter longitude
"100.0099647"
redis> HINCRBYFLOAT geo::peter longitude 13.2 -- 将字段的值加上13.2
"113.2099647"
  • 只能通过向HINCRBYFLOAT命令传入负值浮点数来实现减法操作。
2.2.6、HSTRLEN:获取字段值的字节长度

用户可以使用HSTRLEN命令获取给定字段值的字节长度:HSTRLEN hash field

代码语言:shell
复制
redis> HSTRLEN article::10086 title
(integer) 8 -- title字段的值"greeting"长8个字节
2.2.7、HEXISTS:检查字段是否存在

HEXISTS命令可用于检查用户给定的字段是否存在于散列当中:HEXISTS hash field

代码语言:shell
复制
redis> HEXISTS article::10086 content
(integer) 1
redis> HEXISTS article::10086 last_updated_at
(integer) 0 -- 不包含该字段
2.2.8、HDEL:删除字段

HDEL命令用于删除散列中的指定字段及其相关联的值:HDEL hash field

代码语言:shell
复制
redis> HDEL article::10086 author
(integer) 1
2.2.9、HLEN:获取散列包含的字段数量

用户可以通过使用HLEN命令获取给定散列包含的字段数量:HLEN hash

代码语言:shell
复制
redis> HLEN article::10086
(integer) 4 -- 这个散列包含4个字段
2.2.10、HMSET:一次为多个字段设置值

用户可以使用HMSET命令一次为散列中的多个字段设置值:HMSET hash field value [field value ...]

代码语言:shell
复制
redis> HSET article::10086 title "greeting"
(integer) 1
2.2.11、HMGET:一次获取多个字段的值

通过使用HMGET命令,用户可以一次从散列中获取多个字段的值:HMGET hash field [field ...]

代码语言:shell
复制
redis> HMGET article::10086 author created_at
1) "peter" -- author字段的值
2) "1442744762.631885" -- created_at字段的值
2.2.12、HKEYS、HVALS、HGETALL:获取所有字段、所有值、所有字段和值

Redis为散列提供了HKEYS、HVALS和HGETALL这3个命令,可以分别用于获取散列包含的所有字段、所有值以及所有字段和值:HKEYS hash, HVALS hash, HGETALL hash

代码语言:shell
复制
redis> HKEYS article::10086
1) "title"
2) "content"
3) "author"
4) "created_at"

redis> HVALS article::10086
1) "greeting"
2) "hello world"
3) "peter"
4) "1442744762.631885"

redis> HGETALL article::10086
1) "title" -- 字段
2) "greeting" -- 字段的值
3) "content"
4) "hello world"
5) "author"
6) "peter"
7) "created_at"
8) "1442744762.631885"
2.2.13、散列与字符串
  • 散列的最大优势,就是它只需要在数据库里面创建一个键,就可以把任意多的字段和值存储到散列里面。相反,因为每个字符串键只能存储一个键值对,所以如果用户要使用字符串键去存储多个数据项,就只能在数据库中创建多个字符串键。
  • 散列键命令和字符串键命令在部分功能上有重合的地方,但是字符串键命令提供的操作比散列键命令更为丰富。比如,字符串能够使用SETRANGE命令和GETRANGE命令设置或者读取字符串值的其中一部分,或者使用APPEND命令将新内容追加到字符串值的末尾,而散列键并不支持这些操作。
  • Redis的键过期功能可以在指定时间到达时,自动删除指定的键。因为键过期功能针对的是整个键,用户无法为散列中的不同字段设置不同的过期时间,所以当一个散列键过期的时候,它包含的所有字段和值都将被删除。与此相反,如果用户使用字符串键存储信息项,就不会遇到这样的问题——用户可以为每个字符串键分别设置不同的过期时间,让它们根据实际的需要自动被删除。
redis-string-hash.png
redis-string-hash.png

2.3 列表

Redis的列表(list)是一种线性的有序结构,可以按照元素被推入列表中的顺序来存储元素,这些元素既可以是文字数据,又可以是二进制数据,并且列表中的元素可以重复出现。

2.3.1、LPUSH:将元素推入列表左端

用户可以通过LPUSH命令,将一个或多个元素推入给定列表的左端:LPUSH list item [item item ...]

代码语言:shell
复制
redis> LPUSH todo "buy some milk"
(integer) 1 -- 列表现在包含1个元素

redis> LPUSH another-todo "buy some milk" "watch tv" "finish homework"
(integer) 3

LPUSH命令将按照元素给定的顺序,从左到右依次将所有给定元素推入列表左端。

redis-lpush.png
redis-lpush.png
2.3.2、RPUSH:将元素推入列表右端

RPUSH命令和LPUSH命令类似,这两个命令执行的都是元素推入操作,唯一区别就在于LPUSH命令会将元素推入列表左端,而RPUSH命令会将元素推入列表右端:RPUSH list item [item item ...]

代码语言:shell
复制
redis> RPUSH todo "buy some milk"
(integer) 1 -- 列表现在包含1个元素

redis> RPUSH another-todo "buy some milk" "watch tv" "finish homework"
(integer) 3

RPUSH命令将按照元素给定的顺序,从左到右依次将所有给定元素推入列表右端。

redis-rpush.png
redis-rpush.png
2.3.3、LPUSHX、RPUSHX:只对已存在的列表执行推入操作

当用户调用LPUSH命令或RPUSH命令尝试将元素推入列表的时候,如果给定的列表并不存在,那么命令将自动创建一个空列表,并将元素推入刚刚创建的列表中。

代码语言:shell
复制
redis> LPUSH list1 "item1"
(integer) 1

redis> RPUSH list2 "item1"
(integer) 1 

Redis还提供了LPUSHX命令和RPUSHX命令,只会在列表已经存在的情况下,将元素推入刚刚创建的列表中。

代码语言:shell
复制
redis> LPUSHX list3 "item-x"
(integer) 0 -- 没有推入任何元素

redis> RPUSHX list3 "item-y"
(integer) 0 -- 没有推入任何元素

redis> LPUSH list3 "item1"
(integer) 1 -- 推入一个元素,使得列表变为非空

redis> LPUSHX list3 "item-x"
(integer) 2 -- 执行推入操作之后,列表包含2个元素
  • 与LPUSH命令和RPUSH命令不一样,LPUSHX命令和RPUSHX命令每次只能推入一个元素.
2.3.4、LPOP:弹出列表最左端的元素

用户可以通过LPOP命令移除位于列表最左端的元素,并将被移除的元素返回给用户:LPOP list

代码语言:shell
复制
redis> LPOP todo
"finish homework"
2.3.5、RPOP:弹出列表最右端的元素

用户可以通过RPOP命令移除位于列表最右端的元素,并将被移除的元素返回给用户:RPOP list

代码语言:shell
复制
redis> RPOP todo
"finish homework"
2.3.6、RPOPLPUSH:将右端弹出的元素推入左端

RPOPLPUSH命令的行为和它的名字一样,首先使用RPOP命令将源列表最右端的元素弹出,然后使用LPUSH命令将被弹出的元素推入目标列表左端,使之成为目标列表的最左端元素:RPOPLPUSH source target

代码语言:shell
复制
redis> RPUSH list1 "a" "b" "c" -- 创建两个示例列表list1和list2
(integer) 3
2.3.7、LLEN:获取列表的长度

用户可以通过执行LLEN命令来获取列表的长度,即列表包含的元素数量:LLEN list

代码语言:shell
复制
redis> LLEN todo
(integer) 3
2.3.8、LINDEX:获取指定索引上的元素

Redis列表包含的每个元素都有与之对应的正数索引和负数索引:LINDEX list index

代码语言:shell
复制
redis> LINDEX alphabets 0
"a"
2.3.9、LRANGE:获取指定索引范围上的元素

用户除了可以使用LINDEX命令获取给定索引上的单个元素之外,还可以使用LRANGE命令获取给定索引范围上的多个元素:LRANGE list start end

代码语言:shell
复制
redis> LRANGE alphabets 0 3 -- 获取列表索引0至索引3上的所有元素
1) "a" -- 位于索引0上的元素
2) "b" -- 位于索引1上的元素
3) "c" -- 位于索引2上的元素
4) "d" -- 位于索引3上的元素
2.3.10、LSET:为指定索引设置新元素

用户可以通过LSET命令,为列表的指定索引设置新元素:LSET list index new_element

代码语言:shell
复制
redis> LRANGE todo 0 -1
1) "buy some milk"
2) "watch tv"
3) "finish homework"
2.3.11、LINSERT:将元素插入列表

通过使用LINSERT命令,用户可以将一个新元素插入列表某个指定元素的前面或者后面:LINSERT list BEFORE|AFTER target_element new_element

代码语言:shell
复制
redis> LRANGE lst 0 -1
1) "a"
2) "b"
3) "c"
2.3.12、LTRIM:修剪列表

LTRIM命令接受一个列表和一个索引范围作为参数,并移除列表中位于给定索引范围之外的所有元素,只保留给定范围之内的元素:LTRIM list start end

代码语言:shell
复制
redis> RPUSH alphabets "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k"
(integer) 11

redis> LTRIM alphabets 0 6
OK

redis> LRANGE alphabets 0 -1
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
6) "f"
7) "g"
2.3.13、BLPOP:阻塞式左端弹出操作

BLPOP命令是带有阻塞功能的左端弹出操作,它接受任意多个列表以及一个秒级精度的超时时限作为参数:BLPOP list [list ...] timeout

代码语言:shell
复制
redis> BLPOP alphabets 5 -- 尝试弹出alphabets列表的最左端元素,最多阻塞5s
1) "alphabets" -- 被弹出元素的来源列表
2) "a" -- 被弹出元素

例子:

A、B、C这3个客户端先后执行了BLPOP lst 10命令,并且都因为lst列表为空而被阻塞,如果在这些客户端被阻塞期间,客户端D执行了RPUSH lst"hello""world""again"命令,那么服务器首先会处理客户端A的BLPOP命令,并将被弹出的"hello"元素返回给它;接着处理客户端B的BLPOP命令,并将被弹出的"world"元素返回给它;最后处理客户端C的BLPOP命令,并将被弹出的"again"元素返回给它。

阻塞效果的范围:

BLPOP命令的阻塞效果只对执行这个命令的客户端有效,其他客户端以及Redis服务器本身并不会因为这个命令而被阻塞。

2.3.14、BRPOP:阻塞式右端弹出操作

BRPOP命令是带有阻塞功能的右端弹出操作,除了弹出的方向不同之外,其他方面都和BLPOP命令一样:BRPOP list [list ...] timeout

代码语言:shell
复制
redis> BRPOP queue1 queue2 queue3 10
1) "queue2" -- 被弹出元素的来源列表
2) "bye bye" -- 被弹出元素
2.3.15、BRPOPLPUSH:阻塞式弹出并推入操作

BRPOPLPUSH命令是RPOPLPUSH命令的阻塞版本,BRPOPLPUSH命令接受一个源列表、一个目标列表以及一个秒级精度的超时时限作为参数:BRPOPLPUSH source target timeout

代码语言:shell
复制
client-1> LRANGE list3 0 -1
1) "hello"

client-1> LRANGE list4 0 -1
1) "a"
2) "b"
3) "c"

client-1> BRPOPLPUSH list3 list4 10
"hello"

client-1> LRANGE list3 0 -1
(empty list or set)

client-1> LRANGE list4 0 -1
1) "hello"
2) "a"
3) "b"
4) "c"

client-1> BRPOPLPUSH list3 list4 10
"world"
(1.42s) -- 被阻塞了1.42s
-- 由于list3为空,如果我们再次执行相同的BRPOPLPUSH命令,那么客户端client-1将被阻塞,直到我们从另一个客户端client-2向list3推入新元素为止

client-1> LRANGE list3 0 -1
(empty list or set)

client-1> LRANGE list4 0 -1
1) "world"
2) "hello"
3) "a"
4) "b"
5) "c"

2.4、集合

Redis的集合(set)键允许用户将任意多个各不相同的元素存储到集合中,这些元素既可以是文本数据,也可以是二进制数据。虽然列表键也允许我们存储多个元素,但集合与列表有以下两个明显的区别:

  • 列表可以存储重复元素,而集合只会存储非重复元素,尝试将一个已存在的元素添加到集合将被忽略。
  • 列表以有序方式存储元素,而集合则以无序方式存储元素。
2.4.1、SADD:将元素添加到集合

通过使用SADD命令,用户可以将一个或多个元素添加到集合中:SADD set element [element ...]

代码语言:shell
复制
redis> SADD databases "MongoDB" "CouchDB"
(integer) 2 -- 集合新添加了2个元素

因为集合不存储相同的元素,所以用户在使用SADD命令向集合中添加元素的时候,SADD命令会自动忽略已存在的元素,只将不存在于集合的新元素添加到集合中。

2.4.2、SREM:从集合中移除元素

通过使用SREM命令,用户可以从集合中移除一个或多个已存在的元素:SREM set element [element ...]

代码语言:shell
复制
redis> SREM databases "MS SQL" "Oracle" "CouchDB"
(integer) 3 -- 有3个元素被移除
2.4.3、SMOVE:将元素从一个集合移动到另一个集合

SMOVE命令允许用户将指定的元素从源集合移动到目标集合:SMOVE source target element

代码语言:shell
复制
redis> SMOVE databases nosql "Redis"
(integer) 1 -- 移动成功
2.4.4、SMEMBERS:获取集合包含的所有元素

通过使用SMEMBERS命令,用户可以取得集合包含的所有元素:SMEMBERS set

代码语言:shell
复制
redis> SMEMBERS fruits
1) "banana"
2) "cherry"
3) "apple"
2.4.4、SCARD:获取集合包含的元素数量

通过使用SCARD命令,用户可以获取给定集合的大小,即集合包含的元素数量:SCARD set

代码语言:shell
复制
redis> SCARD databases
(integer) 4 -- 这个集合包含4个元素
2.4.5、SISMEMBER:检查给定元素是否存在于集合

通过使用SISMEMBER命令,用户可以检查给定的元素是否存在于集合当中:SISMEMBER set element

代码语言:shell
复制
redis> SMEMBERS databases
1) "Redis"
2) "MySQL"
3) "MongoDB"
4) "PostgreSQL"

redis> SISMEMBER databases "MongoDB"
(integer) 1 -- 存在

redis> SISMEMBER databases "Oracle"
(integer) 0 -- 不存在
2.4.6、SRANDMEMBER:随机获取集合中的元素

通过使用SRANDMEMBER命令,用户可以从集合中随机地获取指定数量的元素。SRANDMEMBER命令接受一个可选的count参数,用于指定用户想要获取的元素数量,如果用户没有给定这个参数,那么SRANDMEMBER命令默认只获取一个元素:SRANDMEMBER set [count]

代码语言:shell
复制
redis> SMEMBERS databases
1) "Neo4j"
2) "Redis"
3) "PostgreSQL"
4) "CouchDB"

redis> SRANDMEMBER databases
"MySQL

redis> SMEMBERS databases -- 集合包含的元素和执行SRANDMEMBER之前完全一样
1) "Neo4j"
2) "Redis"
3) "PostgreSQL"
4) "CouchDB"

redis> SRANDMEMBER databases 2 -- 随机地返回2个不重复的元素
1) "MySQL"
2) "Oracle"

redis> SRANDMEMBER databases -3 -- 随机地返回3个可能会重复的元素
1) "Neo4j"
2) "CouchDB"
3) "MongoDB"

count参数的值既可以是正数也可以是负数。

如果count参数的值为正数,那么SRANDMEMBER命令将返回count个不重复的元素。

如果count参数的值为负数,那么SRANDMEMBER命令将随机返回abs(count)个元素(abs(count)也即是count的绝对值),并且在这些元素当中允许出现重复的元素。

2.4.7、SPOP:随机地从集合中移除指定数量的元素

通过使用SPOP命令,用户可以从集合中随机地移除指定数量的元素。SPOP命令接受一个可选的count参数,用于指定需要被移除的元素数量。如果用户没有给定这个参数,那么SPOP命令默认只移除一个元素:SPOP key [count]

代码语言:shell
复制
redis> SPOP databases -- 随机地移除1个元素
"CouchDB" -- 被移除的是"CouchDB"元素

redis> SPOP databases 3 -- 随机地移除3个元素
1) "Neo4j" -- 被移除的元素是"Neo4j"、"PostgreSQL"和"MySQL"
2) "PostgreSQL"
3) "MySQL"
  • SPOP命令和SRANDMEMBER命令的主要区别在于,SPOP命令会移除被随机选中的元素,而SRANDMEMBER命令则不会移除被随机选中的元素。
  • SPOP命令与SRANDMEMBER命令的另一个不同点在于,SPOP命令只接受正数count值,而SRANDMEMBER命令则随机返回abs(count)个元素。
2.4.8、SINTER、SINTERSTORE:对集合执行交集计算

SINTER命令可以计算出用户给定的所有集合的交集,然后返回这个交集包含的所有元素:SINTER set [set ...]

SINTERSTORE命令,可以把给定集合的交集计算结果存储到指定的键里面:SINTERSTORE destination_key set [set ...]

代码语言:shell
复制
redis> SMEMBERS s1
1) "a"
2) "b"
3) "c"
4) "d"

redis> SMEMBERS s2
1) "c"
2) "d"
3) "e"
4) "f"

redis> SINTER s1 s2
1) "c"
2) "d"

redis> SINTERSTORE s1-inter-s2 s1 s2
(integer) 2 -- 交集包含两个元素

redis> SMEMBERS s1-inter-s2
1) "c"
2) "d"
2.4.9、SUNION、SUNIONSTORE:对集合执行并集计算

SUNION命令可以计算出用户给定的所有集合的并集,然后返回这个并集包含的所有元素:SUNION set [set ...]

SUNIONSTORE命令,可以把给定集合的并集计算结果存储到指定的键中,并在键已经存在的情况下自动覆盖已有的键:SUNIONSTORE destination_key set [set ...]

代码语言:shell
复制
redis> SMEMBERS s1
1) "a"
2) "b"
3) "c"
4) "d"

redis> SMEMBERS s2
1) "c"
2) "d"
3) "e"
4) "f"

redis> SUNION s1 s2
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
6) "f"

redis> SUNIONSTORE s1-union-s2 s1 s2
(integer) 6 -- 并集共包含6个元素

redis> SMEMBERS s1-union-s2
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
6) "f"
2.4.10、SDIFF、SDIFFSTORE:对集合执行差集计算

SDIFF命令可以计算出给定集合之间的差集,并返回差集包含的所有元素:SDIFF set [set ...]

SDIFFSTORE命令,可以把给定集合之间的差集计算结果存储到指定的键中,并在键已经存在的情况下自动覆盖已有的键:SDIFFSTORE destination_key set [set ...]

代码语言:shell
复制
redis> SMEMBERS s1
1) "a"
2) "b"
3) "c"
4) "d"

redis> SMEMBERS s2
1) "c"
2) "d"
3) "e"
4) "f"

redis> SMEMBERS s3
1) "b"
2) "f"
3) "g"

redis> SDIFF s1 s2 s3
1) "a"

redis> SDIFFSTORE diff-result s1 s2 s3
(integer) 1 -- 计算出的差集只包含一个元素

redis> SMEMBERS diff-result
1) "a"
  • 因为集合计算需要使用大量的计算资源,所以我们应该尽量存储并重用集合计算的结果,在有需要的情况下,还可以把集合计算放到从服务器中进行。

2.5、有序集合

Redis的有序集合(sorted set)同时具有“有序”和“集合”两种性质,这种数据结构中的每个元素都由一个成员和一个与成员相关联的分值组成,其中成员以字符串方式存储,而分值则以64位双精度浮点数格式存储。

2.5.1、ZADD:添加或更新成员

通过使用ZADD命令,用户可以向有序集合添加一个或多个新成员:ZADD sorted_set score member [score member ...]

代码语言:shell
复制
redis> ZADD salary 3500 "peter" 4000 "jack" 2000 "tom" 5500 "mary"
(integer) 4 -- 这个命令向有序集合新添加了4个成员,既更新又添加

redis> ZADD salary XX 4500 "jack" 3800 "bob"
(integer) 0 -- 只更新已有成员分值

redis> ZADD salary NX 1800 "jack" 3800 "bob"
(integer) 1 -- 只添加新成员

redis> ZADD salary CH 3500 "peter" 4000 "bob" 9000 "david"
(integer) 2 -- 既更新又添加
  • 带有XX选项的ZADD命令只会对有序集合已有的成员进行更新,而不会向有序集合添加任何新成员。
  • 带有NX选项的ZADD命令只会向有序集合添加新成员,而不会对已有的成员进行任何更新。
  • 在默认情况下,可以向有序集合添加新成员,也可以对有序集合中已存在成员的分值进行更新。
  • 通过给定CH选项,让ZADD命令返回被修改(changed)成员的数量作为返回值。
2.5.2、ZREM:移除指定的成员

通过使用ZREM命令,用户可以从有序集合中移除指定的一个或多个成员以及与这些成员相关联的分值:ZREM sorted_set member [member ...]

代码语言:shell
复制
redis> ZREM salary "peter"
(integer) 1 -- 移除了一个成员
2.5.3、ZSCORE:获取成员的分值

通过使用ZSCORE命令,用户可以获取与给定成员相关联的分值:ZSCORE sorted_set member

代码语言:shell
复制
redis> ZSCORE salary "peter"
"3500"
2.5.4、ZINCRBY:对成员的分值执行自增或自减操作

通过使用ZINCRBY命令,用户可以对有序集合中指定成员的分值执行自增操作,为其加上指定的增量:ZINCRBY sorted_set increment member

代码语言:shell
复制
redis> ZINCRBY salary 1000 "tom" -- 将成员"tom"的分值加上1000
"3000" -- 成员"tom"现在的分值为3000
  • 可以将一个负数增量传递给ZINCRBY命令,从而达到对分值执行自减操作的目的。
2.5.5、ZCARD:获取有序集合的大小

通过执行ZCARD命令可以取得有序集合的基数,即有序集合包含的成员数量:ZCARD sorted_set

代码语言:shell
复制
redis> ZCARD salary
(integer) 4 -- 这个有序集合包含4个成员
2.5.6、ZRANK、ZREVRANK:获取成员在有序集合中的排名

通过ZRANK命令和ZREVRANK命令,用户可以取得给定成员在有序集合中的排名:ZRANK sorted_set memberZREVRANK sorted_set member

代码语言:shell
复制
redis> ZRANK salary "tom"
(integer) 3

redis> ZREVRANK salary "peter"
(integer) 4
2.5.7、ZRANGE、ZREVRANGE:获取指定索引范围内的成员

通过ZRANGE命令和ZREVRANGE命令,用户可以以升序排列或者降序排列方式,从有序集合中获取指定索引范围内的成员:ZRANGE sorted_set start endZREVRANGE sorted_set start end

代码语言:shell
复制
redis> ZRANGE salary 0 -1 -- 以升序排列方式获取所有成员
1) "peter"
2) "bob"
3) "jack"
4) "tom"
5) "mary"

redis> ZREVRANGE salary 0 -1 -- 以降序排列方式获取所有成员
1) "mary"
2) "tom"
3) "jack"
4) "bob"
5) "peter"

redis> ZRANGE salary 0 3 WITHSCORES -- 获取指定索引范围内的成员以及与这些成员相关联的分值
1) "peter"
2) "3500" -- 成员"peter"的分值
3) "bob"
4) "3800" -- 成员"bob"的分值
5) "jack"
6) "4500" -- 成员"jack"的分值
7) "tom"
8) "5000" -- 成员"tom"的分值
2.5.8、ZRANGEBYSCORE、ZREVRANGEBYSCORE:获取指定分值范围内的成员

通过使用ZRANGEBYSCORE命令或者ZREVRANGEBYSCORE命令,用户可以以升序排列或者降序排列的方式获取有序集合中分值介于指定范围内的成员:ZRANGEBYSCORE sorted_set min max,ZREVRANGEBYSCORE sorted_set max min

代码语言:shell
复制
redis> ZRANGEBYSCORE salary 3800 5000 -- 闭区间
1) "bob"
2) "jack"
3) "tom"

redis> ZREVRANGEBYSCORE salary 5000 3000 -- 闭区间
1) "tom"
2) "jack"
3) "bob"
4) "peter"

redis> ZRANGEBYSCORE salary 3800 5000 WITHSCORES -- 通过在执行时给定可选的WITHSCORES选项来同时获取成员及其分值
1) "bob"
2) "3800" -- 成员"bob"的分值
3) "jack"
4) "4500" -- 成员"jack"的分值
5) "tom"
6) "5000" -- 成员"tom"的分值

redis> ZREVRANGEBYSCORE salary 5000 3000 WITHSCORES -- 通过在执行时给定可选的WITHSCORES选项来同时获取成员及其分值
1) "tom"
2) "5000" -- 成员"tom"的分值
3) "jack"
4) "4500" -- 成员"jack"的分值
5) "bob"
6) "3800" -- 成员"bob"的分值

redis> ZRANGEBYSCORE salary 3000 5000 LIMIT 1 2 -- 取出排序集合里面的第二和第三个成员
1) "bob"
2) "jack"

redis> ZRANGEBYSCORE salary (3500 (5000 WITHSCORES -- 开区间
1) "bob"
2) "3800"
3) "jack"
4) "4500"

redis> ZRANGEBYSCORE salary -inf (5000 WITHSCORES -- 无穷小到5000
1) "peter"
2) "3500"
3) "bob"
4) "3800"
5) "jack"
6) "4500"

redis> ZRANGEBYSCORE salary (4000 +inf WITHSCORES -- 4000到无穷大
1) "jack"
2) "4500"
3) "tom"
4) "5000"
5) "mary"
6) "5500"
2.5.9、ZCOUNT:统计指定分值范围内的成员数量

通过使用COUNT命令,用户可以统计出有序集合中分值介于指定范围之内的成员数量:ZCOUNT sorted_set min max

代码语言:shell
复制
redis> ZCOUNT salary 3000 5000 -- 也可以使用开区间或-inf或+inf
(integer) 4 -- 有序集合里面有4个成员的分值介于3000~5000之间
2.5.10、ZREMRANGEBYRANK:移除指定排名范围内的成员

ZREMRANGEBYRANK命令可以从升序排列的有序集合中移除位于指定排名范围内的成员,然后返回被移除成员的数量:ZREMRANGEBYRANK sorted_set start end

代码语言:shell
复制
redis> ZREMRANGEBYRANK salary 0 3
(integer) 4 -- 这个命令移除了4个成员
2.5.11、ZREMRANGEBYSCORE:移除指定分值范围内的成员

ZREMRANGEBYSCORE命令可以从有序集合中移除位于指定分值范围内的成员,并在移除操作执行完毕返回被移除成员的数量:ZREMRANGEBYSCORE sorted_set min max

代码语言:shell
复制
redis> ZREMRANGEBYSCORE salary 3000 4000
(integer) 2 -- 有2个成员被移除了
2.5.12、ZUNIONSTORE、ZINTERSTORE:有序集合的并集运算和交集运算

与集合一样,Redis也为有序集合提供了相应的并集运算命令ZUNIONSTORE和交集运算命令ZINTERSTORE,这两个命令的基本格式如下:ZUNIONSTORE destination numbers sorted_set [sorted_set ...],ZINTERSTORE destination numbers sorted_set [sorted_set ...]

代码语言:shell
复制
redis> ZUNIONSTORE union-result-1 2 sorted_set1 sorted_set2
(integer) 5 -- 这个并集包含了5个成员,也可以把集合当做参数传入,集合为参数时看作所有成员的分值都为1的有序集合

redis> ZINTERSTORE inter-result-1 2 sorted_set1 sorted_set2
(integer) 1 -- 这个交集只包含了一个成员

redis> ZINTERSTORE agg-sum 3 ss1 ss2 ss3 AGGREGATE SUM
(integer) 1
redis> ZRANGE agg-sum 0 -1 WITHSCORES
1) "a"
2) "8"  -- 三个集合交集成员为a,然后求和

redis> ZINTERSTORE agg-min 3 ss1 ss2 ss3 AGGREGATE MIN
(integer) 1
redis> ZRANGE agg-min 0 -1 WITHSCORES
1) "a"
2) "1" -- 选出交集成员中最小分值

redis> ZINTERSTORE agg-max 3 ss1 ss2 ss3 AGGREGATE MAX
(integer) 1
redis> ZRANGE agg-max 0 -1 WITHSCORES
1) "a"
2) "5" -- 选出交集成员中最大分值

并集也可以使用sum、min、max

设置权重

在默认情况下,ZUNIONSTORE和ZINTERSTORE将直接使用给定有序集合的成员分值去计算结果有序集合的成员分值,但是在有需要的情况下,用户也可以通过可选的WEIGHTS参数为各个给定有序集合的成员分值设置权重:

ZUNIONSTORE destination numbers sorted_set [sorted_set ...] [WEIGHTS weight [weight ...]]

ZINTERSTORE destination numbers sorted_set [sorted_set ...] [WEIGHTS weight [weight ...]]

在使用WEIGHTS选项时,用户需要为每个给定的有序集合分别设置一个权重,命令会将这个权重与成员的分值相乘,得出成员的新分值,然后执行聚合计算;与此相反,如果用户在使用WEIGHTS选项时,不想改变某个给定有序集合的分值,那么只需要将那个有序集合的权重设置为1即可。

代码语言:shell
复制
redis> zadd ss1 2 "a"
(integer) 1

redis> zadd ss2 3 "b"
(integer) 1

redis> zadd ss3 4 "c"
(integer) 1

redis> ZUNIONSTORE weighted-result 3 ss1 ss2 ss3 weights 3 5 1
(integer) 3

redis> ZRANGE weighted-result 0 -1 withscores  -- 集合成员乘以对应的集合权重,然后几个集合中相同成员的分值相加,ss1中的成员都乘以3,ss2中都乘以5
1) "c" 
2) "4"
3) "a"
4) "6"
5) "b"
6) "15"
2.5.13、ZRANGEBYLEX、ZREVRANGEBYLEX:返回指定字典序范围内的成员

对于拥有不同分值的有序集合成员来说,成员的大小将由分值决定,至于分值相同的成员,它们的大小则由该成员在字典序中的大小决定。ZRANGEBYLEX sorted_set min max,ZREVRANGEBYLEX sorted_set max min

命令的min参数和max参数用于指定用户想要获取的字典序范围,它们的值可以是以下4种值之一:

  • 带有[符号的值表示在结果中包含与给定值具有同等字典序大小的成员。
  • 带有(符号的值表示在结果中不包含与给定值具有同等字典序大小的成员。
  • 加号+表示无穷大。
  • 减号-表示无穷小。
代码语言:shell
复制
redis> ZRANGEBYLEX words - +
1) "address"
2) "after"
3) "apple"
4) "bamboo"

redis> ZREVRANGEBYLEX words (c [a
1) "book"
2) "bear"
3) "banana"
4) "bamboo"
5) "apple"
6) "after"
7) "address"

redis> ZRANGEBYLEX words [b + LIMIT 0 1
1) "bamboo"

ZRANGEBYLEX sorted_set min max [LIMIT offset count],ZREVRANGEBYLEX sorted_set max min [LIMIT offset count] 限制命令返回的成员数量

2.5.14、ZLEXCOUNT:统计位于字典序指定范围内的成员数量

对于按照字典序排列的有序集合,用户可以使用ZLEXCOUNT命令统计有序集合中位于字典序指定范围内的成员数量:ZLEXCOUNT sorted_set min max

代码语言:shell
复制
redis> ZLEXCOUNT words [a (b
(integer) 3 -- 这个有序集合中有3个以字母a开头的成
2.5.15、ZREMRANGEBYLEX:移除位于字典序指定范围内的成员

对于按照字典序排列的有序集合,用户可以使用ZREMRANGEBYLEX命令去移除有序集合中位于字典序指定范围内的成员:ZREMRANGEBYLEX sorted_set min max

代码语言:shell
复制
redis> ZREMRANGEBYLEX words [b (c
(integer) 4 -- 有4个成员被移除了
2.5.16、ZPOPMAX、ZPOPMIN:弹出分值最高和最低的成员

ZPOPMAXZPOPMIN是Redis 5.0版本新添加的两个命令,分别用于移除并返回有序集合中分值最大和最小的N个元素:ZPOPMAX sorted_set [count]ZPOPMIN sorted_set [count]

代码语言:shell
复制
redis> ZPOPMAX salary
1) "mary" -- 被移除元素的成员
2) "5500" -- 被移除元素的分值

redis> ZPOPMIN salary
1) "peter"
2) "3500"
2.5.17、BZPOPMAX、BZPOPMIN:阻塞式最大/最小元素弹出操作

BZPOPMAX命令和BZPOPMIN命令分别是ZPOPMAX命令以及ZPOPMIN命令的阻塞版本,这两个阻塞命令都接受任意多个有序集合和一个秒级精度的超时时限作为参数:BZPOPMAX sorted_set [sorted_set ...] timeoutBZPOPMIN sorted_set [sorted_set ...] timeout

代码语言:shell
复制
redis> ZRANGE ss1 0 -1 WITHSCORES
(empty list or set)

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

redis> ZRANGE ss3 0 -1 WITHSCORES
1) "c"
2) "3"

redis> BZPOPMAX ss1 ss2 ss3 10 -- 跳过空集ss1
1) "ss2" -- 被弹出元素所在的有序集合
2) "b" -- 被弹出元素的成员
3) "2" -- 被弹出元素的分值

redis> BZPOPMAX ss1 ss2 ss3 10 -- 再次执行
1) "ss2"
2) "a"
3) "1"

redis> BZPOPMAX ss1 ss2 ss3 10 -- 再次执行
1) "ss3" -- 跳过空集ss1,ss2
2) "c"
3) "3"

2.6、HyperLogLog

HyperLogLog是一个专门为了计算集合的基数而创建的概率算法,对于一个给定的集合,HyperLogLog可以计算出这个集合的近似基数:近似基数并非集合的实际基数,它可能会比实际的基数小一点或者大一点,但是估算基数和实际基数之间的误差会处于一个合理的范围之内,因此那些不需要知道实际基数或者因为条件限制而无法计算出实际基数的程序就可以把这个近似基数当作集合的基数来使用。

HyperLogLog的优点在于它计算近似基数所需的内存并不会因为集合的大小而改变,无论集合包含的元素有多少个,HyperLogLog进行计算所需的内存总是固定的,并且是非常少的。具体到实现上,Redis的每个HyperLogLog只需要使用12KB内存空间,就可以对接近:2 64 个元素进行计数,而算法的标准误差仅为0.81%,因此它计算出的近似基数是相当可信的。

2.6.1、PFADD:对集合元素进行计数

用户可以通过执行PFADD命令,使用HyperLogLog对给定的一个或多个集合元素进行计数:PFADD hyperloglog element [element ...]

代码语言:shell
复制
redis> PFADD alphabets "a" "b" "c"
(integer) 1

redis> PFADD alphabets "a" -- 已经计过数的元素
(integer) 0
2.6.2、PFCOUNT:返回集合的近似基数

在使用PFADD命令对元素进行计数之后,用户可以通过执行PFCOUNT命令来获取HyperLogLog为集合计算出的近似基数:PFCOUNT hyperloglog [hyperloglog ...]

代码语言:shell
复制
redis> PFCOUNT alphabets
(integer) 3
返回并集的近似基数
代码语言:shell
复制
redis> PFADD alphabets1 "a" "b" "c"
(integer) 1

redis> PFADD alphabets2 "c" "d" "e"
(integer) 1

redis> PFCOUNT alphabets1 alphabets2
(integer) 5
2.6.3、PFMERGE:计算多个HyperLogLog的并集

PFMERGE命令可以对多个给定的HyperLogLog执行并集计算,然后把计算得出的并集HyperLogLog保存到指定的键中:PFMERGE destination hyperloglog [hyperloglog ...]

代码语言:shell
复制
redis> PFADD numbers1 128 256 512
(integer) 1

redis> PFADD numbers2 128 256 512
(integer) 1

redis> PFADD numbers3 128 512 1024
(integer) 1

redis> PFMERGE union-numbers numbers1 numbers2 numbers3
OK

redis> PFCOUNT union-numbers
(integer) 4
PFCOUNT与PFMERGE

PFCOUNT命令在计算多个HyperLogLog的近似基数时会执行以下操作:

1) 在内部调用PFMERGE命令,计算所有给定HyperLogLog的并集,并将这个并集存储到一个临时的HyperLogLog中。

2) 对临时HyperLogLog执行PFCOUNT命令,得到它的近似基数(因为这是针对单个HyperLogLog的PFCOUNT,所以这个操作不会引起循环调用)。

3) 删除临时HyperLogLog。

4) 向用户返回之前得到的近似基数。

PFCOUNT将执行以下以下操作:

1) 执行PFMERGE<temp-hyperloglog>numbers1numbers2numbers3,把3个给定HyperLogLog的并集结果存储到临时HyperLogLog中。

2) 执行PFCOUNT<temp-hyperloglog>,取得并集HyperLogLog的近似基数4。

3) 执行DEL<temp-hyperloglog>,删除临时HyperLogLog。

4) 向用户返回之前得到的近似基数4。

2.7、位图

Redis的位图(bitmap)是由多个二进制位组成的数组,数组中的每个二进制位都有与之对应的偏移量(也称索引),用户通过这些偏移量可以对位图中指定的一个或多个二进制位进行操作。

2.7.1、SETBIT:设置二进制位的值

通过使用SETBIT命令,用户可以为位图指定偏移量上的二进制位设置值:SETBIT bitmap offset value

代码语言:shell
复制
redis> SETBIT bitmap001 0 1
(integer) 0 -- 二进制位原来的值为0

redis> SETBIT bitmap001 3 1
(integer) 0

redis> SETBIT bitmap001 -1 1  -- SETBIT命令只能使用正数偏移量
(error) ERR bit offset is not an integer or out of range
2.7.2、GETBIT:获取二进制位的值

使用GETBIT命令,用户可以获取位图指定偏移量上的二进制位的值:GETBIT bitmap offset

代码语言:shell
复制
redis> GETBIT bitmap001 0  -- GETBIT命令只能使用正数偏移量
(integer) 1
2.7.3、BITCOUNT:统计被设置的二进制位数量

用户可以通过执行BITCOUNT命令统计位图中值为1的二进制位数量:BITCOUNT key

代码语言:shell
复制
redis> BITCOUNT bitmap001
(integer) 3 -- 这个位图有3个二进制位被设置成了1

redis> BITCOUNT bitmap003 0 1  -- 统计第一个字节和第二个字节中有多少个二进制位被设置成了1
(integer) 9
  • BITCOUNT命令的start参数和end参数定义的是字节偏移量范围,而不是二进制位偏移量范围。
redis-bitcount.png
redis-bitcount.png
2.7.4、BITPOS:查找第一个指定的二进制位值

用户可以通过执行BITPOS命令,在位图中查找第一个被设置为指定值的二进制位,并返回这个二进制位的偏移量:BITPOS bitmap value

代码语言:shell
复制
redis> BITPOS bitmap003 1
(integer) 0 -- 位图第一个被设置为1的二进制位的偏移量为0

redis> BITPOS bitmap003 1 1 1  -- 在第二个字节中找到第一个值为1的二进制位所处的偏移量,BITPOS bitmap value [start end],索引可以使用负数
(integer) 12
2.7.5、BITOP:执行二进制位运算

用户可以通过BITOP命令,对一个或多个位图执行指定的二进制位运算,并将运算结果存储到指定的键中:BITOP operation result_key bitmap [bitmap ...]

代码语言:shell
复制
redis> BITOP AND and_result bitmap001 bitmap002 bitmap003
(integer) 3 -- 运算结果and_result位图的长度为3字节
2.7.6、BITFIELD:在位图中存储整数值

BITFIELD命令允许用户在位图中的任意区域(field)存储指定长度的整数值,并对这些整数值执行加法或减法操作。BITFIELD命令支持SETGETINCRBYOVERFLOW这4个子命令

通过使用BITFIELD命令的SET子命令,用户可以在位图的指定偏移量offset上设置一个type类型的整数值value:BITFIELD bitmap SET type offset value

  • offset参数用于指定设置的起始偏移量。这个偏移量从0开始计算,偏移量为0表示设置从位图的第1个二进制位开始,偏移量为1则表示设置从位图的第2个二进制位开始,以此类推。如果被设置的值长度不止一位,那么设置将自动延伸至之后的二进制位。
  • type参数用于指定被设置值的类型,这个参数的值需要以i或者u为前缀,后跟被设置值的位长度,其中i表示被设置的值为有符号整数,而u则表示被设置的值为无符号整数。比如i8表示被设置的值为有符号8位整数,而u16则表示被设置的值为无符号16位整数,诸如此类。BITFIELD的各个子命令目前最大能够对64位长的有符号整数(i64)和63位长的无符号整数(u63)进行操作。
  • value参数用于指定被设置的整数值,这个值的类型应该和type参数指定的类型一致。如果给定值的长度超过了type参数指定的类型,那么SET命令将根据type参数指定的类型截断给定值。比如,如果用户尝试将整数123(二进制表示为01111011)存储到一个u4类型的区域中,那么命令会先将该值截断为4位长的二进制数字1011(即十进制数字11),然后再进行设置。
代码语言:shell
复制
redis> BITFIELD bitmap SET u8 0 198
1) (integer) 0  -- 该区域被设置之前存储的整数值为0

redis> BITFIELD bitmap SET u8 0 123 SET i32 20 10086 SET i64 188 123456789  -- 允许在一次调用中执行多个子命令
1) (integer) 198
2) (integer) 0
3) (integer) 0

假设现在有一个位图,它存储着多个8位长的无符号整数,而我们想要把它的第133个8位无符号整数的值设置为22。如果使用SET子命令的偏移量设置格式,就需要先使用算式(133-1)*8计算出第133个8位无符号整数在位图中的起始偏移量1056,然后再执行以下命令:

代码语言:shell
复制
BITFIELD bitmap SET u8 1056 22

很明显,这种手动计算偏移量然后进行设置的做法非常麻烦也很容易出错。与此相反,如果我们使用的是SET子命令的索引设置格式,那么只需要执行以下命令就可以对位图的第133个8位无符号整数进行设置了:

代码语言:shell
复制
BITFIELD bitmap SET u8 #132 22  -- 根据索引对区域进行设置,BITFIELD bitmap SET type #index value

通过使用BITFIELD命令的GET子命令,用户可以从给定的偏移量或者索引中取出指定类型的整数值:BITFIELD bitmap GET type offset

代码语言:shell
复制
redis> BITFIELD bitmap SET u8 0 123 SET i32 20 10086 SET i64 188 123456789
1) (integer) 0
2) (integer) 0
3) (integer) 0

redis> BITFIELD bitmap GET u8 0 GET i32 20 GET i64 188
1) (integer) 123
2) (integer) 10086
3) (integer) 123456789

redis> BITFIELD unsigned-8bits SET u8 #0 13 SET u8 #1 100 SET u8 #7 73
1) (integer) 0
2) (integer) 0
3) (integer) 0

redis> BITFIELD unsigned-8bits GET u8 #0 GET u8 #1 GET u8 #7  -- 根据索引去除指定类型的整数值,BITFIELD bitmap GET type #index
1) (integer) 13
2) (integer) 100
3) (integer) 73
执行加法操作或减法操作

除了设置和获取整数值之外,BITFIELD命令还可以对位图存储的整数值执行加法操作或者减法操作,这两个操作都可以通过INCRBY子命令实现:

BITFIELD bitmap INCRBY type offset increment

BITFIELD bitmap INCRBY type #index increment

代码语言:shell
复制
redis> BITFIELD numbers SET u8 #0 10 -- 将区域的值设置为整数10
1) (integer) 0

redis> BITFIELD numbers GET u8 #0
1) (integer) 10

redis> BITFIELD numbers INCRBY u8 #0 15 -- 将整数的值加上15
1) (integer) 25

redis> BITFIELD numbers INCRBY u8 #0 -20 -- 将整数的值减去20
1) (integer) 5
处理溢出

OVERFLOW子命令的参数可以是WRAP、SAT或者FAIL中的一个:

  • WRAP表示使用回绕(wrap around)方式处理溢出,这也是C语言默认的溢出处理方式。在这一模式下,向上溢出的整数值将从类型的最小值开始重新计算,而向下溢出的整数值则会从类型的最大值开始重新计算。
  • SAT表示使用饱和运算(saturation arithmetic)方式处理溢出,在这一模式下,向上溢出的整数值将被设置为类型的最大值,而向下溢出的整数值则会被设置为类型的最小值。
  • FAIL表示让INCRBY子命令在检测到计算会引发溢出时拒绝执行计算,并返回空值表示计算失败。
代码语言:shell
复制
redis> BITFIELD unsigned-4bits GET u4 #0 GET u4 #1 GET u4 #2
1) (integer) 15
2) (integer) 15
3) (integer) 15

redis> BITFIELD unsigned-4bits OVERFLOW WRAP INCRBY u4 #0 1 OVERFLOW SAT INCRBY u4 #1 1 OVERFLOW FAIL INCRBY u4 #2 1
1) (integer) 0
2) (integer) 15
3) (nil)
使用位图存储整数的原因

在一般情况下,当用户使用字符串或者散列去存储整数的时候,Redis都会为被存储的整数分配一个long类型的值(通常为32位长或者64位长),并使用对象去包裹这个值,然后再把对象关联到数据库或者散列中。

与此相反,BITFIELD命令允许用户自行指定被存储整数的类型,并且不会使用对象去包裹这些整数,因此当我们想要存储长度比long类型短的整数,并且希望尽可能地减少对象包裹带来的内存消耗时,就可以考虑使用位图来存储整数。

2.7.7、使用字符串命令对位图进行操作
代码语言:shell
复制
redis> GET 8bit-int
"\x04"

redis> GET 32bit-ints
"\x00\x00\x00{\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00'f"

redis> STRLEN 8bit-int -- 这个位图长1字节
(integer) 1

redis> STRLEN 32bit-ints -- 这个位图长16字节
(integer) 16

redis> GETRANGE 32bit-ints 0 3 -- 获取位图的前4个字节
"\x00\x00\x00{"

2.8、地理坐标

2.8.1、GEOADD:存储坐标

通过使用GEOADD命令,用户可以将给定的一个或多个经纬度坐标存储到位置集合中,并为这些坐标设置相应的名字:GEOADD location_set longitude latitude name [longitude latitude name ...]

代码语言:shell
复制
redis> GEOADD Guangdong-cities 113.2278442 23.1255978 Guangzhou 113.106308 23.0088312 Foshan 113.7943267 22.9761989 Dongguan 114.0538788 22.5551603 Shenzhen
4 -- 添加广州、佛山、东莞和深圳市的坐标
2.8.2、GEOPOS:获取指定位置的坐标

在使用GEOADD命令将位置及其坐标存储到位置集合之后,用户可以使用GEOPOS命令去获取给定位置的坐标:GEOPOS location_set name [name ...]

代码语言:shell
复制
redis> GEOPOS Guangdong-cities Qingyuan Guangzhou Zhongshan
1) 1) "113.20996731519699" -- 清远市的经度
 2) "23.593675019671288" -- 清远市的纬度
2) 1) "113.22784155607224" -- 广州市的经度
 2) "23.125598202060807" -- 广州市的纬度
3) 1) "113.40603142976761" -- 中山市的经度
 2) "22.511156445825442" -- 中山市的纬度
2.8.3、GEODIST:计算两个位置之间的直线距离

在使用GEOADD命令将位置及其坐标存储到位置集合中之后,可以使用GEODIST命令计算两个给定位置之间的直线距离:GEODIST location_set name1 name2

代码语言:shell
复制
redis> GEODIST Guangdong-cities Qingyuan Guangzhou
"52094.433840356309" -- 清远和广州之间的直线距离约为52094m

redis> GEODIST Guangdong-cities Qingyuan Guangzhou km -- 指定距离的单位
"52.094433840356309" -- 清远和广州之间直线距离约为52km
  • m——以米为单位,为默认单位。
  • km——以千米为单位。
  • mi——以英里 1 为单位。
  • ft——以英尺 2 为单位。
2.8.4、GEORADIUS:查找指定坐标半径范围内的其他位置

通过使用GEORADIUS命令,用户可以指定一个经纬度作为中心点,并从位置集合中找出位于中心点指定半径范围内的其他位置:GEORADIUS location_set longitude latitude radius unit

  • location_set参数用于指定执行查找操作的位置集合。
  • longitude参数和latitude参数分别用于指定中心点的经度和纬度。
  • radius参数用于指定查找半径。
  • unit参数用于指定查找半径的单位,与GEODIST命令中的unit参数一样,这个参数的值可以是m(米)、km(千米)、mi(英里)或者ft(英尺)中的任意一个。
代码语言:shell
复制
-- 向位置集合中添加清远、广州、佛山、东莞、深圳、中山这6座城市的坐标
redis> GEOADD Guangdong-cities 113.2099647 23.593675 Qingyuan 113.2278442 23.1255978 
Guangzhou 113.106308 23.0088312 Foshan 113.7943267 22.9761989 Dongguan 114.0538788 22.5551603 
Shenzhen 113.4060288 22.5111574 Zhongshan

redis> GEORADIUS Guangdong-cities 112.3351942 23.0586893 50 km 
(empty list or set) -- 距离肇庆市50km范围内,没有其他城市

redis> GEORADIUS Guangdong-cities 112.3351942 23.0586893 100 km
1) "Foshan" -- 佛山和广州都位于距肇庆100km范围之内
2) "Guangzhou"

redis> GEORADIUS Guangdong-cities 112.3351942 23.0586893 200 km WITHDIST  -- GEORADIUS命令具有可选的WITHDIST选项,会返回这些位置与中心点之间的距离
1) 1) "Zhongshan" -- 被匹配的位置
 2) "125.5669" -- 位置与中心点之间的距离
2) 1) "Shenzhen"
 2) "184.9015"
3) 1) "Foshan"
 2) "79.1250"
4) 1) "Guangzhou"
 2) "91.6332"
5) 1) "Dongguan"
 2) "149.6536"
6) 1) "Qingyuan"
 2) "107.3463"

 redis> GEORADIUS Guangdong-cities 112.3351942 23.0586893 150 km ASC  -- GEORADIUS location_set longitude latitude radius unit [ASC|DESC],排序查找结果,默认无序
1) "Foshan"
2) "Guangzhou"
3) "Qingyuan"
4) "Zhongshan"
5) "Dongguan"

redis> GEORADIUS Guangdong-cities 112.3351942 23.0586893 150 km ASC WITHDIST
1) 1) "Foshan"
 2) "79.1250"
2) 1) "Guangzhou"
 2) "91.6332"
3) 1) "Qingyuan"
 2) "107.3463"
4) 1) "Zhongshan"
 2) "125.5669"
5) 1) "Dongguan"
 2) "149.6536"

redis> GEORADIUS Guangdong-cities 112.3351942 23.0586893 200 km COUNT 3  -- GEORADIUS location_set longitude latitude radius unit [COUNT n],限制命令获取的位置数量
1) "Foshan"
2) "Guangzhou"
3) "Qingyuan"

可同时使用多个选项

2.8.5、GEORADIUSBYMEMBER:查找指定位置半径范围内的其他位置

GEORADIUSBYMEMBER命令的作用和GEORADIUS命令的作用一样,都是找出中心点指定半径范围内的其他位置,这两个命令的主要区别在于GEORADIUS命令通过给定经纬度来指定中心点,而GEORADIUSBYMEMBER命令则通过选择位置集合中的一个位置作为中心点:GEORADIUSBYMEMBER location_set name radius unit [WITHDIST] [WITHCOORD] [ASC|DESC] [COUNT n]

代码语言:shell
复制
redis> GEORADIUSBYMEMBER Guangdong-cities Guangzhou 150 km WITHDIST ASC
1) 1) "Guangzhou"
 2) "0.0000"
2) 1) "Foshan"
 2) "17.9819"
3) 1) "Qingyuan"
 2) "52.0944"
4) 1) "Dongguan"
 2) "60.3114"
5) 1) "Zhongshan"
 2) "70.7415"
6) 1) "Shenzhen"
 2) "105.8068" 
2.8.6、GEOHASH:获取指定位置的Geohash值

用户可以通过向GEOHASH命令传入一个或多个位置来获得这些位置对应的经纬度坐标的Geohash表示:

代码语言:shell
复制
redis> GEOHASH Guangdong-cities Qingyuan Guangzhou Shenzhen
1) "ws0w0phgp70" -- 清远市经纬度坐标的Geohash值
2) "ws0e89curg0" -- 广州市经纬度坐标的Geohash值
3) "ws107659240" -- 深圳市经纬度坐标的Geohash值

redis> GEORADIUSBYMEMBER Guangdong-cities "Guangzhou" 200 km WITHHASH  -- 在进行范围查找时获取Geohash值
1) 1) "Zhongshan" -- 被匹配的位置
 2) (integer) 4046330600091985 -- 该位置经纬度坐标的Geohash值
2) 1) "Shenzhen"
 2) (integer) 4046432447218769
3) 1) "Foshan"
 2) (integer) 4046506835759376
4) 1) "Guangzhou"
 2) (integer) 4046533621643967
5) 1) "Dongguan"
 2) (integer) 4046540375616238
6) 1) "Qingyuan"
 2) (integer) 4046597933543051
2.8.7、使用有序集合命令操作GEO数据

Redis使用有序集合存储GEO数据,一个位置集合实际上就是一个有序集合:当用户调用GEO命令对位置集合进行操作时,这些命令实际上是在操作一个有序集合。

代码语言:shell
复制
redis> ZRANGE Guangdong-cities 0 -1 WITHSCORES
1) "Zhongshan" -- 位置
2) "4046330600091985" -- Geohash值
3) "Shenzhen"
4) "4046432447218769"
5) "Foshan"
6) "4046506835759376"

2.9 流

流(stream)是Redis 5.0版本中新增加的数据结构,也是该版本最重要的更新。在以往的版本中,为了实现消息队列这一常见应用,用户往往会使用列表、有序集合和发布与订阅这3种功能,但这些不同的实现都有各自的缺陷:

  • 列表实现的消息队列虽然可以快速地将新消息追加到列表的末尾,但因为列表为线性结构,所以程序如果想要查找包含指定数据的元素,或者进行范围查找,就需要遍历整个列表。
  • 有序集合虽然可以有效地进行范围查找,但缺少列表和发布与订阅提供的阻塞弹出原语,这使得程序无法使用有序集合去实现可阻塞的消息弹出操作。
  • 发布与订阅虽然拥有将消息传递给多个客户端的能力,并且也拥有相应的阻塞弹出原语,但发布与订阅的“发送即忘(fire andforget)”策略会导致离线的客户端丢失消息,所以它是无法实现可靠的消息队列的。

除了以上3种数据结构各自具有的问题之外,还有一个问题是3种数据结构共有的:无论是列表、有序集合还是发布与订阅,它们的元素都只能是单个值。换句话说,如果用户想要用这些数据结构实现的消息队列传递多项信息,那么必须使用JSON之类的序列化格式来将多项信息打包存储到单个元素中,然后再在取出元素之后进行相应的反序列化操作。

Redis流的出现解决了上述提到的所有问题,它是上述3种数据结构的综合体,具备它们各自的所有优点以及特点,是使用Redis实现消息队列应用的最佳选择。流是一个包含零个或任意多个流元素的有序队列,队列中的每个元素都包含一个ID和任意多个键值对,这些元素会根据ID的大小在流中有序地进行排列。

2.9.1、XADD:追加新元素到流的末尾

用户可以通过执行XADD命令,将一个带有指定ID以及包含指定键值对的元素追加到流的末尾:XADD stream id field value [field value ...]

流元素的ID由毫秒时间(millisecond)和顺序编号(sequcen number)两部分组成,其中使用UNIX时间戳表示的毫秒时间用于标识与元素相关联的时间,而以0为起始值的顺序编号则用于区分同一时间内产生的多个不同元素。因为毫秒时间和顺序编号都使用64位的非负整数表示,所以整个流ID的总长为128位,而Redis在接受流ID输入以及展示流ID的时候都会使用连字符-分割这两个部分。

代码语言:shell
复制
redis> XADD s1 1100000000000-12345 k1 v1
1100000000000-12345

redis> XADD temp-stream 1000000000000 k1 v1  -- Redis会自动将ID的顺序编号部分设置为0
1000000000000-0

redis> XADD s1 1100000000000-12345 k2 v2  -- 同一个流中的不同元素是不允许使用相同ID
(error) ERR The ID specified in XADD is equal or smaller than the target stream top item

redis> XADD s1 1000000000000-12345 k2 v2 -- 毫秒时间部分太小
(error) ERR The ID specified in XADD is equal or smaller than the target stream 
top item

redis> XADD s1 1100000000000-100 k2 v2 -- 毫秒时间相同,但顺序编号部分太小
(error) ERR The ID specified in XADD is equal or smaller than the target stream 
top item

Redis还要求新元素的ID必须比流中所有已有元素的ID都要大。具体来说,Redis会记住每个流已有元素的最大ID,并在用户尝试向流里面添加新元素的时候,使用新元素的ID与流目前最大的ID进行对比:

  • 如果新ID的毫秒时间部分比最大ID的毫秒时间部分要大,那么允许添加新元素。
  • 如果新ID的毫秒时间部分与最大ID的毫秒时间部分相同,那么对比两个ID的顺序编号部分,如果新ID的顺序编号部分比最大ID的顺序编号部分要大,那么允许添加新元素。 相反,不符合上述两种情况的添加操作将会被拒绝,并返回一个错误。

Redis流对元素ID的要求非常严格,并且还会拒绝不符合规则的ID。为了方便用户执行添加操作,RedisXADD命令的id参数设定了一个特殊值:当用户将符号用作id参数的值时,Redis将自动为新添加的元素生成一个可用的新ID。

具体来说,自动生成的新ID会将Redis所在宿主机器当前毫秒格式的UNIX时间戳用作ID的毫秒时间,并根据当前已有ID的最大顺序编号来设置新ID的顺序编号部分:

·如果在当前毫秒之内还没有出现过任何ID,那么新ID的顺序编号将被设置为0。比如,如果当前时间为1530200000000ms,并且在这一毫秒之内没有任何现存的ID,那么新元素的ID将被设置为1530200000000-0。

·如果在当前毫秒内已经存在其他ID,那么这些ID中顺序编号最大的那个加上1就是新ID的顺序编号。比如,如果在1530200000000ms内,现存最大的顺序编号为10086,那么新ID的顺序编号将是10086加1的结果10087,而完整的新ID则是1530200000000-10087。

代码语言:shell
复制
redis> XADD s1 * k2 v2
"1663308584308-0"

如果用户使用了*作为ID参数的值,但是宿主机器的当前时间比流中已有最大ID的毫秒时间要小,那么Redis将使用该ID的毫秒时间来作为新ID的毫秒时间,以此来避免机器时间倒流产生错误。

限制流的长度

XADD命令还提供了MAXLEN选项,让用户可以在添加新元素的同时删除旧元素,以此来限制流的长度:XADD stream [MAXLEN len] id field value [field value ...]

代码语言:shell
复制
redis> XADD mini-stream MAXLEN 3 * k4 v4
1400000000000-0
2.9.2、XTRIM:对流进行修剪

用户除了可以在执行XADD命令的同时使用MAXLEN命令对流进行修剪之外,还可以通过执行XTRIM命令直接将流修剪至指定长度:XTRIM stream MAXLEN len

代码语言:shell
复制
redis> XTRIM mini-stream MAXLEN 3
(integer) 2
2.9.3、XDEL:移除指定元素

XDEL命令接受一个流以及任意多个元素ID作为输入,并从流中移除ID对应的元素:XDEL stream [id id ... id]

代码语言:shell
复制
redis> XDEL trash-stream 2000000000000
(integer) 1
2.9.4、XLEN:获取流包含的元素数量

用户可以通过对流执行XLEN命令,获取流目前包含的元素数量:XLEN stream

代码语言:shell
复制
redis> XLEN stream-1
(integer) 3 -- 流包含3个元素
2.9.5、XRANGE、XREVRANGE:访问流中元素

正如前面所说,流本质上是一个有序序列,对于这种序列,使用有序方式获取序列中的各项元素是一种非常常见的操作。正如Redis为另一种有序序列数据结构列表提供了LRANGE命令一样,Redis也为流提供了XRANGE命令,这个命令可以以遍历或者迭代的方式,访问流中的单个或者任意多个元素。

XRANGE命令接受一个流、一个起始ID、一个结束ID以及一个可选的COUNT选项作为参数:XRANGE stream start-id end-id [COUNT n]

代码语言:shell
复制
redis> XRANGE temp-stream 3000000000000 3000000000000  -- 可以使用特殊值减号-和加号+
1) 1) 3000000000000-0 -- 流元素的ID
   2) 1) "k3" -- 流元素包含的键
      2) "v3" -- 流元素包含的值

redis> XRANGE temp-stream - + COUNT 3  -- 获取指定数量的元素,XRANGE stream start-id end-id [COUNT n]
1) 1) 1000000000000-0
   2) 1) "k1"
      2) "v1"
2) 1) 2000000000000-0
   2) 1) "k2"
      2) "v2"
3) 1) 3000000000000-0
   2) 1) "k3"
      2) "v3"

redis> XREVRANGE temp-stream + - COUNT 3  -- 获取temp-stream中ID最大的3个元素
1) 1) 9000000000000-0
 2) 1) "k9"
 2) "v9"
2) 1) 8000000000000-0
 2) 1) "k8"
 2) "v8"
3) 1) 7000000000000-0
 2) 1) "k7"
 2) "v7"
2.9.6、XREAD:以阻塞或非阻塞方式获取流元素

除了XRANGE命令和XREVRANGE命令之外,Redis还提供了XREAD命令用于获取流中元素:XREAD [BLOCK ms] [COUNT n] STREAMS stream1 stream2 stream3 ... id1 id2 id3 ...

COUNT选项限制命令对于每个流最多可以返回多少个元素:XREAD [COUNT n] STREAMS stream1 stream2 stream3 ... id1 id2 id3 ...

代码语言:shell
复制
redis> XREAD COUNT 3 STREAMS s1 1000000000000
1) 1) "s1" -- 元素的来源流
   2) 1) 1) 1100000000000-0 -- 第一个元素及其ID
         2) 1) "k1" -- 第一个元素包含的键值对
            2) "v1"
      2) 1) 1200000000000-0 -- 第二个元素
         2) 1) "k2"
            2) "v2"
      3) 1) 1300000000000-0 -- 第三个元素
         2) 1) "k3"
            2) "v3"

redis> XREAD COUNT 1 STREAMS s1 s2 s3 1000000000000 1000000000000 1000000000000
1) 1) "s1" -- 这个元素来源于流s1
   2) 1) 1) 1100000000000-0
         2) 1) "k1"
            2) "v1"
2) 1) "s2" -- 这个元素来源于流s2
   2) 1) 1) 1531743117644-0
         2) 1) "k1"
            2) "v1"
3) 1) "s3" -- 这个元素来源于流s3
   2) 1) 1) 1531748220373-0
         2) 1) "k1"
            2) "v1"
阻塞

通过使用BLOCK选项并给定一个毫秒精度的超时时间作为参数,用户能够以可阻塞的方式执行XREAD命令:XREAD BLOCK ms STREAMS stream1 stream2 stream3 ... id1 id2 id3 ...

BLOCK选项的值可以是任何大于等于0的数值,给定0则表示阻塞直到出现可返回的元素为止。根据用户给定的流是否拥有符合条件的元素,带有BLOCK选项的XREAD命令的行为也会有所不同。

代码语言:shell
复制
redis> XREAD BLOCK 10000000 COUNT 2 STREAMS s1 s2 bs1 0 0 0
1) 1) "s1"
   2) 1) 1) 1100000000000-0
         2) 1) "k1"
            2) "v1"
     2) 1) 1200000000000-0
        2) 1) "k2"
           2) "v2"
2) 1) "s2"
   2) 1) 1) 1531751993870-0
         2) 1) "k1"
            2) "v1"
      2) 1) 1531751997935-0
         2) 1) "k2"
            2) "v2"

虽然流bs1没有可供读取的元素,但是由于流s1和s2都拥有可供读取的元素,所以命令没有进入阻塞状态,而是直接返回了可供读取的元素,并且元素的数量没有超过COUNT选项的限制。

如果用户在执行带有BLOCK选项的XREAD命令时,给定的所有流都不存在可供读取的元素,那么命令将进入阻塞状态。如果在给定的阻塞时长之内有一个可供读取的元素出现,那么Redis将把这个元素分发给所有因为该元素而被阻塞的客户端,这种情况下的XREAD命令会无视用户给定的COUNT选项,只返回一个元素。

RedisXREAD命令提供了特殊ID参数$符号,用户在执行阻塞式的XREAD命令时,只要将$符号用作ID参数的值,XREAD命令就会只获取给定流在命令执行之后新出现的元素:

XREAD BLOCK ms STREAMS stream1 stream2 stream3 ... $ $ $ ...

举个例子,假设我们现在想要获取流bs1接下来将要出现的第一个新元素,那么可以执行以下命令:

代码语言:shell
复制
redis> XREAD BLOCK 10000000 STREAMS bs1 $

执行这个调用的客户端将进入阻塞状态。在此之后,如果在给定的时限内,有另一个客户端向流s1推入新元素,那么原客户端的阻塞状态就会被解除,并返回被推入的元素,就像这样:

代码语言:shell
复制
1) 1) "bs1" -- 元素的来源流
   2) 1) 1) 1300000000000-0 -- 元素的ID
         2) 1) "k3" -- 元素的键值对
            2) "v3"
(2.64s) -- 客户端被阻塞的时长
2.9.7、消费者组

在某些问题中,我们想要做的是从同一流中向许多客户端提供不同的消息子集。一个明显有用的例子是处理缓慢的消息:让N个不同的客户端接收流的不同部分来加快消息的处理。例如:如果有三个消费者A1、A2、A3和一个包含消息1、2、3、4、5、6、7的流,那么我们想要达到的是像下面这样分配消息。

代码语言:txt
复制
1 --> A1
2 --> A2
3 --> A3
4 --> A1
5 --> A2
6 --> A3
7 --> A1

为了实现这一点,Redis使用了一个叫做消费者组的概念。从实现的角度来看,Redis消费者组与Kafka (TM)消费者组没有任何关系,只是在功能上是相似的:允许一组客户端合作消费同一消息流的不同部分。

我们可以将一个消费者组简单理解为一个从流中获取数据的特殊的消费者。它从流中获取数据,然后再服务于多个消费者,同时提供了如下的保证:

1) 每条消息都提供给不同的消费者,不会将相同的消息传递给多个消费者。

2) 在消费者组中,消费者通过客户端的名称(区分大小写的字符串)进行区分,当断开连接重新连通后,消费者客户端还是提供相同的名字,会被当做同一个消费者。这意味着在消费者组中由客户端提供唯一标识符。

3) 每个消费者组都有未被消费的第一个ID的概念,这样当消费者请求新消息时,它可以只提供以前未传递的消息。

4) 消费消息需要使用特定命令进行显式确认。Redis将该确认解释为:此消息已正确处理,可以从消费者组中移除。

5) 消费者组跟踪所有当前挂起的消息,即已传递给消费者组的某个消费者但尚未确认为已处理的消息。由于此功能,当访问流的消息历史记录时,每个使用者将只看到传递给它的消息。

创建消费者组

Redis流的消费者组(consumer group)允许用户将一个流从逻辑上划分为多个不同的流,并让消费者组属下的消费者去处理组中的消息。

XGROUP CREATE stream group start_id

命令中的stream参数用于指定流的名字,group参数用于指定将要创建的消费者组的名字。start_id参数用于指定消费者组在流中的起始ID,这个ID决定了消费者组要从流的哪个ID之后开始进行读取。

读取消费者组

客户端可以通过执行XREADGROUP命令来读取消费者组中的消息:

XREADGROUP GROUP group consumer [COUNT n] [BLOCK ms] STREAMS stream [stream ...] id [id ...]

消费者

从逻辑上来说,消费者就是负责处理消息的客户端。与创建消费者组不一样,消费者不用显式地创建,用户只要在执行XREADGROUP命令时给定消费者的名字,Redis就会自动为新出现的消费者创建相应的数据结构。

与消费者组一样,消费者也会维护一个属于自己的待处理消息队列:每当用户使用XREADGROUP命令读取出一条消息,并将这条消息指派给一个消费者处理时,该消费者就会把所指派的消息添加到自己的待处理消息队列中。

需要注意的是,与多个消费者组能够共享同一个流中的元素不一样,同一消费者组中的每条消息只能有一个消费者,换句话说,不同的消费者将独占组中的不同消息:当一个消费者读取了组中的一条消息之后,同组的其他消费者将无法读取这条消息。

消息的状态转换

当消费者处理完一条消息之后,它需要向Redis发送一条针对该消息的XACK命令:XACK stream group id [id id ...]

当Redis接收到消费者发来的XACK命令之后,就会从消费者组的待处理消息队列以及消费者的待处理消息队列中移除指定的消息。这样一来,这些消息的状态就会从“待处理”转换为“已确认”,以此来表示消费者已经处理完这些消息了。

综合起来,一条消费者组消息从出现到处理完毕,需要经历以下阶段:

  • 首先,当一个生产者通过XADD命令向流中添加一条消息时,该消息就从原来的“不存在”状态转换成了“未递送”状态。
  • 然后,当一个消费者通过XREADGROUP命令从流中读取一条消息时,该消息就从原来的“未递送”状态转换成了“待处理”状态。
  • 最后,当消费者完成了对消息的处理,并通过XACK命令向服务器进行确认时,该消息就从原来的“待处理”状态转换成了“已确认”状态。
代码语言:shell
复制
redis> XGROUP CREATE mystream mygroup 0   -- 创建消费者组,cgs是必须存在的流
OK

redis> XREADGROUP GROUP mygroup worker1 count 1 STREAMS mystream >  -- 以消费者worker1的身份,从消费者组mygroup中读取出相应的消息, > 表示只接收从来没有被投递给其他消费者的消息,即新的消息
1) 1) "cgs" -- 来源流
   2) 1) 1) 1535875626221-0 -- 消息
         2) 1) "k1"
            2) "v1"
      2) 1) 1535875628970-0 -- 消息
         2) 1) "k2"
            2) "v2"

redis> XPENDING cgs mygroup
1) (integer) 5  -- 消费者组目前处于待处理状态的消息数量,已读取,未处理的消息5条
2) "1663911944636-0"   -- 最小的待处理消息ID
3) "1663916290849-0"   -- 最大的待处理消息ID
4) 1) 1) "consumerA"   -- 消费者的名字
      2) "1"    -- 该消费者正在处理的消息数量
   2) 1) "worker1"
      2) "4"

redis> XINFO GROUPS cgs
1) 1) "name"   -- 消费者组的名字
   2) "mygroup"
   3) "consumers"   -- 属下消费者的数量
   4) (integer) 2
   5) "pending"   -- 该组目前的待处理消息数量
   6) (integer) 5
   7) "last-delivered-id"   -- 该组目前的最后递送消息ID
   8) "1663916290849-0"

redis> XACK cgs mygroup 1663916290849-0  -- 消费者处理完ID为1663916290849的消息之后,对其进行确认
(integer) 1

redis> xpending cgs mygroup  -- 以通过再次执行XPENDING命令来确认队列中的待处理消息
1) (integer) 4
2) "1663911944636-0"
3) "1663913588314-0"
4) 1) 1) "worker1"
      2) "4"   --  有四条待处理消息,consumerA 的消息已经被处理 

可以看到,使用 start end count 选项可以获取详细信息,从读取到现在经历的毫秒数、消息被读取的次数。再加上消费者参数,可以获取具体某个消费者的Pending列表。

2.9.8、XGROUP:管理消费者组
创建消费者组

通过执行XGROUP CREATE命令,用户可以为流创建一个具有指定名字的消费者组:XGROUP CREATE stream group id

代码语言:shell
复制
redis> xgroup create s1 mygroup 0  -- 创建消费者组,cgs是必须存在的流
OK

redis> XGROUP CREATE mystream1 mygroup 0 MKSTREAM  -- 如果流不存在,使用可选的MKSTREAM子命令作为最后一个参数可以自动创建对应的流
OK

redis> XGROUP CREATE userinfo GRP-AFEI   -- streams不存在时报错
$(error) ERR The XGROUP subcommand requires the key to exist. Note that for CREATE you may want to use the MKSTREAM option to create an empty stream automatically.
修改消费者组的最后递送消息ID

对于一个已经存在的消费者组来说,用户可以通过执行XGROUP SETID命令来为消费者组设置新的最后递送消息ID:XGROUP SETID stream group id

代码语言:shell
复制
redis> XINFO GROUPS cgs
1) 1) "name"
   2) "mygroup"
   3) "consumers"
   4) (integer) 2
   5) "pending"
   6) (integer) 4
   7) "last-delivered-id"
   8) "1663916290849-0"

redis> xgroup setid cgs mygroup 1663911944636-0
OK

redis> XINFO GROUPS cgs
1) 1) "name"
   2) "mygroup"
   3) "consumers"
   4) (integer) 2
   5) "pending"
   6) (integer) 4
   7) "last-delivered-id"
   8) "1663911944636-0" -- ID已改变

除了合法的消息ID之外,特殊符号$也可以用作id参数的值,这个符号可以把消费者组的最后递送消息ID设置为流最新消息的ID:

代码语言:shell
复制
redis> xadd cgs * message h  -- 向流插入一条新消息
"1663919899444-0" 

redis> XGROUP SETID cgs mygroup $ -- 执行修改命令
OK

redis> XINFO GROUPS cgs -- 最后递送ID已被修改
1) 1) "name"
   2) "mygroup"
   3) "consumers"
   4) (integer) 2
   5) "pending"
   6) (integer) 4
   7) "last-delivered-id"
   8) "1663919899444-0"  -- ID 已改变

需要注意的是,使用XGROUP SETID命令显式地修改最后递送消息ID将对后续执行的XREADGROUP命令的结果产生影响,简单来说:

  • 如果新ID大于旧ID,那么消费者可能会漏掉一些原本应该读取的消息。
  • 如果新ID小于旧ID,那么消费者可能会重新读取到一些之前已经被确认过的消息。
删除消费者

当用户不再需要某个消费者的时候,可以通过执行以下命令将其删除:XGROUP DELCONSUMER stream group consumer

代码语言:shell
复制
redis> XINFO CONSUMERS cgs mygroup
1) 1) name -- 消费者的名字
   2) "worker1"
   3) pending -- 消费者正在处理的消息数量
   4) (integer) 2
   5) idle -- 消费者闲置的时间
   6) (integer) 44481
2) 1) name
   2) "worker2"
   3) pending
   4) (integer) 3
   5) idle
   6) (integer) 24816

redis> XGROUP DELCONSUMER cgs mygroup worker1  -- 将消费者worker1删除
(integer) 2 -- 这个消费者还有两条消息未确认

redis> XINFO CONSUMERS cgs mygroup
1) 1) name
 2) "worker2"
 3) pending
 4) (integer) 3
 5) idle
 6) (integer) 72596

当消费者被删除之后,它在被删除时处理的消息也会从消费者组的待处理消息队列中移除。换句话说,属于被删除消费者的待处理消息将不再处于“待处理”状态,这些消息可能已经被消费者处理掉了,但也可能尚未得到妥善的处理。

删除消费者组

与上一个命令类似,当一个消费者组完成了它的任务之后,用户可以通过执行以下命令来删除它:XGROUP DESTROY stream group

代码语言:txt
复制
redis> XGROUP DESTROY cgs mygroup  -- 删除cgs流的mygroup消费者组
(integer) 1 

redis> XINFO GROUPS cgs  -- cgs 流现在已经不再拥有任何消费者组了
(empty array)
2.9.9、XREADGROUP:读取消费者组中的消息

XREADGROUP命令是消费者组版本的XREAD命令,用户可以使用这个命令读取消费者组中的消息:

XREADGROUP GROUP group consumer [COUNT n] [BLOCK ms] STREAMS stream [stream ...] id [id ...]

代码语言:shell
复制
redis> XREADGROUP GROUP mygroup worker1 count 1 STREAMS mystream >  -- 以消费者worker1的身份,从消费者组mygroup中读取出相应的消息, > 表示只接收从来没有被投递给其他消费者的消息,即新的消息
1) 1) "cgs" -- 来源流
   2) 1) 1) 1535875626221-0 -- 消息
         2) 1) "k1"
            2) "v1"
      2) 1) 1535875628970-0 -- 消息
         2) 1) "k2"
            2) "v2"

redis> xreadgroup group mygroup consumerA count 1 streams s1 0  -- count 1 表示只读一条,指定0表示访问所有投递给该消费者的历史消息, 消费者在消费时指定即可,不用预先创建
1) 1) "s1"
   2) 1) 1) "1663408584308-1"
         2) 1) "k3"
            2) "v3"

redis> xreadgroup group mygroup consumerC streams s1 1663408584308-1  -- 指定1663408584308-1表示投递给该消费者且大于这个ID的历史消息
1) 1) "s1"
   2) 1) 1) "1663833859359-0"
         2) 1) "message"
            2) "b"
      2) 1) "1663833861537-0"
         2) 1) "message"
            2) "c"
      3) 1) "1663833863881-0"
         2) 1) "message"
            2) "d"
      4) 1) "1663833866316-0"
         2) 1) "message"
            2) "e"
      5) 1) "1663833870573-0"
         2) (nil)
redis-stream-group.png
redis-stream-group.png

可以看出,三个消费者都可以消费到这 10 条消息,有着互斥原则,三个消费者配合协作来消费同一个消息队列,可以在消费能力不足,也就是消息处理程序效率不高时,使用该模式。

XREADGROUP GROUP myGroup consumerA COUNT 1 STREAMS s1 >,用于组myGroup 内消费者 consumerA 在队列 s1 中消费,参数 > 表示未被组内消费的起始消息,参数 count 1 表示获取一条。

2.9.10、XPENDING:显示待处理消息的相关信息

用户可以通过XPENDING命令,获取指定流的指定消费者组目前的待处理消息的相关信息:

XPENDING stream group [start stop count] [consumer]

代码语言:shell
复制
redis> XPENDING cgs mygroup
1) (integer) 2 -- 待处理消息的数量
2) 1534435172217-0 -- 首条消息的ID
3) 1534435256529-0 -- 最后一条消息的ID
4) 1) 1) "worker1" -- 各个消费者目前正在处理的消息数量
      2) "1"
   2) 1) "worker2"
      2) "1"

redis> XPENDING cgs mygroup 1534435172217-0 1534435172217-0 1  -- 限制起始的消息ID和消息count
1) 1) 1534435172217-0 -- 消息ID
   2) "worker1" -- 所属消费者
   3) (integer) 52490194 -- 消息最后一次递送给消费者之后,过去了多少毫秒
   4) (integer) 1 -- 消息被递送的次数
2.9.11、XACK:将消息标记为“已处理”

通过执行XACK命令,用户可以将消费者组中的指定消息标记为“已处理”。被标记的消息将从当前消费者的待处理消息队列中移除,而之后执行的XREADGROUP命令也不会再读取这些消息:

XACK stream group id [id id ...]

代码语言:shell
复制
redis> XPENDING cgs mygroup - + 1 worker1
1) 1) 1534498374797-0
   2) "worker1"
   3) (integer) 19027
   4) (integer) 1

redis> XACK cgs mygroup 1534498374797-0  -- 该消息将被标记为“已处理
(integer) 1 -- 有一条消息被标记了

redis> XPENDING cgs mygroup - + 1 worker1  -- 消息将从消费者worker1的待处理消息队列中消失
(empty list or set)
2.9.12、XCLAIM:转移消息的归属权

用户可以通过执行XCLAIM命令,将指定消息的归属权从一个消费者转向另一个消费者,这个命令的基本格式并不复杂:

XCLAIM stream group new_consumer max_pending_time id [id id ...]

命令中的stream参数和group参数指定了消息所在的流和消费者组,new_consumer指定了消息的新消费者,而命令中的任意多个id参数则指明了需要转移归属权的消息。

除此之外,命令中毫秒格式的max_pending_time参数指定了执行归属权转移操作所需的最大消息处理时限,具体来说:

  • 如果XCLAIM命令执行的时候,消息原来的消费者用在处理该消息上的时间已经超过了指定的时限,那么归属权转移操作就会被执行。现
  • 与此相反,如果原消费者处理该消息的时间并未超过给定的时限,或者该消息已经被原消费者确认,那么归属权转移操作将放弃执行。 这里的消息处理时间指的是从消费者组将消息递送给原消费者开始,直到XCLAIM命令执行为止,所用的时间总长。 举个例子,如果一个用户执行以下命令:
代码语言:txt
复制
redis> XCLAIM cgs mygroup worker2 60000 1535002039330-0
1) 1) 1535002039330-0 -- 被转移消息的ID
   2) 1) "k1" -- 被转移消息的内容
      2) "v1"

如果消息1535002039330-0在的消费者处理该消息的时间超过了60000ms,那么将该消息的归属权转移给消费者worker2。

XCLAIM命令在成功执行之后将会返回被转移的消息作为结果;相反,如果转移操作因为处理时限未到等原因而未能顺利执行,那么命令将返回一个空列表:

代码语言:shell
复制
redis> XCLAIM cgs mygroup worker2 60000 1535002039330-0
(empty list or set)
2.9.13、XINFO:查看流和消费者组的相关信息

XINFO CONSUMERS命令用于打印指定消费者组的所有消费者,以及这些消费者的相关信息:XINFO CONSUMERS stream group-name

命令打印的信息包括消费者的名字、它们正在处理的消息数量以及消费者的闲置时长。

以下是一个XINFO CONSUMERS命令的使用示例:

代码语言:shell
复制
redis> XINFO CONSUMERS cgs mygroup
1) 1) name -- 消费者的名字
   2) "worker1"
   3) pending -- 正在处理的消息数量
   4) (integer) 1
   5) idle -- 毫秒格式的闲置时长
   6) (integer) 50899
2) 1) name
   2) "worker2"
   3) pending
   4) (integer) 0
   5) idle
   6) (integer) 7371
打印消费者组信息

XINFO GROUPS命令用于打印与给定流相关联的所有消费者组,以及这些消费者组的相关信息:XINFO GROUPS stream

命令打印的信息包括消费者组的名字、它拥有的消费者数量、组中正在处理消息的数量以及该组最后递送消息的ID。

以下是一个XINFO GROUPS命令的使用示例:

代码语言:shell
复制
redis> XINFO GROUPS cgs
1) 1) name -- 组名
   2) "mygroup"
   3) consumers -- 消费者数量
   4) (integer) 2
   5) pending -- 组中正在处理的消息数量
   6) (integer) 1
   7) last-delivered-id -- 最后递送消息的ID
   8) 1532339991221-0
打印流消息

XINFO STREAM命令用于打印给定流的相关信息:XINFO STREAM stream

命令打印的信息包括流的长度(包含的消息数量)、流在底层的基数树表示的相关信息、流相关的消费者组数量、流最后生成的消息的ID以及流的第一个节点和最后一个节点。

以下是对cgs流执行XINFO STREAM命令的结果:

代码语言:shell
复制
redis> XINFO STREAM cgs
 1) length -- 长度
 2) (integer) 1
 3) radix-tree-keys -- 基数树的键数量
 4) (integer) 1
 5) radix-tree-nodes -- 基数树的节点数量
 6) (integer) 2
 7) groups -- 与之相关联的消费者组数量
 8) (integer) 1
 9) last-generated-id -- 最后生成的消息的ID
10) 1532339991221-0
11) first-entry -- 流的第一个节点
12) 1) 1532339991221-0
    2) 1) "msg"
       2) "initial message"
13) last-entry -- 流的第二个节点
14) 1) 1532339991221-0
    2) 1) "msg"
       2) "initial message"

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、redis入门
    • 1、redis简介
      • 1.1、预备工作
    • 2、数据结构与应用
      • 2.1、字符串
      • 2.2、散列
      • 2.3 列表
      • 2.4、集合
      • 2.5、有序集合
      • 2.6、HyperLogLog
      • 2.7、位图
      • 2.8、地理坐标
      • 2.9 流
相关产品与服务
云数据库 Redis®
腾讯云数据库 Redis®(TencentDB for Redis®)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档