Redis大key问题,如何优化?
前言
最近缺项目经历想快速提升项目实战能力(包含多个AI项目),或者最近找工作,或者想学习AI的小伙伴,可以看看下面👇🏻的这个链接(或许真的能够帮到你)。
我们日常工作中,在使用Redis的时候,不知道你有没有遇到过这种情况。
我们定义了某对key/value保存数据,然后在查询接口中,通过该key可以查询出value的数据,返回给用户。
这个功能刚开始上线的一两年,性能都OK。
但随着时间的推移,会发现通过key查询value的性能越来越差。
我们定位原因之后发现,是value值越来越大导致的问题。
这就是Redis的大key问题。
1 大key问题的危害
1.1 性能下降
操作大key可能会导致Redis的命令执行时间显著增加,影响整体性能。
2.2 占用大量内存
大key会占用大量内存,这可能导致Redis实例内存不足,从而影响其他键的存储。
2.3 影响持久化
在Redis持久化操作(如 RDB 快照)或备份过程中,大key的处理时间会增加,可能导致Redis持久化效率降低。
2.4 网络延迟
大key的传输可能会消耗较多的网络带宽和时间,影响客户端的响应时间。
由此可见,大key问题带来的危害还是挺多的,我们在日常工作中,要尽量避免大key。
2 如何查出大key?
我们想要尽量避免大key问题,要定期排查Redis中有哪些大key,然后逐步优化。
那么,问题来了,我们要如何查询出Redis当中的大key呢?
2.1 使用memory usage命令
在redis4.0之后,提供了memory usage命令,可以查看一个键的内存使用情况。
用法:
memory usage key比如下面的这个例子:
memory usage indexNotice返回:568
表示568个字节。
如果key不存在,则返回nil。
如果遇到内存使用大于10KB的就是大key。
2.2 使用redis-cli工具
redis-cli是原生Redis自带的命令行工具,可以帮助我们通过简单的命令连接Redis服务,并进行数据管理,即redis键(key)和redis数据结构的管理。
我们可以使用redis-cli命令来查找和分析大Key。
例如,使用 redis-cli 的 --bigkeys 选项扫描数据库中的大 Key。
redis-cli -h 127.0.0.1 -p 16379 --bigkeys执行结果如下: 
图中的Biggest就是大key。
2.3 使用scan命令
Redis提供了scan命令,可以用于迭代遍历所有key。
最近缺项目经历想快速提升项目实战能力(包含多个AI项目),或者最近找工作,或者想学习AI的小伙伴,可以看看下面👇🏻的这个链接(或许真的能够帮到你)。
它是一个非阻塞操作,支持游标(cursor)的方式来逐步遍历所有key。
使用SCAN命令可以避免阻塞,减少对Redis性能的影响。
我们也可以通过scan命令,扫描Redis中的数据,来识别一些key。
127.0.0.1:16379> scan 1000 MATCH "string*"
1) "0"
2) 1) "string_large_key2"
2) "string_large_key1"
127.0.0.1:16379>
127.0.0.1:16379> scan 0 MATCH "string*" count 20
1) "0"
2) 1) "string_large_key2"
2) "string_large_key1"
127.0.0.1:16379>然后再配合memory usage命令:
127.0.0.1:16379> memory usage string_large_key1
(integer) 104858313 如何优化大key?
通过上面介绍的这些方法,我们可以查到Redis中,哪些是大key。
接下来,我们要如何做优化呢?
3.1 大key拆成多个小key
想要优化大key问题,首先想到的解决办法是将大key拆成多个小key。
我们以分类树为例。
之前一个大key,在Redis中保存了一颗完整的分类树型数据。
如果这棵树很大,就会出现大key问题。
如果可以将分类树查询接口改成逐级加载的方式,不用一次性查询所有的分类数据。
通过key改成parentId,值是等于该parentId的分类列表。
第一级分类parentId=0。
这样每次就可以通过parentId查询一部分数据,不需要查询所有数据。
3.2 缩减字段
为了优化在Redis中存储数据的大小,我们首先需要对数据进行瘦身。
只保存需要用到的字段。
例如:
@AllArgsConstructor
@Data
public class Category {
private Long id;
private String name;
private Long parentId;
private Date inDate;
private Long inUserId;
private String inUserName;
private List<Category> children;
}像这个分类对象中inDate、inUserId和inUserName字段是可以不用保存的。
修改自动名称。
例如:
@AllArgsConstructor
@Data
public class Category {
/**
* 分类编号
*/
@JsonProperty("i")
private Long id;
/**
* 分类层级
*/
@JsonProperty("l")
private Integer level;
/**
* 分类名称
*/
@JsonProperty("n")
private String name;
/**
* 父分类编号
*/
@JsonProperty("p")
private Long parentId;
/**
* 子分类列表
*/
@JsonProperty("c")
private List<Category> children;
}由于在一万多条数据中,每条数据的字段名称是固定的,他们的重复率太高了。
由此,可以在json序列化时,改成一个简短的名称,以便于返回更少的数据大小。
3.3 数据压缩
之前在Redis中保存的key/value,其中的value是json格式的字符串。
其实RedisTemplate支持,value保存byte数组。
先将json字符串数据用GZip工具类压缩成byte数组,然后保存到Redis中。
再获取数据时,将byte数组转换成json字符串,然后再转换成分类树。
3.4 选择合适的数据结构
不是所有的value都需要定义成String类型的,我们需要根据数据的特性选择合适的Redis数据结构。
Redis 提供了多种数据结构(如列表、集合、有序集合、哈希表),可以根据实际需求选择合适的数据结构来优化存储和访问性能。
如果有多个字段的情况,使用hash结构性能更好一些。
最近缺项目经历想快速提升项目实战能力(包含多个AI项目),或者最近找工作,或者想学习AI的小伙伴,可以看看下面👇🏻的这个链接(或许真的能够帮到你)。