这取决于您访问数据的方式:
选择方案1:
选择2:
P.S。:作为一个经验法则,选择在大多数用例中需要较少查询的选项。
一组给定答案的一些补充:
首先,如果您要有效地使用Redis散列,您必须知道 密钥计数最大数量和值最大大小 - 否则,如果它们突破hash-max-ziplist-value或hash-max-ziplist-entries,Redis会将其转换为引擎盖下几乎常用的键/值对。 (请参阅hash-max-ziplist-value,hash-max-ziplist-entries)并且从哈希选项中破解引擎真的很糟糕,因为Redis中的每个常用键/值对每对使用+90字节。
这意味着如果你从选项2开始并意外地突破max-hash-ziplist-value,你将获得每个每个ATTRIBUTE +90字节的内部用户模型! (实际上不是+90但是+70见下面的控制台输出)
# you need me-redis and awesome-print gems to run exact code redis = Redis.include(MeRedis).configure( hash_max_ziplist_value: 64, hash_max_ziplist_entries: 512 ).new => #<Redis client v4.0.1 for redis://127.0.0.1:6379/0> > redis.flushdb => "OK" > ap redis.info(:memory) { "used_memory" => "529512", **"used_memory_human" => "517.10K"**, .... } => nil # me_set( 't:i' ... ) same as hset( 't:i/512', i % 512 ... ) # txt is some english fictionary book around 56K length, # so we just take some random 63-symbols string from it > redis.pipelined{ 10000.times{ |i| redis.me_set( "t:#{i}", txt[rand(50000), 63] ) } }; :done => :done > ap redis.info(:memory) { "used_memory" => "1251944", **"used_memory_human" => "1.19M"**, # ~ 72b per key/value ..... } > redis.flushdb => "OK" # setting **only one value** +1 byte per hash of 512 values equal to set them all +1 byte > redis.pipelined{ 10000.times{ |i| redis.me_set( "t:#{i}", txt[rand(50000), i % 512 == 0 ? 65 : 63] ) } }; :done > ap redis.info(:memory) { "used_memory" => "1876064", "used_memory_human" => "1.79M", # ~ 134 bytes per pair .... } redis.pipelined{ 10000.times{ |i| redis.set( "t:#{i}", txt[rand(50000), 65] ) } }; ap redis.info(:memory) { "used_memory" => "2262312", "used_memory_human" => "2.16M", #~155 byte per pair i.e. +90 bytes .... }
对于TheHippo的回答,对备选方案一的评论具有误导性:
如果你需要所有字段或多个get / set操作,hgetall / hmset / hmget来解救。
对于BMiner的回答。
对于具有max(id)&lt;的数据集,第三选项实际上非常有趣。 has-max-ziplist-value这个解决方案具有O(N)复杂度,因为,令人惊讶的是,Reddis将小哈希存储为长度/键/值对象的数组式容器!
但很多时候哈希只包含几个字段。当散列较小时,我们可以只将它们编码为O(N)数据结构,就像具有长度前缀键值对的线性数组。由于我们只在N很小时才这样做,因此HGET和HSET命令的分摊时间仍为O(1):一旦包含的元素数量增加太多,哈希将被转换为真实的哈希表
但是你不应该担心,你会非常快地打破hash-max-ziplist-entries,而你现在实际上是在解决方案1号。
第二个选项很可能会转到引擎盖下的第四个解决方案,因为问题表明:
请记住,如果我使用哈希值,则值的长度是不可预测的。它们并非都是短的,例如上面的生物例子。
正如您已经说过的那样:第四种解决方案是每个属性最昂贵的+70字节。
我的建议如何优化这样的数据集:
你有两个选择:
如果你不能保证某些用户属性的最大大小比你第一个解决方案的最大大小,并且内存问题是至关重要的 在redis中存储之前压缩用户json。
如果可以强制所有属性的最大大小。 您可以设置hash-max-ziplist-entries / value,并将哈希值用作每个用户表示的一个哈希值,或者使用Redis指南的主题中的哈希内存优化: https://redis.io/topics/memory-optimization 并将用户存储为json字符串。无论哪种方式,您还可以压缩长用户属性。
这篇文章可以提供很多见解: http://redis.io/topics/memory-optimization
有许多方法可以在Redis中存储对象数组( 的 扰流板 强> :我喜欢大多数用例的选项1):
将整个对象作为JSON编码的字符串存储在单个键中,并使用集合(或列表,如果更合适)跟踪所有对象。例如:
INCR id:users SET user:{id} '{"name":"Fred","age":25}' SADD users {id}
一般来说,这可能是大多数情况下最好的方法。如果对象中有很多字段,则对象不会与其他对象嵌套,并且您一次只能访问一小部分字段,最好选择选项2。
的 好处 强> :被认为是“良好做法”。每个对象都是一个完整的Redis密钥。 JSON解析很快,特别是当您需要同时访问此Object的许多字段时。 的 缺点 强> :当您只需要访问单个字段时速度较慢。
将每个Object的属性存储在Redis哈希中。
INCR id:users HMSET user:{id} name "Fred" age 25 SADD users {id}
的 好处 强> :被认为是“良好做法”。每个对象都是一个完整的Redis密钥。无需解析JSON字符串。 的 缺点 强> :当您需要访问Object中的所有/大多数字段时,可能会更慢。此外,无法轻松存储嵌套对象(对象内的对象)。
将每个Object作为JSON字符串存储在Redis哈希中。
INCR id:users HMSET users {id} '{"name":"Fred","age":25}'
这允许您合并一点,只使用两个键而不是许多键。明显的缺点是你不能在每个用户对象上设置TTL(和其他东西),因为它只是Redis哈希中的一个字段而不是一个完整的Redis密钥。
的 好处 强> :JSON解析很快,尤其是当您需要同时访问此Object的许多字段时。减少主要名称空间的“污染”。 的 缺点 强> :当你有很多对象时,与#1相同的内存使用量。当您只需要访问单个字段时,比#2慢。可能不被认为是“良好做法”。
将每个Object的每个属性存储在专用键中。
INCR id:users SET user:{id}:name "Fred" SET user:{id}:age 25 SADD users {id}
根据上面的文章,这个选项是 几乎从不 首选(除非Object的属性需要具体 TTL 或者其他的东西)。
的 好处 强> :对象属性是完整的Redis密钥,对您的应用程序来说可能不会有点过分。 的 缺点 强> :缓慢,使用更多内存,而不是“最佳实践”。很多污染主键名称空间。
方案4通常不是优选的。选项1和2非常相似,它们都很常见。我更喜欢选项1(一般来说),因为它允许您存储更复杂的对象(具有多层嵌套等)。当您使用选项3时 真的在乎 关于不污染主键名称空间(即你不希望数据库中有很多键,你不关心TTL,键分片等等)。
如果我在这里遇到问题,请考虑发表评论并允许我在下注之前修改答案。谢谢! :)