Redis10大数据类型

一、数据类型

Redis 是 Key-Value 类型缓存型数据库,Redis 为了存储不同类型的数据,提供了10种常用数据类型(参考网址:https://redis.io/docs/data-types/),如下所示:

二、Redis 键(key)

下表给出了与 Redis 键相关的基本命令:

1 DEL key
该命令用于在 key 存在时删除 key。
2 DUMP key
序列化给定 key ,并返回被序列化的值。序列化:把对象转化为可传输的字节序列过程称为序列化。反序列化:把字节序列还原为对象的过程称为反序列化。
3 EXISTS key
检查给定 key 是否存在。
4 EXPIRE key seconds
设置 key 的过期时间,key 过期后将不再可用。单位以秒计。
5 EXPIREAT key timestamp
EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置过期时间。 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。
6 PEXPIRE key milliseconds
设置 key 的过期时间以毫秒计。
7 PEXPIREAT key milliseconds-timestamp
设置 key 过期时间的时间戳(unix timestamp) 以毫秒计
8 KEYS pattern
查找所有符合给定模式( pattern)的 key 。
9 MOVE key db
将当前数据库的 key 移动到给定的数据库 db 当中。
10 PERSIST key
移除 key 的过期时间,key 将持久保持。
11  PTTL key
以毫秒为单位返回 key 的剩余的过期时间。
12 TTL key
以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。-1表示永不过期,-2表示已过期
13 RANDOMKEY
从当前数据库中随机返回一个 key 。
14 RENAME key newkey
修改 key 的名称
15 RENAMENX key newkey
仅当 newkey 不存在时,将 key 改名为 newkey 。
16 SCAN cursor [MATCH pattern] [COUNT count]
遍历输出数据库中的数据库键。可以具体指定哪个库
17 TYPE key
返回 key 所储存的值的类型。
18 select dbindex 用于切换数据库,共有16个库,索引是从0到15,默认为0
19 dbsize 查看当前数据库key的数量
20  flushdb 清空当前库
21  flushall 通杀全部库

三、数据类型命令

在Redis中命令不区分大小写,而key是区分大小写的,帮助命令是:help @类型 或者 help 命令 ,也可以去官网查看:

  • 英文:https://redis.io/commands/
  • 中文:http://www.redis.cn/commands.html

3.1.Redis字符串(String)

3.1.1字符串类型说明

string(字符串)是 Redis 中最简单的数据类型。Redis 所有数据类型都是以 key 作为键,通过检索这个 key 就可以获取相应的 value 值。Redis 存在多种数据类型,比如字符串、列表、哈希散列等,它们对应的 value 结构各不相同。Redis 使用标准 C 语言编写,但在存储字符时,Redis 并未使用 C 语言的字符类型,而是自定义了一个属于特殊结构 SDS(Simple Dynamic  String)即简单动态字符串),这是一个可以修改的内部结构,非常类似于 Java 的 ArrayList。

1) SDS动态字符串

SDS 的结构定义如下:

struct sdshdr{
     //记录buf数组中已使用字符的数量,等于 SDS 保存字符串的长度
     int len;
     //记录 buf 数组中未使用的字符数量
     int free;
     //字符数组,用于保存字符串
     char buf[];

从上面看,Redis string 将字符串存储到字符类型的buf[]中,并使用 lenfreebuf[]数组的长度和未使用的字符数进行描述。下图是SDS 字符串的结构示意图:

上图 存储了一个len为 5 的 “hello\0”字符串,并且未使用的字符数free为 0。需要注意到 buf 数组存储的字符串仍然以 C语言字符格式的“\0”结尾的,这样做的目的是为了能够重用 C语言库 <string.h> 中的部分函数。

在 C语言中,字符串类型的结尾以空字符串 ‘\0’来标识的。但在某些情况下,字符串可能会包含具有实际意义的“空字符”,此时 C语言就无法正确的存取这个字符了,而 Redis 通过 len 来标识字符串的总长度,从而保证了数据的二进制安全特性。

2) 分配冗余空间

string 采用了预先分配冗余空间的方式来减少内存的频繁分配,Redis 每次给 string 分配的空间都要大于字符串实际占用的空间,这样就在一定程度上提升了 Redis string 存储的效率,比如当字符串长度变大时,无需再重新申请内存空间。

3) string自动扩容

当字符串所占空间小于 1MB 时,Redis 对字符串存储空间的扩容是以成倍的方式增加的;而当所占空间超过 1MB 时,每次扩容只增加 1MB。Redis 字符串允许的最大值字节数是 512 MB。

3.1.2.常见命令和描述

命令及描述
SET key value
设置指定 key 的值。
GET key
获取指定 key 的值。
GETRANGE key start end
返回 key 中字符串值的子字符
GETSET key value
将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
GETBIT key offset
对 key 所储存的字符串值,获取指定偏移量上的位(bit)。
MGET key1 [key2..]
获取所有(一个或多个)给定 key 的值。
SETBIT key offset value
对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。
SETEX key seconds value
将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。
SETNX key value
只有在 key 不存在时设置 key 的值。
SETRANGE key offset value
用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。
STRLEN key
返回 key 所储存的字符串值的长度。
MSET key value [key value ...]
同时设置一个或多个 key-value 对。
MSETNX key value [key value ...]
同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。
PSETEX key milliseconds value
这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。
INCR key
将 key 中储存的数字值增一。
INCRBY key increment
将 key 所储存的值加上给定的增量值(increment) 。
INCRBYFLOAT key increment
将 key 所储存的值加上给定的浮点增量值(increment) 。
DECR key
将 key 中储存的数字值减一。
DECRBY key decrement
key 所储存的值减去给定的减量值(decrement) 。
APPEND key value
如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。

 

3.1.3.案例:常见命令

SET key value:设置指定 key 的值。语法如下:

SET命令将键key设定为指定的“字符串”值。如果 key 已经保存了一个值,那么这个操作会直接覆盖原来的值,并且忽略原始类型。当set命令执行成功之后,之前设置的过期时间都将失效

SET命令支持一组修改其行为的选项:

  • EX seconds——设置指定的过期时间,以秒为单位。
  • PX milliseconds——设置指定的过期时间,单位为毫秒。
  • EXAT timestamp-seconds——设置密钥过期的指定Unix时间,单位为秒。
  • PXAT timestamp-milliseconds——设置密钥过期的指定Unix时间,单位为毫秒。
  • NX—仅在该键不存在时设置该键。
  • XX—只在已经存在的情况下设置键。
  • KEEPTTL——保留与密钥相关联的生存时间。
  • GET——返回存储在key的旧字符串,如果key不存在,则返回nil。如果key存储的值不是字符串,则返回错误并中止SET。

注意:由于SET命令选项可以替代SETNX, SETEX, PSETEX, GETSET,在Redis的未来版本中,这些命令可能会被弃用并最终删除。

返回值

  • 如果SET命令正常执行那么回返回OK,否则如果加了NX 或者 XX选项,但是没有设置条件。那么会返回nil。

GET key:获取指定 key 的值。语法如下:

返回keyvalue。如果key不存在,返回特殊值nil。如果keyvalue不是string,就返回错误,因为GET只处理string类型的values

代码演示

127.0.0.1:6379> SET k1 v1 EX 20
OK
127.0.0.1:6379> TTL k1
(integer) 3
127.0.0.1:6379> TTL k1
(integer) 2
127.0.0.1:6379> TTL k1
(integer) 1
127.0.0.1:6379> TTL k1
(integer) -2
127.0.0.1:6379> GET k1
(nil)
127.0.0.1:6379>

3.1.4.案例:同时设置/获取多个键值

MSET key value [key value ...] :同时设置一个或多个 key-value 对。语法:

对应给定的keys到他们相应的values上。MSET会用新的value替换已经存在的value,和SET命令一样。如果你不想覆盖已经存在的values,请参看命令MSETNX。MSET的设置具有原子性,所有的键值都设置对,才能成功。

返回值

  • 总是OK,因为MSET不会失败。
127.0.0.1:6379> MSET k6 v6 k7
(error) ERR wrong number of arguments for 'mset' command
127.0.0.1:6379> MSET k6 v6 k7 v7
OK
127.0.0.1:6379>

MGET key [key ...],语法如下:

返回所有指定的key的value。对于每个不对应string或者不存在的key,都返回特殊值nil。正因为此,这个操作从来不会失败。

返回值

  • 指定的key对应的values的list
127.0.0.1:6379> SET key1 hello
OK
127.0.0.1:6379> SET key2 world
OK
127.0.0.1:6379> MGET key1 key2
1) "hello"
2) "world"
127.0.0.1:6379> MGET key1 key2 key3
1) "hello"
2) "world"
3) (nil)
127.0.0.1:6379>

MSETNX key value [key value ...] 

对应给定的keys到他们相应的values上。只要有一个key已经存在,MSETNX一个操作都不会执行。 由于这种特性,MSETNX可以实现要么所有的操作都成功,要么一个都不执行,这样可以用来设置不同的key,来表示一个唯一的对象的不同字段。MSETNX是原子的,所以所有给定的keys是一次性set的。客户端不可能看到这种一部分keys被更新而另外的没有改变的情况。

返回值

有以下两种值:

  • 1 如果所有的key被set
  • 0 如果没有key被set(至少其中有一个key是存在的)
127.0.0.1:6379> MSETNX key1 hello key2 world
(integer) 1
# 这里kay2之前已经设置过了,再次设置或报错,导致 key也就没有设置成功 127.0.0.1:6379> MSETNX key2 world key3 there (integer) 0 127.0.0.1:6379> MGET key1 key2 key3 1) "hello" 2) "world" 3) (nil) 127.0.0.1:6379>

3.1.5.案例:获取指定区间范围内的值

GETRANGE key start end

这个命令是被改成GETRANGE的,在小于2.0的Redis版本中叫SUBSTR。 返回key对应的字符串value的子串,这个子串是由start和end位移决定的(两者都在string内)。可以用负的位移来表示从string尾部开始数的下标。所以-1就是最后一个字符,-2就是倒数第二个,以此类推。这个函数处理超出范围的请求时,都把结果限制在string内。

127.0.0.1:6379> SET mykey "this is a string"
OK
127.0.0.1:6379> GETRANGE mykey 0 3
"this"
127.0.0.1:6379> GETRANGE mykey 0 -1
"this is a string"
127.0.0.1:6379> GETRANGE mykey -5 -1
"tring"
127.0.0.1:6379> GETRANGE mykey 4 10
" is a s"
127.0.0.1:6379>

SETRANGE key offset value

这个命令的作用是覆盖key对应的string的一部分,从指定的offset处开始,覆盖value的长度。如果offset比当前key对应string还要长,那这个string后面就补0以达到offset。不存在的keys被认为是空字符串,所以这个命令可以确保key有一个足够大的字符串,能在offset处设置value。

127.0.0.1:6379> SET key10 "hello world"
OK
127.0.0.1:6379> SETRANGE key10 6 "redis"
(integer) 11
127.0.0.1:6379> GET key10
"hello redis"
#下面是补0的例子
127.0.0.1:6379> SETRANGE key10 15  "hi"
(integer) 17
127.0.0.1:6379> GET key10
"hello redis\x00\x00\x00\x00hi"
127.0.0.1:6379>

3.1.6.案例:数值增减

INCR key

应用场景:

  • 比如抖音无限点赞某个视频或者商品,点一下加一次
  • 例如在线文章的阅读次数

对存储在指定key的数值执行原子的加1操作。

  • 如果指定的key不存在,那么在执行incr操作之前,会先将它的值设定为0
  • 如果指定的key中存储的值不是字符串类型(fix:)或者存储的字符串类型不能表示为一个整数,那么执行这个命令时服务器会返回一个错误(eq:(error) ERR value is not an integer or out of range)。这个操作仅限于64位的有符号整型数据。

注意: 由于redis并没有一个明确的类型来表示整型数据,所以这个操作是一个字符串操作。执行这个操作的时候,key对应存储的字符串被解析为10进制的64位有符号整型数据。事实上,Redis 内部采用整数形式(Integer representation)来存储对应的整数值,所以对该类字符串值实际上是用整数保存,也就不存在存储整数的字符串表示(String representation)所带来的额外消耗。

返回值

  • 执行递增操作后key对应的值。
127.0.0.1:6379> SET mykey 10
OK
127.0.0.1:6379> INCR mykey
(integer) 11
127.0.0.1:6379> INCR mykey
(integer) 12
127.0.0.1:6379> INCR mykey
(integer) 13

INCRBY key increment

将key对应的数字加decrement。如果key不存在,操作之前,key就会被置为0。如果key的value类型错误或者是个不能表示成数字的字符串,就返回错误。这个操作最多支持64位有符号的正型数字。

返回值

  • 增加之后的value值。
127.0.0.1:6379> SET mykey 10
OK
127.0.0.1:6379> INCRBY mykey 5
(integer) 15
127.0.0.1:6379> GET mykey
"15"
127.0.0.1:6379> INCRBY mykey 5
(integer) 20
127.0.0.1:6379> GET mykey
"20"

DECR key

对key对应的数字做减1操作。如果key不存在,那么在操作之前,这个key对应的值会被置为0。如果key有一个错误类型的value或者是一个不能表示成数字的字符串,就返回错误。这个操作最大支持在64位有符号的整型数字。

返回值

  • 数字:减小之后的value
127.0.0.1:6379> SET mykey 10
OK
127.0.0.1:6379> DECR mykey
(integer) 9
127.0.0.1:6379> DECR mykey
(integer) 8
127.0.0.1:6379>

DECRBY key decrement

将key对应的数字减decrement。如果key不存在,操作之前,key就会被置为0。如果key的value类型错误或者是个不能表示成数字的字符串,就返回错误。这个操作最多支持64位有符号的正型数字。

返回值

  • 返回一个数字:减少之后的value值。
127.0.0.1:6379> SET mykey 10
OK
127.0.0.1:6379> DECRBY mykey 2
(integer) 8
127.0.0.1:6379> DECRBY mykey 2
(integer) 6
127.0.0.1:6379> DECRBY mykey 2
(integer) 4

3.1.6.案例:获取字符串长度和内容追加

STRLEN key

返回key的string类型value的长度。如果key对应的非string类型,就返回错误。

返回值

  • key对应的字符串value的长度,或者0(key不存在)
127.0.0.1:6379> SET mykey "hello world"
OK
127.0.0.1:6379> STRLEN mykey
(integer) 11
# key不存在返回0 127.0.0.1:6379> STRLEN mykey12 (integer) 0

APPEND key value

如果 key 已经存在,并且值为字符串,那么这个命令会把 value 追加到原来值(value)的结尾。 如果 key 不存在,那么它将首先创建一个空字符串的key,再执行追加操作,这种情况 APPEND 将类似于 SET 操作。

返回值

  • 返回append后字符串值(value)的长度。
127.0.0.1:6379> EXISTS mykey
(integer) 0
127.0.0.1:6379> get mykey
(nil)
127.0.0.1:6379> APPEND mykey "hello"
(integer) 5
127.0.0.1:6379> APPEND mykey "world"
(integer) 10
127.0.0.1:6379> get mykey
"helloworld"
127.0.0.1:6379>

3.1.7.案例:下面命令可应用于分布式锁

SETNX key value

key设置值为value,如果key不存在,这种情况下等同SET命令。 当key存在时,什么也不做。SETNX是”SET if Not eXists”的简写。

返回特定值:

  • 1 如果key被设置了
  • 0 如果key没有被设置
127.0.0.1:6379> SETNX myhi "hello"
(integer) 1
127.0.0.1:6379> get myhi
"hello"
127.0.0.1:6379> SETNX myhi "world"
(integer) 0
127.0.0.1:6379> get myhi
"hello"

SETEX key seconds value

设置key对应字符串value,并且设置key在给定的seconds时间之后超时过期。这个命令等效于执行下面的命令:

SET mykey value
EXPIRE mykey seconds

SETEX是原子的,也可以通过把上面两个命令放到MULTI/EXEC块中执行的方式重现。相比连续执行上面两个命令,它更快,因为当Redis当做缓存使用时,这个操作更加常用。

127.0.0.1:6379> SETEX mykey 10 "helloworld"
OK
127.0.0.1:6379> TTL mykey
(integer) 7
127.0.0.1:6379> TTL mykey
(integer) 6
127.0.0.1:6379> GET mykey
(nil)

3.1.8.案例:getset

GETSET key value

自动将key对应到value并且返回原来key对应的value。如果key存在但是对应的value不是字符串,就返回错误。

设计模式

  • GETSET可以和INCR一起使用实现支持重置的计数功能。举个例子:每当有事件发生的时候,一段程序都会调用INCR给key mycounter加1,但是有时我们需要获取计数器的值,并且自动将其重置为0。这可以通过GETSET mycounter “0”来实现:

返回值

  • 返回之前的旧值,如果之前Key不存在将返回nil
127.0.0.1:6379> INCR mycounter
(integer) 1
127.0.0.1:6379> GETSET mycounter "0"
"1"
127.0.0.1:6379> GET mycounter
"0"

3.2.Redis列表(List)

Redis 列表的底层存储结构,其实是一个被称为快速链表(quicklist)的结构。当列表中存储的元素较少时,Redis 会使用一块连续的内存来存储这些元素,这个连续的结构被称为 ziplist(压缩列表),它将所有的元素紧挨着一起存储。

压缩列表是 Redis 为节省内存而开发的,它是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表了可以包含任意多个节点,每个节点都可以保存一个字符数组或者整数值。

而当数据量较大时,Redis 列表就会是用 quicklist(快速链表)存储元素。Redis 之所以采用两种方法相结合的方式来存储元素。这是因为单独使用普通链表存储元素时,所需的空间较大,会造成存储空间的浪费。因此采用了链表和压缩列表相结合的方式,也就是 quicklist + ziplist,结构如下图:

如上图 将多个 ziplist 使用双向指针串联起来,这样既能满足快速插入、删除的特性,又节省了一部分存储空间。

应用场景:实现微信公众号订阅的消息,例如关注了某个人的公众号,只要他们发布了新文章,就会安装进我的List,lpush likearticle到个人id,查看自己的号订阅的全部文章,则类似分页,0~10就是一次显示10条,通过 lrange likearticle:用户id 0 9 实现即可

3.2.1.Redis 列表命令

下表列出了列表相关的基本命令:

命令及描述
BLPOP key1 [key2 ] timeout
移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
BRPOP key1 [key2 ] timeout
移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
BRPOPLPUSH source destination timeout
从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
LINDEX key index
通过索引获取列表中的元素
LINSERT key BEFORE|AFTER pivot value
在列表的元素前或者后插入元素
LLEN key
获取列表长度
LPOP key
移出并获取列表的第一个元素
LPUSH key value1 [value2]
将一个或多个值插入到列表头部
LPUSHX key value
将一个值插入到已存在的列表头部
LRANGE key start stop
获取列表指定范围内的元素
LREM key count value
移除列表元素
LSET key index value
通过索引设置列表元素的值
LTRIM key start stop
对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
RPOP key
移除列表的最后一个元素,返回值为移除的元素。
RPOPLPUSH source destination
移除列表的最后一个元素,并将该元素添加到另一个列表并返回
RPUSH key value1 [value2]
在列表中添加一个或多个值到列表尾部
RPUSHX key value
为已存在的列表添加值

3.2.2.案例演示

LPUSH key value [value ...]

将所有指定的值插入到存于 key 的列表的头部。如果 key 不存在,那么在进行 push 操作前会创建一个空列表。 如果 key 对应的值不是一个 list 的话,那么会返回一个错误。

可以使用一个命令把多个元素 push 进入列表,只需在命令末尾加上多个指定的参数。元素是从最左端的到最右端的、一个接一个被插入到 list 的头部。 所以对于这个命令例子 LPUSH mylist a b c,返回的列表是 c 为第一个元素, b 为第二个元素, a 为第三个元素。

返回值

  • 在 push 操作后的 list 长度。
127.0.0.1:6379> LPUSH mylist "world"
(integer) 1
127.0.0.1:6379> LPUSH mylist "hello"
(integer) 2127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "world"
127.0.0.1:6379>

RPUSH key value [value ...]

向存于 key 的列表的尾部插入所有指定的值。如果 key 不存在,那么会创建一个空的列表然后再进行 push 操作。 当 key 保存的不是一个列表,那么会返回一个错误。可以使用一个命令把多个元素打入队列,只需要在命令后面指定多个参数。元素是从左到右一个接一个从列表尾部插入。 比如命令 RPUSH mylist a b c 会返回一个列表,其第一个元素是 a ,第二个元素是 b ,第三个元素是 c。

返回值

  • 在 push 操作后的列表长度。
127.0.0.1:6379> RPUSH mylist2 "hello"
(integer) 1
127.0.0.1:6379> RPUSH mylist2 "world"
(integer) 2
127.0.0.1:6379> LRANGE mylist2 0 -1
1) "hello"
2) "world"
127.0.0.1:6379>

LRANGE key start stop

返回存储在 key 的列表里指定范围内的元素。 start 和 end 偏移量都是基于0的下标,即list的第一个元素下标是0(list的表头),第二个元素下标是1,以此类推。偏移量也可以是负数,表示偏移量是从list尾部开始计数。 例如, -1 表示列表的最后一个元素,-2 是倒数第二个,以此类推。

127.0.0.1:6379> RPUSH mylist "ONE"
(integer) 1
127.0.0.1:6379> RPUSH mylist "two"
(integer) 2
127.0.0.1:6379> RPUSH mylist "three"
(integer) 3
127.0.0.1:6379>
127.0.0.1:6379> LRANGE mylist 0 -1
1) "ONE"
2) "two"
3) "three"
127.0.0.1:6379> LRANGE mylist 1 3
1) "two"
2) "three"

LPOP key

移除并且返回 key 对应的 list 的第一个元素。

返回值

  • 返回第一个元素的值,或者当 key 不存在时返回 nil。
127.0.0.1:6379> RPUSH mylist "ONE"
(integer) 1
127.0.0.1:6379> RPUSH mylist "two"
(integer) 2
127.0.0.1:6379> RPUSH mylist "three"
(integer) 3
127.0.0.1:6379> LRANGE mylist 0 -1
1) "ONE"
2) "two"
3) "three"
127.0.0.1:6379> LPOP mylist
"ONE"
127.0.0.1:6379> LRANGE mylist 0 -1
1) "two"
2) "three"

RPOP key

移除并返回存于 key 的 list 的最后一个元素。

返回值

  • 最后一个元素的值,或者当 key 不存在的时候返回 nil。
127.0.0.1:6379> RPUSH list1 "a"
(integer) 1
127.0.0.1:6379> RPUSH list1 "b"
(integer) 2
127.0.0.1:6379> RPUSH list1 "c"
(integer) 3
127.0.0.1:6379>
127.0.0.1:6379> LRANGE list1 0 -1
1) "a"
2) "b"
3) "c"
127.0.0.1:6379> RPOP list1
"c"

LINDEX key index:按照索引下标获得元素

返回列表里的元素的索引 index 存储在 key 里面。 下标是从0开始索引的,所以 0 是表示第一个元素, 1 表示第二个元素,并以此类推。 负数索引用于指定从列表尾部开始索引的元素。在这种方法下,-1 表示最后一个元素,-2 表示倒数第二个元素,并以此往前推。

当 key 位置的值不是一个列表的时候,会返回一个error。

返回值

  • 请求的对应元素,或者当 index 超过范围的时候返回 nil。
127.0.0.1:6379> LINDEX mylist 0
"two"
127.0.0.1:6379>
127.0.0.1:6379> LINDEX mylist 1
"three"
127.0.0.1:6379> LINDEX mylist 2
(nil)

LLEN key:获取列表中元素的个数

返回存储在 key 里的list的长度。 如果 key 不存在,那么就被看作是空list,并且返回长度为 0。 当存储在 key 里的值不是一个list的话,会返回error。

返回值

  • key对应的list的长度。
127.0.0.1:6379> LPUSH mylist "World"
(integer) 1
127.0.0.1:6379> LPUSH mylist "Hello"
(integer) 2
127.0.0.1:6379> LLEN mylist
(integer) 2
127.0.0.1:6379> LRANGE mylist 0 -1
1) "Hello"
2) "World"

LREM key count value

从存于 key 的列表里移除前 count 次出现的值为 value 的元素。 这个 count 参数通过下面几种方式影响这个操作:

  • count > 0: 从头往尾移除值为 value 的元素。
  • count < 0: 从尾往头移除值为 value 的元素。
  • count = 0: 移除所有值为 value 的元素。

比如, LREM list -2 “hello” 会从存于 list 的列表里移除最后两个出现的 “hello”。

需要注意的是,如果list里没有存在key就会被当作空list处理,所以当 key 不存在的时候,这个命令会返回 0。

返回值

  • 被移除的元素个数。
127.0.0.1:6379>  RPUSH mylist "hello"
(integer) 1
127.0.0.1:6379> RPUSH mylist "world"
(integer) 2
127.0.0.1:6379> RPUSH mylist "hi"
(integer) 3
127.0.0.1:6379> RPUSH mylist "hello"
(integer) 4
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "world"
3) "hi"
4) "hello"
127.0.0.1:6379> LREM mylist -2 "hello"
(integer) 2
127.0.0.1:6379> LRANGE mylist 0 -1
1) "world"
2) "hi"

LTRIM key start stop

修剪(trim)一个已存在的 list,这样 list 就会只包含指定范围的指定元素。start 和 stop 都是由0开始计数的, 这里的 0 是列表里的第一个元素(表头),1 是第二个元素,以此类推。例如: LTRIM foobar 0 2 将会对存储在 foobar 的列表进行修剪,只保留列表里的前3个元素。

start 和 end 也可以用负数来表示与表尾的偏移量,比如 -1 表示列表里的最后一个元素, -2 表示倒数第二个,等等。超过范围的下标并不会产生错误:如果 start 超过列表尾部,或者 start > end,结果会是列表变成空表(即该 key 会被移除)。 如果 end 超过列表尾部,Redis 会将其当作列表的最后一个元素。LTRIM 的一个常见用法是和 LPUSH / RPUSH 一起使用。 例如:

  • LPUSH mylist someelement
  • LTRIM mylist 0 99

这一对命令会将一个新的元素 push 进列表里,并保证该列表不会增长到超过100个元素。这个是很有用的,比如当用 Redis 来存储日志。 需要特别注意的是,当用这种方式来使用 LTRIM 的时候,操作的复杂度是 O(1) , 因为平均情况下,每次只有一个元素会被移除。

127.0.0.1:6379> RPUSH mylist "a"
(integer) 1
127.0.0.1:6379> RPUSH mylist "b"
(integer) 2
127.0.0.1:6379> RPUSH mylist "c"
(integer) 3
127.0.0.1:6379>
127.0.0.1:6379> LRANGE mylist 0 -1
1) "a"
2) "b"
3) "c"
127.0.0.1:6379> LTRIM mylist 1 2
OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "b"
2) "c"
127.0.0.1:6379>

RPOPLPUSH source destination

移除列表的最后一个元素,并将该元素添加到另一个列表并返回

返回值

  • 被移除和放入的元素
127.0.0.1:6379> LPUSH mylist "a"
(integer) 1
127.0.0.1:6379> LPUSH mylist "b"
(integer) 2
127.0.0.1:6379> LPUSH mylist "c"
(integer) 3
127.0.0.1:6379> LRANGE mylist 0 -1
1) "c"
2) "b"
3) "a"
127.0.0.1:6379> RPOPLPUSH mylist mylist2
"a"
127.0.0.1:6379> LRANGE mylist 0 -1
1) "c"
2) "b"
127.0.0.1:6379>

LSET key index value

设置 index 位置的list元素的值为 value。当index超出范围时会返回一个error。(指定索引设置对应的值)

127.0.0.1:6379> RPUSH mylist3 "yxy"
(integer) 1
127.0.0.1:6379> RPUSH mylist3 "wwj"
(integer) 2
127.0.0.1:6379> RPUSH mylist3 "ln"
(integer) 3127.0.0.1:6379> LRANGE mylist3 0 -1
1) "yxy"
2) "wwj"
3) "ln"
127.0.0.1:6379> LSET mylist3 0 "a"
OK
127.0.0.1:6379> LSET mylist3 1 "b"
OK
127.0.0.1:6379> LSET mylist3 2 "c"
OK
127.0.0.1:6379> LRANGE mylist3 0 -1
1) "a"
2) "b"
3) "c"
127.0.0.1:6379>

LINSERT key BEFORE|AFTER pivot value

在list某个已有值的前后再添加具体值

  • 把 value 插入存于 key 的列表中在基准值 pivot 的前面或后面。
  • 当 key 不存在时,这个list会被看作是空list,任何操作都不会发生。
  • 当 key 存在,但保存的不是一个list的时候,会返回error。

返回值

  • 经过插入操作后的list长度,或者当 pivot 值找不到的时候返回 -1。
127.0.0.1:6379> RPUSH mylist "hello"
(integer) 1
127.0.0.1:6379> RPUSH mylist "world"
(integer) 2
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "world"
# 在world之前插入hi 127.0.0.1:6379> LINSERT mylist BEFORE "world" "hi" (integer) 3 127.0.0.1:6379> LRANGE mylist 0 -1 1) "hello" 2) "hi" 3) "world" # 在world之后插入tom 127.0.0.1:6379> LINSERT mylist after "world" "tom" (integer) 4 127.0.0.1:6379> LRANGE mylist 0 -1 1) "hello" 2) "hi" 3) "world" 4) "tom" 127.0.0.1:6379>

3.3.Redis哈希(Hash)

Redis hash(哈希散列)是由字符类型的 field(字段)和 value 组成的哈希映射表结构(也称散列表),它非常类似于表格结构。在 hash 类型中,field 与 value 一一对应,且不允许重复。Redis hash 特别适合于存储对象。一个 filed/value 可以看做是表格中一条数据记录;而一个 key 可以对应多条数据。下面举一个例子,使用 hash 类型存储表格中的数据,这里以user为 key, id:1为字段,name:yxy为 value:

用户表格
idname
1 yxy
2 wwj

命令实例演示:

#以user为key,设置 id+序号为字段,name+名字为值
127.0.0.1:6379> HMSET user id:1 name:yxy id:2 name:wwj
OK
# 查询 user 这个key下所有的数据,并以字符串的形式将值返回
127.0.0.1:6379>  HGETALL user
1) "id:1"
2) "name:yxy"
3) "id:2"
4) "name:wwj"
127.0.0.1:6379>

注意:当我们对 value 进行查询时,这个值只能以字符串的形式返回。通过上述方法,我们就把表格中的数据存储在了内存中。Redis hash 的存储结构如下图所示:

一个 hash 类型的 key 最多可以存储 2^32-1(约 40 亿个)字段/值。同时 Redis hash 会为这个 key 额外储存一些附加的管理信息,比如这个键的类型、最后一次访问这个键的时间等,所以 hash 键越来越多时,Redis 耗费在管理信息方面的内存就越多。当 hash 类型移除最后一个元素后,该存储结构就会被自动删除,其占用内存也会被系统回收。

3.3.1.初识hash类型

hash 类型是 Redis 常用数据类型之一,其底层存储结构有两种实现方式。第一种,当存储的数据量较少的时,hash 采用 ziplist 作为底层存储结构,此时要求符合以下两个条件:

  • 哈希对象保存的所有键值对(键和值)的字符串长度总和小于 64 个字节。
  • 哈希对象保存的键值对数量要小于 512 个。

当无法满足上述条件时,hash 就会采用第二种方式来存储数据,也就是 dict(字典结构),该结构类似于 Java 的 HashMap,是一个无序的字典,并采用了数组和链表相结合的方式存储数据。在 Redis 中,dict 是基于哈希表算法实现的,因此其查找性能非常高效,其时间复杂度为 O(1)。
哈希表又称散列表,其初衷是将数据映射到数组中的某个位置上,这样就能够通过数组下标来访问该数据,从而提高数据的查找效率。下面通过案例说明什么是哈希表。
现在有 1、5、7、三个数字,需要把这三个数字映射到数组中,由于哈希表规定必须使用下标来访问数据,因此你需要构建一个 0 到 7 的数组,如下所示:

上图将需要查找的数字,在相应的下标数组上标记出来,它们之间一一对应。虽然这样做能实现元素的查找,但却很浪费存储空间,效率很低。而如果采用哈希表的话,我们只需要申请一个长度为 3 的数组(与待查找的元素个数相同),如下图所示:

将 1、5、7 分别对数组长度 3 做取模运算,然后把它们指向运算结果对应的数组槽位,这样就把一组离散的数据映射到了连续的空间中,从而在最大限度上提高了空间的利用率,并且也提高了元素的查找效率。但是你可能会发现一个问题,数字 1、7 竟然映射到同一个槽位上,这样就导致其中一个数字无法查找到。上述这种情况在实际中也会遇到,称为“哈希冲突”或者“哈希碰撞”。

“哈希冲突”的解决方式很多,比如开放地址法、链表地址法,再次散列法等,而 Redis 采用是链表地址法。故而这里只对链表地址法做简单介绍,很容易理解,这种方法就是将有冲突的数据使用链表把它们串联起来,这样即使发生了冲突,也可以将数据存储在一起,最后,通过遍历链表的方式就找到上述发生“冲突”的数据。如下图:

应用场景:早期JD购物车就是采用hash设计,目前已经不采用,实现如下:

  1. 新增商品 → hset shopcar:uid001 4396 1
  2. 增加商品数量 → hincrby shopcar:uid001 4398 1
  3. 商品总数 → hlen shopcar:uid001
  4. 全部选择 → hgetall shopcar:uid001

3.3.2.Redis hash 命令

下表列出了 redis hash 基本的相关命令:

命令命令对应描述
HDEL key field1 [field2] 删除一个或多个哈希表字段
HEXISTS key field 查看哈希表 key 中,指定的字段是否存在。
HGET key field 获取存储在哈希表中指定字段的值。
HGETALL key 获取在哈希表中指定 key 的所有字段和值
HINCRBY key field increment 为哈希表 key 中的指定字段的整数值加上增量 increment 。
HINCRBYFLOAT key field increment 为哈希表 key 中的指定字段的浮点数值加上增量 increment 。
HKEYS key 获取所有哈希表中的字段
HLEN key 获取哈希表中字段的数量
HMGET key field1 [field2] 获取所有给定字段的值
HMSET key field1 value1 [field2 value2 ] 同时将多个 field-value (域-值)对设置到哈希表 key 中。
HSET key field value 将哈希表 key 中的字段 field 的值设为 value 。
HSETNX key field value 只有在字段 field 不存在时,设置哈希表字段的值。
HVALS key 获取哈希表中所有值。
HSCAN key cursor [MATCH pattern] [COUNT count] 迭代哈希表中的键值对。

3.3.3.Redis hash 命令演示

HSET key field value

设置 key 指定的哈希集中指定字段的值。

  • 如果 key 指定的哈希集不存在,会创建一个新的哈希集并与 key 关联。
  • 如果字段在哈希集中存在,它将被重写。

返回值含义如下

  • 1如果field是一个新的字段
  • 0如果field原来在map里面已经存在
# 创建哈希名为myhash,其中user为字段名,tom为值
127.0.0.1:6379> HSET myhash user tom
(integer) 1
# 查看myhash这key中字典user的值
127.0.0.1:6379> HGET myhash user
"tom"
127.0.0.1:6379>

HGET key field

返回 key 指定的哈希集中该字段所关联的值

返回值

  • 该字段所关联的值。当字段不存在或者 key 不存在时返回nil。
127.0.0.1:6379> HSET student id 12
(integer) 1
127.0.0.1:6379> HGET student id
"12"
127.0.0.1:6379>

HMGET key field [field ...]

返回 key 指定的哈希集中指定字段的值。

对于哈希集中不存在的每个字段,返回 nil 值。因为不存在的keys被认为是一个空的哈希集,对一个不存在的 key 执行 HMGET 将返回一个只含有 nil 值的列表

返回值

  • 含有给定字段及其值的列表,并保持与请求相同的顺序。
127.0.0.1:6379> HSET myhash field1 "Hello"
(integer) 0
127.0.0.1:6379> HSET myhash field2 "World"
(integer) 1
127.0.0.1:6379> HMGET myhash field1 field2
1) "Hello"
2) "World"
127.0.0.1:6379>

HGETALL key

返回 key 指定的哈希集中所有的字段和值。返回值中,每个字段名的下一个是它的值,所以返回值的长度是哈希集大小的两倍

返回值

  • 哈希集中字段和值的列表。当 key 指定的哈希集不存在时返回空列表。
127.0.0.1:6379> HGETALL myhash
1) "field1"
2) "Hello"
3) "field2"
4) "World"

HDEL key field [field ...]

从 key 指定的哈希集中移除指定的域。在哈希集中不存在的域将被忽略。如果 key 指定的哈希集不存在,它将被认为是一个空的哈希集,该命令将返回0。

返回值

  • 返回从哈希集中成功移除的域的数量,不包括指出但不存在的那些域
127.0.0.1:6379> HSET myhash id 1
(integer) 1
127.0.0.1:6379> HDEL myhash id
(integer) 1
127.0.0.1:6379> HDEL myhash name
(integer) 0
127.0.0.1:6379>

HLEN key

返回 key 指定的哈希集包含的字段的数量(获取某个key内的全部数量)。

返回值

  • 哈希集中字段的数量,当 key 指定的哈希集不存在时返回 0
7.0.0.1:6379> HSET myhash field2 "World"
(integer) 1
127.0.0.1:6379> HLEN myhash
(integer) 2
127.0.0.1:6379>

HEXISTS key field

返回hash里面field是否存在

返回值含义如下:

  • 1 hash里面包含该field。
  • 0 hash里面不包含该field或者key不存在。
127.0.0.1:6379> HSET myhash id 1
(integer) 1
127.0.0.1:6379> HEXISTS myhash id
(integer) 1
127.0.0.1:6379> HEXISTS myhash name
(integer) 0

HKEYS key

返回 key 指定的哈希集中所有字段的名字。

返回值

  • 哈希集中的字段列表,当 key 指定的哈希集不存在时返回空列表。
127.0.0.1:6379> HSET myhash field1 "Hello"
(integer) 1
127.0.0.1:6379> HSET myhash field2 "World"
(integer) 1
127.0.0.1:6379> HKEYS myhash
1) "field1"
2) "field2"
127.0.0.1:6379>

HVALS key

返回 key 指定的哈希集中所有字段的值。

返回值

  • 哈希集中的值的列表,当 key 指定的哈希集不存在时返回空列表。
127.0.0.1:6379> HSET myhash field1 "Hello"
(integer) 1
127.0.0.1:6379> HSET myhash field2 "World"
(integer) 1
127.0.0.1:6379> HKEYS myhash
1) "field1"
2) "field2"
127.0.0.1:6379> HVALS myhash
1) "Hello"
2) "World"

HINCRBY key field increment

增加 key 指定的哈希集中指定字段的数值。如果 key 不存在,会创建一个新的哈希集并与 key 关联。如果字段不存在,则字段的值在该操作执行前被设置为 0,HINCRBY 支持的值的范围限定在 64位 有符号整数。

返回值

  • 增值操作执行后的该字段的值。
127.0.0.1:6379> HSET myhash f1 5
(integer) 1
127.0.0.1:6379> HINCRBY myhash f1 1
(integer) 6
127.0.0.1:6379> HINCRBY myhash f1 -10
(integer) -4
127.0.0.1:6379>

HINCRBYFLOAT key field increment

为指定key的hash的field字段值执行float类型的increment加。如果field不存在,则在执行该操作前设置为0.如果出现下列情况之一,则返回错误:

  • field的值包含的类型错误(不是字符串)。
  • 当前field或者increment不能解析为一个float类型。

此命令的确切行为与INCRBYFLOAT命令相同,请参阅INCRBYFLOAT命令获取更多信息。

返回值

  • field执行increment加后的值
127.0.0.1:6379> HSET mykey f1 3.14
(integer) 1
127.0.0.1:6379> HINCRBYFLOAT mykey f1 0.1
"3.24"
127.0.0.1:6379> HSET mykey f2 'tom'
(integer) 1
127.0.0.1:6379> HINCRBYFLOAT mykey f2 0.2
(error) ERR hash value is not a float
127.0.0.1:6379>

HSETNX key field value

只在 key 指定的哈希集中不存在指定的字段时,设置字段的值。如果 key 指定的哈希集不存在,会创建一个新的哈希集并与 key 关联。如果字段已存在,该操作无效果。

返回值含义如下

  • 1:如果字段是个新的字段,并成功赋值
  • 0:如果哈希集中已存在该字段,没有操作被执行
# 设置myhashkay中的字段名为f1,值为hello
127.0.0.1:6379> HSETNX myhash f1 "Hello"
(integer) 1
# 再次设置myhashkay中的字段名为f1,值为World返回0,表示改字段已经存在
127.0.0.1:6379> HSETNX myhash f1 "World"
(integer) 0
127.0.0.1:6379> HGET myhash f1
"Hello"
127.0.0.1:6379>

3.4.Redis集合(Set)

3.4.1.初识Redis Set

Redis set(集合)遵循无序排列的规则,集合中的每一个元素都是字符串类型,且不可重复。Redis set 是通过哈希映射表实现的,所以它的添加、删除、查找操作的时间复杂度为 O(1)。集合中最多可容纳 2^32 - 1 个成员(40 多亿个)。和其他数据类型一样,当集合中最后一个成员被删除时,存储成员所用的数据结构也会被自动删除。集合有一个非常重要的特性就是“自动去重”,这使得它可以适用于许多场景,比如过滤掉已中奖用户的 id,保证该用户不会被第二次抽中。

Redis set 采用了两种方式相结合的底层存储结构,分别是 intset(整型数组)与 hash table(哈希表),当 set 存储的数据满足以下要求时,使用 intset 结构:

  • 集合内保存的所有成员都是整数值;
  • 集合内保存的成员数量不超过 512 个。

当不满足上述要求时,则使用 hash table 结构。Redis 中 intset 的结构体定义如下:

typedf struct inset{
    uint32_t encoding;//指定编码方式,默认为INSET_ENC_INT16
    uint32_t length;//集合内成员的总个数
    int8_t contents[];//实际存储成员的数组,并且数组中的数值从小到大依次排列
}inset;
  • encoding:用来指定编码格式,共有三种,分别是 INTSET_ENC_INT16、INSET_ENC_INT32 和 INSET_ENC_INT64,它们对应不同的数值范围。Redis 为了尽可能地节省内存,它会根据插入数据的大小来选择不同的编码格式。
  • length:集合内成员的数量,记录 contents 数组中共有多少个成员。
  • contents:存储成员的数组,数组中的成员从小到大依次排列,且不允许重复。

intset 结构示意图如下所示:

上一节已经对哈希表原理做了说明, set 的哈希表与其相似,这里不再赘述。

应用场景:

  • QQ推荐可能认识的人:SDIFF myset2 myset1
  • 微信朋友圈点赞查看同赞朋友
  • 微信抽奖小程序

3.4.2.Redis Set命令

下表列出了 Redis 集合基本命令:

命令命令描述
SADD key member1 [member2] 向集合添加一个或多个成员
SCARD key 获取集合的成员数
SDIFF key1 [key2] 返回第一个集合与其他集合之间的差异。
SDIFFSTORE destination key1 [key2] 返回给定所有集合的差集并存储在 destination 中
SINTER key1 [key2] 返回给定所有集合的交集
SINTERSTORE destination key1 [key2] 返回给定所有集合的交集并存储在 destination 中
SISMEMBER key member 判断 member 元素是否是集合 key 的成员
SMEMBERS key 返回集合中的所有成员
SMOVE source destination member 将 member 元素从 source 集合移动到 destination 集合
SPOP key 移除并返回集合中的一个随机元素
SRANDMEMBER key [count] 返回集合中一个或多个随机数
SREM key member1 [member2] 移除集合中一个或多个成员
SUNION key1 [key2] 返回所有给定集合的并集
SUNIONSTORE destination key1 [key2] 所有给定集合的并集存储在 destination 集合中
SSCAN key cursor [MATCH pattern] [COUNT count] 迭代集合中的元素

 

3.4.3.Redis Set命令演示

SADD key member [member ...]

添加一个或多个指定的member元素到集合的 key中.指定的一个或者多个元素member 如果已经在集合key中存在则忽略.如果集合key 不存在,则新建集合key,并添加member元素到集合key中.如果key 的类型不是集合则返回错误.

返回值

  • 返回新成功添加到集合里元素的数量,不包括已经存在于集合中的
# 创建集合myset给里面添加三个值"hello" "world" "hi"
127.0.0.1:6379> SADD myset "hello" "world" "hi"
(integer) 3
# 查看集合信息
127.0.0.1:6379> SMEMBERS myset
1) "hi"
2) "world"
3) "hello"
# 给集合添加world元素,由于之前已存在,所以忽略
127.0.0.1:6379> SADD myset "world"
(integer) 0
127.0.0.1:6379> SMEMBERS myset
1) "hi"
2) "world"
3) "hello"
127.0.0.1:6379>

SMEMBERS key

遍历输出集合中的所有元素

返回值

  • 集合中的所有元素.

SISMEMBER key member

判断元素是否在集合中

返回值详细说明:

  • 如果member元素是集合key的成员,则返回1
  • 如果member元素不是key的成员,或者集合key不存在,则返回0
# 给集合中添加两个元素
127.0.0.1:6379> SADD myset1 "one" "two"
(integer) 2
# 判断集合中是否存在“one”,存在返回了1
127.0.0.1:6379> SISMEMBER myset1 "one"
(integer) 1
# 判断集合中是否存在“two”
127.0.0.1:6379> SISMEMBER myset1 "two"
(integer) 1
# 判断集合中是否存在“three”,不存在返回了0
127.0.0.1:6379> SISMEMBER myset1 "three"
(integer) 0
127.0.0.1:6379>

SREM key member [member ...]

在key集合中移除指定的元素. 如果指定的元素不是key集合中的元素则忽略 如果key集合不存在则被视为一个空的集合,该命令返回0.

如果key的类型不是一个集合,则返回错误.

返回值

  • 从集合中移除元素的个数,不包括不存在的成员.
# 创建集合,指定三个内容
127.0.0.1:6379> SADD myset2 "a" "b" "c"
(integer) 3
127.0.0.1:6379> SMEMBERS myset2
1) "a"
2) "b"
3) "c"
# 删除集合中元素a
127.0.0.1:6379> SREM myset2 "a"
(integer) 1
# 再次查看a不存在了
127.0.0.1:6379> SMEMBERS myset2
1) "b"
2) "c"
127.0.0.1:6379>

SCARD key

返回集合存储的key的基数 (集合元素的数量).

返回值

  • 集合的基数(元素的数量),如果key不存在,则返回 0.
# 创建集合添加三个元素
127.0.0.1:6379> SADD myset3 "a" "b" "c"
(integer) 3
# 查看集合中的元素
127.0.0.1:6379> SMEMBERS myset3
1) "a"
2) "b"
3) "c"
# 输出集合中元素的个数
127.0.0.1:6379> SCARD myset3
(integer) 3
127.0.0.1:6379>

SPOP key [count]

从存储在key的集合中移除并返回一个或多个随机元素(从集合中随机弹出一个元素,出一个删一个,也可以指定元素个数删除)。

返回值

  • 被删除的元素,或者当key不存在时返回nil
127.0.0.1:6379> SMEMBERS myset3
1) "a"
2) "b"
3) "c"
127.0.0.1:6379>
# 随机删除一个元素
127.0.0.1:6379> SPOP myset3
"a"
127.0.0.1:6379> SMEMBERS myset3
1) "b"
2) "c"
# 指定删除两个元素
127.0.0.1:6379> SPOP myset3 2
1) "b"
2) "c"
127.0.0.1:6379> SMEMBERS myset3
(empty array)
127.0.0.1:6379>

SMOVE source destination member(将一个集合中的元素,移动到另一个集合中去)

将member从source集合移动到destination集合中. 对于其他的客户端,在特定的时间元素将会作为source或者destination集合的成员出现.

如果source 集合不存在或者不包含指定的元素,这smove命令不执行任何操作并且返回0.否则对象将会从source集合中移除,并添加到destination集合中去,如果destination集合已经存在该元素,则smove命令仅将该元素充source集合中移除. 如果source 和destination不是集合类型,则返回错误.

返回值

  • 如果该元素成功移除,返回1
  • 如果该元素不是 source集合成员,无任何操作,则返回0.
# 创建集合myset1,添加元素 a b
127.0.0.1:6379> SADD myset1 "a" "b"
(integer) 2
# 创建集合myset2,添加元素c
127.0.0.1:6379> SADD myset2 "c"
(integer) 1
# 将集合,myset1中的a元素移动到myset2中去
127.0.0.1:6379> SMOVE myset1 myset2 "a"
(integer) 1
# myset1就没有a元素了
127.0.0.1:6379> SMEMBERS myset1
1) "b"
# 而myset2中元素就变为了a c
127.0.0.1:6379> SMEMBERS myset2
1) "a"
2) "c"
127.0.0.1:6379>

SDIFF key [key ...]

返回一个集合与给定集合的差集的元素.

# 创建myset1集合
127.0.0.1:6379> SADD myset1 "a" "b" "c"
(integer) 3
# 创建myset2集合
127.0.0.1:6379> SADD myset2 "c" "d" "e"
(integer) 3
# 求集合myset1和myset2的差集
127.0.0.1:6379> SDIFF myset1 myset2
1) "a"
2) "b"
127.0.0.1:6379>

SUNION key [key ...]

求两个集合的并集

127.0.0.1:6379> SADD myset1 "a" "b" "c"
(integer) 3
127.0.0.1:6379> SADD myset2 "c" "d" "e"
(integer) 3
# 求myset1和myset2的并集,
127.0.0.1:6379> SUNION myset1 myset2
1) "e"
2) "b"
3) "c"
4) "a"
5) "d"

SINTER key [key ...]

求指定集合的交集.

127.0.0.1:6379> SADD myset1 "a" "b" "c"
(integer) 3
127.0.0.1:6379> SADD myset2 "c" "d" "e"
(integer) 3
127.0.0.1:6379> SINTER myset1 myset2
1) "c"

SINTERCARD numkeys key [key ...] [LIMIT 限制]

这个是Redis7.0新版本增加的,它不返回结果集,而是仅返回结果的基数。返回由所有给定集的交集产生的集的基数。不存在的键被认为是空集。如果其中一个键是空集,则结果集也为空(因为集与空集的交集总是导致空集)。默认情况下,该命令计算所有给定集的交集的基数。当提供可选LIMIT参数(默认为 0,表示无限制)时,如果交集基数在计算中途达到 limit,则算法将退出并产生 limit 作为基数。这样的实现确保了限制低于实际交集基数的查询的显着加速。

返回值

  • 结果交集中的元素数。
# 创建几个myset1
127.0.0.1:6379> SADD myset1 "a" "b" "c"
(integer) 3
# 创建几个myset2
127.0.0.1:6379> SADD myset2 "a" "b" "c" "e" "f" "h"
(integer) 6
# 求交集
127.0.0.1:6379> SINTER myset1 myset2
1) "a"
2) "b"
3) "c"
# 指定求2个集合,myset1和myset2交集的元素个数
127.0.0.1:6379> SINTERCARD 2 myset1 myset2
(integer) 3
127.0.0.1:6379> SINTERCARD 2 myset1 myset2 limit 2
(integer) 2

3.5.Redis有序集合Zset(sorted set)

3.5.1.认识Zset

Redis zset(有序集合)中的元素是有序排列的,它和 set 集合的相同之处在于,集合中的每一个成员都是字符串类型,并且不允许重复;而它们最大区别是,有序集合是有序的,set 是无序的,这是因为有序集合中每个成员都会关联一个 double(双精度浮点数)类型的 score (分数值),Redis 正是通过 score 实现了对集合成员的排序。
zset 适用于排行榜类型的业务场景,比如 QQ 音乐排行榜、用户贡献榜等。在音乐排行榜单中,可以将歌曲的点击次数作为 score 值,把歌曲的名字作为 value 值,通过对 score 排序就可以得出歌曲“热度榜单”。

Redis 使用以下命令创建一个有序集合:

127.0.0.1:6379> ZADD key score member [score member ...]  
  • key:指定一个键名;
  • score:分数值,用来描述  member,它是实现排序的关键;
  • member:要添加的成员(元素)。

当 key 不存在时,将会创建一个新的有序集合,并把分数/成员(score/member)添加到有序集合中;当 key 存在时,但 key 并非 zset 类型,此时就不能完成添加成员的操作,同时会返回一个错误提示。

注意:在有序集合中,成员是唯一存在的,但是分数(score)却可以重复。有序集合的最大的成员数为 2^32 - 1 (大约 40 多亿个)。

有序集合(zset)同样使用了两种不同的存储结构,分别是 zipList(压缩列表)和 skipList(跳跃列表)

1) 压缩列表

当 zset 满足以下条件时使用压缩列表:

  • 成员的数量小于128 个;
  • 每个 member (成员)的字符串长度都小于 64 个字节。

下面对压缩列表做简单介绍,它由以下五部分组成:

 

zset 使用压缩列表保存数据时,entry 的第一个节点保存 member,第二个节点保存 score。依次类推,集合中的所有成员最终会按照 score 从小到大排列。

2) 跳跃列表

当有序结合不满足使用压缩列表的条件时,就会使用 skipList 结构来存储数据。跳跃列表(skipList)又称“跳表”是一种基于链表实现的随机化数据结构,其插入、删除、查找的时间复杂度均为 O(logN)。从名字可以看出“跳跃列表”,并不同于一般的普通链表,它的结构较为复杂.
在 Redis 中一个 skipList 节点最高可以达到 64 层,一个“跳表”中最多可以存储 2^64 个元素,每个节点都是一个 skiplistNode(跳表节点)。skipList 的结构体定义如下:

typedf struct zskiplist{
    //头节点
    struct zskiplistNode *header;
    //尾节点
    struct zskiplistNode *tail;
    // 跳表中的元素个数
    unsigned long length;
    //表内节点的最大层数
    int level;
}zskiplist;
  • header:指向 skiplist 的头节点指针,通过它可以直接找到跳表的头节点,时间复杂度为 O(1);
  • tail:指向 skiplist 的尾节点指针,通过它可以直接找到跳表的尾节点,时间复杂度为 O(1);
  • length:记录 skiplist 的长度,也就跳表中有多少个元素,但不包括头节点;
  • level:记录当前跳表内所有节点中的最大层数(level);

跳跃列表的每一层都是一个有序的链表,链表中每个节点都包含两个指针,一个指向同一层的下了一个节点,另一个指向下一层的同一个节点。最低层的链表将包含 zset 中的所有元素。如果说一个元素出现在了某一层,那么低于该层的所有层都将包含这个元素,也就说高层是底层的子集。 

3.5.2.Zset常用命令

下表列出了 redis 有序集合的基本命令:

序号命令及描述
ZADD key score1 member1 [score2 member2] 向有序集合添加一个或多个成员,或者更新已存在成员的分数
ZCARD key
获取有序集合的成员数
ZCOUNT key min max
计算在有序集合中指定区间分数的成员数
ZINCRBY key increment member
有序集合中对指定成员的分数加上增量 increment
ZINTERSTORE destination numkeys key [key ...]
计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 destination 中
ZLEXCOUNT key min max
在有序集合中计算指定字典区间内成员数量
ZRANGE key start stop [WITHSCORES]
通过索引区间返回有序集合指定区间内的成员
ZRANGEBYLEX key min max [LIMIT offset count]
通过字典区间返回有序集合的成员
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT]
通过分数返回有序集合指定区间内的成员
ZRANK key member
返回有序集合中指定成员的索引
ZREM key member [member ...]
移除有序集合中的一个或多个成员
ZREMRANGEBYLEX key min max
移除有序集合中给定的字典区间的所有成员
ZREMRANGEBYRANK key start stop
移除有序集合中给定的排名区间的所有成员
ZREMRANGEBYSCORE key min max
移除有序集合中给定的分数区间的所有成员
ZREVRANGE key start stop [WITHSCORES]
返回有序集中指定区间内的成员,通过索引,分数从高到低
ZREVRANGEBYSCORE key max min [WITHSCORES]
返回有序集中指定分数区间内的成员,分数从高到低排序
ZREVRANK key member
返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
ZSCORE key member
返回有序集中,成员的分数值
ZUNIONSTORE destination numkeys key [key ...]
计算给定的一个或多个有序集的并集,并存储在新的 key 中
ZSCAN key cursor [MATCH pattern] [COUNT count]
迭代有序集合中的元素(包括元素成员和元素分值)

3.5.3.Zset常用命令演示

ZADD key [NX|XX] [CH] [INCR] score member [score member ...]

将所有指定成员添加到键为key有序集合(sorted set)里面。 添加时可以指定多个分数/成员(score/member)对。 如果指定添加的成员已经是有序集合里面的成员,则会更新改成员的分数(scrore)并更新到正确的排序位置。如果key不存在,将会创建一个新的有序集合(sorted set)并将分数/成员(score/member)对添加到有序集合,就像原来存在一个空的有序集合一样。如果key存在,但是类型不是有序集合,将会返回一个错误应答。分数值是一个双精度的浮点型数字字符串。+inf-inf都是有效值。

ZADD 命令在key后面分数/成员(score/member)对前面支持一些参数,他们是:

  • XX: 仅仅更新存在的成员,不添加新成员。
  • NX: 不更新存在的成员。只添加新成员。
  • CH: 修改返回值为发生变化的成员总数,原始是返回新添加成员的总数 (CH 是 changed 的意思)。更改的元素是新添加的成员,已经存在的成员更新分数。 所以在命令中指定的成员有相同的分数将不被计算在内。注:在通常情况下,ZADD返回值只计算新添加成员的数量。
  • INCR: 当ZADD指定这个选项时,成员的操作就等同ZINCRBY命令,对成员的分数进行递增操作。

返回值

  • 添加到有序集合的成员数量,不包括已经存在更新分数的成员。
  • 如果指定INCR参数, 返回将会变成bulk-string-reply :成员的新分数(双精度的浮点型数字)字符串。
# 创建一个有序集合
127.0.0.1:6379> ZADD myzset 1 python 2 go 3 java 4 js
(integer) 4
# 获取集合中所有的值
127.0.0.1:6379> ZRANGE myzset 0 -1
1) "python"
2) "go"
3) "java"
4) "js"
# 输出分数对应的成员
127.0.0.1:6379> ZRANGE myzset 0 -1 WITHSCORES
1) "python"
2) "1"
3) "go"
4) "2"
5) "java"
6) "3"
7) "js"
8) "4"
127.0.0.1:6379>

ZRANGE key start stop [WITHSCORES]

返回存储在有序集合key中的指定范围的元素。 返回的元素可以认为是按得分从最低到最高排列(按照元素分数从小到大的顺序)。 如果得分相同,将按字典排序。当你需要元素从最高分到最低分排列时,请参阅ZREVRANGE(相同的得分将使用字典倒序排序)。

参数startstop都是基于零的索引,即0是第一个元素,1是第二个元素,以此类推。 它们也可以是负数,表示从有序集合的末尾的偏移量,其中-1是有序集合的最后一个元素,-2是倒数第二个元素,等等。startstop都是全包含的区间,因此例如ZRANGE myzset 0 1将会返回有序集合的第一个和第二个元素。超出范围的索引不会产生错误。 如果start参数的值大于有序集合中的最大索引,或者start > stop,将会返回一个空列表。 如果stop的值大于有序集合的末尾,Redis会将其视为有序集合的最后一个元素。可以传递WITHSCORES选项,以便将元素的分数与元素一起返回。这样,返回的列表将包含value1,score1,...,valueN,scoreN,而不是value1,...,valueN。 客户端类库可以自由地返回更合适的数据类型(建议:具有值和得分的数组或记录)。

返回值

  • 给定范围内的元素列表(如果指定了WITHSCORES选项,将同时返回它们的得分)。

ZREVRANGE key start stop [WITHSCORES](反转输出,从大到小)

返回有序集key中,指定区间内的成员。其中成员的位置按score值递减(从大到小)来排列。具有相同score值的成员按字典序的反序排列。 除了成员按score值递减的次序排列这一点外。

返回值

  • 指定范围的元素列表(可选是否含有分数)。
127.0.0.1:6379> ZREVRANGE myzset 0 -1
1) "js"
2) "java"
3) "go"
4) "python"
127.0.0.1:6379> ZREVRANGE myzset 0 -1 WITHSCORES
1) "js"
2) "4"
3) "java"
4) "3"
5) "go"
6) "2"
7) "python"
8) "1"
127.0.0.1:6379>

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

获取指定分数范围的元素

返回值

  • 指定分数范围的元素列表(也可以返回他们的分数)。
127.0.0.1:6379> ZRANGEBYSCORE myzset 2 4
1) "go"
2) "java"
3) "js"
# 取集合2到4直接的元素(包含边界),输出值和分值
127.0.0.1:6379> ZRANGEBYSCORE myzset 2 4 WITHSCORES
1) "go"
2) "2"
3) "java"
4) "3"
5) "js"
6) "4"
# 取集合2到4直接的元素(不包含2对应元素),输出值和分值
127.0.0.1:6379> ZRANGEBYSCORE myzset (2 4 WITHSCORES
1) "java"
2) "3"
3) "js"
4) "4"
# 取集合2到4直接的元素(不包含4对应元素),输出值和分值
127.0.0.1:6379> ZRANGEBYSCORE myzset 2 (4 WITHSCORES
1) "go"
2) "2"
3) "java"
4) "3"

ZSCORE key member

获取元素的分数,返回有序集key中,成员member的score值。如果member元素不是有序集key的成员,或key不存在,返回nil。

返回值

  • member成员的score值(double型浮点数),以字符串形式表示。
# 创建有序集合,设置元素python的分值为100
127.0.0.1:6379> ZADD myzset10 100 "python"
(integer) 1
# 查看元素python的分值
127.0.0.1:6379> ZSCORE myzset10 "python"
"100"
127.0.0.1:6379>

ZCARD key

获取集合中元素的数量

返回值

  • key存在的时候,返回有序集的元素个数,否则返回0。
127.0.0.1:6379> ZRANGE myzset 0 -1
1) "python"
2) "go"
3) "java"
4) "js"
# 查看有序集合的元素数量
127.0.0.1:6379> ZCARD myzset
(integer) 4

ZREM key member [member ...]

删除元素,格式是zrem zset的key 项的值,项的值可以是多个,当key存在,但是其不是有序集合类型,就返回一个错误。

返回值

  • 返回的是从有序集合中删除的成员个数,不包括不存在的成员。
127.0.0.1:6379> ZRANGE myzset 0 -1 WITHSCORES
1) "python"
2) "1"
3) "go"
4) "2"
5) "java"
6) "3"
7) "js"
8) "4"
# 删除元素js
127.0.0.1:6379> ZREM myzset "js"
(integer) 1
# 再次查看元素js已经被删除
127.0.0.1:6379> ZRANGE myzset 0 -1 WITHSCORES
1) "python"
2) "1"
3) "go"
4) "2"
5) "java"
6) "3"
127.0.0.1:6379>

ZINCRBY key increment member

增加某个元素的分数,为有序集key的成员member的score值加上增量increment。如果key中不存在member,就在key中添加一个member,score是increment(就好像它之前的score是0.0)。如果key不存在,就创建一个只含有指定member成员的有序集合。

当key不是有序集类型时,返回一个错误。

score值必须是字符串表示的整数值或双精度浮点数,并且能接受double精度的浮点数。也有可能给一个负数来减少score的值。

返回值

  • member成员的新score值,以字符串形式表示。
# 创建有序集合
127.0.0.1:6379> ZADD hizset 1 "spring" 2 "spring mvc"
(integer) 2
127.0.0.1:6379>
# 输出集合的分值和内容
127.0.0.1:6379> ZRANGE hizset 0 -1 WITHSCORES
1) "spring"
2) "1"
3) "spring mvc"
4) "2"
# 修改元素spring的分值给加5变成6
127.0.0.1:6379> ZINCRBY hizset 5 "spring"
"6"
127.0.0.1:6379> ZRANGE hizset 0 -1 WITHSCORES
1) "spring mvc"
2) "2"
3) "spring"
4) "6"
127.0.0.1:6379>

ZCOUNT key min max

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

返回值

  • 指定分数范围的元素个数。
127.0.0.1:6379> ZRANGE myzset 0 -1 WITHSCORES
 1) "python"
 2) "1"
 3) "go"
 4) "2"
 5) "java"
 6) "3"
 7) "js"
 8) "4"
 9) "c++"
10) "5"
11) "kion"
12) "6"
# 获取集合中的所有内容
127.0.0.1:6379> ZCOUNT myzset -inf +inf
(integer) 6
# 获取指定范围内的元素个数
127.0.0.1:6379> ZCOUNT myzset 1 8
(integer) 6
# 获取指定范围内的元素个数,不包含1
127.0.0.1:6379> ZCOUNT myzset (1 8
(integer) 5

ZMPOP numkeys 键 [key ...] <MIN | MAX> [COUNT 计数]

Redis7.0.0新出的命令,从提供的键名列表中的第一个非空排序集中弹出一个或多个元素,即成员分数对。

ZMPOP并且BZMPOP类似于以下更有限的命令:

  • ZPOPMIN或者ZPOPMAX只接受一个键,并且可以返回多个元素。
  • BZPOPMINBZPOPMAX采用多个键,但仅从一个键返回一个元素。

BZMPOP有关此命令的阻塞变体,请参见。

使用修饰符时MIN,弹出的元素是第一个非空有序集合中得分最低的元素。修饰符MAX导致得分最高的元素被弹出。optionalCOUNT可用于指定要弹出的元素数,默认情况下设置为 1。

COUNT弹出元素的数量是排序集合的基数和的值中的最小值。

返回值

  • nil无法弹出任何元素时
  • 一个双元素数组,第一个元素是从中弹出元素的键的名称,第二个元素是弹出元素的数组。elements 数组中的每个条目也是一个包含成员及其分数的数组。
127.0.0.1:6379> ZRANGE myzset 0 -1 WITHSCORES
 1) "python"
 2) "1"
 3) "go"
 4) "2"
 5) "java"
 6) "3"
 7) "js"
 8) "4"
 9) "c++"
10) "5"
11) "kion"
12) "6"
# 弹出1个最小的分值,将其删除掉,count后面是删除的个数
127.0.0.1:6379> ZMPOP 1 myzset min count 1
1) "myzset"
2) 1) 1) "python"
      2) "1"
127.0.0.1:6379> ZRANGE myzset 0 -1 WITHSCORES
 1) "go"
 2) "2"
 3) "java"
 4) "3"
 5) "js"
 6) "4"
 7) "c++"
 8) "5"
 9) "kion"
10) "6"
127.0.0.1:6379>

ZRANK key member

作用是获得下标值,返回有序集key中成员member的排名。其中有序集成员按score值递增(从小到大)顺序排列。排名以0为底,也就是说,score值最小的成员排名为0。使用ZREVRANK命令可以获得成员按score值递减(从大到小)排列的排名。

返回值

  • 如果member是有序集key的成员,返回integer-reply:member的排名。
  • 如果member不是有序集key的成员,返回bulk-string-reply: nil
127.0.0.1:6379> ZADD myzset 1 "a" 2 "b" 3 "c"
(integer) 3
# 获取元素a的索引
127.0.0.1:6379> ZRANK myzset "a"
(integer) 0

ZREVRANK key member

作用是逆序获得下标值,返回有序集key中成员member的排名,其中有序集成员按score值从大到小排列。排名以0为底,也就是说,score值最大的成员排名为0。

返回值

  • 如果member是有序集key的成员,返回integer-reply:member的排名。
  • 如果member不是有序集key的成员,返回bulk-string-reply: nil
127.0.0.1:6379> ZADD myzset 1 "a" 2 "b" 3 "c"
(integer) 3
# 倒叙输出元素a的索引
127.0.0.1:6379> ZREVRANK myzset "a"
(integer) 2
127.0.0.1:6379>

3.6.Redis位图(bitmap)

3.6.1.初识位图

在日常开发过程中会有一些 bool 类型数据需要存取。例如:记录用户一年内签到的次数,签了则是1,没签则是 0。如果使用 key-value 来存储,那么每个用户都要记录 365 次,当用户成百上亿时,需要的存储空间将非常巨大。为了解决这个问题,Redis 提供了位图结构。
位图(bitmap)同样属于 string 数据类型。Redis 中一个字符串类型的值最多能存储 512 MB 的内容,每个字符串由多个字节组成,每个字节又由 8 个 Bit 位组成。位图结构正是使用“位”来实现存储的,它通过将比特位设置为 0 或 1来达到数据存取的目的,这大大增加了 value 存储数量,它存储上限为2^32。位图本质上就是一个普通的字节串,也就是 bytes 数组。可使用getbit/setbit命令来处理这个位数组,位图的结构如下所示:

 

位图适用于一些特定的应用场景,比如用户签到次数、或者登录次数等。上面表示一位用户 9 天内来网站的签到次数,1 代表签到,0 代表未签到,这样可以很轻松地统计出用户的活跃程度。相比于直接使用字符串而言,位图中的每一条记录仅占用一个 bit 位,从而大大降低了内存空间使用率。
Redis 官方曾经模拟了一个拥有 1 亿 2 千 8 百万用户的系统,然后使用 Redis 的位图来统计“日均用户数量”,最终所用时间的约为 50ms,且仅仅占用  16 MB内存。

应用:

某网站要统计一个用户一年的签到记录,若用 sring 类型存储,则需要 365 个键值对。若使用位图存储,用户签到就存 1,否则存 0。最后会生成 11010101... 这样的存储结果,其中每天的记录只占一位,一年就是 365 位,约为 46 个字节。如果只想统计用户签到的天数,那么统计 1 的个数即可。

位图操作的优势,相比于字符串而言,它不仅效率高,而且还非常的节省空间。Redis  的位数组是自动扩展的,如果设置了某个偏移位置超出了现有的内容范围,位数组就会自动扩充。

3.6.2.常用命令

SETBIT key offset value

设置或者清空key的value(字符串)在offset处的bit值。

  • 那个位置的bit要么被设置,要么被清空,这个由value(只能是0或者1)来决定。当key不存在的时候,就创建一个新的字符串value。要确保这个字符串大到在offset处有bit值。参数offset需要大于等于0,并且小于232(限制bitmap大小为512)。当key对应的字符串增大的时候,新增的部分bit值都是设置为0。
  • 注意:当set最后一个bit(offset等于232-1)并且key还没有一个字符串value或者其value是个比较小的字符串时,Redis需要立即分配所有内存,这有可能会导致服务阻塞一会。在一台2010MacBook Pro上,offset为232-1(分配512MB)需要~300ms,offset为230-1(分配128MB)需要~80ms,offset为228-1(分配32MB)需要~30ms,offset为226-1(分配8MB)需要8ms。注意,一旦第一次内存分配完,后面对同一个key调用SETBIT就不会预先得到内存分配。

返回值

  • 在offset处原来的bit值
# 其中 offset 表示偏移量,从 0 开始。下面分别是设置0 1 2的偏移量的值,只能设置为0或者1
127.0.0.1:6379> SETBIT mykey 0 1
(integer) 0
127.0.0.1:6379> SETBIT mykey 1 1
(integer) 0
127.0.0.1:6379> SETBIT mykey 2 1
(integer) 0
# 当对应位的字符是不可打印字符,redis会以16进制形式显示
127.0.0.1:6379> GET mykey
"\xe0"
127.0.0.1:6379> GETBIT mykey 2
(integer) 1
127.0.0.1:6379>

GETBIT key offset

用来获取某一位上的值。当偏移量 offset 比字符串的长度大,或者当 key 不存在时,返回 0。

# 获取偏移量1对应的值
127.0.0.1:6379> GETBIT mykey 1
(integer) 1
# 当偏移量不存在的时候返回0
127.0.0.1:6379> GETBIT mykey 10000
(integer) 0
127.0.0.1:6379> EXISTS mykey
(integer) 1
# 当位图mykey1不存在的时候,返回0
127.0.0.1:6379> GETBIT mykey1 1
(integer) 0
127.0.0.1:6379>

STRLEN key

返回key的string类型value的长度。如果key对应的非string类型,就返回错误。

返回值

  • key对应的字符串value的长度,或者0(key不存在)
# 给位图设置对应的值
127.0.0.1:6379> SETBIT mykey 0 1
(integer) 0
127.0.0.1:6379> SETBIT mykey 1 1
(integer) 0
127.0.0.1:6379> SETBIT mykey 2 1
(integer) 0
127.0.0.1:6379> SETBIT mykey 100 1
(integer) 0
# 查看位图 查看的是占据几个字节
127.0.0.1:6379> STRLEN mykey
(integer) 13
127.0.0.1:6379> set k2  v2
OK
# 查看字符串,则是字符的个数
127.0.0.1:6379> STRLEN k2
(integer) 2
127.0.0.1:6379>

BITCOUNT key [start end]

统计字符串被设置为1的bit数.

  • 一般情况下,给定的整个字符串都会被进行计数,通过指定额外的 start 或 end 参数,可以让计数只在特定的位上进行。
  • start 和 end 参数的设置和 GETRANGE 命令类似,都可以使用负数值:比如 -1 表示最后一个位,而 -2 表示倒数第二个位,以此类推。
  • 不存在的 key 被当成是空字符串来处理,因此对一个不存在的 key 进行 BITCOUNT 操作,结果为 0 。

返回值

  • 被设置为 1 的位的数量。
127.0.0.1:6379> SETBIT mykey1 0 1
(integer) 0
127.0.0.1:6379> SETBIT mykey1 1 1
(integer) 0
127.0.0.1:6379> SETBIT mykey1 2 1
(integer) 0
# 设置是1的位的数量
127.0.0.1:6379> BITCOUNT mykey1
(integer) 3
127.0.0.1:6379>

BITOP operation destkey key [key ...]

对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种参数:

  • BITOP AND destkey srckey1 srckey2 srckey3 ... srckeyN ,对一个或多个 key 求逻辑并,并将结果保存到 destkey 。
  • BITOP OR destkey srckey1 srckey2 srckey3 ... srckeyN,对一个或多个 key 求逻辑或,并将结果保存到 destkey 。
  • BITOP XOR destkey srckey1 srckey2 srckey3 ... srckeyN,对一个或多个 key 求逻辑异或,并将结果保存到 destkey 。
  • BITOP NOT destkey srckey,对给定 key 求逻辑非,并将结果保存到 destkey 。

除了 NOT 操作之外,其他操作都可以接受一个或多个 key 作为输入。执行结果将始终保持到destkey里面。处理不同长度的字符串

  • 当 BITOP 处理不同长度的字符串时,较短的那个字符串所缺少的部分会被看作 0 。
  • 空的 key 也被看作是包含 0 的字符串序列。

返回值

  • 保存到 destkey 的字符串的长度,和输入 key 中最长的字符串长度相等。
# 这里设置位图230301
127.0.0.1:6379> SETBIT 230301 0 1
(integer) 0
127.0.0.1:6379> SETBIT 230301 1 1
(integer) 0
127.0.0.1:6379> SETBIT 230301 2 1
(integer) 0
127.0.0.1:6379> SETBIT 230301 3 1
(integer) 0
# 这里设置位图230301
127.0.0.1:6379> SETBIT 230302 0 1
(integer) 0
127.0.0.1:6379> SETBIT 230302 1 1
(integer) 0
127.0.0.1:6379>
# 分别求两个位图的中值为1的个事
127.0.0.1:6379> BITCOUNT 230302
(integer) 2
127.0.0.1:6379> BITCOUNT 230301
(integer) 4
# 求位图230301和位图230302中位置相同都为1的值,保存到destkey中,1表示存在
127.0.0.1:6379> BITOP and destkey 230301 230302
(integer) 1
# 查看发现有两个值
127.0.0.1:6379> BITCOUNT destkey
(integer) 2

3.7.Redis基数统计(HyperLogLog)

3.7.1.什么是HyperLoglog

Redis 2.8.9 版本中新增了 HyperLogLog 类型。HyperLoglog 是 Redis 适用于海量数据的计算、统计,其特点是占用空间小,计算速度快。

HyperLoglog 采用了一种基数估计算法,因此,最终得到的结果会存在一定范围的误差(标准误差为 0.81%)。每个 HyperLogLog key 只占用 12 KB 内存,所以理论上可以存储大约2^64个值,而 set(集合)则是元素越多占用的内存就越多,两者形成了鲜明的对比 。

基数定义

基数定义:一个集合中不重复的元素个数就表示该集合的基数,比如集合 {1,2,3,4,1,2} ,它的基数集合为 {1,2,3,4} ,所以基数为 4。HyperLogLog 正是通过基数估计算法来统计输入元素的基数。

场景应用

HyperLogLog 也有一些特定的使用场景,它最典型的应用场景就是统计网站用户月活量,或者网站页面的 UV(网站独立访客)数据等。UV 与 PV(页面浏览量) 不同,UV 需要去重,同一个用户一天之内的多次访问只能计数一次。这就要求用户的每一次访问都要带上自身的用户 ID,无论是登陆用户还是未登陆用户都需要一个唯一 ID 来标识。当一个网站拥有巨大的用户访问量时,我们可以使用 Redis 的 HyperLogLog 来统计网站的 UV (网站独立访客)数据,它提供的去重计数方案,虽说不精确,但 0.81% 的误差足以满足 UV 统计的需求。

3.7.2.基本命令

下表列出了 redis HyperLogLog 的基本命令:

命令命令及描述
PFADD key element [element ...] 添加指定元素到 HyperLogLog 中。
PFCOUNT key [key ...] 返回给定 HyperLogLog 的基数估算值。
PFMERGE destkey sourcekey [sourcekey ...] 将多个 HyperLogLog 合并为一个 HyperLogLog

3.7.3.案例演示

HyperLogLog 提供了三个常用命令,分别是PFADDPFCOUNTPFMERGE。下面看一组实例演示:假设有 6 个用户(user01-user06),他们分别在上午 10 与 11 点访问了某个网站。

#向指定的key中添加用户
127.0.0.1:6379> PFADD user:uv:2023030110 user01 user02 user03
(integer) 1
#向指定的key中添加用户
127.0.0.1:6379> PFADD user:uv:2023030111 user04 user05
(integer) 1
#统计基数值
127.0.0.1:6379> PFCOUNT user:uv:2023030110
(integer) 3
#重复元素不能添加成功返回0,其基数仍然为3
127.0.0.1:6379> PFADD user:uv:2023030110 user01 user02
(integer) 0
127.0.0.1:6379> PFCOUNT user:uv:2023030110
(integer) 3
#添加新元素值
127.0.0.1:6379> PFADD user:uv:2023030110 user06
(integer) 1
#基数值变为4
127.0.0.1:6379> PFCOUNT user:uv:2023030110
(integer) 4
#统计两个key的基数值为6
127.0.0.1:6379> PFCOUNT user:uv:2023030110 user:uv:2023030111 
(integer) 6
#将两个key值合并为一个,赋值给user:uv:2023030110-11
127.0.0.1:6379> PFMERGE user:uv:2023030110-11 user:uv:2023030110 user:uv:2023030111
OK
#使用合并后key统计基数值
127.0.0.1:6379> PFCOUNT user:uv:2023030110-11
(integer) 6
127.0.0.1:6379>

3.8.Redis地理空间(GEO)

3.8.1.初始化GEO

Redis 3.2 版本新增了存储地理位置信息的功能,即 GEO(英文全称 geographic),它的底层通过 Redis 有序集合(zset)实现。不过 Redis GEO 并没有与 zset 共用一套的命令,而是拥有自己的一套命令。

Redis GEO 在目前项目研发的时候有很多应用场景,举一个简单的例子,外卖软件,或者打车软件,在这种 APP上会显示“店家距离你有多少米”或者“司机师傅距离你有多远”,类似功能就可以通过 Redis GEO 实现。数据库中存放着商家所处的经纬度,你的位置则由手机定位获取,这样 APP 就计算出了最终的距离。再比如微信中附近的人、摇一摇、实时定位等功能都依赖地理位置实现。

3.8.2.常用命令

GEO 提供以下操作命令,如下表所示:

Redis GEO命令
序号命令说明
1 GEOADD 将指定的地理空间位置(纬度、经度、名称)添加到指定的 key 中。
2 GEOPOS 从 key 里返回所有给定位置元素的位置(即经度和纬度)
3 GEODIST 返回两个地理位置间的距离,如果两个位置之间的其中一个不存在, 那么命令返回空值。
4 GEORADIUS 根据给定地理位置坐标(经纬度)获取指定范围内的地理位置集合。
5 GEORADIUSBYMEMBER 根据给定地理位置(具体的位置元素)获取指定范围内的地理位置集合。
6 GEOHASH 获取一个或者多个的地理位置的 GEOHASH 值。
7 ZREM 通过有序集合的 zrem 命令实现对地理位置信息的删除。

1) GEOADD

将指定的地理空间位置(纬度、经度、名称)添加到指定的 key 中。语法格式如下:

GEOADD key longitude latitude member [longitude latitude member ...]
  • longitude:位置地点所处的经度;
  • latitude:位置地点所处的纬度;
  • member:位置名称。

将给定的经纬度的位置名称(纬度、经度、名字)与  key 相对应,这些数据以有序集合的形式进行储存。
GEOADD命令以标准的x,y形式接受参数, 所以用户必须先输入经度,然后再输入纬度。GEOADD命令能够记录的坐标数量是有限的,如果位置非常接近两极(南极/北极)区域,那么将无法被索引到。因此当您输入经纬度时,需要注意以下规则:

  • 有效的经度介于 -180 度至 180 度之间。
  • 有效的纬度介于 -85.05112878 度至 85.05112878 度之间。

注意:如果您输入一个超出范围的经纬度时,GEOADD 命令将返回一个错误。

示例演示如下:

# 添加城市地理位置
127.0.0.1:6379> GEOADD city 116.20 39.56 beijing 120.52 30.40 sh                                                                                               anghai
(integer) 2
#查询城市地理位置
127.0.0.1:6379> GEOPOS city beijing shanghai
1) 1) "116.19999736547470093"
   2) "39.56000019952067248"
2) 1) "120.52000075578689575"
   2) "30.39999952668997452"

2) GEODIST

 该命令用于获取两个地理位置间的距离。返回值为双精度浮点数,其语法格式如下:

GEODIST key member1 member2 [unit]   

参数 unit 是表示距离单位,取值如下所示:

  • m 表示单位为米;
  • km 表示单位为千米;
  • mi 表示单位为英里;
  • ft 表示单位为英尺。

如果没有指出距离单位,那么默认取值为m。示例如下:

# 求北京到上海之间的距离,单位为米
127.0.0.1:6379> GEODIST city beijing shanghai
"1091868.8970"
# 求北京到上海之间的距离,单位为千米
127.0.0.1:6379> GEODIST city beijing shanghai km
"1091.8689"
# 求北京到上海之间的距离,单位为mi “Mi是英制单位中的长度计量单位英里
127.0.0.1:6379> GEODIST city beijing shanghai mi
"678.4576"
127.0.0.1:6379>

注意:计算举例时存在 0.5% 左右的误差,这是由于 Redis GEO 把地球假设成了完美的球体。

3) GEORADIUS

以给定的经纬度为中心,计算出 key 包含的地理位置元素与中心的距离不超过给定最大距离的所有位置元素,并将其返回。

GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC]

参数说明:

  • WITHDIST :在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。
  • WITHCOORD :返回位置元素的经度和维度。
  • WITHHASH :采用 GEOHASH 对位置元素进行编码,以 52 位有符号整数的形式返回有序集合的分值,该选项主要用于底层调试,实际作用不大。
  • COUNT:指定返回位置元素的数量,在数据量非常大时,可以使用此参数限制元素的返回数量,从而加快计算速度。

注意:该命令默认返回的是未排序的位置元素。通过 ASC 与 DESC 可让返回的位置元素以升序或者降序方式排列。

# #添加几个地理位置元素
127.0.0.1:6379> GEOADD city 116.20 39.56 beijing 120.52 30.40 shanghai
(integer) 2
127.0.0.1:6379> GEOADD city 106.71 26.56 guiyang
(integer) 1
#以兰州的坐标(103.73 36.03)为中心,计算各个城市距离首都的距离,最大范围设置为1500km
#同时返回距离,与位置元素的经纬度
127.0.0.1:6379> GEORADIUS city 103.73 36.03 1500 km WITHCOORD WITHDIST
1) 1) "guiyang"
   2) "1090.5486"
   3) 1) "106.70999854803085327"
      2) "26.56000089385899798"
2) 1) "beijing"
   2) "1162.9918"
   3) 1) "116.19999736547470093"
      2) "39.56000019952067248"
# 上面会发现由于上海路径兰州的距离超过了1500km所以没显示,下面设置到了3000km则显示成功
127.0.0.1:6379> GEORADIUS city 103.73 36.03 3000 km WITHCOORD WITHDIST
1) 1) "guiyang"
   2) "1090.5486"
   3) 1) "106.70999854803085327"
      2) "26.56000089385899798"
2) 1) "shanghai"
   2) "1680.2975"
   3) 1) "120.52000075578689575"
      2) "30.39999952668997452"
3) 1) "beijing"
   2) "1162.9918"
   3) 1) "116.19999736547470093"
      2) "39.56000019952067248"
127.0.0.1:6379>

4) GEORADIUSBYMEMBER

根据给定的地理位置坐标(即经纬度)获取指定范围内的位置元素集合。其语法格式如下:

GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DES]
  • m :米,默认单位;
  • km :千米(可选);
  • mi :英里(可选);
  • ft :英尺(可选);
  • ASC:根据距离将查找结果从近到远排序;
  • DESC:根据距离将查找结果从远到近排序。

该命令与 GEORADIUS 命令相似,不过它选择的中心的是具体的位置元素,而非经纬度坐标。示例如下:

#以北京为中心,最大距离不超过1500km
127.0.0.1:6379> GEORADIUSBYMEMBER city beijing 1500 km WITHCOORD WITHDIST
# 只有上海符合要求
1) 1) "shanghai" 2) "1091.8689" 3) 1) "120.52000075578689575" 2) "30.39999952668997452" 2) 1) "beijing" 2) "0.0000" 3) 1) "116.19999736547470093" 2) "39.56000019952067248"

5) GEOHASH

返回一个或多个位置元素的哈希字符串,该字符串具有唯一 ID 标识,它与给定的位置元素一一对应。示例如下:

127.0.0.1:6379> GEOHASH city beijing shanghai guiyang
1) "wx49h1wm8n0"
2) "wtmsyqpuzd0"
3) "wkezh5pg5p0"

6) ZREM

用于删除指定的地理位置元素,示例如下:

127.0.0.1:6379> zrem city beijing shanghai
(integer) 2
127.0.0.1:6379>

3.9.Redis流(Stream)

3.9.1.初识是Stream?

Redis Stream 是 Redis 5.0 版本引入的一种新数据类型,同时它也是 Redis 中最为复杂的数据结构,Stream 实际上是一个具有消息发布/订阅功能的组件,也就常说的消息队列。其实这种类似于 broker/consumer(生产者/消费者)的数据结构很常见,比如 RabbitMQ 消息中间件、Celery 消息中间件,以及 Kafka 分布式消息系统等,而 Redis Stream 正是借鉴了 Kafaka 系统。

1) 优点

Strean 除了拥有很高的性能和内存利用率外, 它最大的特点就是提供了消息的持久化存储,以及主从复制功能,从而解决了网络断开、Redis 宕机情况下,消息丢失的问题,即便是重启 Redis,存储的内容也会存在。

2) 流程

Stream 消息队列主要由四部分组成,分别是:消息本身、生产者、消费者和消费组,对于前述三者很好理解,下面了解什么是消费组。
一个 Stream 队列可以拥有多个消费组,每个消费组中又包含了多个消费者,组内消费者之间存在竞争关系。当某个消费者消费了一条消息时,同组消费者,都不会再次消费这条消息。被消费的消息 ID 会被放入等待处理的 Pending_ids 中。每消费完一条信息,消费组的游标就会向前移动一位,组内消费者就继续去争抢下消息。

Redis Stream 消息队列结构程如下图所示:

下面对上图涉及的专有名词做简单解释:

  • Stream direction:表示数据流,它是一个消息链,将所有的消息都串起来,每个消息都有一个唯一标识 ID 和对应的消息内容(Message content)。
  • Consumer Group :表示消费组,拥有唯一的组名,使用 XGROUP CREATE 命令创建。一个 Stream 消息链上可以有多个消费组,一个消费组内拥有多个消费者,每一个消费者也有一个唯一的 ID 标识。
  • last_delivered_id :表示消费组游标,每个消费组都会有一个游标 last_delivered_id,任意一个消费者读取了消息都会使游标 last_delivered_id 往前移动。
  • pending_ids :Redis 官方称为 PEL,表示消费者的状态变量,它记录了当前已经被客户端读取的消息 ID,但是这些消息没有被 ACK(确认字符)。如果客户端没有 ACK,那么这个变量中的消息 ID 会越来越多,一旦被某个消息被 ACK,它就开始减少。

3) ACK 

ACK(Acknowledge character)即确认字符,在数据通信中,接收方传递给发送方的一种传输类控制字符。表示发来的数据已确认接收无误。在 TCP/IP 协议中,如果接收方成功的接收到数据,那么会回复一个 ACK 数据。通常 ACK 信号有自己固定的格式,长度大小,由接收方回复给发送方。

3.9.2.相关命令

Redis Stream常用队列相关命令如下:

Redis Stream常用消费组相关指令

Redis Stream常用消费组相关指令

符合 含义说明
- + 最小和最大可能出现的Id
$ $ 表示只消费新的消息,当前流中最大的 id,可用于将要到来的信息
> 用于XREADGROUP命令,表示迄今还没有发送给组中使用者的信息,会更新消费者组的最后 
* 用于XADD命令中,让系统自动生成 id

3.9.3.相关实例演示-队列相关指令

XADD

添加消息到队列末尾,XADD 用于向Stream 队列中添加消息,如果指定的Stream 队列不存在,则该命令执行时会新建一个Stream 队列

* 号表示服务器自动生成 MessageID(类似mysql里面主键auto_increment),后面顺序跟着一堆 业务key/value,如下所示

127.0.0.1:6379> XADD mystream * id 1 name tom age 23
"1677735415420-0"

需要注意:

  • 上面案例中MessageID,由两部分组成,分别是毫秒时间戳和该毫秒内产生的第几条消息
  • 信息条目指的是序列号,在相同的毫秒下序列号从0开始递增,序列号是64位长度,理论上在同一毫秒内生成的数据量无法到达这个级别,因此不用担心序列号会不够用。millisecondsTime指的是Redis节点服务器的本地时间,如果存在当前的毫秒时间戳比以前已经存在的数据的时间戳小的话(本地时间钟后跳),那么系统将会采用以前相同的毫秒创建新的ID,也即redis 在增加信息条目时会检查当前 id 与上一条目的 id, 自动纠正错误的情况,一定要保证后面的 id 比前面大,一个流中信息条目的ID必须是单调增的,这是流的基础。

​客户端显示传入规则:

  • Redis对于ID有强制要求,格式必须是时间戳-自增Id这样的方式,且后续ID不能小于前一个ID
  • Stream的消息内容,也就是Message Content它的结构类似Hash结构,以key-value的形式存在。​
# 向队列中添加消息,使用* 表示以时间戳自动创建id
127.0.0.1:6379> XADD mystream * id 1 name tom age 23
"1677735415420-0"
# 默认创建消息时ID重复,则创建失败
127.0.0.1:6379> XADD mystream 1677735415420-0 id 2 name yxy age 27
(error) ERR The ID specified in XADD is equal or smaller than the target stream top item

XRANGE

用于获取消息列表(可以指定范围),忽略删除的消息:

  • start 表示开始值,-代表最小值
  • end 表示结束值,+代表最大值
  • count 表示最多获取多少个值
# 获取消息队列中所有的内容 - 表示最小 + 表示最大
127.0.0.1:6379> XRANGE mystream - +
1) 1) "1677735415420-0"
   2) 1) "id"
      2) "1"
      3) "name"
      4) "tom"
      5) "age"
      6) "23"
2) 1) "1677735415420-1"
   2) 1) "id"
      2) "2"
      3) "name"
      4) "yxy"
      5) "age"
      6) "27"
3) 1) "1677736498822-0"
   2) 1) "id"
      2) "3"
      3) "name"
      4) "wwj"
      5) "age"
      6) "28"
# 获取消息队列中所有内容,count1表示最多获取1个值
127.0.0.1:6379> XRANGE mystream - + count 1
1) 1) "1677735415420-0"
   2) 1) "id"
      2) "1"
      3) "name"
      4) "tom"
      5) "age"
      6) "23"
127.0.0.1:6379>

XREVRANGE

用于获取消息列表(可以指定范围),忽略删除的消息,与XRANGE 的区别在于,获取消息列表元素的方向是相反的,end在前,start在后。

# 获取消息列表元素的方向是相反的,end在前,start在后
127.0.0.1:6379> XREVRANGE mystream + -
1) 1) "1677736498822-0"
   2) 1) "id"
      2) "3"
      3) "name"
      4) "wwj"
      5) "age"
      6) "28"
2) 1) "1677735415420-1"
   2) 1) "id"
      2) "2"
      3) "name"
      4) "yxy"
      5) "age"
      6) "27"
3) 1) "1677735415420-0"
   2) 1) "id"
      2) "1"
      3) "name"
      4) "tom"
      5) "age"
      6) "23"
# 也可指定获取的条数,这里指定为2
127.0.0.1:6379> XREVRANGE mystream + - COUNT 2
1) 1) "1677736498822-0"
   2) 1) "id"
      2) "3"
      3) "name"
      4) "wwj"
      5) "age"
      6) "28"
2) 1) "1677735415420-1"
   2) 1) "id"
      2) "2"
      3) "name"
      4) "yxy"
      5) "age"
      6) "27"
127.0.0.1:6379>

XDEL

根据messageid删除队列中的数据信息

127.0.0.1:6379> XRANGE mystream - +
1) 1) "1677735415420-0"
   2) 1) "id"
      2) "1"
      3) "name"
      4) "tom"
      5) "age"
      6) "23"
2) 1) "1677735415420-1"
   2) 1) "id"
      2) "2"
      3) "name"
      4) "yxy"
      5) "age"
      6) "27"
3) 1) "1677736498822-0"
   2) 1) "id"
      2) "3"
      3) "name"
      4) "wwj"
      5) "age"
      6) "28"
# 根据id删除队列中信息
127.0.0.1:6379> XDEL mystream 1677735415420-1
(integer) 1
# 删除后再次查看id为1677735415420-1的信息不存在了
127.0.0.1:6379> XRANGE mystream - +
1) 1) "1677735415420-0"
   2) 1) "id"
      2) "1"
      3) "name"
      4) "tom"
      5) "age"
      6) "23"
2) 1) "1677736498822-0"
   2) 1) "id"
      2) "3"
      3) "name"
      4) "wwj"
      5) "age"
      6) "28"
127.0.0.1:6379>

XLEN

用于获取Stream 队列的消息的长度

# 查看发现队列中有两条信息
127.0.0.1:6379> XRANGE mystream - +
1) 1) "1677735415420-0"
   2) 1) "id"
      2) "1"
      3) "name"
      4) "tom"
      5) "age"
      6) "23"
2) 1) "1677736498822-0"
   2) 1) "id"
      2) "3"
      3) "name"
      4) "wwj"
      5) "age"
      6) "28"
# 查看队列长度
127.0.0.1:6379> XLEN mystream
(integer) 2
127.0.0.1:6379>

XTRIM

用于对Stream的长度进行截取,如超长会进行截取:

  • MAXLEN:允许的最大长度,对流进行修剪限制长度
# 查看队列中所有信息
127.0.0.1:6379> XRANGE mystream - +
1) 1) "1677735415420-0"
   2) 1) "id"
      2) "1"
      3) "name"
      4) "tom"
      5) "age"
      6) "23"
2) 1) "1677736498822-0"
   2) 1) "id"
      2) "3"
      3) "name"
      4) "wwj"
      5) "age"
      6) "28"
3) 1) "1677737615769-0"
   2) 1) "id"
      2) "4"
      3) "name"
      4) "wy"
      5) "age"
      6) "27"
4) 1) "1677737634493-0"
   2) 1) "id"
      2) "5"
      3) "name"
      4) "haha"
      5) "age"
      6) "25"
# 指定截取2条消息
127.0.0.1:6379> XTRIM mystream maxlen 2
(integer) 2
# 再次查看队列就剩下两条信息
127.0.0.1:6379> XRANGE mystream - +
1) 1) "1677737615769-0"
   2) 1) "id"
      2) "4"
      3) "name"
      4) "wy"
      5) "age"
      6) "27"
2) 1) "1677737634493-0"
   2) 1) "id"
      2) "5"
      3) "name"
      4) "haha"
      5) "age"
      6) "25"
127.0.0.1:6379>
  • MINID:允许的最小id,从某个id值开始比该id值小的将会被抛弃
127.0.0.1:6379> XRANGE mystream - +
1) 1) "1677737615769-0"
   2) 1) "id"
      2) "4"
      3) "name"
      4) "wy"
      5) "age"
      6) "27"
2) 1) "1677737634493-0"
   2) 1) "id"
      2) "5"
      3) "name"
      4) "haha"
      5) "age"
      6) "25"
# 设置允许的最小id为1677737634493-0
127.0.0.1:6379> XTRIM mystream minid 1677737634493-0
(integer) 1
# 再次查看,只有1677737634493-0符合要求
127.0.0.1:6379> XRANGE mystream - +
1) 1) "1677737634493-0"
   2) 1) "id"
      2) "5"
      3) "name"
      4) "haha"
      5) "age"
      6) "25"
127.0.0.1:6379>

XREAD

用于获取消息(阻塞/非阻塞),只会返回大于指定ID的消息,语法结构如下:

XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]

参数说明:

  • COUNT 最多读取多少条消息
  • BLOCK 是否已阻塞的方式读取消息,默认是非阻塞方式,如果milliseconds为0,则表示永远阻塞

非阻塞

  • $代表特殊ID,表示以当前Stream已经存储的最大的ID作为最后一个ID,当前Stream中不存在大于当前最大ID的消息,因此此时返回nil
  • 0-0代表从最小的ID开始获取Stream中的消息,当不指定count,将会返回Stream中的所有消息,注意也可以使用0(00/000也都是可以的……)
# $代表特殊ID,表示以当前Stream已经存储的最大的ID作为最后一个ID,但是Stream中不存在大于当前最大ID的消息,因此此时返回nil
127.0.0.1:6379> XREAD count 2 streams mystream $
(nil)
# 0-0代表从最小的ID开始获取Stream中的消息,当不指定count,将会返回Stream中的所有消息,
127.0.0.1:6379> XREAD count 2 streams mystream 0-0
1) 1) "mystream"
   2) 1) 1) "1677737634493-0"
         2) 1) "id"
            2) "5"
            3) "name"
            4) "haha"
            5) "age"
            6) "25"
# 0-0代表从最小的ID开始获取Stream中的消息(使用0、00、000也都是可以),当不指定count,将会返回Stream中的所有消息,
127.0.0.1:6379> XREAD count 2 streams mystream 000
1) 1) "mystream"
   2) 1) 1) "1677737634493-0"
         2) 1) "id"
            2) "5"
            3) "name"
            4) "haha"
            5) "age"
            6) "25"
127.0.0.1:6379>
  • 阻塞

先执行一个客户端,让其处理阻塞 状态,在监听当前队列的消息:

XREAD count 1 block 0 streams mystream $

如下图:

然后再次启动一个Redis客户端,连接上去,给mystream队列中添加一条信息:

再次查看处于监听状态的客户端,发现就收到了另一个客户端刚刚添加进来的信息:

3.9.4.相关实例演示-消费者组相关指令

XGROUP CREATE

用于创建消费者组:

  • $表示从Stream尾部开始消费
  • 0表示从Stream头部开始消费
  • 创建消费者组的时候必须指定 ID, ID 为 0 表示从头开始消费,为 $ 表示只消费新的消息,队尾新来
# 创建消费者组,$表示从队列尾部开始消费
127.0.0.1:6379> XGROUP CREATE mystream group-A $
OK
# 创建消费者组,0表示从队列头部开始消费
127.0.0.1:6379> XGROUP CREATE mystream group-B 0
OK
127.0.0.1:6379>

XREADGROUP GROUP

XREADGROUP命令是XREAD命令的特殊版本,支持消费者组。

  • 消费组group-C内的消费者consumer1从mystream消息队列中读取所有消息,注意“>”,表示从第一条尚未被消费的消息开始读取
# 创建两个mystream队列中的两个消费者组group-C、group-D
127.0.0.1:6379> XGROUP CREATE mystream group-C 0-0
OK
127.0.0.1:6379> XGROUP CREATE mystream group-D 0-0
OK
# 指定消费组group-C内的消费者consumer1从mystream消息队列中读取所有消息
127.0.0.1:6379> XREADGROUP GROUP group-C consumer1 STREAMS mystream >
1) 1) "mystream"
   2) 1) 1) "1677737634493-0"
         2) 1) "id"
            2) "5"
            3) "name"
            4) "haha"
            5) "age"
            6) "25"
      2) 1) "1677739739325-0"
         2) 1) "id"
            2) "8"
            3) "name"
            4) "liming"
            5) "age"
            6) "23"
      3) 1) "1677739961764-0"
         2) 1) "id"
            2) "9"
            3) "name"
            4) "liuyan"
            5) "age"
            6) "29"
# 会发现下面在使用consumer2 、consumer3读取的时候是空值,这是因为:stream中的消息—旦被消费组里的一个消费者读取了,就不能再被该消费组内的其他消费者读取了,
# 即同一个消费组里的消费者不能消费同—条消息。刚才的XREADGROUP命令再执行—次,此时读到的就是空值
127.0.0.1:6379> XREADGROUP GROUP group-C consumer2 STREAMS mystream > (nil) 127.0.0.1:6379> XREADGROUP GROUP group-C consumer3 STREAMS mystream > (nil)
  • 但是,不同消费组的消费者可以消费同一条消息:
# 之前group-C组读取一次后不能在读取,这里换一个组group-D则又可以开始读取
127.0.0.1:6379> XREADGROUP GROUP group-D consumer1 STREAMS mystream >
1) 1) "mystream"
   2) 1) 1) "1677737634493-0"
         2) 1) "id"
            2) "5"
            3) "name"
            4) "haha"
            5) "age"
            6) "25"
      2) 1) "1677739739325-0"
         2) 1) "id"
            2) "8"
            3) "name"
            4) "liming"
            5) "age"
            6) "23"
      3) 1) "1677739961764-0"
         2) 1) "id"
            2) "9"
            3) "name"
            4) "liuyan"
            5) "age"
            6) "29"
# 但是group-D组consumer1读取了一个,后续consumer2也是无法读取了
127.0.0.1:6379> XREADGROUP GROUP group-D consumer2 STREAMS mystream >
(nil)
127.0.0.1:6379>
  • 那么创建消费组的目的是什么呢?

目的是为了让组内的多个消费者共同分担读取消息,所以,我们通常会让每个消费者读取部分消息,从而实现消息读取负载在多个消费者间是均衡分布的

# 给队列mystream创建一个消费者组group-E
127.0.0.1:6379> XREADGROUP GROUP group-E consumer1 count 1 STREAMS mystream >
1) 1) "mystream"
   2) 1) 1) "1677737634493-0"
         2) 1) "id"
            2) "5"
            3) "name"
            4) "haha"
            5) "age"
            6) "25"
# 使用group-E组中consumer1、consumer2、consumer3分摊读取信息
127.0.0.1:6379> XREADGROUP GROUP group-E consumer2 count 1 STREAMS mystream >
1) 1) "mystream"
   2) 1) 1) "1677739739325-0"
         2) 1) "id"
            2) "8"
            3) "name"
            4) "liming"
            5) "age"
            6) "23"
127.0.0.1:6379> XREADGROUP GROUP group-E consumer3 count 1 STREAMS mystream >
1) 1) "mystream"
   2) 1) 1) "1677739961764-0"
         2) 1) "id"
            2) "9"
            3) "name"
            4) "liuyan"
            5) "age"
            6) "29"
# 由于队列中只有三条信息,已经被读取完了,consumer4在读取就为nil
127.0.0.1:6379> XREADGROUP GROUP group-E consumer4 count 1 STREAMS mystream >
(nil)
127.0.0.1:6379>
  • 基于 Stream 实现的消息队列,如何保证消费者在发生故障或宕机再次重启后,仍然可以读取未处理完的消息(重点!!!)?

Streams 会自动使用内部队列(也称为 PENDING List)留存消费组里每个消费者读取的消息保底措施,直到消费者使用 XACK 命令通知 Streams“消息已经处理完成”。其次,消费通过确认机制增加了消息的可靠性,一般在业务处理完成之后,需要执行 XACK 命令确认消息已经被消费完成

XPENDING

  • 询每个消费组内所有消费者「已读取、但尚未确认」的消息,如下:

  • 查看某个消费者具体读取了哪些数据

 

下图所示consumer2已读取的消息的 ID是1677739739325-0,后续处理一旦消息 1677739739325-0 被consumer2处理了,consumer2就可以使用 XACK 命令通知 Streams,然后这条消息就会被删除

127.0.0.1:6379> XRANGE mystream - +
1) 1) "1677737634493-0"
   2) 1) "id"
      2) "5"
      3) "name"
      4) "haha"
      5) "age"
      6) "25"
2) 1) "1677739739325-0"
   2) 1) "id"
      2) "8"
      3) "name"
      4) "liming"
      5) "age"
      6) "23"
3) 1) "1677739961764-0"
   2) 1) "id"
      2) "9"
      3) "name"
      4) "liuyan"
      5) "age"
      6) "29"
# 查看consumer2读取那些信息,后续可以使用ack命令通知streams,消息将会被删除
127.0.0.1:6379> XPENDING mystream group-E - + 10 consumer2
1) 1) "1677739739325-0"
   2) "consumer2"
   3) (integer) 425073
   4) (integer) 1
127.0.0.1:6379>

XACK

向消息队列确认消息处理已完成

# 查看consumer2读取了那些信息
127.0.0.1:6379> XPENDING mystream group-E - + 10 consumer2
1) 1) "1677739739325-0"
   2) "consumer2"
   3) (integer) 1047871
   4) (integer) 1
# consumer2使用 XACK 命令通知 Streams,然后这条消息就会被删除
127.0.0.1:6379> XACK mystream group-E 1677739739325-0
(integer) 1
# 再次查看没有内容了
127.0.0.1:6379> XPENDING mystream group-E - + 10 consumer2
(empty array)
127.0.0.1:6379>

XINFO

用于打印输出队列中消费者的Group的详细信息

3.10.Redis位域(bitfield)

3.10.1.认识bitfield

BITFIELD 命令可以将一个 Redis 字符串看作是一个由二进制位组成的数组, 并对这个数组中储存的长度不同的整数进行访问 (被储存的整数无需进行对齐)。 换句话说, 通过这个命令, 用户可以执行诸如 “对偏移量 1234 上的 5 位长有符号整数进行设置”(本质上是对二进制码进行修改)、 “获取偏移量 4567 上的 31 位长无符号整数”等操作。 此外, BITFIELD 命令还可以对指定的整数执行加法操作和减法操作, 并且这些操作可以通过设置妥善地处理计算时出现的溢出情况。

BITFIELD 命令可以在一次调用中同时对多个位范围进行操作: 它接受一系列待执行的操作作为参数, 并返回一个数组作为回复, 数组中的每个元素就是对应操作的执行结果。语法如下:

BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]

以下命令是对位于偏移量 100 的 8 位长有符号整数执行加法操作, 并获取位于偏移量 0 上的 4 位长无符号整数:

127.0.0.1:6379> BITFIELD mykey INCRBY i8 100 1 GET u4 0
1) (integer) 1
2) (integer) 0
127.0.0.1:6379>

注意:

  • 使用 GET 子命令对超出字符串当前范围的二进制位进行访问(包括键不存在的情况), 超出部分的二进制位的值将被当做是 0 。
  • 使用 SET 子命令或者 INCRBY 子命令对超出字符串当前范围的二进制位进行访问将导致字符串被扩大, 被扩大的部分会使用值为 0 的二进制位进行填充。 在对字符串进行扩展时, 命令会根据字符串目前已有的最远端二进制位, 计算出执行操作所需的最小长度。

3.10.2.支持的子命令以及数字类型

以下是 BITFIELD 命令支持的子命令:

  • GET <type> <offset> —— 返回指定的二进制位范围。
  • SET <type> <offset> <value> —— 对指定的二进制位范围进行设置,并返回它的旧值。
  • INCRBY <type> <offset> <increment> —— 对指定的二进制位范围执行加法操作,并返回它的旧值。用户可以通过向 increment 参数传入负值来实现相应的减法操作。

除了以上三个子命令之外, 还有一个子命令, 它可以改变之后执行的 INCRBY 子命令在发生溢出情况时的行为:

  • OVERFLOW [WRAP|SAT|FAIL]

当被设置的二进制位范围值为整数时, 用户可以在类型参数的前面添加 i 来表示有符号整数, 或者使用 u 来表示无符号整数。 比如说, 我们可以使用 u8 来表示 8 位长的无符号整数, 也可以使用 i16 来表示 16 位长的有符号整数。

BITFIELD 命令最大支持 64 位长的有符号整数以及 63 位长的无符号整数, 其中无符号整数的 63 位长度限制是由于 Redis 协议目前还无法返回 64 位长的无符号整数而导致的。

1)BITFIELD key [GET type offset]

hello 的二进制字节码等价于 0110100001100101011011000110110001101111

 

 代码演示如下:

# 设置键为mykey,值为hello
127.0.0.1:6379> set mykey hello
OK
# 由于字母等于二进制的构成的8位,所有0、81624、32分别取的hello的每一个字母所对应的十进制数字
127.0.0.1:6379> BITFIELD mykey get i8 0
1) (integer) 104
127.0.0.1:6379> BITFIELD mykey get i8 8
1) (integer) 101
127.0.0.1:6379> BITFIELD mykey get i8 16
1) (integer) 108
127.0.0.1:6379> BITFIELD mykey get i8 24
1) (integer) 108
127.0.0.1:6379> BITFIELD mykey get i8 32

2)BITFIELD key [SET type offset value]

127.0.0.1:6379> set mykey hello
OK
127.0.0.1:6379> BITFIELD mykey get i8 0
1) (integer) 104
127.0.0.1:6379> BITFIELD mykey get i8 8
1) (integer) 101
127.0.0.1:6379> BITFIELD mykey get i8 16
1) (integer) 108
127.0.0.1:6379> BITFIELD mykey get i8 24
1) (integer) 108
127.0.0.1:6379> BITFIELD mykey get i8 32
1) (integer) 111
# 从第9位开始,将接下来8个位用符号数替换为ascill码表117对应的字母u,返回的是其原来的值对应的十进制数101对应的是e
127.0.0.1:6379> BITFIELD mykey set i8 8 117
1) (integer) 101
# 输出后如下
127.0.0.1:6379> get mykey
"hullo"
127.0.0.1:6379>

BITFIELD key  [INCRBY type offset increment]

默认情况下, INCRBY 使用 WRAP 参数,这是因为ASCII码表中最大的十进制是127,超过了就溢出,而对于溢出有Redis这里有三种方式,默认的就是WRAP模式:

127.0.0.1:6379> get mykey
"hello"
#u4表示无符合的4位,2表示从第二位之后,也就是第三位开始,加一
127.0.0.1:6379> BITFIELD mykey incrby u4 2 1
1) (integer) 11
127.0.0.1:6379> BITFIELD mykey incrby u4 2 1
1) (integer) 12
127.0.0.1:6379> BITFIELD mykey incrby u4 2 1
1) (integer) 13
127.0.0.1:6379> BITFIELD mykey incrby u4 2 1
1) (integer) 14
127.0.0.1:6379> BITFIELD mykey incrby u4 2 1
1) (integer) 15
# 默认是WRAP,0表示出现了循环溢出
127.0.0.1:6379> BITFIELD mykey incrby u4 2 1
1) (integer) 0
127.0.0.1:6379> BITFIELD mykey incrby u4 2 1
1) (integer) 1
127.0.0.1:6379> BITFIELD mykey incrby u4 2 1
1) (integer) 2
127.0.0.1:6379> BITFIELD mykey incrby u4 2 1
1) (integer) 3
127.0.0.1:6379> get mykey

溢出控制OVERFLOW [WRAP|SAT|FAIL]

  • WRAP: 使用回绕(wrap around)方法处理有符号整数和无符号整数的溢出情况
# 设置一个字符串,键为mykey 值为a
127.0.0.1:6379> set mykey1 a
OK
# 获取a对应ASCII表中十进制的值
127.0.0.1:6379> BITFIELD mykey1 get i8 0
1) (integer) 97
# i8表示有符号8位二进制,范围是(-128-127),这里是将其设置为127,返回旧值
127.0.0.1:6379> BITFIELD mykey1 set i8 0 127
1) (integer) 97
# 查看修改后对应的值
127.0.0.1:6379> BITFIELD mykey1 get i8 0
1) (integer) 127
127.0.0.1:6379> BITFIELD mykey1 set i8 0 188
1) (integer) 127
# 再次设置,超过范围,则会溢出,默认为wrap模式
127.0.0.1:6379> BITFIELD mykey1 get i8 0
1) (integer) -68
127.0.0.1:6379>
  • SAT: 使用饱和计算(saturation arithmetic)方法处理溢出,下溢计算的结果为最小的整数值,而上溢计算的结果为最大的整数值
# 设置是188,超过范围
127.0.0.1:6379> BITFIELD mykey1 overflow sat set i8 0 188
1) (integer) -68
# 查看显示上溢计算的结果为最大的整数值127
127.0.0.1:6379> BITFIELD mykey1 get i8 0
1) (integer) 127
# # 设置为是-188,超过范围
127.0.0.1:6379> BITFIELD mykey1 overflow sat set i8 0 -277
1) (integer) 127
# 所以下溢计算的结果为最小的整数值-128
127.0.0.1:6379> BITFIELD mykey1 get i8 0
1) (integer) -128
  • FAIL: 命令将拒绝执行那些会导致上溢或者下溢情况出现的计算,并向用户返回空值表示计算未被执行
# overflow的值改为fail,这时候下溢的太多到了-277,拒绝执行了
127.0.0.1:6379> BITFIELD mykey1 overflow fail set i8 0 -277
1) (nil)
127.0.0.1:6379>
posted @ 2023-03-02 19:17  酒剑仙*  阅读(374)  评论(0编辑  收藏  举报