目 录CONTENT

文章目录

Redis高级数据类型

Sakura
2023-08-09 / 0 评论 / 0 点赞 / 30 阅读 / 22788 字 / 正在检测是否收录...

一: 位图 ( BitMaps )

用String类型作为底层数据结构实现的一种统计二值状态的数据类型

位图本质是数组,它是基于String数据类型的按位的操作。该数组由多个二进制位组成,每个二进制位都对应一个偏移量(我们称之为一个索引)。

Bitmap支持的最大位数是2^32位,它可以极大的节约存储空间,使用512M内存就可以存储多达42.9亿的字节信息(2^32 = 4294967296)

1. setbit 设置值

setbit返回修改之前位的值

setbit key offset value


127.0.0.1:6379> setbit user:10 7 1
(integer) 0
127.0.0.1:6379> setbit user:10 7 0
(integer) 1
  • offset 设置键的第 offset 个位的值 ( 从0算起 )

# 假设现在有20个用户,userid=0,2,4,6,8的用户对网站进行了访问,存储键名为日期
setbit 2023-8-15 0 1
setbit 2023-8-15 2 1
setbit 2023-8-15 4 1
setbit 2023-8-15 8 1

# 某个用户一周或者一个月七天登录情况
setbit User:1 0 1
setbit User:1 1 0
setbit User:1 2 0 

2. getbit 获取值

getbit key offset
  • offset 获取键的第 offset 个位的值 ( 从0算起 )

# 获取userid=8的用户是否在2023.8.15访问过,返回0说明没有访问过
getbit 2023-8-15 8

2. strlen

用于统计字节数 , 不是字符串长度而是占据几个字节,超过8位后自己按照8位一组一byte再扩容

127.0.0.1:6379> setbit user:10 7 1
(integer) 0
127.0.0.1:6379> strlen user:10
(integer) 1
127.0.0.1:6379> setbit user:10 8 1
(integer) 0
127.0.0.1:6379> strlen user:10
(integer) 2

3. bitcount 全部或指定范围值为 1 的个数

  1. 查看全部范围为 1 的个数

bitcount key 

127.0.0.1:6379> bitcount user:10
(integer) 2
  1. 查看指定范围为 1 的个数

bitcount key [start] [end]

# 统计8-15有多少用户访问了
bitcount 2023-8-15 0 9999 #5

4. bitop Bitmaps 间的运算

bitop是一个复合操作,它可以做多个 Bitmaps 的 and ( 交集 ) or ( 并集 ) not ( 非 ) xor ( 异或 )操作并将结果保存在 destkey 中

bitop operation destkey key [key ...]
  1. 计算连续两天登录的用户

127.0.0.1:6379> bitcount user:10
(integer) 2
127.0.0.1:6379> setbit 829 0 1
(integer) 0
127.0.0.1:6379> setbit 829 1 1
(integer) 0
127.0.0.1:6379> setbit 829 2 1
(integer) 0
127.0.0.1:6379> setbit 830 0 0
(integer) 0
127.0.0.1:6379> setbit 830 2 1
(integer) 0
127.0.0.1:6379> bitcount 829
(integer) 3
127.0.0.1:6379> bitcount 830
(integer) 1
127.0.0.1:6379> bitop and destkey 829 830 --
(integer) 1
127.0.0.1:6379> bitcount destkey --连续两天登录的用户只有一个
(integer) 1
  1. 计算某个员工一个月打卡次数

127.0.0.1:6379> setbit user:10012 0 1
(integer) 0
127.0.0.1:6379> setbit user:10012 1 1
(integer) 0
127.0.0.1:6379> setbit user:10012 2 1
(integer) 0
127.0.0.1:6379> setbit user:10012 3 1
(integer) 0
127.0.0.1:6379> setbit user:10012 15 1
(integer) 0
127.0.0.1:6379> setbit user:10012 30 1
(integer) 0
127.0.0.1:6379> bitcount user:10012  -- 这位员工本月打卡6次
(integer) 6

5. bitpos 计算Bitmaps中第一个值 为targetBit 的偏移量

bitpos key targetBit [start] [end]

6. bitmap优势

假设网站有1亿用户,每天独立访问的用户有5千万,如果每天用集合类型和 Bitmaps 分别存储活跃用户

集合类型 : id ( long型 64位 ) = 8 * 50000000 = 400M

Bitmaps : id = ( 1/8位 ) * 100000000 = 12.5M

一年356天,全年天天登录占用多少字节 , 可以看到仅仅占用 45 个字节

按年去存储一个用户的签到情况,365 天只需要 365 / 8 ≈ 46 Byte,1000W 用户量一年也只需要 44 MB 就足够了

127.0.0.1:6379> setbit k1 0 1
(integer) 0
127.0.0.1:6379> setbit k1 2 1
(integer) 0
127.0.0.1:6379> setbit k1 151 1
(integer) 0
127.0.0.1:6379> setbit k1 354 1
(integer) 0
127.0.0.1:6379> setbit k1 354 1
(integer) 1
127.0.0.1:6379> bitcount k1
(integer) 4
127.0.0.1:6379> strlen k1
(integer) 45

7. 面试题

  1. 目前有10亿数量的自然数,乱序排列,需要对其排序。限制条件-在32位机器上面完成,内存限制为 2G。如何完成?

  2. 如何快速在亿级黑名单中快速定位URL地址是否在黑名单中?(每条URL平均64字节)

  3. 需要进行用户登陆行为分析,来确定用户的活跃情况?

  4. 网络爬虫-如何判断URL是否被爬过?

  5. 快速定位用户属性(黑名单、白名单等)

  6. 数据存储在磁盘中,如何避免大量的无效IO?

二: 基数统计( HyperLogLog )

去重复统计功能的基数估计算法-就是 HyperLogLog

  1. 统计某个网站的 UV

  2. 统计某个文章的 UV

  3. 用户搜索网站关键词的数量

  4. 统计用户每天搜索不同词条个数

UV : Unique Visitor,独立访客,一般理解为客户端P , 需要去重考虑

1. 基本命令

  1. PFADD 添加指定元素到 HyperLogLog 中

PFADD key [element [element ...]]
  1. PFCOUNT 返回给定 HyperLogLog 的基数估算值

 PFCOUNT key [key ...]
  1. PFMERAGE 将多个HyperLogLog合并为一个HyperLogLog

PFMERGE destkey sourcekey [sourcekey ...]
127.0.0.1:6379> pfadd 2023-8-29 1 2 2 2 3 4 4 5 8 9
(integer) 1
127.0.0.1:6379> pfcount 2023-8-29
(integer) 7
127.0.0.1:6379> pfadd 2023-8-30 8 9 34 6 14 1 2
(integer) 1
127.0.0.1:6379> pfcount 2023-8-30
(integer) 7
127.0.0.1:6379> pfmerge destkey1 2023-8-29 2023-8-30
OK
127.0.0.1:6379> pfcount destkey1
(integer) 10

三: 地理空间 ( GEO )

1. 基本命令

  1. GEOADD 多个经度 ( longitude )、纬度 ( latitude ) 、位置名称 ( member ) 添加到指定的 key 中

127.0.0.1:6379> GEOADD city 86.117768 41.726879 "学校"
(integer) 1

通过TYPE 查看GEOZSET类型

127.0.0.1:6379> type city
zset

遍历所有value , 可以看出是乱码

127.0.0.1:6379> zrange city 0 -1
1) "\xe5\xad\xa6\xe6\xa0\xa1"

乱码解决方法

redis-cli -- raw

  1. GEOPOS 从键里面返回所有给定位置元素的位置(经度和纬度)

127.0.0.1:6379> GEOPOS city 学校
1) 1) "86.11776798963546753"
   2) "41.72688008949518945"
  1. GEOHASH 返回坐标的 geohash 表示

geohash 算法生成的 base32 编码值

127.0.0.1:6379> GEOHASH school 东校区 西校区
1) "tzmm43su6t0"
2) "tzmjrsjh9q0"
  1. GEODIST 两个位置之间的距离

127.0.0.1:6379> GEODIST school 东校区 西校区 km
"11.8936"
127.0.0.1:6379> GEODIST school 东校区 西校区 m
"11893.6128"
  1. GEORADIUS 以半径为中心,查找附近的XXX

georadius以给定的经纬度为中心,返回键包含的位置元素当中,与中心的距离不超过给定最大距离的所有位置元素

WITHDIST: 在返回位置元素的同时,将位置元素与中心之间的距离也一并返回。距离的单位和用户给定的范围单位保持一致
WITHCOORD: 将位置元素的经度和维度也一并返回
WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大
COUNT: 限定返回的记录数
127.0.0.1:6379> GEORADIUS school 86.238322 41.669157 13 km withdist withcoord withhash count 10 desc
1) 1) "\xe8\xa5\xbf\xe6\xa0\xa1\xe5\x8c\xba"
   2) "11.8936"
   3) (integer) 3658511915615390
   4) 1) "86.11776798963546753"
      2) "41.72688008949518945"
2) 1) "\xe4\xb8\x9c\xe6\xa0\xa1\xe5\x8c\xba"
   2) "0.0000"
   3) (integer) 3658515182823201
   4) 1) "86.23832255601882935"
      2) "41.66915688453357092

通过这个命令查出来 key 里面半径 13km 的地址 , 包含自己

  1. GEORADIUSBYMEMBER 找出位于指定范围内的元素,中心点是由给定的位置元素决定

127.0.0.1:6379> GEORADIUSBYMEMBER school 西校区 13 km withdist withcoord withhash count 10 desc
1) 1) "\xe4\xb8\x9c\xe6\xa0\xa1\xe5\x8c\xba"
   2) "11.8936"
   3) (integer) 3658515182823201
   4) 1) "86.23832255601882935"
      2) "41.66915688453357092"
2) 1) "\xe8\xa5\xbf\xe6\xa0\xa1\xe5\x8c\xba"
   2) "0.0000"
   3) (integer) 3658511915615390
   4) 1) "86.11776798963546753"
      2) "41.72688008949518945"

四: 流 ( Stream )

Redis 版的 MQ 消息中间件 + 阻塞队列

实现消息队列,它支持消息的持久化、支持自动生成全局唯一D、支持ack确认消息的模式、支持消费组模式等,让消息队列更加的稳定和可靠

1. 底层结构和原理

127.0.0.1:6379> type mystream
stream

Stream 类型不是任何基本类型

2. 队列相关指令

  1. XADD 添加消息到队列末尾

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

XADD key [NOMKSTREAM] [MAXLEN|MINID [=|~] threshold [LIMIT count]] *|id field value [field value ...]

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

127.0.0.1:6379> XADD mystream * id 1 name sakura0
"1693374873306-0"

生成的一串数字就是消息 ID , 分为两部分 , 前半部分是毫秒时间戳 , 后半部分是这个毫秒时间戳下的第几个消息

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

客户端显示传入规则:

  • Redis 对于 ID 有强制要求,格式必须是时间戳-自增 ID 这样的方式,且后续 ID 不能小于前一个 ID

  • Stream 的消息内容,也就是图中的 Message Content 它的结构类似 Hash 结构,以 key-value 的形式存在。

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

start 表示开始值,- 代表最小值

end 表示结束值,+ 代表最大值

count 表示对多获取几个值

xrevrange区别在于反向获取 , ID 从大到小

127.0.0.1:6379> xrange mystream - + count 1
1) 1) "1693374873306-0"
   2) 1) "id"
      2) "1"
      3) "name"
      4) "sakura0"
  1. xdel 删除消息

根据消息的主键 , 消息 ID 删除消息

127.0.0.1:6379> xdel mystream 1693375740661-0
(integer) 1
  1. xlen 获取Stream中的消息长度

127.0.0.1:6379> xlen mystream
(integer) 3
  1. xtrim 限制Stream的长度,如果已经超长会进行截取

MAXLEN 允许的最大长度,对流进行修剪限制长度 , 截掉的是靠前的时间戳

MINID 允许的最小id,从某个id值开始比该id值小的将会被抛弃

127.0.0.1:6379> xtrim mystream maxlen 2
(integer) 1
127.0.0.1:6379> xtrim mystream minid 1693375730352-0
(integer) 1
  1. xread 用于获取消息(阻塞/非阻塞),只会返回大于指定ID的消息

 xread [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] id [id ...]

COUNT 最多读取多少条消息

BLOCK 是否已阻塞的方式读取消息,默认不阻塞,如果 millisecondsi 设置为0,表示永远阻塞

127.0.0.1:6379> xread count 2  streams mystream $
(nil)
127.0.0.1:6379> xread count 2  streams mystream 0-0
1) 1) "mystream"
   2) 1) 1) "1693375730352-0"
         2) 1) "id"
            2) "2"
            3) "name"
            4) "sakuras"
      2) 1) "1693378778221-0"
         2) 1) "k1"
            2) "v1"

$ 表示特殊 ID , 表示以当前 Stream 已经存储的最大的 ID 作为最后一个 ID , 当前Stream中不存在大于当前最大ID的消息,因此此时返回nil

0-0 代表从最小的ID开始获取Stream中的消息,当不指定count,将会返回Stream中的所有消息,注意也可以使用0(O0/000也都是可以的

  1. 阻塞队列

-- 读取比最新的消息还要新的消息,并且阻塞
XREAD count 1 block o streams mystream s

-- 在另一个客户端重新开一个reidis,并且发动消息
XADD mystream * id 1 name sakura0

3. 消费组相关指令

  1. xgroup create 用于创建消费者组

$ 表示从 Stream 尾部开始消费

0 表示从 Stream 头部开始消费

xgroup create key groupname id|$ [MKSTREAM] [ENTRIESREAD entries_read]

127.0.0.1:6379> xgroup create mystream group1 $
OK
127.0.0.1:6379> xgroup create mystream group2 0
OK
  1. xreadgroup group

xreadgroup GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] id [id ...]

127.0.0.1:6379> xreadgroup group group2 consumer1 streams mystream >
1) 1) "mystream"
   2) 1) 1) "1693375730352-0"
         2) 1) "id"
            2) "2"
            3) "name"
            4) "sakuras"
      2) 1) "1693378778221-0"
         2) 1) "k1"
            2) "v1"
      3) 1) "1693378784042-0"
         2) 1) "k2"
            2) "v2"
      4) 1) "1693378787477-0"
         2) 1) "k3"
            2) "v3"
127.0.0.1:6379> xreadgroup group group2 consumer2 streams mystream >
<nil>

同一个消费组里的消费者不能消费同一条信息 , 所以第二条得到的就是一个空值

但是不同消费组的消费者可以消费同一条消息

xreadgroup group group3 consumer1 streams mystream >

还可以设置某个组里的消费者读几条

-- 设置组里面每个消费者读1或2条
xreadgourp group group2 consumer1 count 1 streams mystream 
xreadgourp group group2 consumer2 count 1 streams mystream
xreadgourp group group2 consumer3 count 1 streams mystream 
  1. XPENDING 查询组内已读取 , 未确认的消息 XACK ack 消息,消息被标记为 "已处理”

pending key group [[IDLE min-idle-time] start end count [consumer]]


-- 查看组里已读取,未确认的消息
127.0.0.1:6379> xpending mystream group2
1) (integer) 4
2) "1693375730352-0"
3) "1693378787477-0"
4) 1) 1) "consumer1"
      2) "4"

-- 查看组里某个消费者已读取,未确认的消息
XPENDING mystream group + - 10 consumer2
XACK key group id [id ...]

-- 确认消息
127.0.0.1:6379> xack mystream group2 1693375730352-0
(integer) 1
  1. XINFO 用于打印Stream\Consumer八Group的详细信,

127.0.0.1:6379> xinfo stream mystream
 1) "length"
 2) (integer) 4
 3) "radix-tree-keys"
 4) (integer) 1
 5) "radix-tree-nodes"
 6) (integer) 2
 7) "last-generated-id"
 8) "1693378787477-0"
 9) "max-deleted-entry-id"
10) "1693375740661-0"
11) "entries-added"
12) (integer) 7
13) "recorded-first-entry-id"
14) "1693375730352-0"
15) "groups"
16) (integer) 2
17) "first-entry"
18) 1) "1693375730352-0"
    2) 1) "id"
       2) "2"
       3) "name"
       4) "sakuras"
19) "last-entry"
20) 1) "1693378787477-0"
    2) 1) "k3"
       2) "v3"

代码 17 行到 20 行为 第一个记录到最后一个记录

4. 四个特殊符号

  1. -+ 最小和最大可能出现的 id

  2. $ 表示只消费新的消息,当前流中最大的 id , $ 可用于将要到来的信息

  3. > 用于 XREADGROUP 命令,表示迄今还没有发送给组中使用者的信息,会更新消费者组的最后 ID

  4. * 用于 XADD 命令中,让系统自动生成 id

五: 位域 ( bitfield )

了解即可

将一个Redis字符串看作是一个由二进制位组成的数组 , 并能对变长位宽和任意没有字节对齐的指定整型位域进行寻址和修改

hello 等价于 0110100001100101011011000110110001101111

1. 基本命令

已支持的命令

有符号整型 i

无符号整型 u

  1. BITFIELD key

BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]
-- 首先先设置值
127.0.0.1:6379> set fieldkey hello 
OK
127.0.0.1:6379> get fieldkey
"hello"

-- 取8位从第0位开始
127.0.0.1:6379> bitfield fieldkey get i8 0
1) (integer) 104

-- 修改8位从第零位开始
127.0.0.1:6379> bitfield fieldkey set i8 0 105
1) (integer) 104
127.0.0.1:6379> get fieldkey
"iello"

-- 从第3个位开始,对接下来的4位无符号数+1
bitfield fieldkey incrby u4 2 1

2. 溢出控制

  • WRAP : 使用回绕 ( wrap around ) 方法处理有符号整数和无符号整数的溢出情况 默认情况

  • SAT : 使用饱和计算 ( saturation arithmetic ) 方法处理溢出,下溢计算的结果为最小的整数值,而上溢计算的结果为最大的整数值

  • FAL : 命令将拒绝执行那些会导致上溢或者下溢情况出现的计算,并向用户返回空值表示计算未被执行

-- 设置溢出控制位SAT
bitfield test overflow sat set i8 0 128

-- 设置溢出控制位fail
bitfield test overflow fail set i8 0 827

0

评论区