Redis的运维我们都知道Redis是互联网产品开发中不可或缺的利器。其优点是速度快、数据结构丰富、使用方便。不管什么样的数据,不管数据有多大,不管里面塞了多少数据,当你想查的时候,一梭子就全部拿出来了。你说你有多信任Redis。运维Redis的同学听到这种说法肯定会露出下面的表情:为了更好的理解我们是如何使用Redis的,除了对Redis做一些使用规范之外,我们还需要对Redis有一个全面的了解在线使用。那么问题来了:一个Redis实例占用这么大的内存,里面存储的是什么?你有那些钥匙吗?每个键使用多少空间?Redis的内存占用越来越大,但是你不知道里面存储的是什么。分析一次需要时间和精力,一旦出错,很容易影响业务。那么有什么方法可以让我们安全高效的看到Redis内存消耗的详细报表呢?像这样直观而美丽:解决方案总是比问题多,有需要的地方就有解决方案。以上就是雪球SRE团队做的Redis数据可视化平台RDR(RedisDataReveal)。RDR可以很方便的分析Reids的内存,知道一个Reids中有哪些key,这些key占用了多少空间,所占的比例是多少,非常直观。那么让我们谈谈我们是如何设计和实现它的。设计思路首先,有没有办法获取到Redis的所有数据?先通过keys*命令获取所有key,然后根据key获取所有内容。优点:可以直接传输网络,不用redis机器的硬盘。缺点:如果key数量特别多,keys命令可能会导致Redis卡死,影响业务;需要多次请求Redis,资源消耗大;遍历数据太慢,开启aof,通过aof文件获取所有数据。优点:不影响Redis服务,完全离线运行,足够安全;缺点:部分Redis实例编写频繁,不适合开启aof,通用性不强;aof文件可能特别大,传输和解析太慢Low。使用bgsave,获取rdb文件,解析后获取数据。优点:机制成熟,可靠性好;文件比较小,传输分析效率高缺点:bgsave虽然会fork子进程,但还是有可能导致主进程卡住一段时间,影响业务。以上几种方式经过我们的评估,我们决定在非高峰时段使用bgsave从节点获取rdb文件,相对安全可靠,也可以覆盖所有业务的Redis集群。每个实例在非高峰期每天自动生成一个rdb文件,即使报表数据延迟一天也是可以接受的。获取rdb文件就相当于获取了Redis实例的所有数据,接下来就是生成报表的过程。解析rdb文件得到key和value的内容。根据相应的数据结构和内容,预估内存消耗等统计并生成报表的逻辑非常简单,因此设计思路非常清晰。数据流图如下:那么我们来看看如何实现。SnowballSRE开发的组件基本都是使用GO作为后端,所以没有语言选择上的纠结,直接使用GO。可以根据Redis协议解析RDB。github上基本都有各种语言的相关库,大家可以使用。需要注意的是,语言本身的性能对解析效率有比较大的影响。估计内存消耗一条记录有多少内存使用?我们知道Redis的实现中有一些基本的数据结构,用于实现对外暴露的各种数据类型:如sds、dict、intset、zipmap、adlist、ziplist、quicklist、skiplist等。只要根据这条记录的数据类型找出使用了哪些数据结构,然后计算这些基本数据结构的内存消耗,加上数据的内存占用,以及一些额外的开销比如过期时间,就可以了估计一个记录。准确记录使用了多少内存。但是由于Redis做了很多优化,对于同一种数据类型,在不同的场景下使用的数据结构可能会有所不同。比如老版本的RedisList,会根据列表元素的多少来决定使用哪种结构。短的时候用adlist,长的时候用ziplist。该值可以通过list-max-ziplist-entries配置。3.2版本之后,全部使用quicklists。事实上,不同的结构对内存的使用是不同的。我们在计算的时候,无法得到具体的配置,所以按照默认的配置进行计算。最后得到的值是一个预估值,但基本上可以反映使用情况。如果你对Redis使用的各种数据结构感兴趣,想了解它的设计和适用场景,可以搜索更多相关资料,阅读Redis源码。举个计算内存占用的例子:假设我们通过解析rdb得到一条key为hello,value为world,type为string,ttl为1440的记录,它的内存占用是这样的:一个dictEntry的消耗,因为它是redisdbdict和一个robj中的一个元素的消费。robj是一种通用的数据结构,用于在同一个dict中存储不同类型的值。全称是RedisObjectstoragekey的sdsconsumption,sds是Redis中用来存储字符串的数据结构,存储过期时间,consumptionsds存储value。前四项基本用于存储任意key,最后一项根据value的数据结构而变化。一个dictEntry有2个指针,一个int64内存消耗(https://github.com/antirez/redis/blob/unstable/src/dict.h)一个robj有1个指针,一个int,还有几个使用位字段的字段消耗一共4个字节(https://github.com/antirez/redis/blob/unstable/src/server.h),过期时间也存储为一个时间戳为int64的dictEntry(https://github.com/antirez/redis/blob/unstable/src/db.c)存储sds需要存储header和字符串长度+1的空间,header长度会根据字符串长度变化(https://github.com/antirez/redis/blob/unstable/src/sds.h)根据以上信息,我们可以计算出真正需要多少内存才能向操作系统申请这些内存。由于redis支持多种malloc算法,我们按照jemalloc的分配方式进行计算,这里可能会出现错误。所以***key为hello的记录在64位操作系统上总共会消耗92个字节。其他类型的计算思路大致相同,只是需要根据不同的数据结构计算不同的内存消耗,计算时要考虑内存对齐。还有,由于zset的算法涉及到层的随机生成,所以我们也是用同样的算法来随机化,但是计算出来的值肯定不准确,也是一个错误点。统计计数最终可以得到任意一个key的内存使用情况。哪些是最有意义和最有价值的数据?topN,毫无疑问,topNkey肯定是关注不同数据类型的key的个数,元素个数的分布,内存占用按照前缀分类。统一前缀一般表示使用特定的业务。计算每个类别的key个数和内存占用的需求也很容易实现:维护一个小的topheap存放topNbest,取出heap中的数据统计。但是一般都会有特定的分隔符,比如:|._等字符,根据这些字符切出常见的前缀然后统计,所有的数字都用0代替,这样便于归类写入***每天打开一个网页都能看到一个Redis实例的内存使用详情,是一件很开心的事情。Redis的内存使用不再是一个黑盒子。
