Redis 用集合存储集合

不要将集合拆分存储,用集合存储集合。

一、优点 - 节省空间

1. key-value 的空间占用

  • 数据结构占用:

    如果是 String 类型,则除了记录实际数据外,还需要额外记录数组结束符、数据长度、空间使用等

  • 元数据占用:

    在 Redis 中,每个值都有一些元数据(包括最后一次访问的时间、被引用的次数)需要记录。Redis 会使用一个 RedisObject 结构体统一记录这些元数据,同时指向实际值,一个 RedisObject 包含 8 字节的元数据和 8 字节的指针。

    • 如果数据是 Long 类型的整数,则指针会被直接复制为数据,避免了额外内存空间和寻址的开销
    • 如果数据是小于等于 44 个字节的字符串,则 Redis 会将 RedisObject、字符串在内存中放置到一起
    • 如果数据是大于 44 个字节的字符串,则 Redis 不会将它们放到一起,而是为字符串分配独立的空间
  • 哈希表项占用:

    由于键值对最终都要放置到哈希表中,因此 Redis 还需要维护一个额外的哈希表项。每个哈希表项是一个 dictEntry 结构体,由三个 8 个字节的指针组成,分别指向 key、value、哈希桶中的下一项,又由于 Redis 的内存分配按 2 的幂次数分配,因此共占用 32 个字节。

2. 集合的空间占用优势

假设要存储每个用户的粉丝数,

可以这样:

1
2
3
4
5
6
key    value
id1 100
id2 200
id3 105
id4 120
···

也可以这样:

1
2
3
4
5
6
key    value
fans 集合 id1 100
id2 200
id3 105
id4 120
···

如果将集合拆分存储,每增加一个集合元素都要额外增加数据结构占用、元数据占用、哈希表项占用。

如果用集合存储集合,一个集合只需要一个元数据和一个哈希表项,且每增加一个集合元素只需要额外增加数据结构占用(并且集合项的数据结构占用经过优化,小于 String)。

二、优点 - 统计

  • 聚合统计:假设有对多个集合的聚合运算(并集、交集、差集计算),可以使用 Set,它自带聚合运算命令

    集合运算的计算复杂度较高,执行可能导致 Redis 实例堵塞,可以用以下两种方式解决:

    • 选择一个从库,让它专门负责聚合运算
    • 将数据读取到 Server 中,在 Server 中完成集合运算
  • 排序统计:假如要存储有序结合,可以使用 List 和 Sorted Set

    List 和 Sorted Set 属于有序集合,其中:

    • List 按照元素进入顺序进行排序
    • Sorted Set 按照元素的权重排序
  • 二值状态统计:假设需要维护一个集合的状态,并且这个状态取值只有两种(0 和 1),可以使用 Bitmap

    Bitmap 的底层是二进制的字节数组,用数组的每一位表示一个元素的二值状态

  • 基数统计:如果希望统计集合中不重复元素的个数,可以使用 Set,它能够保证元素不重复。另外,也可以使用 HyperLogLog,它能够在节省空间的情况下 “估算” 基数。

参考