乐趣区

关于java:Redis-内存压缩实战学习了

作者:Xie Zefan

起源:https://xiezefan.me/2017/05/0…

在探讨 Redis 内存压缩的时候,咱们须要理解一下几个 Redis 的相干常识。

压缩列表 ziplist

Redis 的 ziplist 是用一段间断的内存来存储列表数据的一个数据结构,它的构造示例如下图

压缩列表组成示例 – 截图来自《Redis 设计与实现》

  1. zlbytes: 记录整个压缩列表应用的内存大小
  2. zltail: 记录压缩列表表尾间隔起始地位有多少字节
  3. zllen: 记录压缩列表节点数量,值得注意的一点是,因为它只占了 2 个字节,所以最大值只能到 65535,这意味着压缩列表长度大于 65535 的时候,就只能通过遍历整个列表来计算长度了
  4. zleng: 压缩列表末端标记位,固定值为OxFF
  5. entry1-N: 压缩列表节点, 具体构造如下图

压缩列表节点组成示例 – 截图来自《Redis 设计与实现》

其中

  1. previous_entry_length: 上一个节点的长度
  2. encoding: content 的编码以及长度
  3. content: 节点数据

当咱们查找一个节点的时候,次要进行一下操作:

  1. 依据 zltail 获取最初一个节点的地位
  2. 判断以后节点是否是指标节点
  3. 如果是,则返回数据
  4. 如果不是,则依据 previous_entry_length 计算上一个节点的起始地位,而后从新进行步骤 2 判断

通过上述的形容,咱们能够晓得,ziplist 每次数据更新的复杂度大概是 O(N),因为它须要对 N 个节点进行内存重调配,查找一个数据的时候,复杂度是 O(N),最坏状况下须要遍历整个列表。

什么状况下会应用到 ziplist 呢?

Redis 会应用到 ziplist 的数据结构是 Hash 与 List。

Hash 构造应用 ziplist 作为底层存储的两个条件是:

  1. 所有的键与值的字符串长度都小于 64 字节的时候
  2. 键与值对数据小于 512 个

只有上述条件任何一个不满足,Redis 就会主动将这个 Hash 对象从 ziplist 转换成 hashtable。但这两个阈值能够通过批改配置文件中的 hash-max-ziplist-valuehash-max-ziplist-entries来变更。

List 构造应用 ziplist 的条件与 Hash 构造一样,当条件不满足的时候,会从 ziplist 转换成 linkedlist,同样咱们能够批改 list-max-ziplist-valuehash-max-ziplist-entries来应用不同的阈值。

为什么 Hash 与 List 会应用 ziplist 来存储数据呢?

因为

  1. ziplist 会比 hashtable 与 ziplist 节俭跟多的内存
  2. 内存中以间断块形式保留的数据比起 hashtable 与 linkedlist 应用的链表能够更快的载入缓存中
  3. 当 ziplist 的长度比拟小的时候,从 ziplist 读写数据的效率比 hashtable 或者 linkedlist 的差别并不大。

实质上,应用 ziplist 就是以工夫换空间的一种优化,然而他的工夫损坏小到简直能够忽略不计,但却能带来可观的内存缩小,所以满足条件时,Redis 会应用 ziplist 作为 Hash 与 List 的存储构造。

实战

咱们先抛出问题,在广告程序化交易的过程中,咱们常常须要为一个广告投放打算定制人群包,其存储的模式如下:

人群包 ID => [设施 ID_1, 设施 ID_2 ... 设施 ID_N]

其中,人群包 ID 是 Long 型整数,设施 ID 是通过 MD5 解决,长度为 32。
在业务场景中,咱们须要判断一个设施 ID 是否在一个人群包中,来决定是否投放广告。

在传统的应用 Redis 的场景, 咱们能够应用规范的 KV 构造来存储定向包数据,则存储形式如下:

{人群包 ID}_{设施 ID_1} => true
{人群包 ID}_{设施 ID_2} => true

如果咱们想应用 ziplist 来持续内存压缩的话,咱们必须保障 Hash 对象的长度小于 512,并且键值的长度小于 64 字节。咱们能够将 KV 构造的数据,存储到事后调配好的 bucket 中。

咱们先预估下,整个 Redis 集群预计包容的数据条数为 10 亿,那么 Bucket 的数量的计算公式如下:

bucket_count = 10 亿 / 512 = 195W

那么咱们大略须要 200W 个 Bucket(预估 Bucket 数量须要多预估一点,以防触发临界值问题)
咱们先以下公式计算 BucketID:

bucket_id = CRC32(人群包 ID + "_" + 设施 ID) % 200W

那么数据在 Redis 的存储构造就变成

bucket_id => {{人群包 ID}_{设施 ID_1} => true
   {人群包 ID}_{设施 ID_2} => true
}

这样咱们保障每个 bucket 中的数据项都小于 512,并且长度均小于 64 字节。

咱们以 2000W 数据进行测试,前后两者的内存应用状况如下:

数据集大小 存储模式 Bucket 数量 所用内存 碎片率 Redis 占用的内存
2000W 压缩列表 200W 928M 1.38 1.25G
2000W 压缩列表 5W 785M 1.48 1.14G
2000W 间接存储 1.44G 1.03 1.48G

在这里须要额定引入一个概念 – 内存碎片率。

内存碎片率 = 操作系统给 Redis 调配的内存 / Redis 存储对象占用的内存

因为压缩列表在更新节点的时候,常常须要进行内存重调配,所以导致比拟高的内存碎片率。咱们在做技术计划比拟的时候,内存碎片率也是十分须要关注的指标之一。

但有很多伎俩能够缩小内存碎片率,比方内存对其,甚至更极其的间接重做整个 Redis 内存(利用快照或者从节点来重做内存)都能无效的减低内存碎片率。

咱们在本次试验中,因为存储的数值比拟大(单个 KEY 约 34 个字节),所以理论节俭内存不是很多,但仍然能节约 35%-50% 的内存应用。

在理论的生产环境中,咱们依据利用场景正当的设计压缩存储构造,局部业务甚至能达到节约 70% 的内存应用的成果。

压缩列表能节俭多少内存?

咱们当初晓得压缩列表是通过将节点紧凑的排列在内存中,从而节俭掉内存的。但他到底节俭了哪些内存从而能达到惊人的压缩率呢?

首先为了明确这个细节,咱们须要晓得一般 Key-Value 构造在 Redis 中是如何存储的。

typedef struct redisObject {
    unsigned type:4;        // 对象的类型
    unsigned encoding:4;    // 对象的编码
    unsigned lru:LRU_BITS;  // LRU 类型
    int refcount;           // 援用计数
    void *ptr;              // 指向底层数据结构的指针
} robj;

Redis 所有的对象都是通过上述构造来存储, 假如我存储 Hello=>World 这样一个健值对到 Redis 中,除了存储自身键值的数据外,还须要额定的 24 个字节来存储 redisObject 对象。

而 Redis 存储字符串应用的 SDS 数据结构

struct sdshdr8 {
    uint8_t len;        // 所保留字符串的长度
    uint8_t alloc;      // 调配的内存数量
    unsigned char flags;// 标记位,用于判断 sdshdr 类型
    char buf[];         // 字节数组,用户保留字符串};

如果字符串的长度无奈用 unsigned int8 来示意的话,Redis 会应用能表白更大长度的 sdshdr16 构造来存储字符串。

并且,为了缩小批改字符串带来的内存重分类问题,Redis 会进行内存预调配,所以可能你仅仅为了保留五个字符,但 Redis 会为你预调配 10 bytes 的内存。

这意味着当咱们存储 Hello 这个字符串的时候,你须要额定的 3 个以上的字节。

Oh~~~,我只想保留 Hello=>World 这十个字符的数据,居然须要的 30~40 个字节的数据来存储额定的信息,比存储数据自身的大小还多一些。这还没包含 Redis 保护字典表所须要的额定的内存空间。

那么假如咱们用 ziplist 来存储这个数据,咱们仅仅须要额定的 2 个字节用于存储 previous_entry_length 与 encoding。具体的计算形式能够参考 Redis 源码或者《Redis 设计与实现》第一局部第 7 章压缩列表。

总结

从以上比照,咱们能够看出,在存储越小的数据的时候,应用 ziplist 来进行数据压缩能失去更好的压缩率。
但副作用也很显著,ziplist 的更新效率远远低于一般 K - V 模式,并且会造成额定的内存碎片率。

在 Redis 中存储大量数据的实际过程中,咱们常常会做一些小技巧来尽可能压迫 Redis 的存储能力。接下来筹备写一篇 Redis 内存压缩的小技巧。

近期热文举荐:

1.1,000+ 道 Java 面试题及答案整顿(2021 最新版)

2. 终于靠开源我的项目弄到 IntelliJ IDEA 激活码了,真香!

3. 阿里 Mock 工具正式开源,干掉市面上所有 Mock 工具!

4.Spring Cloud 2020.0.0 正式公布,全新颠覆性版本!

5.《Java 开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞 + 转发哦!

退出移动版