Redis 基本数据类型与高级数据结构
孙玉超
2021-03-21 13:01:33
0 评论
1183 浏览
0 收藏
0 赞
Redis 五种基本数据类型
众所周知,Redis 五种基本数据类型:string,hash,list,set,zset。其中 string 可以说是我们最常用的类型,往 Redis 存入一些缓存数据。例如本人在做公司对接钉钉审批流的时候,就把访问钉钉接口的 accessToken 给缓存在 Redis 中,不然每次调用接口还要去远程请求获取 accessToken 实在是太浪费网络消耗。
hash 也是很常用的数据类型,比如之前做的会员档案模块,我们的工作人员在手机上录入会员的档案,保存操作的功能。由于工作人员不止一个,所以我以工作人员的 userId 作为 Redis 的 key,然后用 hash 存储会员档案的草稿信息。其中当前时间戳作为 hash 的 key,用户档案信息作为 value 。
list 也是较常用的数据类型,比如我们第二版秒杀模型,准备用它来做库存校验。因为秒杀的特点,在同一个时间点,大量请求涌过来,但是一个商品的秒杀库存通常是很有限的。比如你商品就 10 个库存,1000个用户来抢,那其实说到底只有 10 次请求是有效的。因此我们可以将商品的 id 在 list 中依次放 10 个。如下图:
当进来一个请求我们就判断 list 里面是否还有元素,如果有就 rpop 一次,如果没有就直接返回抢完,这样一来可以过滤掉无效请求。当然 list 还有其他很多用法,视具体场景。
set 和 zset(sortedset) 我用的比较少,set 是无顺序不重复集合,而 zset 是有顺序的,主要可以通过一个权重参数 score 来进行排序。还可以通过 score 的范围来获取元素列表。
如果不熟悉 Redis 基本命令,可以去官网或菜鸟教程等网站查阅 Redis 菜鸟教程
Redis 高级数据结构
bitmap
bitmap 和 string 类似,只是 bitmap 以位来存储,存储的是连续的二进制数字 0 和 1,只需要一个 bit 位来表示某个元素对应的值。因为 1 byte = 8 bit,所以 bitmap 存储能节省很大的内存空间。其实 bitmap 并不是一种新的数据类型,它就是字符串,不过它也可以对字符串的位进行操作。
例如我们要用 string 类型的命令存储一个字符串 "ABC"。
使用 getbit 命令可以获取偏移量位置上的值
set str ABC #设置字符串 getbit str 0 #0 getbit str 1 #1 getbit str 2 #0
上面的表格很清晰的反应了 bitmap 存储数据的结构。知道 bitmap 的存储方式,下面来看一个具体场景。
例如我们要存储一个用户 2021 年一年的签到记录,只要使用一个标识用户 id 的 key "user:sign:userId:2021:month",然后每个位代表那个月的每一天的签到记录,签了是 1,没签是 0 。这样就可以以很小的内存空间来完成这个需求。
setbit user:sign:userId:2021:3 0 1 #设置该用户三月第一天签到了 setbit user:sign:userId:2021:3 1 1 #设置该用户三月第二天签到了 setbit user:sign:userId:2021:3 2 0 #设置该用户三月第三天没签到 setbit user:sign:userId:2021:3 3 0 #设置该用户三月第四天没签到 ...依次类推
我们还可以使用 bitcount 命令来统计偏移位置上所有值为 1 的数量。
bitcount user:sign:userId:2021:3 #统计该用户三月份签到的天数
试想如果使用 string 的 KV 来记录,如果用户量稍微大一点,你将需要多少的内存来完成。
HyperLogLog
Redis 2.8.9 版本新增了 HyperLogLog 数据结构,这个数据结构用来计算基数(在一个集合中去除掉重复的元素之后剩余的个数),它是有误差的,但是误差率很小,在可接受范围内。并且它在输入元素的数量非常多时,所需的空间是固定的,并且很小。每个 HyperLoglog 的 只需要花费 12KB 内存,就可以计算接近 2^64 个不同元素的基数。
for (int i = 0; i < 100000; i++) { stringRedisTemplate.opsForHyperLogLog().add("test", String.valueOf(i)); } Long test = stringRedisTemplate.opsForHyperLogLog().size("test"); System.out.println(test); // 99556
用上述代码随便测试一下 HyperLogLog 的计算。
HyperLogLog 就像是一个 set 但是它比 set 要节省空间的多,实际业务场景中可以用来统计网站的注册 IP 数量、页面的 UV 等需要去重统计的需求,虽然结果并不精确,但是对于业务而言,注册的 IP 数量是 5W 还是 5W 零 100 其实并不重要,他们只需要一个大概的数据。
BloomFilter
BloomFilter 叫做布隆过滤器,用来判断一个元素是否存在于一个集合中,底层数据结构和 bitmap 相关,所以很节省内存空间。与 HyperLogLog 一样,它存在一些误差,但是误差很小。当它说一个元素在一个集合中存在时,可能并不存在。当它说一个元素在一个集合中不存在时,就一定不存在。
默认安装的 Redis 没有这个模块,需要额外安装。可以去官网的 Modules 页面找到 RedisBloom 的 github 地址,下面安装过程
#安装布隆过滤器 wget https://github.com/RedisLabsModules/rebloom/archive/v2.2.4.tar.gz tar -zxvf v1.1.1.tar.gz cd redisbloom-2.2.4/ make ls #在 redis.conf 中加入该配置 loadmodule 路径/redisbloom-2.2.4/rebloom.so #重启 redis ,然后使用布隆过滤器的命令来测试是否添加成功, bf.add users 1 bf.add users 2 bf.exists users 1 bf.exists users 3
1. 布隆过滤器可以用来解决缓存穿透问题,我们可以将要请求的资源id 全部放在布隆过滤器中,访问时先判断布隆过滤器中有没有,如果没有直接 return;
2. 布隆过滤器还可以用来应对爬虫,将爬到的数据比如 URL 放入布隆过滤器,判断如果不存在就添加进去。
3. 布隆过滤器还可以用来对用户进行喜好推送,比如一个视频 APP,要给用户推荐短视频,那要保证推荐的是用户没有看过的,我们把用户已经看过的视频放进布隆过滤器,推送之前判断一下推送的视频是否不存在于布隆过滤器,如果不存在,说明用户一定没有看过。
具体 BloomFilter 的命令以及原理可以参考:https://zhuanlan.zhihu.com/p/102067467
SpringBoot 的 Redis Starter 并没有做 BloomFilter 的 API,可以选择 Google 的一个依赖,提供了很多 API。
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>30.1.1-jre</version> </dependency>
Redisson Starter 也对此提供了相关 API
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.15.2</version> </dependency>
GEO
Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增。
待完善