寻求优化Redis内存使用,以缓存许多JSON API结果

3
我刚接触Redis,正在尝试缓存一些数据并比较内存使用/性能与其他选项(如Memcached)的差异。我使用IRedisClient的ServiceStack.Redis客户端库。
我已经测试了Redis,25000个键值对象占用大约250MB内存,并生成一个100MB的dump.rdb文件。我需要缓存更多数据,并希望尽可能地减少内存消耗。我的猜测是每个缓存项的文本(JSON blob)大小约为4k,但如果我的基本数学正确,每个项目至少从内存占用的角度来看占用大约10k的Redis内存。转储大小和内存大小之间的巨大差异有点令人震惊。
我现在也在64位虚拟机上运行,我知道它浪费了比32位更多的空间,所以我也会考虑这个问题。看起来Redis需要为每个指针(每个键值缓存?)使用2倍的内存。这是否是2.5倍磁盘:内存比率的原因?
我知道我可以在我的代码中处理Redis中数据的压缩/解压缩,但是我很好奇是否有一些方法可以配置客户端库来执行类似于StreamExtensions的操作。
使用模式准备就绪,写入不频繁,或批量缓存刷新写入。
无论如何,寻求任何关于如何获得更多缓存项目以达到给定内存量的建议。

你应该阅读这篇关于Redis内存使用的有趣文章:http://nosql.mypopescu.com/post/1010844204/redis-memory-usage。你尝试过使用Hashes吗? - Emmanuel Tabard
Emmanuel,这看起来很有前途。你知道ServiceStack是否提供了一些方法来生成这些哈希值,还是需要手动完成? - JesseP
1
您可能还想阅读此Wiki-https://github.com/sripathikrishnan/redis-rdb-tools/wiki/Redis-Memory-Optimization - Sripathi Krishnan
2个回答

2
有多个需要考虑的点。以下假设您的数据存储在字符串中,每个字符串包含一个JSON对象。
第一点是存储了4 KB的JSON对象。由于Redis动态数据结构和指针的开销与有用数据的大小相比绝对可以忽略不计。如果您有很多非常小的对象(大约每个键约为80个字节),则这种开销会很高,但使用4 KB对象不应该是问题。
因此,使用32位版本(减小指针的大小)也没有帮助。
第二点是内存占用和转储文件大小之间的差异很容易解释,因为转储文件中的字符串使用LZF算法进行了压缩(而JSON确实可以很好地压缩)。对于未压缩的数据,内存占用通常比转储文件大小要大得多。
现在,您在实际数据大小和内存占用之间看到的差异可能是由于分配器的内部碎片。一般来说,人们只考虑外部碎片(即大多数人普遍称为内存碎片的那种),但在某些情况下,内部碎片也可能代表主要开销。请参见此处的定义。
在您的情况下,4 KB 的对象实际上是最糟糕的情况之一。Redis 使用 jemalloc 分配器,具有明确定义的分配类。您可以看到 4 KB 是一个分配类,下一个分配类是 8 KB。这意味着如果您的一些对象的重量略高于 4 KB(包括 Redis 字符串开销的 8 个字节),则会分配 8 KB 而不是 4 KB,并且将浪费一半的内存。
您可以通过仅存储比 4 KB 稍小的对象并计算内存占用量与有用数据预期大小之间的比率来轻松检查此点。使用比 4 KB 稍大的对象重复相同的操作并比较结果。
减少开销的可能解决方案:
客户端压缩。使用任何轻量级压缩算法(如LZF、LZO、quicklz、snappy)。如果您能将大多数对象的大小保持在4 KB以下,则它将工作得很好。
更改内存分配器。Redis的Makefile也支持tcmalloc(Google分配器)作为内存分配器。由于分配类别不同,它可以减少这些4 KB对象的内存开销。
请注意,对于其他内存存储,您也会得到相同类型的开销。例如,在memcached中,slab分配器的工作是优化内存消耗,并最小化内部和外部碎片。

你对其他答案中提到的键哈希有什么想法?这会影响大小还是只是搜索? - JesseP
只有在值非常小(几个字节)的情况下,使用哈希对象作为内存优化才是有趣的。在这种情况下,它在大小方面带来了重大的收益,而在搜索时的CPU开销非常小。如果您的值是4 KB对象,则根本不会有帮助。 - Didier Spezia

1

我曾经也遇到过理解如何高效使用Redis的困难。特别是当你熟悉Memcache(get/set)而不熟悉Redis(字符串,哈希,列表,集合和有序集合)时更容易发生。

你应该阅读一下关于Redis内存使用的文章:http://nosql.mypopescu.com/post/1010844204/redis-memory-usage。虽然这篇文章早在2010年就发布了,但仍然很有参考价值。

我认为有两种解决方案:

  • 编译并使用32位实例。转储文件在32位和64位之间是兼容的,如果需要可以稍后切换。

  • 对于我来说,使用哈希表看起来更好一些:http://redis.io/topics/memory-optimization。请阅读“使用哈希表在Redis上抽象出一个非常内存高效的纯键值存储”的部分。ServiceStack.Redis提供了一个RedisClientHash。它应该很容易使用!

希望对你有所帮助!


看起来IRedisClient.SetEntryInHash()没有提供像IRedisClient.SetEntry()方法那样的过期日期重载... :( - JesseP

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