Redis内存使用量比数据高10倍

25

我试图将一个单词列表存储在Redis中,性能非常好。

我的方法是创建一个称为“words”的集合,通过'sadd'添加每个新单词。

当添加一个大小为15.9 MB并包含约一百万个单词的文件时,redis-server进程会消耗160 MB内存。为什么我使用了10倍的内存,有没有更好的方法来解决这个问题?

3个回答

94
任何高效的数据存储都需要将单词索引到内存中的动态数据结构中,这些结构由指针链接的单元组成。结构元数据、指针和内存分配器内部碎片化是导致数据占用比相应的平面文件更多内存的原因。
Redis集合是作为哈希表实现的,其中包括:
  • 指数增长的指针数组(2的幂)
  • 当增量重新哈希时可能需要第二个数组
  • 表示哈希表中条目的单向链表单元格(3个指针,每个条目24字节)
  • Redis对象包装器(每个值一个)(每个条目16字节)
  • 实际数据本身(每个数据前缀8字节大小和容量)
以上所有大小均适用于64位实现。考虑到内存分配器开销,对于使用jemalloc分配器(>= 2.4)的最新版本的Redis,每个集合项至少需要64字节的空间(除数据外)。

Redis提供了内存优化来优化某些数据类型,但它们并不包括字符串集合。如果您真的需要优化集合的内存消耗,有一些技巧可以使用。我不会为只有160 MB的RAM这样做,但如果您有更大的数据,以下是您可以做的。

如果您不需要集合的并集、交集、差异功能,则可以将单词存储在哈希对象中。好处是,如果哈希对象足够小,Redis可以自动使用zipmap对其进行优化。在Redis >= 2.6中,zipmap机制已被ziplist取代,但思想是相同的:使用序列化数据结构,该结构可以适合CPU缓存,以获得性能和紧凑的内存占用。

为了保证哈希对象足够小,可以根据某种哈希机制分配数据。假设您需要存储1M个项目,可以按以下方式实现添加单词:

  • 对其进行哈希取模10000(在客户端完成)
  • HMSET words:[hashnum] [word] 1

而不是存储:

words => set{ hi, hello, greetings, howdy, bonjour, salut, ... }

你可以存储:

words:H1 => map{ hi:1, greetings:1, bonjour:1, ... }
words:H2 => map{ hello:1, howdy:1, salut:1, ... }
...

为了检索或检查单词的存在,可以使用哈希函数并使用HGET或HEXISTS。

采用这种策略,可以显著节省内存,前提是哈希的模数根据zipmap配置(或Redis >= 2.6的ziplist)进行选择:

# Hashes are encoded in a special way (much more memory efficient) when they
# have at max a given number of elements, and the biggest element does not
# exceed a given threshold. You can configure this limits with the following
# configuration directives.
hash-max-zipmap-entries 512
hash-max-zipmap-value 64

注意:这些参数的名称已经在Redis >= 2.6中更改。

在这里,对于1M个项目的模数10000意味着每个哈希对象100个项目,这将确保它们全部存储为zipmaps / ziplists。


迷人而详细的答案;我不知道那个。谢谢@Didier! - Ofer Zelig
好的,非常感谢,我相信这能解决我的问题。对于160MB大小的数据来说没问题,但是我想要处理高达1GB的纯文本数据,并且不希望将其增加到10GB。再次感谢,非常感激您提供了详细的答案。 - cwoebker
3
@Didier - 回答得非常好!不过需要做几个更正:a)Hashtable条目是单向链表,而不是双向的,24字节的开销是正确的;b)Redis对象封装不适用于每个集合/哈希条目。它只适用于顶层键/值对-因此开销是恒定的;c)您可能需要指出,在2.6 /不稳定版本中已弃用zipmap,而ziplist则执行了等效操作。 - Sripathi Krishnan
@SripathiKrishnan - 谢谢,我已经更新了我的答案。我仍然认为robj的用法适用于所有set键。我指的是redis.c中的setDictType结构和相应的函数,它们定义了这种行为。 - Didier Spezia
@DidierSpezia - 关于 robj 的使用:是的,你说得对。不知道我怎么会忽略了那个包装器! - Sripathi Krishnan
显示剩余3条评论

5
对于我的实验来说,最好将数据存储在哈希表/字典中。经过多次基准测试后,我得出的最佳情况是将数据条目存储在哈希表中,不超过500个键。
我尝试了标准的字符串设置/获取方法,在处理100万个键/值时,其大小为79 MB。如果您有像1亿这样的大数字,它将使用约8 GB的空间,这非常巨大。
我尝试使用哈希来存储相同的数据,在处理相同的100万个键/值时,其大小逐渐变小,为16 MB。
如果有人需要基准测试代码,请发邮件给我。

1
你是如何进行这些测量的?谢谢。 - Jorge Lavín

3

你尝试过持久化数据库(例如使用 BGSAVE 命令),关闭 Redis 服务器,再次启动吗?由于分片行为,在从保存的 RDB 文件中填充数据并重新启动后,内存可能会减少。

另外:您使用的 Redis 版本是什么?请查看此博客文章 - 它说从 2.4 版本开始部分解决了分片问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接