一、慢操作分析

redis 的慢操作已经有了,如果没有,我们可以自己去 redis 服务器查看历史的慢日志操作,或者有对应的慢操作监控系统也可以发现问题,这里不做展开。

接下来我们就要看一看为什么这么慢。

看了下项目中的实现代码,结合日志一分析,发现是一个 redis bigkey。

一个 redis key,对应的是一个 map, 里面防了几十万的 key/value。删除的时候一把直接删除,自然是慢的。

本文带大家一起分析下 redis bigkey 删除的解决方案,希望你工作中遇到类似问题提供一个解决思路。

二、处理 bigkey

bigkey是指key对应的value所占的内存空间比较大。

例如一个字符串类 型的value可以最大存到512MB,一个列表类型的value最多可以存储2^32-1个元素。

如果按照数据结构来细分的话,一般分为字符串类型bigkey和非字符串类型bigkey。

三、危害

bigkey的危害体现在三个方面:

1.内存空间不均匀(平衡):

例如在Redis Cluster中,bigkey会造成节点的内存空间使用不均匀。

2.超时阻塞:

由于Redis单线程的特性,操作bigkey比较耗时,也就意味着阻塞Redis可能性增大。

3.网络拥塞:

每次获取bigkey产生的网络流量较大,假设一个bigkey为1MB,每秒访问量为1000,那么每秒产生1000MB的流量,对于普通的千兆网卡(按照字节算是128MB/s)的服务器来说简直是灭顶之灾,而且一般服务器会采用单机多实例的方式来部署,也就是说一个bigkey可能会对其他实例造成影响,其后果不堪设想。图12-3演示了网络带宽bigkey占用的瞬间。

四、如何发现

redis-cli --bigkeys 可以命令统计bigkey的分布。

但是在生产环境中,开发和运维人员更希望自己可以定义bigkey的大小,而且更希望找到真正的bigkey都有哪些,这样才可以去定位、解决、优化问题。

判断一个key是否为bigkey,只需要执行 debug object key 查看serializedlength属性即可,它表示key对应的value序列化之后的字节数,

例如我们执行如下操作:

127.0.0.1:6379> debug object key
Value at:0x7fc06c1b1430 refcount:1 encoding:raw serializedlength:1256350 lru:11686193
lru_seconds_idle:20

可以发现serializedlength=11686193字节,约为1M,同时可以看到encoding是raw,也就是字符串类型。

那么可以通过strlen来看一下字符串的字节数为2247394字节,约为2MB:

127.0.0.1:6379> strlen key
(integer) 2247394

serializedlength不代表真实的字节大小,它返回对象使用RDB编码序列化后的长度,值会偏小,但是对于排查bigkey有一定辅助作用,因为不是每种数据结构都有类似strlen这样的方法。

五、实际生产的操作方式

在实际生产环境中发现bigkey的两种方式如下:

被动收集:

许多开发人员确实可能对bigkey不了解或重视程度不够,但是这种bigkey一旦大量访问,很可能就会带来命令慢查询和网卡跑满问题,开发人员通过对异常的分析通常能找到异常原因可能是bigkey,这种方式虽然不是被笔者推荐的,但是在实际生产环境中却大量存在,建议修改Redis客户端,当抛出异常时打印出所操作的key,方便排bigkey问题。

主动检测:

scan+debug object:如果怀疑存在bigkey,可以使用scan命令渐进的扫描出所有的key,分别计算每个key的serializedlength,找到对应bigkey进行相应的处理和报警,这种方式是比较推荐的方式。

六、如何删除

因为 redis 是单线程的,删除比较大的 keys 就会阻塞其他的请求。

当发现Redis中有bigkey并且确认要删除时,如何优雅地删除bigkey?

无论是什么数据结构,del命令都将其删除。

但是相信通过上面的分析后你一定不会这么做,因为删除bigkey通常来说会阻塞Redis服务。

下面给出一组测试数据分别对string、hash、list、set、sorted set五种数据结构的bigkey进行删除,bigkey的元素个数和每个元素的大小不尽相同。

七、删除时间测试

下面测试和服务器硬件、Redis版本比较相关,可能在不同的服务器上执行速度不太相同,但是能提供一定的参考价值

1.字符串类删除测试

表12-3展示了删除512KB~10MB的字符串类型数据所花费的时间,总体来说由于字符串类型结构相对简单,删除速度比较快,但是随着value值的不断增大,删除速度也逐渐变慢。

表12-3

2.非字符串类删除测试

表12-4展示了非字符串类型的数据结构在不同数量级、不同元素大小下对bigkey执行del命令的时间,总体上看元素个数越多、元素越大,删除时间越长,相对于字符串类型,这种删除速度已经足够可以阻塞Redis。

表12-4 删除hash、list、set、sorted set四种数据结构不同数量不同元素大小的耗时

从上分析可见,除了string类型,其他四种数据结构删除的速度有可能很慢,这样增大了阻塞Redis的可能性。

八、如何提升删除的效率

既然不能用del命令,那有没有比较优雅的方式进行删除呢,这时候就需要将第2章介绍的scan命令的若干类似命令拿出来:sscan、hscan、zscan。

1.string

字符串删除一般不会造成阻塞

del bigkey

2.hash、list、set、sorted set

下面以hash为例子,使用hscan命令,每次获取部分(例如100个)fieldvalue,再利用hdel删除每个field(为了快速可以使用Pipeline):

public void delBigHash(String bigKey) {Jedis jedis = new Jedis(“127.0.0.1”, 6379);// 游标String cursor = “0”;while (true) {ScanResult<Map.Entry<String, String>> scanResult = jedis.hscan(bigKey, cursor, new ScanParams().count(100));// 每次扫描后获取新的游标cursor = scanResult.getStringCursor();// 获取扫描结果List<Entry<String, String>> list = scanResult.getResult();if (list == null || list.size() == 0) {continue;}String[] fields = getFieldsFrom(list);// 删除多个fieldjedis.hdel(bigKey, fields);// 游标为0时停止if (cursor.equals(“0”)) {break;}}// 最终删除keyjedis.del(bigKey);
}/**
* 获取field数组
* @param list
* @return
*/
private String[] getFieldsFrom(List<Entry<String, String>> list) {List<String> fields = new ArrayList<String>();for(Entry<String, String> entry : list) {fields.add(entry.getKey());}return fields.toArray(new String[fields.size()]);
}

请勿忘记每次执行到最后执行del key操作。

九、最佳实践思路

由于开发人员对Redis的理解程度不同,在实际开发中出现bigkey在所难免,重要的是,能通过合理的检测机制及时找到它们,进行处理。

作为开发人员在业务开发时应注意不能将Redis简单暴力的使用,应该在数据结构的选择和设计上更加合理,例如出现了bigkey,

(1)要思考一下可不可以做一些优化(例如拆分数据结构)尽量让这些bigkey消失在业务中,

(2)如果bigkey不可避免,也要思考一下要不要每次把所有元素都取出来(例如有时候仅仅需要hmget,而不是hgetall)。

(3)最后,可喜的是,Redis将在4.0版本支持lazy delete free的模式,那时删除bigkey不会阻塞Redis。

十、如何优雅的删除

1.重构

重新构建自己的业务 key。

让 key/value 更加小,使用纯字符串。

  • 缺点

有时候难以对旧的代码进行兼容,调整难度较大。

2.使用 lazy free

这个是 redis 4.0 以后的特性。

可能会受限于版本,导致无法使用。

  • 查看版本

redis 压缩包有文件 cat 00-RELEASENOTES 可以查看对应的版本信息。

Redis 2.8 release notes
=======================** IMPORTANT ** Check the 'Migrating from 2.6 to 2.8' section at the end ofthis file for information about what changed between 2.6 and2.8 and how this may affect your application.--------------------------------------------------------------------------------
Upgrade urgency levels:LOW:      No need to upgrade unless there are new features you want to use.
MODERATE: Program an upgrade of the server, but it's not urgent.
HIGH:     There is a critical bug that may affect a subset of users. Upgrade!
CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
----------------------------------------------------------------------------------[ Redis 2.8.6 ] Release date: 13 Feb 2014

可知当前版本为:2.8.6

3.使用 expire 设置过期

需要熟知 redis 的淘汰策略。

(1)惰性淘汰

(2)定时删除

(3)定期删除

其中定期删除,是一个异步的进程去处理的,不会阻塞主进程。

其中设置超时时间,是为了限制每一次的操作时间,从而更好的清空数据,释放内存。

  • 缺点

需要知道具体的淘汰策略

对内存是不够友好的

可能要根据业务进行调整,比如本来显式删除,可以放在凌晨。

如果使用定期删除,被淘汰的时间就变得不固定了。

十一、实战代码

1.JedisCluster示例

/*** 刪除 BIG key* 应用场景:对于 big key,可以使用 hscan 首先分批次删除,最后统一删除* (1)比直接删除的耗时变长,但是不会产生慢操作。* (2)新业务实现尽可能拆开,不要依赖此方法。* @param key key* @param scanCount 单次扫描总数(建议值:100)* @param intervalMills 分批次的等待时间(建议值:5)*/
void removeBigKey(final String key, final int scanCount, final long intervalMills)

实现

JedisCluster jedisCluster = redisClusterTemplate.getJedisClusterInstance();
// 游标初始值为0
String cursor = ScanParams.SCAN_POINTER_START;
ScanParams scanParams = new ScanParams();
scanParams.count(scanCount);
while (true) {// 每次扫描后获取新的游标ScanResult<Map.Entry<String, String>> scanResult = jedisCluster.hscan(key, cursor, scanParams);cursor = scanResult.getStringCursor();// 获取扫描结果为空List<Map.Entry<String, String>> list = scanResult.getResult();if (CollectionUtils.isEmpty(list)) {break;}// 构建多个删除的 keyString[] fields = getFieldsKeyArray(list);jedisCluster.hdel(key, fields);// 游标为0时停止if (ScanParams.SCAN_POINTER_START.equals(cursor)) {break;}// 沉睡等待,避免对 redis 压力太大DateUtil.sleepInterval(intervalMills, TimeUnit.MILLISECONDS);
}
// 执行 key 本身的删除
jedisCluster.del(key);
  • 构建的 key
/*** 获取对应的 keys 信息* @param list 列表* @return 结果*/
private String[] getFieldsKeyArray(List<Map.Entry<String, String>> list) {String[] strings = new String[list.size()];for(int i = 0; i < list.size(); i++) {strings[i] = list.get(i).getKey();}return strings;
}

redisTemplate 的写法

语法

估计是 redis 进行了一次封装,发现还是存在很多坑。

语法如下:

/*** 获取集合的游标。通过游标可以遍历整个集合。* ScanOptions 这个类中使用了构造者 工厂方法 单例。 通过它可以配置返回的元素* 个数 count  与正则匹配元素 match. 不过count设置后不代表一定返回的就是count个。这个只是参考* 意义** @param key* @param options * @return* @since 1.4*/
Cursor<V> scan(K key, ScanOptions options);

注意的坑

实际上这个方法存在很多需要注意的坑:

(1)cursor 要关闭,否则会内存泄漏

(2)cursor 不要重复关闭,或者会报错

(3)cursor 经测试,直接指定的 count 设置后,返回的结果其实是全部,所以需要自己额外处理

参考代码如下:

声明

@Autowired
private StringRedisTemplate template;

核心代码

public void removeBigKey(String key, int scanCount, long intervalMills) throws CacheException {final ScanOptions scanOptions = ScanOptions.scanOptions().count(scanCount).build();//TRW 避免内存泄漏try(Cursor<Map.Entry<Object,Object>> cursor =template.opsForHash().scan(key, scanOptions)) {if(ObjectUtil.isNotNull(cursor)) {// 执行循环删除List<String> fieldKeyList = new ArrayList<>();while (cursor.hasNext()) {String fieldKey = String.valueOf(cursor.next().getKey());fieldKeyList.add(fieldKey);if(fieldKeyList.size() >= scanCount) {// 批量删除Object[] fields = fieldKeyList.toArray();template.opsForHash().delete(key, fields);logger.info("[Big key] remove key: {}, fields size: {}",key, fields.length);// 清空列表,重置操作fieldKeyList.clear();// 沉睡等待,避免对 redis 压力太大DateUtil.sleepInterval(intervalMills, TimeUnit.MILLISECONDS);}}}// 最后 fieldKeyList 中可能还有剩余,不过一般数量不大,直接删除速度不会很慢// 执行 key 本身的删除this.opsForValueDelete(key);} catch (Exception e) {// log.error();}
}

这里我们使用 TRW 保证 cursor 被关闭,自己实现 scanCount 一次进行删除,避免 1 个 1 个删除网络交互较多。

使用睡眠保证对 Redis 压力不要过大。

redis bigkey 删除问题相关推荐

  1. Redis BigKey优化与使用方式

    一.什么是BigKey 在Redis中,一个字符串最大512MB,一个二级数据结构(例如hash.list.set.zset)可以存储大约40亿个(2^32-1)个元素,但实际上中如果下面两种情况,我 ...

  2. redis惰性删除 lazy free 源码剖析,干货满满

    目录 前言 数据删除场景 lazy free 概念 配置 源码剖析(版本 6.2.6) 场景一:客户端执行的显示删除/清除命令 场景二:某些指令带有的隐式删除命令 场景三:删除过期数据 场景四:内存淘 ...

  3. 为什么集群要奇数_面试系列 redis数据删除amp;集群

    redis数据删除/内存淘汰 如果我们设置一批key只能存活1小时,那么1小时之后,redis是怎么对这批数据进行删除的? 答案:定期删数+惰性删除 (1)定期删除 指的是redis默认是每隔100m ...

  4. redis批量删除key

    批量删除Key Redis 中有删除单个 Key 的指令 DEL,但好像没有批量删除 Key 的指令,不过我们可以借助 Linux 的 xargs 指令来完成这个动作 1 2 3 redis-cli ...

  5. Redis淘汰删除策略

    Redis淘汰删除策略 Redis淘汰删除策略6种淘汰Key策略3种删除过期键策略定时删除惰性删除定期删除其他模块的淘汰处理RDB 快照持久化创建载入AOF 只追加持久化写入重写主从复模式下对过期键的 ...

  6. php redis删除所有key,php redis批量删除key的方法

    php redis批量删除key的方法,遍历,命令,前缀,数组,增量 php redis批量删除key的方法 易采站长站,站长之家为您整理了php redis批量删除key的方法的相关内容. php ...

  7. redis 批量删除操作

    redis 原生删除方法 del key1 key2 ... 只支持显示删除 使用*通配符 和 xargs可以很方便地进行批量删除 形式如下: redis-cli -h 192.168.1.45 -p ...

  8. php redis 删除key 通配符,php中redis批量删除key的方法是什么

    php中redis批量删除key的方法是什么 发布时间:2020-08-28 14:42:05 来源:亿速云 阅读:159 作者:小新 小编给大家分享一下php中redis批量删除key的方法是什么, ...

  9. 如履薄冰 —— Redis懒惰删除的巨大牺牲

    前言 之前我们介绍了Redis懒惰删除的特性,它是使用异步线程对已经删除的节点进行延后内存回收.但是还不够深入,所以本节我们要对异步线程逻辑处理的细节进行分析,看看Antirez是如何实现异步线程处理 ...

  10. redis批量删除指定的key

    批量删除Key Redis 中有删除单个 Key 的指令 DEL,可以借助 Linux 的 xargs 指令来完成这个动作 [plain] view plain copy redis-cli keys ...

最新文章

  1. thread.sleep是让哪个线程休眠_java开发两年,这些线程知识你都不知道,你怎么涨薪?...
  2. DNS扫盲系列之五:域名配置ZONE文件
  3. ios 跨域_如何在iOS和Android中建立跨域通信桥
  4. 我算是优秀的程序员吗?
  5. 计算机专业3d游戏设计,史塔福郡大学3D计算机游戏设计理学硕士研究生申请要求及申请材料要求清单...
  6. Spring : Spring Aop JDK动态代理调用过程
  7. Django里面的sql查询语句
  8. 固若金汤 - PostgreSQL pgcrypto加密插件
  9. 惠普HP CQ40系列笔记本windows SP2/SP3系统安装声卡驱动问题解决!
  10. ZOJ3332 Strange Country II java
  11. 开源基础软件大时代,与国产深度学习框架一起乘风破浪
  12. 【LeetCode】 374. 猜数字大小 骚气的猴子算法 打死都找不着系列 JAVA
  13. 如何评估开发代码质量
  14. 电力电子技术笔记(7)——器件的保护
  15. element UI的带输入建议el-autocomplete总结(详细,全)
  16. PHP水果店管理系统,水果店管理系统设计是?水果店管理系统优势是?
  17. android文字转语音模板,Android-文字转语音
  18. git 强制同步远端仓库
  19. Cannot enable Hyper-V service
  20. 使用OpenCV和C++实现的分水岭算法(Watershed)

热门文章

  1. Linux的c编程-文件节点的打开与读写操作
  2. 【 Educational Codeforces Round 51 (Rated for Div. 2) F】The Shortest Statement
  3. ios中UIView和CALayer关系
  4. Oracle中判断字段是否为数字
  5. Javascript中call()和apply()的用法 ----1
  6. 开源大数据查询分析引擎现状
  7. inux中tail命令---用于查看文件内容
  8. 2021年软考+BGP邻居实验
  9. Comparable与Comparator
  10. Android开发过程中的坑及解决方法收录