今天看啥  ›  专栏  ›  linuxzw

12.4、处理bigkey

linuxzw  · 简书  ·  · 2019-05-26 16:40

处理bigkey

bigkey是指key对应的value所占的内存空间比较大,例如一个字符串类型的value可以最大存到512MB,一个列表类型的value最多可以存储2^32-1个元素。如果按照数据结构来细分的话,一般分为字符串类型bigkey和非字符串类型bigkey。

  • 字符串类型:体现在单个value值很大,一般认为超过10KB就是bigkey,但这个值和具体的OPS相关。

  • 非字符串类型:哈希、列表、集合、有序集合,体现在元素个数过多。

bigkey无论是空间复杂度和时间复杂度都不太友好,下面我们将介绍它的危害。

注意:因为非字符串数据结构中,每个元素实际上也是一个字符串,但这里只讨论元素个数过多的情况。

  1. bigkey的危害

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

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

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

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

    bigkey的存在并不是完全致命的,如果这个bigkey存在但是几乎不被访问,那么只有内存空间不均匀的问题存在,相对于另外两个问题没有那么重要紧急,但是如果bigkey是一个热点key(频繁访问),那么其带来的危害不可想象,所以在实际开发和运维是一定要密切关注bigkey的存在。

  2. 如何发现

    redis-cli --bigkeys可以命令统计bigkey的分布,但是在生产环境中,开发和运维人员更希望自己可以定义bigkey的大小,而且更希望找到真正的bigkey都有哪些,这样才可以去定位、解决、优化问题。判断一个key是否为bigkey,只需要执行debug object key查看serializedlength属性即可,它表示key对应的value序列化之后的字节数。

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

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

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

    开发提示:

    • 如果兼职个数比较多,scan + debug object会比较慢,可以利用Pipeline机制完成。

    • 对于元素个数较多的数据结构,debug object执行速度比较慢,存在阻塞Redis的可能。

    • 如果有从节点,可以考虑在从节点上执行。

  3. 如何删除

    当发现Redis中有bigkey并且确认要删除时,如何优雅地删除bigkey?无论是什么数据结构,del命令都将其删除。但是相信通过上面的分析后你一定不会这么做,因为删除bigkey通常来说会阻塞Redis服务。下面给出一组测试数据分别对string、hash、list、set、sorted set五种数据结构的bigkey进行删除,bigkey的元素个数和每个元素的大小不尽相同。

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

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

    key类型 512KB 1MB 2MB 5MB 10MB
    string 0.22ms 0.31ms 0.32ms 0.56ms 1ms

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

    key类型 10万(8个字节) 100万(8个字节) 10万(16个字节) 100万(16个字节) 10万(128字节) 100(128字节)
    hash 51ms 950ms 58ms 970ms 96ms 2000ms
    list 23ms 134ms 23ms 138ms 23ms 266ms
    set 44ms 873ms 58ms 881ms 73ms 1319ms
    sorted set 51ms 845ms 57ms 859ms 59ms 969ms

    从上分析可见,除了string类型,其他四种数据删除的速度有可能很慢,这样增大了阻塞Redis的可能性。既然不能用del命令,那有没有比较优雅的方式进行删除呢,这时候就需要将第2章介绍的scan命令的若干类似命令拿出来:sscan、hscan、zscan。

    1. string

      对于string类型使用del命令一般不会产生阻塞:

      del bigkey

    2. hash、list、set、sorted set

      使用hscan命令,每次获取部分(例如100g个)field-value,再利用hdel删除每个field(为了快速可以使用Pipeline)。

  4. 最佳实践思路

    由于开发人员对Redis的理解程度不同,在实际开发中出现bigkey在所难免,重要的是,能通过合理的检测机制及时找到它们,进行处理。作为开发人员在业务开发时应注意不能将Redis简单暴力的使用,应该在数据结构的选择和设计上更加合理,例如出现了bigkey,要思考一下可不可以做一些优化(例如拆分数据结构)尽量让这些bigkey消失在业务中,如果bigkey不可避免,也要思考一下要不要每次把所有元素都取出来(例如有时候仅仅需要hmget,而不是hgetall)。最后,可喜的是,Redis将在4.0版本支持lazy delete free的模式,那是删除bigkey不会阻塞Redis。




原文地址:访问原文地址
快照地址: 访问文章快照