处理bigkey1、bigkey的危害2、如何发现3、如何删除3.1、string3.2、hash、list、set、sorted set4、最佳实践思路bigkey是指key对应的value所占的内存空间比较大例如一个字符串类型的value可以最大存到512MB一个列表类型的value最多可以存储232-1个元素。如果按照数据结构来细分的话一般分为字符串类型bigkey和非字符串类型bigkey。字符串类型体现在单个value值很大一般认为超过10KB就是bigkey但这个值和具体的OPS相关。10KB就是bigkey无论是空间复杂度和时间复杂度都不太友好下面我们将介绍它的危害。因为非字符串数据结构中每个元素实际上也是一个字符串但这里只讨论元素个数过多的情况。1、bigkey的危害bigkey的危害体现在三个方面内存空间不均匀平衡例如在Redis Cluster中bigkey会造成节点的内存空间使用不均匀。超时阻塞由于Redis单线程的特性操作bigkey比较耗时也就意味着阻塞Redis可能性增大。网络拥塞每次获取bigkey产生的网络流量较大假设一个bigkey为1MB每秒访问量为1000那么每秒产生1000MB的流量对于普通的千兆网卡按照字节算是128MB/s的服务器来说简直是灭顶之灾而且一般服务器会采用单机多实例的方式来部署也就是说一个bigkey可能会对其他实例造成影响其后果不堪设想。下图演示了网络带宽被bigkey占用的瞬间。bigkey的存在并不是完全致命的如果这个bigkey存在但是几乎不被访问那么只有内存空间不均匀的问题存在相对于另外两个问题没有那么重要紧急但是如果bigkey是一个热点key频繁访问​那么其带来的危害不可想象所以在实际开发和运维时一定要密切关注bigkey的存在。2、如何发现redis-cli–bigkeys可以命令统计bigkey的分布但是在生产环境中开发和运维人员更希望自己可以定义bigkey的大小而且更希望找到真正的bigkey都有哪些这样才可以去定位、解决、优化问题。判断一个key是否为bigkey只需要执行debug object key查看serializedlength属性即可它表示key对应的value序列化之后的字节数例如我们执行如下操作127.0.0.1:6379debug object key Value at:0x7fc06c1b1430 refcount:1 encoding:raw serializedlength:1256350 lru:11686193 lru_seconds_idle:20可以发现serializedlength11686193字节约为1M同时可以看到encoding是raw也就是字符串类型那么可以通过strlen来看一下字符串的字节数为2247394字节约为2MB127.0.0.1:6379strlen key(integer)2247394serializedlength不代表真实的字节大小它返回对象使用RDB编码序列化后的长度值会偏小但是对于排查bigkey有一定辅助作用因为不是每种数据结构都有类似strlen这样的方法。在实际生产环境中发现bigkey的两种方式如下被动收集许多开发人员确实可能对bigkey不了解或重视程度不够但是这种bigkey一旦大量访问很可能就会带来命令慢查询和网卡跑满问题开发人员通过对异常的分析通常能找到异常原因可能是bigkey这种方式虽然不是被笔者推荐的但是在实际生产环境中却大量存在建议修改Redis客户端当抛出异常时打印出所操作的key方便排查bigkey问题。主动检测scandebug object如果怀疑存在bigkey可以使用scan命令渐进的扫描出所有的key分别计算每个key的serializedlength找到对应bigkey进行相应的处理和报警这种方式是比较推荐的方式。开发提示如果键值个数比较多scandebug object会比较慢可以利用Pipeline机制完成。对于元素个数较多的数据结构debug object执行速度比较慢存在阻塞Redis的可能。如果有从节点可以考虑在从节点上执行。3、如何删除当发现Redis中有bigkey并且确认要删除时如何优雅地删除bigkey无论是什么数据结构del命令都将其删除。但是相信通过上面的分析后你一定不会这么做因为删除bigkey通常来说会阻塞Redis服务。下面给出一组测试数据分别对string、hash、list、set、sorted set五种数据结构的bigkey进行删除bigkey的元素个数和每个元素的大小不尽相同。下面测试和服务器硬件、Redis版本比较相关可能在不同的服务器上执行速度不太相同但是能提供一定的参考价值下表展示了删除512KB~10MB的字符串类型数据所花费的时间总体来说由于字符串类型结构相对简单删除速度比较快但是随着value值的不断增大删除速度也逐渐变慢。下表展示了非字符串类型的数据结构在不同数量级、不同元素大小下对bigkey执行del命令的时间总体上看元素个数越多、元素越大删除时间越长相对于字符串类型这种删除速度已经足够可以阻塞Redis。从上分析可见除了string类型其他四种数据结构删除的速度有可能很慢这样增大了阻塞Redis的可能性。既然不能用del命令那有没有比较优雅的方式进行删除呢这时候就需要sscan、hscan、zscan。3.1、string对于string类型使用del命令一般不会产生阻塞del bigkey3.2、hash、list、set、sorted set下面以hash为例子使用hscan命令每次获取部分例如100个field-value再利用hdel删除每个field为了快速可以使用Pipeline​publicvoiddelBigHash(StringbigKey){JedisjedisnewJedis(“127.0.0.1”,6379);// 游标Stringcursor“0”;while(true){ScanResultMap.EntryString,StringscanResultjedis.hscan(bigKey,cursor,newScanParams().count(100));// 每次扫描后获取新的游标cursorscanResult.getStringCursor();// 获取扫描结果ListEntryString,StringlistscanResult.getResult();if(listnull||list.size()0){continue;}String[]fieldsgetFieldsFrom(list);// 删除多个fieldjedis.hdel(bigKey,fields);// 游标为0时停止if(cursor.equals(“0”)){break;}}// 最终删除keyjedis.del(bigKey);}/** * 获取field数组 * param list * return */privateString[]getFieldsFrom(ListEntryString,Stringlist){ListStringfieldsnewArrayListString();for(EntryString,Stringentry:list){fields.add(entry.getKey());}returnfields.toArray(newString[fields.size()]);}请勿忘记每次执行到最后执行del key操作。4、最佳实践思路由于开发人员对Redis的理解程度不同在实际开发中出现bigkey在所难免重要的是能通过合理的检测机制及时找到它们进行处理。作为开发人员在业务开发时应注意不能将Redis简单暴力的使用应该在数据结构的选择和设计上更加合理例如出现了bigkey要思考一下可不可以做一些优化例如拆分数据结构尽量让这些bigkey消失在业务中如果bigkey不可避免也要思考一下要不要每次把所有元素都取出来例如有时候仅仅需要hmget而不是hgetall​。最后可喜的是Redis将在4.0版本支持lazy delete free的模式那时删除bigkey不会阻塞Redis。