Redis的键值设计:
(1)优雅的key设计 :
- 遵循基本格式 : [业务名称] : [数据名] : [id]
- 长度不超过44字节
- 不包含特殊字符
例如我们的登录业务 , 保存用户信息 , 其key是这样的
优点 :
- 可读性强
- 避免key冲突
- 方便管理
- 更节省内存 : key 是String 类型, 底层编码包含 int , embstr 和 raw 三种 ,
- embstr在小于 44字节使用 , 采用连续内存空间 , 内存占用更小 ,
- 如果超过44字节 , 就会使用 raw 编码方式 , 通过指针指向内存地址 , 这会在性能上造成一定的影响
在redis中 , 使用 object encoding key , 可以看到key的类型是什么
注意:
- 如果redis版本是低于4.0的版本 , 那么限制就是39个字节
(2)Bigkey问题:
Bigkey通常以key的大小和key中成员的数量来判断 , 例如
- key本身的数据量过大 : 一个String类型的key , 它的值为5MB
- key中的成员数量过多 : 一个Zset类型的key , 它的成员数量为10000个
- key中的成员的数据量过大 : 一个hash类型的key , 它的成员数量虽然只有1000个 , 但是这些成员的Value值总大小为100MB
推荐值:
- 单个key的value小于10kb
- 对于集合类型的key , 建议元素数量小于1000
redis中提供的可以查询key的内存大小的命令:
MEMORY USAGE KEY
不推荐使用 : 这个命令对于CPU的使用较大
实际操作中我们通过预估即可 :
-
STRLEN KEY
可以查看这个key的value有几个字节
-
LLEN l2
可以查看数组的大小
- String ==> Strlen
- hash ==> hlen
- list ==> llen
- set ==> scard
- zset ==> zcard
BigKey的危害:
写数据的时候需要序列化 , 读数据的时候需要反序列化
如何发现BigKey:
-
redis-cli --bigkeys
- 利用redis-cli提供的 --bigkeys 参数 , 可以遍历分析所有的key , 并返回key的整体统计信息与,每个数据的Top1 , 的bigkey
-
scan 扫描 :
-
自己编程 , 利用scan扫描redis中的所有key , 利用strlen , hlen , 等命令判断key的长度 , (此处不建议使用 MEMORY USAGE)
-
SCAN 光标 : 代表扫描到那个位置了 , 下一次扫描从这个光标开始
-
@Test void test(){ String cursor = "0"; long len = 0; do { //扫描并获取一部分key ScanResult<String> result = jedis.scan(cursor); // 记录cursor cursor = result.getCursor(); // 获取集合 List<String> list = result.getResult(); if (list == null || list.isEmpty()){ break; } // 循环遍历数组 for (String key : list){ // 判断key的类型 String type = jedis.type(key); switch(type){ //根据不同的key的值 , 来进行不同的操作 case "string": len = jedis.strlen(key); maxlen = STR_MAX_LEN; break; case "set": .... } } if(len > maxlen){ sout("长度超过预设计的值 , 打印") } } }
-
-
第三方工具 :
- 利用第三方工具 , 如 Redis-Rdb-Tools 分析 RDB快照文件 , 全面分析内存使用情况
-
网络监控 :
- 自定义工具 , 监控进出Redis的网络数据 , 超出预警值是主动告警
删除bigkey:
Bigkey内存占用较多 , 即便是删除bigkey也需要耗时很长时间 , 导致Redis主线程阻塞 , 引发一系列问题
-
Redis 3.0 及以下版本
-
如果是集合类型 , 则遍历BigKey的元素 , 先逐个删除子元素 , 最后删除Bigkey
-
HScan , ZScan ,
-
-
Redis 4.0 以后 ,
-
Redis在4.0后提供了异步删除的命令 , unlink
-
unlink key
-
恰当的数据类型 :
Redis中的hash结构 , 底层使用ziplist , 空间占用小 , 可以灵活访问对象的任意字段 ,
但是 , 如果hash的entry(键值对)数量超过500时 , 会使用哈希表而不是 ziplist , 内存占用较多
-
可以使用 hash-max-ziplist-entries 命令来查询和修改 , 默认的键值对数量的大小
-
config set hash-max-ziplist-entries 指定的大小
-
注意:
- String 类型的数据结构 , 底层并没有太多的内存优化 , 在存储key的时候 , 也会存储大量的元数据 , 这会造成数据占用的内存大大提高 ,
- 所以不能一味的使用String , hash也是存在一定的缺点 ,
- 所以 , 只有选择最正确的数据结构 , 才能大大优化内存的占用
批处理优化 :
批处理的方案 :
- 原生的M操作
- Pipeline批处理操作
M操作 : 是redis的内置操作 , 它会将多组key和value , 作为一个原子操作 ,一次性全执行完 不会有其他操作插队
pipeline : 多个命令之间并不是原子性的 , 会造成其他命令的插队
- 单个命令传输的时候 :
- 一次操作的时间是 = 往返的网络时间 + redis操作命令的时间
- 大量命令的话 , 使用单个命令的话 , 会在网络传输的时间上浪费大量的时间
- 使用批处理的时候 :
- 一次可以传输大量的命令 , 减少往返的网络时间的消耗 ,
- 但是 , 一次批处理中传输太多的命令 , 会导致单次命令占用带宽过多 , 会导致网络阻塞
pipeline :
mset虽然可以批处理 , 但是却只能操作部分的数据类型 , 因此 , 如果对复杂数据类型的批处理需要 , 建议使用Pipeline功能:
// 获取批处理通道
Pipeline pipeline = jedis.pipeline();
// 放入命令到管道 , 这里可以对大部分的数据类型进行操作
pipeline.set("") // || hset ...
//批量执行
pipeline.sync();
集群下的批处理:
如果MSET和Pipeline这样的批处理需要在一次请求中携带多条命令 , 而此时如果Redis是一个集群 ,那批处理命令的多个key必须落在一个插槽中 , 否则就会导致执行失败