Redis 其他常见类型 + 渐进式遍历
- 一 . 其他类型
- 1.1 streams 类型
- 1.2 geospatial 类型
- 1.3 HyperLogLog 类型
- 1.4 bitmaps 类型
- 1.5 bitfields
- 二 . 渐进式遍历
Hello , 大家好 , 这个专栏给大家带来的是 Redis 系列 !
本篇文章给大家讲解的是 Redis 中其他常见的数据类型的介绍 , 我们还给大家介绍一种 Redis 中的渐进式命令 , 让操作 Redis 更加安全 , 避免将 Redis 服务器阻塞住 .本专栏旨在为初学者提供一个全面的 Redis 学习路径,从基础概念到实际应用,帮助读者快速掌握 Redis 的使用和管理技巧。通过本专栏的学习,能够构建坚实的 Redis 知识基础,并能够在实际学习以及工作中灵活运用 Redis 解决问题 .
专栏地址 : Redis 入门实践
一 . 其他类型
1.1 streams 类型
streams 类型是专门用来追加日志的类型 , 可以将事件分发给不同的目标 .
事件简单点说指的就是一些情况 , 我们也没办法预知它什么时候出现 . 但是等它出现之后我们该采取什么动作就采取什么动作
具体的命令我们也不去关心了 , streams 使用场景不大 , 而且面试也不常考 .
后续工作用到了 , 我们就可以去 Redis 官网来去阅读文档
1.2 geospatial 类型
geospatial 描述的是地理位置信息 .
我们描述一个具体的位置 , 都会以经纬度的概念来去描述 , 那 geospatial 就是用来存储经纬度坐标的 .
存储了一些位置之后 , 就可以让用户给定一个坐标 , 从刚才存储的点附近寻找 (按照半径 / 矩形区域) , 主要应用在地图相关的业务中
那具体的操作我们也可以从 Redis 官方文档中学习
https://redis.io/docs/data-types/geospatial/
1.3 HyperLogLog 类型
它的应用场景 , 只有一个 : 估算集合中不重复的元素个数
Set 有一个应用场景 , 统计服务器的 UV (用户访问的次数)
但是使用 Set 存在一个最大的问题 , 如果 UV 的数据量特别大 , Set 就会消耗很多的内存空间
假设 Set 里面存储 userId , 每个 userId 按照 8 个字节算
1 亿 UV -> 8 亿字节 -> 0.8 G -> 800 MB
而 Hyperloglog 可以最多使用 12KB 空间 , 来实现上面的效果
之所以 Set 需要消耗这么大的空间 , 这是因为 Set 需要存储每个元素 , 而 Hyperloglog 并不存储具体的元素 , 但是能够记录 “元素的特征” , 从而在新增元素的时候 , 能够知道当前新增的元素 , 是一个已经存在的元素还是一个崭新的元素 .
所以 Hyperloglog 最大的特点就是用来计数 (用来记录出当前集合中有多少个不同的元素) , 但是不能告诉你具体的元素都是什么
那 Hyperloglog 它的内部实现原理是 “位操作” , 是存在一定误差的 .
官方文档也详细给出了误差的大概范围 : https://redis.io/docs/data-types/probabilistic/hyperloglogs/
我们来看一下它的用法
添加元素 : pfadd key value
并不真正的添加元素本身的值 , 只是去记录该元素的特征 , 用来判断集合中是否存在
获取集合元素个数 : pfcount members
127.0.0.1:6379> pfadd members 123 # 添加元素
(integer) 1
127.0.0.1:6379> pfcount members # 获取集合元素个数
(integer) 1
127.0.0.1:6379>
1.4 bitmaps 类型
位图就是使用 bit 位来表示整数
举个例子 :
位图本质上就还是一个集合 , 它属于是 Set 类型针对整数的特殊版本 , 目的就是节省空间
位图当中保存的数字都必须是整数 , 所以被称为 Set 类型的特殊版本
那我们刚才介绍的 Hyperloglog 不是也省空间吗 ?
Hyperloglog 既可以存储数字 , 也可以存储字符串 . 但是 Hyperloglog 不存储元素内容 , 只是计数效果
而 bitmaps 他是可以存储元素的
我们可以借助官网给我们的例子来了解 bitmaps 的语法
https://redis.io/docs/data-types/bitmaps/
我们现在有 1000 个主机需要监控 , 把这些主机标记成 0~999 , 我们现在需要快速地判断当前给定主机在 1h 以内是否监控过了
那我们就可以使用位图
127.0.0.1:6379> setbit pings:2023-08-08-17:00 123 1 # 把 123 这一位设置成 1
(integer) 0
127.0.0.1:6379> getbit pings:2023-08-08-17:00 123 # 查询 123 机器是否监测过了
(integer) 1 # 返回值为 1 代表被监测过了
127.0.0.1:6379> getbit pings:2023-08-08-17:00 456 # 查询 456 机器是否监测过了
(integer) 0 # 返回值为 0 代表未被监测过
1.5 bitfields
bitfields 表示位域 , 他可以理解成是一串二进制序列 (字节数组) , 同时可以把这个字节数组中的某几个位赋予特定含义 , 并且可以进行读取 / 修改 / 算术运算等相关操作
可以类比 C 语言中的位段进行理解
struct Test {
int a:8;
int b:16;
int c:8;
}
这里的数字 , 就描述了这个成员实际上占几个 bit 位
他的目的仍然是为了节省一定的空间
我们可以看一下官方文档提供的例子 : https://redis.io/docs/data-types/bitfields/
他的意思是你现在设计了一个网游 , 你想要给每个用户分配两个属性 : 金钱数和杀敌数 , 你的游戏很火很火 , 所以你需要一个至少 32 位的字节数组
新玩家刚开始有 1000 金币
# 设置该玩家初始金币
# u32 代表 unsigned int , 利用无符号整形来去操作 32 个比特位
# #0 相当于一个名字 , 相当于玩家的金钱
# 1000 给 #0 也就是玩家的金钱设置成 1000
127.0.0.1:6379> bitfield player:1:stats set u32 #0 1000
1) (integer) 0
杀死一个哥布林 , 获得 50 金
# 金币增加 , 杀敌数增加
# incrby u32 #0 50 代表利用无符号整形去让 #0(玩家金币) 增加 50
# incrby u32 #1 1 代表利用无符号整形去让 #1(玩家杀敌数) 增加 1
127.0.0.1:6379> bitfield player:1:stats incrby u32 #0 50 incrby u32 #1 1
1) (integer) 1050
2) (integer) 1
花 999 金币买了个装备
# 花 999 金币买了个装备
# incrby u32 #0 -999 表示利用无符号整形去让 #0(玩家金币) 减少 999
127.0.0.1:6379> bitfield player:1:stats incrby u32 #0 -999
1) (integer) 51
查询一下玩家的金币数和杀敌数
# 查询一下玩家的金币数和杀敌数
# get u32 #0 get u32 #1 表示利用无符号整数来去获取 #0(金币数) 和 #1(杀敌数) 的值
127.0.0.1:6379> bitfield player:1:stats get u32 #0 get u32 #1
1) (integer) 51
2) (integer) 1
二 . 渐进式遍历
我们之前在介绍过 keys 这个命令的时候 , 提到了渐进式遍历这个命令 .
keys 会一次性的把整个 Redis 中的所有的 key 都去获取到 , 但是这个操作比较危险 , 可能一下子会得到非常多的 key , 就会将 Redis 服务器阻塞住 .
那我们通过渐进式遍历 , 就可以逐步的获取到全部的 key , 并且不会阻塞住服务器 .
就类似于 MySQL 中的 limit N offset M 操作 , 每次获取一小部分数据
渐进式遍历其实是一组命令 , 但是一组命令中都非常类似 , 我们以 scan 举例
渐进式遍历.png
那接下来我们通过 Redis 官网看一下 scan 命令的用法 : https://redis.io/commands/scan/
语法 : scan cursor [Match pattern] [Count count] [Type type]
- cursor 并不是实际意义的下标 , 只是一个虚拟意义的光标
- [Match pattern] 指的是匹配的模式 , 跟我们之前学习过的 keys pattern 都是一样的用法
- [Count count] 限制这一次遍历能够获取到多少个元素 , 默认是 10 . 但是他与 MySQL 中的 limit 不一样 , MySQL 中的 limit 他是非常精确的 , 绝对会返回 <= limit 个数据 . 但是 Redis 中的 count 只能提供一个建议 , 实际返回的 key 的个数并不一定完全与 count 相同
- [Type type] : 虽然说 Redis 的 key 都是 string 类型 , 但是 value 的类型各不相同 . type 的作用是根据 value 的类型继续进行筛选
127.0.0.1:6379> mset k1 111 k2 222 k3 333 k4 444 k5 555 k6 666 k7 777 k8 888 k9 999 k10 1000 k11 11111 k12 22222 k13 33333
OK
127.0.0.1:6379> scan 0 # 利用光标来去获取元素
1) "7" # 下次 scan 光标的位置
# 本次遍历获取到的 key
2) 1) "k6"2) "k7"3) "k13"4) "k12"5) "k5"6) "k1"7) "k3"8) "k2"9) "k9"10) "k10"
因为 scan 命令默认就打印 10 个元素左右 , 所以我们并不知道所有的 key 是否已经打印完毕 , 那我们就可以根据下次 scan 光标的位置来去判断是否还有剩余元素 , 为 0 的时候就代表元素已经遍历结束了
然后我们还可以加上 count 选项
127.0.0.1:6379> scan 0 count 3 # 利用光标来去获取三个元素
1) "1" # 表示下次 scan 光标的位置
2) 1) "k6"2) "k7"3) "k13"
127.0.0.1:6379> scan 1 count 3 # 那我们这次光标就应该按照上一次提示的位置开始遍历
1) "9" # 表示下次 scan 光标的位置
2) 1) "k12"2) "k5"3) "k1"
127.0.0.1:6379> scan 9 count 3 # 那我们这次光标就应该按照上一次提示的位置开始遍历
1) "11" # 表示下次 scan 光标的位置
2) 1) "k3"2) "k2"3) "k9"
127.0.0.1:6379> scan 11 count 3 # 那我们这次光标就应该按照上一次提示的位置开始遍历
1) "0" # 此时代表元素已经遍历结束
2) 1) "k10"2) "k8"3) "k11"4) "k4"
那在使用 scan 命令的时候 , 还要注意一些事项
- count 不用每一次都设置成一样
- 渐进式遍历在遍历过程中不会在服务器这边存储任何的状态信息 , 也就是输随时可以终止 , 不会对服务器产生任何影响
- 渐进性遍历 scan 虽然解决了阻塞的问题 , 但如果在遍历期间键有所变化 (增加、修改、删除) , 可能就会导致遍历的时候键重复遍历或者遗漏 .
对于 Redis 常见命令我们已经介绍完毕 , 不知道大家吸收了多少 .
如果对您有帮助的话 , 还请一键三连~