Redis容量估计

引言: 我们经常会使用到Redis,但是如何估计我们的数据量可能会占用多大的Redis内存呢?

Redis容量估计

错误的理解:误以为序列化后的Size与存到redis后的Size差不多,但实际上,差距还是蛮大的。

实际工程中,可以直接使用这个工具快速判断可能的占用大小:Redis容量估计工具

错误的估计

现有任务1与任务2需要向redis写入数据:
任务1:

  • 其一天的的数据个数有:293, 659, 385个(约3亿数据)
  • 其序列化后的value的大小共有:50.14 GB
  • 均到3亿数据上,大约每一个value大小为:180 字节

任务2:

  • 其一天的数据个数有:231419688个(约2.3亿数据)
  • 其序列化后的value的大小有:23.55GB
  • 均到2.3亿数据上,平均每一个value大小为:107 字节

就认为总共的数据也就:23 + 50 GB,但是实际占用Redis集群空间:303 GB(2倍副本,实际存储150GB数据)
这和预估数据远远不符,因此使用Redis容量预估工具 http://www.redis.cn/redis_memory/ 重新进行估算:
任务1

任务2
预估的容量为89GB与49GB,和为138GB(与实际占用150GB差距不大)

原理探究

为什么我们23 + 50 GB的数据存入Redis变为了150GB?Redis都维护了怎样的结构?

Redis String的编码方式

由于我们只使用了redis的String存储结构(本质是byte[]),因此这里只分析String。
String在redis内有三种编码方式(如下图所示)

  • int 编码:在保存 64 位有符号整数时
  • embstr编码:在保存的字符串小于 44 字节时
  • raw 编码:大于 44 字节时(embstr与raw的区别仅在于SDS是否与元数据的指针紧挨)

String的编码方式

Redis存储数据需要维护的结构

Redis存储数据,需要维护的数据有:(如下图所示)

  • dictEntry结构:24字节,向上取整为32字节
  • key:存储键key,自己维护9字节的信息,因此大小为key+9,且大小向上取整 16/32/64/128/256/…字节
  • redisObjet:16字节
    • 元数据:存放LRU、LFU的关键信息:时间戳、频次
    • 指针:指向具体结构,对于String就是一个SDS(简单动态字符串)
  • value:存储对应value,由于存储String,也需要维护9字节信息,也是向上取整16/32/64/128/256/…字节
  • bucket个数信息:key的个数增多,redis需要rehash扩展Dict数组,每一个数组的元素是一个8字节指针,因此需要存储key的个数的幂次向上取整。(比如有2000个key,需要有2048个bucket,每个bucket需要8字节)

Redis全局哈希表结构

Redis容量预估计算公式

因此,可以得出Redis容量计算的推理公式

1
2
-- 此处 Pow(x) 表示对x求 最近的2的幂次且向上取整
RedisSize = (32 + Pow(Size(key) + 9) + 16 + Pow(Size(value) + 9)) * Num + Pow(Num)*8

我们可以自己估算一下,上一节提到的两个任务的大小:

对于任务1:

1
2
3
4
Value = (32 + Pow(22 + 9) + Pow(180 + 9) + 16) * 300_000_000 + Pow(300_000_000) * 8
= (32 + 32 + 256 + 16) * 300_000_000 + 2^29 * 8
= 100800000000 + 4294967296
= 105094967296 Byte

105094967296换算为97GB,这与Redis容量工具计算基本一致

对于任务2:

1
2
3
Value = (32 + 32 + 128 + 16) * 230_000_000 + 2^28 * 8
= 47840000000 + 268_435_456 * 8
= 49987483648

49987483648字节换算为46GB,这也与Redis容量工具计算基本一致