侧边栏壁纸
  • 累计撰写 57 篇文章
  • 累计创建 23 个标签
  • 累计收到 4 条评论

Redis常见数据结构

cluski
2022-03-16 / 2 评论 / 1 点赞 / 898 阅读 / 22,810 字
温馨提示:
本文最后更新于 2022-04-03,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

Redis常见数据结构

在线Redis: https://try.redis.io/

Redis是基于键值对的NoSQL数据库,键都是字符串,值是不同的数据结构。值可以是: string (字符串) 、Hash (哈希) 、List (列表) 、Set(集合) ,、Zset (sorted set:有序集合), Bitmap (位图) 、HyperLogLogGEO (地理信息定位) 、Stream(流)等多种数据结构。

1 String

String是Redis最基本的类型,一个key对应一个value,value是字符串类型,也可以是数字类型,如果是数字类型, Redis会自动识别。

String类型是二进制安全的,也就意味着Redis的String可以存储任何数据。比如jpg图片或者序列化的对象。

String类型是Redis最基本的数据类型, String类型的值最大能存储512MB

image-20210907220119729

常用命令

1.1 赋值

命令:SET key value [EX seconds] [PX milliseconds] [NX|XX]

set命令有几个选项:

  • EX seconds:为键设置过期时间,单位为
  • PX milliseconds:为键设置过期时间,单位为毫秒
  • NX:只有当键不存在时,才可设置成功,用于添加。
  • XX:只有当键存在时,才可设置成功,用于更新。

代码演示:

设置name为“yangmi”

127.0.0.1:6379> SET name "yangmi"
OK

设置过期时间为5秒

127.0.0.1:6379> SET name "yangmi" EX 5	
OK

设置过期时间为500毫秒

127.0.0.1:6379> SET name "yangmi" PX 500	
OK

当键不存在时才能设置成功:

127.0.0.1:6379> SET name "yangmi" 
OK
127.0.0.1:6379> SET name "yangmi1" NX
nil

当键存在时才可设置成功

127.0.0.1:6379> SET add "beijing" XX
nil

1.2 取值

命令: GET key

当键不存在时返回nil(空)

127.0.0.1:6379> GET name
"yangmi"

127.0.0.1:6379> GET address
(ni1)

1.3 删除

命令:DEL key [key ...]

127.0.0.1:6379> DEL name
(integer) 1

1.4 计数

命令: INCR key

INCR命令用于对值做自增操作,返回结果分为三种情况:

  1. 自增

    • 值不是整数,返回错误。

    • 值是整数,返回自增后的结果。

    • 键不存在,按照值为0自增,返回结果为1

      127.0.0.1:6379> incr age
      (integer) 1
      127.0.0.1:6379> incr age
      (integer) 2
      
  2. 增加指定的整数

    命令: INCRBY key increment

    127.0.0.1:6379> incrby age 5
    (integer) 7
    
  3. 自减

    命令: DECR key

    127.0.0.1:6379> decrby age 5
    (integer) 2
    

1.5 批量设置值

命令: MSET kely value [key value ...]

127.0.0.1:6379> mset name "yangmi" age 30 address beijing
ОL

1.6 批量获取值

命令: MGET key [key..]

127.0.0.1:6379> mget name age address
1) "yangmi"
2) "30"
3) "beijing"

应用场景

  • 缓存、缓存用户信息、广告、文章等
  • 计数器、点赞、商品库存、视频浏览次数等
  • 分布式锁

2 Hash

hash叫也散列类型,类似其它语言的Map,HashMap等。其本身又是一个键值对结构,提供了field (字段)和value (字段值)的映射。

字段值只能是字符串类型,不支持散列类型、集合类型等其它类型。相当于一个key对应一个map。(注意这里的value是指field对应的值,不是键对应的值,注意value在不同上下文的作用。)

示例:image-20210909211425385

常用命令:

2.1 设置值

命令:HSET key field value

例如:

127.0.0.1:6379> hset user:1 name wyf
(integer) 1

2.2 获取值

命令:HGET key field

例如:

127.0.0.1:6379> hget user:1 name
"wyf"

2.3 删除field

命令:HDEL key field

例如:

127.0.0.1:6379> hdel user:1 name
(integer) 1
127.0.0.1:6379> hget user:1 name
(nil)

2.4 批量设置field-value

命令:HMSET key field [field value ...]

例如:

127.0.0.1:6379> hmset user:1 name "wyf" age "18" city "beijing"
OK

2.5 获取所有的field-value

命令:HGETALL key

例如:

127.0.0.1:6379> hgetall user:1
1) "name"
2) "cl"
3) "age"
4) "18"
5) "city"
6) "beijing"
7) "addr"
8) "nantong"

2.6 获取所有field

命令:HKEYS key

例如:

127.0.0.1:6379> HKEYS user:1
1) "name"
2) "age"
3) "city"
4) "addr"

2.7 获取所有value

命令:HVALS key

例如:

127.0.0.1:6379> HVALS user:1
1) "cl"
2) "18"
3) "beijing"
4) "nantong"

2.8 获取field个数

命令:HLEN key

例如:

127.0.0.1:6379> hlen user:1
(integer) 4

应用场景

  • 存储用户信息

3 List

Redis的list (列表)是一种线性的有序结构,可以按照元素被推入列表中的顺序来存储元素,并且列表中的元素可以重复。

你可以添加一个元素到列表的头部(左边)或者尾部(右边)一个列表最多可以包含232-1个元素(4294967295,每个列表超过40亿个元素)。

由于列表中的元素是有序的,也就意味着通过索引下标获取元素,索引从0开始,-1表示「倒数第一」,-2表示「倒数第二」。

image-20210909212929376

常用命令:

3.1 添加

3.1.1 从左边插入数据

命令:LPUSH key value [value ...]
从左边一次插入“one”、“two”、“three”、“four”四个元素的图解过程:

当然也可以一次性插入多个元素

image-20210909213341111

127.0.0.1:6379> lpush numbers "one"
(integer) 1
127.0.0.1:6379> lpush numbers "two"
(integer) 2
127.0.0.1:6379> lpush numbers "three"
(integer) 3
127.0.0.1:6379> lpush numbers "four"
(integer) 4
127.0.0.1:6379> lpush numbers "one" "two" "three" "four"
(integer) 4

3.1.2 从右边插入数据

命令:RPUSH key value [value ...]
从右边一次插入“one”、“two”、“three”、“four”四个元素的图解过程:

当然也可以一次性插入多个元素

image-20210909213700041

127.0.0.1:6379> rpush numbers "one"
(integer) 1
127.0.0.1:6379> rpush numbers "two"
(integer) 2
127.0.0.1:6379> rpush numbers "three"
(integer) 3
127.0.0.1:6379> rpush numbers "four"
(integer) 4
127.0.0.1:6379> rpush numbers "one" "two" "three" "four"
(integer) 4

3.1.3 在某个元素的前后插入元素

命令:LINSET key BEFORE|AFTER pivot value

linsert numbers after "two" "six"命令的图解:

127.0.0.1:6379> linsert numbers after "two" "six"
(integer) 5

image-20210909213950669

3.2 查找

3.2.1获取指定范围内的元素列表

命令:LRANGE key start stop

注意:lrange中的stop选项包含了自身。例如想获取列表的第2个到第4个元素,命令为-1为列表中最后一个元素,所以查看所有元素命令为

127.0.0.1:6379> lrange numbers 1 3
1) "two"
2) "six"
3) "three"

获取全部:

127.0.0.1:6379> lrange numbers 0 -1
1) "one"
2) "two"
3) "six"
4) "three"
5) "four"

3.2.2 获取列表长度

命令:LLEN key

127.0.0.1:6379>  llen numbers
(integer) 5

3.3 删除

3.3.1 从列表左侧弹出元素

命令:LPOP key

假设现有列表numbers有“one”、“two”、“three”、“four”四个元素,依次从左侧弹出

图解:

image-20210909214631138

127.0.0.1:6379> lpop numbers
"one"
127.0.0.1:6379> lpop numbers
"two"
127.0.0.1:6379> lpop numbers
"three"
127.0.0.1:6379> lpop numbers
"four"

3.3.2 从列表右侧弹出

命令:RPOP key

假设现有列表numbers有“one”、“two”、“three”、“four”四个元素,依次从右侧弹出

图解:image-20210909215033219

127.0.0.1:6379> rpop numbers
"four"
127.0.0.1:6379> rpop numbers
"three"
127.0.0.1:6379> rpop numbers
"two"
127.0.0.1:6379> rpop numbers
"one"
127.0.0.1:6379> rpop numbers
(nil)

3.4 修改

3.4.1 修改指定索引下标的元素

命令:LSET key index value

127.0.0.1:6379> lset numbers 0 "six"
OK

应用场景

  • 队列,先进先出

4 Set

Set (集合)类型也是用来保存多个的字符串元素,但和列表类型不一样的是,集合中不允许有重复元素,并且集合中的元素(member)是无序的,不能通过索引下标获取元素。

Redis除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集,合理地使用好集合类型,能在
实际开发中解决很多实际问题。

集合中最大的成员数为232-1 (4294967295,每个集合可存储40多亿个成员)。

image-20210909215539426

常用命令:

4.1 添加元素

命令:SADD key member [member ...]

127.0.0.1:6379> sadd user:1 "java" "go" "vue" "python"
(integer) 4

4.2 获取所有元素

命令:SADD key member [member ...]
注意顺序,和插入的顺序并不一致,因为set是无序的。

127.0.0.1:6379> smembers user:1
1) "vue"
2) "java"
3) "python"
4) "go"

4.3 删除元素

命令:SREM key member [member ...]

127.0.0.1:6379> srem user:1 go
(integer) 1
127.0.0.1:6379> smembers user:1
1) "vue"
2) "java"
3) "python"

没有直接修改元素的命令,如需修改先移除元素后再添加水元素,这是set的底层数据结构决定的。

4.4 集合间操作

现有两个集合

image-20210909220121477

4.4.1 求多个集合的交集

命令:SINTER key [key...]

127.0.0.1:6379> sadd user:1 "go" "python" "java" "vue"
(integer) 4
127.0.0.1:6379> sadd user:2 "java" "vue" "react" "js"
(integer) 4
127.0.0.1:6379> sinter user:1 user:2
1) "vue"
2) "java"

image-20210909220158652

4.4.2 求多个集合的并集

命令:SUNION key [key...]

127.0.0.1:6379> sunion user:1 user:2
1) "vue"
2) "java"
3) "react"
4) "js"
5) "python"
6) "go"

image-20210909220339425

4.4.3 求多个集合的差集

命令:SDIFF key [key...]

127.0.0.1:6379> sdiff user:1 user:2
1) "go"
2) "python"
127.0.0.1:6379> sdiff user:2 user:1
1) "js"
2) "react"

image-20210909220551345

应用场景:

  • 给用户添加标签

    image-20210909220658810

  • 共同好友

5 HyperLogLog

5.1 问题场景

有一个功能需求,需要统计一个网站或者一个页面,每天大致有多少用户点击进入。同一个用户的点击i
入多次记为1次。

假设就以用户的IP作为唯一表示,同一IP地址多次访问网站,只记作一次用户访问。

我们可能马上想到,用一个计数器和一个容器(HashMap、HashSet)来解决。

用容器来记录每次来访问的IP地址,如果这个IP地址以前没有访问过我们的网站,那我们就让计数器+1;
如果这个IP已经存在我们的容器中了,计数器不变。

但这样会存在一个问题,就是随着访问量的增加,以及时间的推移,存储的IP会越来越多,为此我们要花
费的内存也会越来越多。我们举例来说:

  • 存储一个IPv4格式的IP最多需要15个字节,比如"127.356.271.310"
  • 网站规模不同,每天出现的唯一IP可能有数十万、数百万个
  • 长期统计就需要存储很多的数据

image-20210911125741679

花这么多的内存,存储并没有太大意义的数据,显然不合适,那我们该如何做呢?

这就引出了我们下面的大佬, HyperLogLog! ! !

5.2 基本概念

HyperLogLog是一个专门为了设计集合的基数而创建的概率算法。

啥是基数?

基数就是某数据集去重后的数据数量。比如数据集{1,2,3,4,4,5},这个数据集的基数集(不重复元素)就是{1,2,3,4,5},基数(不重复元素的个数)就是5。

对于一个给定的集合,可以计算出这个集合的近似基数,并非集合的精准基数,他可能会比实际的基数小一点或大一点。但估算误差会处于一个合理的范围内。

特点:

  • 在Redis中每个HyperLogLog只需要使用12KB内存空间就可以对264个元素进行计数,而算法的标准
    误差仅为0.81%,因此它计算出的近似基数是相当可信的。

因此那些不需要知道准确基数,或者因为条件限制而无法计算出准确基数的程序,就可以把这个近似基数当作集合的基数来使用。

接下来我们来看一下HyperLogLog如何使用。

5.3 常用命令

5.3.1 添加指定到元素到HyperLogLog中

命令:PFADD key element [element ...]

讲解:根据给定的元素是否已经进行过计数,,PFADD命令可能返回0,也可能返回1。

  • 如果给定的所有元素都已经进行过计数,那么命令将返回0,表示HyperLogLog计算出的近似基数没有发生变化。
  • 如果给定的元素至少有一个之前没有进行过计数的元素,导致计算出的近似基数发生了变化,那么
    HyperLogLog命令将返回1。
127.0.0.1:6379> pfadd mylog a b c
(integer) 1
127.0.0.1:6379> pfadd mylog c
(integer) 0
127.0.0.1:6379> pfadd mylog c d
(integer) 1

5.3.2 统计集合的近似基础

命令:PFCOUNT key [key ...]

我们可以通过PFCOUNT命令来获取HyperLogLog为集合计算出的近似基数。当输入的key不存在时返回0
代码演示:

127.0.0.1:6379> pfcount mylog
(integer) 4
127.0.0.1:6379> pfcount my
(integer) 0

如果传入多个key的时候,PFCOUNT命令将所有给定的HyperLogLog进行合并,然后返回合并之后的近似基数。

也就是说会将多个集合合并去重,求合并去重之后的元素个数。

代码演示:

127.0.0.1:6379> pfadd one a b c
(integer) 1
127.0.0.1:6379> pfadd two c d e
(integer) 1
127.0.0.1:6379> pfcount one two
(integer) 5

5.3.3 合并多个HyperLogLog

命令:PFMERGE key [key ...]

合并多个key,并将结果存储在新创建的new_key中,返回结果OK

127.0.0.1:6379> pfadd one a b c
(integer) 1
127.0.0.1:6379> pfadd two c d e
(integer) 1
127.0.0.1:6379> pfmerge three one two
OK
127.0.0.1:6379> pfcount three
(integer) 5
127.0.0.1:6379> pfadd three f
(integer) 1
127.0.0.1:6379> pfcount three
(integer) 6

PFCOUNT与PFMERGE的区别与联系:

  • PFCOUNT命令传入多个集合时,内部调用了PFMERGE命令,然后进行了如下操作
  • 进行合并HyperLogLog,并将结果集存储到临时HyperLogLog中
  • 对临时HyperLogLog执行PFCOUNT命令,求出基数
  • 删除临时HyperLogLog
  • 将结果返回给客户端

因此当程序需要对多个HyperLogLog调用PFCOUNT命令,且这个调用需要重复执行时,可以考虑把这
一调用替换成相应的PFMERGE命令调用:通过把并集的计算结果存储到指定的HyperLogLog中,而不是
每次都重新计算并集程序,可以很大程度的减少不必要的合并计算。

6 GEO

6.1 应用场景

你在玩游戏时,可能会用到【附近的人】这个功能来匹配附近玩家;订外卖时,系统会推荐你当前定位附
近的商家;骑行共享单车时,系统会帮你搜索附近的单车和停车位置。那你知道这些地理位置在Redis中
是如何存储和计算搜索的吗?

在Redis中有专门存储和计算地理位置的数据结构GEO,让我们起来学习吧!

6.2 基本概念

GEO是Redis在3.2版本中新添加的特性,通过这一特性,用户可以将经纬度格式的地理坐标存储到Redis
中,并对坐标执行距离计算、范围查找等操作。

6.3 常用命令

6.3.1 储存坐标

命令:GEOADD key Longitude latitude member[longitude latitude member...]

longitude经度、latitude纬度

该命令返回新添加到key中的坐标数量作为返回值。

当添加的坐标在集合中已存在时,就会用新坐标代替已有的旧坐标

代码演示:

百度地图经纬度拾取:
http://api.map.baidu.com/lbsapi/getpoint/index.html

127.0.0.1:6379> geoadd Beijing-Universities 116.316833 39.998877 beida
(integer) 1
127.0.0.1:6379> geoadd Beijing-Universities 116.333374 40.009645 qinghua 116.319769 39.976546 renda 116.353792 39.98766 beihang
(integer) 3

6.3.2 获取指定位置的坐标

命令:GEOPOS key member[member...]

代码演示:

127.0.0.1:6379> geopos Beijing-Universities beida
1) 1) "116.31683439016342163"
   2) "39.99887702937557066"
127.0.0.1:6379> geopos Beijing-Universities qinghua
1) 1) "116.33337289094924927"
   2) "40.00964452486044109"
127.0.0.1:6379> geopos Beijing-Universities beida qinghua renda
1) 1) "116.31683439016342163"
   2) "39.99887702937557066"
2) 1) "116.33337289094924927"
   2) "40.00964452486044109"
3) 1) "116.31976872682571411"
   2) "39.97654613596179729"

为什么获取的经纬度跟传进去的不一样呢?

Geohash是一种编码格式,这种格式可以将用户给定的经度和纬度转化为单个Geohash值,也可以根据
给定的Geohash还原出被转换的经度和纬度。

通过GEOADD的坐标会被转换成一个52位的Geohash值,查询的时候会根据Geohash还原经纬度,所
以也会存在小范围的误差。

6.3.3 查询两个地理位置之间的直线距离

命令:GEODIST key member1 member2 [unit]

返回两个给定位置之间的距离。如果两个位罩之间的其中一个不存在,那么命令返回空值。

指定单位的参数unit必须是以下单位的其中一

  • m表示单位为米。
  • km表示单位为千米。
  • mi表示单位为英里。
  • ft表示单位为英尺。
127.0.0.1:6379> geodist Beijing-Universities beida qinghua
"1849.2630"
127.0.0.1:6379> geodist Beijing-Universities beida qinghua km
"1.8493"

6.3.4 查询指定坐标半径范围内的其他位置

命令:GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]

以给定的经纬度为中心,从位置集合中找出位于中心点指定半径范围内的其他的位置。

在给定以下可选项时,命令会返回额外的信息:

  • WITHDIST :在返回位置元素的同时,将位置元素与中心之间的距离也一并返回。距离的单位和用户给定的范围单位保持一致。
  • WITHCOOR:将位置元素的经度和纬度也一并返回。
  • WITHHASH:以52位有符号整数的形式,返回位置元素经过原始Geohash 编码的有序集合分值。这个选项主要用于底层应用或者调试,实际中的作用并不大。

命令默认返回未排序的位置元素。通过以下两个参数,用户可以指定被返回位置元素的排序方式:

  • ASC:根据中心的位置,按照从近到远的方式返回位置元素。
  • DESC:根据中心的位置,按照从远到近的方式返回位置元素。
127.0.0.1:6379> georadius Beijing-Universities 116.316833 39.998877 500 m
1) "beida"  --匹配的位置的名称
127.0.0.1:6379> georadius Beijing-Universities 116.316833 39.998877 500 km
1) "renda"
2) "beihang"
3) "beida"
4) "qinghua"
127.0.0.1:6379> georadius Beijing-Universities 116.316833 39.998877 500 km withcoord
1) 1) "renda"
   2) 1) "116.31976872682571411" --经度
      2) "39.97654613596179729"  --纬度
2) 1) "beihang"
   2) 1) "116.35378986597061157"
      2) "39.98766088824549314"
3) 1) "beida"
   2) 1) "116.31683439016342163"
      2) "39.99887702937557066"
4) 1) "qinghua"
   2) 1) "116.33337289094924927"
      2) "40.00964452486044109"
127.0.0.1:6379> georadius Beijing-Universities 116.316833 39.998877 500 km withcoord withdist
1) 1) "renda"
   2) "2.4963"  --与中心点的距离
   3) 1) "116.31976872682571411"
      2) "39.97654613596179729"
2) 1) "beihang"
   2) "3.3873"
   3) 1) "116.35378986597061157"
      2) "39.98766088824549314"
3) 1) "beida"
   2) "0.0001"
   3) 1) "116.31683439016342163"
      2) "39.99887702937557066"
4) 1) "qinghua"
   2) "1.8494"
   3) 1) "116.33337289094924927"
      2) "40.00964452486044109"

127.0.0.1:6379> georadius Beijing-Universities 116.316833 39.998877 200 km count 3
1) "beida"
2) "qinghua"
3) "renda"
127.0.0.1:6379> georadius Beijing-Universities 116.316833 39.998877 200 km asc count 3
1) "beida"
2) "qinghua"
3) "renda"
127.0.0.1:6379> georadius Beijing-Universities 116.316833 39.998877 200 km desc count 3
1) "beihang"
2) "renda"
3) "qinghua"

6.3.5 根据名称查找指定半径范围内的其他位置

命令:GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]

这个命令和GEORADIUS命令一样,都可以找出位于指定范围内的元素,不同的是GEORADIUSBYMEMBER传入的是位置名称。
代码演示:

127.0.0.1:6379> georadiusbymember Beijing-Universities beida 2000 m withdist
1) 1) "beida"
   2) "0.0000"
2) 1) "qinghua"
   2) "1849.2630"

6.3.6 获取指定位置的Geohash值

命令:GEOHASH key menber [member ...]

传入一个或多个位置来获得这些位置对应的经纬度坐标的Geohash表示

代码演示:

127.0.0.1:6379> geohash Beijing-Universities beida qinghua beihang
1) "wx4ewce66e0"
2) "wx4ex5x3h40"
3) "wx4erxw1pb0"

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

7 Sorted Set

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

image-20210911135753374

值得注意的是,有序集合中的每个成员都是独一无二的,同一个有序集合中不会出现重复的成员。但不同成员的分值却可以是相同的,而且分值除了可以是数字外还可以是两个特殊的字符串: "+inf"、"-inf"。这两个特殊的字符串分别表示:无穷大和无穷小。

当两个或多个成员拥有相同的分值时, Redis将按照这些成员在字典序中的大小对其进行排列。

举个例子:如果成员成员"apple"和成员"zero"都拥有相同的分值100,那么Redis将认为成员"apple"小于成员"zero",这是因为在字典中,字母"a"开头的单词要小于字母"2"开头的单词。

集合中最大的成员数为232-1 (4294967295,每个集合可存储40多亿个成员)。

由于SortedSet所有的命令都是以2开头所以SortedSet也叫做zset

常用命令:

7.1 添加元素

命令:ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
选项:

  • NX:member必须不存在,才可以设置成功,用于添加。
  • XX:member必须存在,才可以设置成功,用于更新。
  • CH:返回此次操作后,有序集合元素和分数发生变化的个数。
  • INCR:对score做增加,相当于后面介绍的ZINCRBY
127.0.0.1:6379> zadd country:ranking 1 "meiguo" 2 "zhongguo" 3 "eluosi" 4 "riben" 5 "deguo"
(integer) 5

7.2 返回索引范围的成员

命令: ZRANGE key start stop [MHCORES]
ZREVRANGE key start stop [WITHSCORES]

有序集合是按照分值排名的, ZRANGE是从低到高返回, ZREVRANGE则相反,是从高到低返回。
WITHSCORES :同时返回成员的分数。

参数startstop都是基于零的索引,即。是第一个元素,1是第二个元素,以此类推。它们也
可以是负数,表示从有序集合的末尾的偏移量,其中-1是有序集合的最后一个元素,-2是倒数第二个
元素,等等。

如图:

image-20210911140927913

start和stop都是全包含的区间,因此例如ZRANGE country: ranking 0 1将会返回有序集合的第
一个和第二个元素。

如果是ZRANGE country:ranking 0 -1将返回所有元素。

127.0.0.1:6379> zrange country:ranking 0 -1
1) "meiguo"
2) "zhongguo"
3) "eluosi"
4) "riben"
5) "deguo"
127.0.0.1:6379> zrange country:ranking 0 -1 withscores
 1) "meiguo"
 2) "1"
 3) "zhongguo"
 4) "2"
 5) "eluosi"
 6) "3"
 7) "riben"
 8) "4"
 9) "deguo"
10) "5"
127.0.0.1:6379> zrevrange country:ranking 0 -1
1) "deguo"
2) "riben"
3) "eluosi"
4) "zhongguo"
5) "meiguo"
127.0.0.1:6379> zrevrange country:ranking 0 -1 withscores
 1) "deguo"
 2) "5"
 3) "riben"
 4) "4"
 5) "eluosi"
 6) "3"
 7) "zhongguo"
 8) "2"
 9) "meiguo"
10) "1"

7.3 增加成员的分数

命令: ZINCRBY key increment member

127.0.0.1:6379> zincrby country:ranking 2 zhongguo
"4"
127.0.0.1:6379> zrevrange country:ranking 0 -1 withscores
 1) "deguo"
 2) "5"
 3) "zhongguo"
 4) "4"
 5) "riben"
 6) "4"
 7) "eluosi"
 8) "3"
 9) "meiguo"
10) "1"

7.4 删除元素

命令:ZREM key member [member ...]

127.0.0.1:6379> zrem country:ranking "riben"
(integer) 1
127.0.0.1:6379> zrevrange country:ranking 0 -1 withscores
1) "deguo"
2) "5"
3) "zhongguo"
4) "4"
5) "eluosi"
6) "3"
7) "meiguo"
8) "1"

应用场景

  • 排行榜,比如歌曲排行榜、下载排行榜(购买排行榜等。

8 Bitmap

8.1 基本概念

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

image-20210911142813774
上图展示了一个包含8个二进制位的位图示例,这个位图存储的值为10110000。

8.2 常用命令

8.2.1 设置二进制为的值

命令:SETBIT key offset value

SETBIT命令在对二进制位进行设置后,将返回二进制位被设置之前的旧值作为结果。
代码演示:

127.0.0.1:6379> setbit mybit 0 1
(integer)
127.0.0.1:6379> setbit mybit 3 1
(integer)
127.0.0.1:6379> setbit mybit 6 1
(integer)

位图的扩展

当用户执行SETBIT 命令对一个位图进行操作时,如果位图当前的大小无法满足用户想要执行的设置操
作,那么Redis将对被设置的位图进行扩展。

Redis对位图的扩展操作是以字节为单位进行的,所以扩展之后的位图包含的二进制位数量可能会比用户要
求的稍微多一些,并且在扩展位图的同时,Redis还会将所有未被设置的二进制位的值初始化为0。

假设我们对不存在的位图mybito2在偏移量10的二进制位进行设置:

127.0.0.1:6379> setbit mybit02 10 1
(integer)

Redis并不是仅仅创建11个二进制位,而是创建两个字节共16个二进制位

image-20210911143017255

8.2.2 获取二进制为的值

命令:GETBIT key offset

获取位图指定偏移量上的二进制位的值

代码演示:

127.0.0.1:6379> getbit mybit 0
(integer) 1
127.0.0.1:6379> getbit mybit 3
(integer) 1
127.0.0.1:6379> getbit mybit 300
(integer) 0

如果用户输入的偏移量超过了位图目前拥有最大的偏移量,那么GETBIT命令将返回0作为结果。

8.2.3 统计被设置的二进制位数量

命令:BITCOUNT key [start end]

统计字符串被设置为1的二进制位数量。

start参数和end参数要特别注意,指的不是偏移量而是字节偏移量

image-20210911143241682

127.0.0.1:6379> bitcount mybit
(integer) 3
127.0.0.1:6379> bitcount mybit2 0 1
(integer) 0
127.0.0.1:6379> bitcount mybit02 0 1
(integer) 1
127.0.0.1:6379> bitcount mybit02 0 0
(integer) 0
127.0.0.1:6379> bitcount mybit02 1 1
(integer) 1

9 Stream

流(Stream)是Redis 5.0版本中新增加的数据结构,也是该版本最重要的更新。

在以往的版本中为了实现消息队列这一常见应用,用户往往需要使用列表(List) 、有序集合(Sorted
Set)和发布/订阅的三个功能,但这些不同的实现都有各自的缺陷。

  • List不能查找指定数据的元素,也不能进行范围查找。
  • Sorted Set虽然能进行范围查找,但缺少发布/订阅提供的弹出操作。
  • 发布/订阅功能,虽然能将消息传递给多个客户端,但消息“发送即忘”的策略会导致客户端丢失消息,所以它是无法实现消息的可靠传递。

Redis流的出现解决了上述提到的所有问题,它是的三种数据结构的综合体,具备它们各自的所有优点及特
点,是使用专利实现消息队列应用的最佳选择。

Stream是一个包含零个或者任意多个流元素的有序队列,队列中的每个元素都包含一个ID和任意多个键值 对,这些元素会根据ID的大小在流中有序的进行排列。

image-20210911145449714

上图所示中,名字为users的流中,存储了三个元素,他们每个都包含了三个键值对。

流元素的ID由毫秒时间和顺序编号两部分组成,毫秒时间顺序编号都用64位的非负整数表示,所以整
个流ID的总长为128位。

并且Redis在接受流ID输入以及展示流ID的时候都会使用连字符-分割这两个部分。

常用命令:

9.1 添加新的元素到流的末尾

语法: XADD key ID field string [field string ...]

将一个带有指定ID以及包含指定键值对的元素追加到流的末尾。如果给定的流不存在,那么Redis会先创
建一个空白的流,然后将给定的元素追加到流中。

如果ID是我们传入的,传入的ID大小一定要比流中已有最大的ID要大,否则Redis不允许填入,会报错。
如果ID参数是字符*, Redis会自动生成一个唯一的ID。

如果同一时间内存入多条数据,ID的毫秒时间部分不变,顺序编号递增。

127.0.0.1:6379> xadd mystream * name zhangsan age 40
"1631343498805-0"
127.0.0.1:6379> xadd mystream * name Lisi age 20
"1631343502755-0"
127.0.0.1:6379> xadd mystream * name wangwu age 30
"1631343521054-0"

9.2 访问流中的元素

语法:XRANGE key start end [COUNT count]
此命令传入start_id和end_id,返回流中满足给定ID范围的 。范围由最小和最大ID指定。所有ID在指
定的两个ID之间或与其中一个ID相等(闭合区间)的条目将会被返回。

特殊ID:-和+

特殊ID-和+分别表示流中最小的ID和最大ID,因此可以借助这两个参数查询流中的每一个元素。

127.0.0.1:6379> xrange mystream - +
1) 1) "1631343498805-0"
   2) 1) "name"
      2) "zhangsan"
      3) "age"
      4) "40"
2) 1) "1631343502755-0"
   2) 1) "name"
      2) "Lisi"
      3) "age"
      4) "20"
3) 1) "1631343521054-0"
   2) 1) "name"
      2) "wangwu"
      3) "age"
      4) "30"

指定count

127.0.0.1:6379> xrange mystream - + count 2
1) 1) "1631343498805-0"
   2) 1) "name"
      2) "zhangsan"
      3) "age"
      4) "40"
2) 1) "1631343502755-0"
   2) 1) "name"
      2) "Lisi"
      3) "age"
      4) "20"

9.3 创建消费者组

image-20210911150757046

读取stream中的数据可以通过xrange命令来读取,也可以通过消费者组来消费,这是为了让同一条消息能
被多次消费,而设计的。

同一个stream从逻辑上被分为多个流,从而达到同一个消息被多次消费的目的。

不同的消费者组可以从同一个流的不同位置开始读取数据。

语法:XGROUP [CREATE key groupname id-or-$]
key:流的名称
id-or-$:指从哪个ID开始读取消息,可以是具体的某个ID,也可以是从零开始0-0,也可以是$,$表
示使用stream中最新消息的ID。

代码演示:

127.0.0.1:6379> xgroup create mystream mygroup 0-0
OK

9.4 读取消费者组中的消息

语法:XREADGROUP GROUP group [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key...] ID [ID ...]

>这个id有特殊的含义,表示读取从来没有分给别的消费者的消息

代码演示:

127.0.0.1:6379> xreadgroup group mygroup myc streams mystream >
1) 1) "mystream"
   2) 1) 1) "1631343498805-0"
         2) 1) "name"
            2) "zhangsan"
            3) "age"
            4) "40"
      2) 1) "1631343502755-0"
         2) 1) "name"
            2) "Lisi"
            3) "age"
            4) "20"
      3) 1) "1631343521054-0"
         2) 1) "name"
            2) "wangwu"
            3) "age"
            4) "30"
127.0.0.1:6379> xreadgroup group mygroup myc streams mystream >
(nil)
127.0.0.1:6379> xreadgroup group mygroup myc streams mystream 0
1) 1) "mystream"
   2) 1) 1) "1631343498805-0"
         2) 1) "name"
            2) "zhangsan"
            3) "age"
            4) "40"
      2) 1) "1631343502755-0"
         2) 1) "name"
            2) "Lisi"
            3) "age"
            4) "20"
      3) 1) "1631343521054-0"
         2) 1) "name"
            2) "wangwu"
            3) "age"
            4) "30"

9.5 将消息标记为已处理

当消息给消费者处理完一条消息后,他需要向Redis发送一条针对该消息的XACK命令。

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

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

  • 首先,当一个生产者通过XADD命令向流中添加一条消息时,该消息就从原来的“不存在”状态转换为了
    “未递送”状态。
  • 然后,当一个消费者通过XREADGROUP命令从流中读取一条消息时,该消息就从原来的“未递送
    (pending ) "状态转换成了“待处理”状态。
  • 最后,当消费者完成了对消息的处理,并通过XACK命令向服务器进行确认时,该消息就从原来的待处
    理状态转化成了“已确认”状态。

image-20210911152203381

语法:XACK key group ID [ID ...]

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

代码演示:

127.0.0.1:6379> xack mystream mygroup 1631343498805-0
(integer) 1
127.0.0.1:6379> xreadgroup group mygroup myc streams mystream 0
1) 1) "mystream"
   2) 1) 1) "1631343502755-0"
         2) 1) "name"
            2) "Lisi"
            3) "age"
            4) "20"
      2) 1) "1631343521054-0"
         2) 1) "name"
            2) "wangwu"
            3) "age"
            4) "30"

9.6 显示已读取但未ACK的相关信息

语法XPENDING key group [start end count] [consumer]

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

这些信息包括:待处理消息的数量、待处理消息队列中的首条新消息和最后一条消息的Id。
start end指起始和结束ID,可以进行区间查询
count查看多少条
consumer某个消费者的名字

127.0.0.1:6379> xpending mystream mygroup
1) (integer) 2
2) "1631343502755-0"
3) "1631343521054-0"
4) 1) 1) "myc"
      2) "2"
127.0.0.1:6379> xack mystream mygroup "1631343502755-0"
(integer) 1
127.0.0.1:6379> xpending mystream mygroup
1) (integer) 1
2) "1631343521054-0"
3) "1631343521054-0"
4) 1) 1) "myc"
      2) "1"

9.7 移除指定元素

语法: XDEL key ID [ID...]

从流中删除任意多个ID对应的元素

127.0.0.1:6379> xrange mystream - +
1) 1) "1631345507330-0"
   2) 1) "name"
      2) "zhangsan"
      3) "age"
      4) "40"
2) 1) "1631345510406-0"
   2) 1) "name"
      2) "Lisi"
      3) "age"
      4) "20"
3) 1) "1631345513385-0"
   2) 1) "name"
      2) "wangwu"
      3) "age"
      4) "30"
127.0.0.1:6379> xdel mystream "1631345507330-0"
(integer) 1
127.0.0.1:6379> xrange mystream - +
1) 1) "1631345510406-0"
   2) 1) "name"
      2) "Lisi"
      3) "age"
      4) "20"
2) 1) "1631345513385-0"
   2) 1) "name"
      2) "wangwu"
      3) "age"
      4) "30"

9.8 获取流包含的元素数量

语法: XLEN key
代码演示:

127.0.0.1:6379> xlen mystream
(integer) 2
1

评论区