Redis字符串占用偏高

前言

直接上测试结果, 向redis中写入值(KV相同)为5000001..5100000共10万个字符串类型KV 数据.

先自以为是的单计算一下需要多少内存: K/V相同, 均为整形, 各占用8字节, 那么一条数据会占用16字节. 10万数据大概占用155kb. 但事实真的是这样么? (如果是这样我也不用写这篇文章了)

实际测试一下, 这是一个刚刚启动的redis状态, 还没有写入任何数据:

redis_init

使用脚本向其中set这10万条数据后:

image-20221029184723768

怎么说? 是不是发现比之前计算的数据要大的多? 算一下每条数据占用的内存大小: (7520440-871864)/100000≈66

也就是说, 只是简单的整形KV, redis写入内存后也会占用66字节. 啊, 这?

为了解释这个问题, 我研究了字符串类型在redis中的实现.

字符串实现

源码

为了搞懂这个问题, 我去翻了Redis的源码. 以下源码基于Redis5.0

首先, Redis在内存中使用一个map来记录所有的K/V映射关系. 其实现就是比较常见的数组加链表. 每一个元素使用结构体dictEntry保存, 源码如下:

typedef struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;
} dictEntry;

其中三个字段各占用8字节, 用于保存元素的结构体占用16字节. next字段用于链表指向下个元素, k/v均是指向redisObject类型的指针(value 的整形先跳过, 后面会提到), 其源码如下:

#define LRU_BITS 24
typedef struct redisObject {
    unsigned type:4; // 4bit
    unsigned encoding:4; // 4bit
    unsigned lru:LRU_BITS; // 3字节
    int refcount; // 4字节
    void *ptr; // 8字节
} robj;

RedisObject类型共占用16字节. 很明显, 对应的数据就保存在ptr指针中. Redis中保存字符串有几种情况, 我用一张小图简单说明不同情况下redisObject的数据存放.

image-20221029194124344

为了减少内存占用, Redis在存储的时候根据不同的值使用了不同的存储方式. 为什么是44B 呢? 在注释中给出了解释, 是为了令连续内存占用为64B. 至于sds结构, 这里不展开了.

再次计算

至此, 我们可以重新计算内存占用了, 每个元素的内存占用大致如下:

image-20221029195303049

加起来就是8*7=56B. 但是这跟我们实际测量的66B还是有些差距的呀? 经过查找, Redis使用的内存分配库为jemalloc, 而jemalloc在内存分配的时候申请的内存为2的n 次方. 因此, 虽然dictEntry只需要24B 的内存, 但还是会分配32B.

这样一来, 每个元素的内存占用就是: 32+8*4=64B, 与我们之前分析的几乎没差.

value union

在刚刚看dictEntry的时候, 还有个疑惑, 就是value是一个union, 整形可以直接保存在value变量中, 不需要额外的redisObject, 这样的话, 不就可以节省16B 么?

是的, 没有错. 如果使用incr key命令将值放进去, 你就会发现内存占用减少了, 每个元素占用大概48B

思考

至此, 经过查找, 我们发现, 在向Redis中使用字符串类型保存整形的时候, 虽然我们以为每个元素只需要16B, 但最终还是会占用64B, 这意味着如果需要存储10G 的数据, 需要至少40G 的内存才能放得下, 这着实有些夸张了. 当存储的元素本身不大的时候, 元素所带的额外信息就会是内存占用的大头了.

那么对于我们遇到的这种情况, 有没有什么办法能够减少内存占用呢?

通过查找, 发现了压缩列表这个结构, 在Redis注释中对其进行了详细的解释.

在元素不多的时候, hash/set/list等均使用ziplist, 其优势是可以减少内存占用, 但缺点是时间复杂度较高. 可以使用命令object encoding key查看元素内部类型

于是, 我尝试着将k=5012345 v=5012345的数据, 改为k=5012 field=345 v=5012345的形式以hash的方式进行存储, 这里为了使用ziplist, 修改了配置文件hash-max-ziplist-entries=1024.当我将这10万条数据以hash的方式放进去后, 浅看结果:

image-20221030115836367

image-20221030113837803

我想到内存占用会降低一些, 但没想到低了这么多, 每个元素占用内存大概: (1824216-871952)/100000≈10B.

其中必然是存在这一些共享内容的, 比如元素5012345/5112345field均为345.

但是, 这个内存占用着实有些令我震惊了, 回头抽时间好好研究研究压缩列表这玩意.

注意, hash-max-ziplist-entries配置不要盲目的调高, 压缩列表虽然会降低内存占用, 但是会将时间复杂度降低到 O(n).

原文地址: https://hujingnb.com/archives/860

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值