Redis字符串与Redis哈希表示JSON:效率?

Hen*_*hiu 262 json redis

我想将JSON有效负载存储到redis中.我有两种方法可以做到这一点:

  1. 一个使用简单的字符串键和值.
    key:user,value:payload(整个JSON blob,可以是100-200 KB)

    SET user:1 payload

  2. 使用哈希

    HSET user:1 username "someone"
    HSET user:1 location "NY"
    HSET user:1 bio "STRING WITH OVER 100 lines"

请记住,如果我使用哈希值,则值的长度是不可预测的.它们并非都是短的,例如上面的生物例子.

哪个内存更有效?使用字符串键和值,还是使用哈希?

BMi*_*ner 386

本文可以在这里提供很多见解:http://redis.io/topics/memory-optimization

有很多方法可以在Redis中存储一个Object数组(扰码:我喜欢大多数用例的选项1):

  1. 将整个对象作为JSON编码的字符串存储在单个键中,并使用集合(或列表,如果更合适)跟踪所有对象.例如:

    INCR id:users
    SET user:{id} '{"name":"Fred","age":25}'
    SADD users {id}
    
    Run Code Online (Sandbox Code Playgroud)

    一般来说,这可能是大多数情况下最好的方法.如果对象中有很多字段,则对象不会与其他对象嵌套,并且您一次只能访问一小部分字段,最好选择选项2.

    Advantages: considered a "good practice." Each Object is a full-blown Redis key. JSON parsing is fast, especially when you need to access many fields for this Object at once. Disadvantages: slower when you only need to access a single field.

  2. Store each Object's properties in a Redis hash.

    INCR id:users
    HMSET user:{id} name "Fred" age 25
    SADD users {id}
    
    Run Code Online (Sandbox Code Playgroud)

    Advantages: considered a "good practice." Each Object is a full-blown Redis key. No need to parse JSON strings. Disadvantages: possibly slower when you need to access all/most of the fields in an Object. Also, nested Objects (Objects within Objects) cannot be easily stored.

  3. Store each Object as a JSON string in a Redis hash.

    INCR id:users
    HMSET users {id} '{"name":"Fred","age":25}'
    
    Run Code Online (Sandbox Code Playgroud)

    这允许您合并一点,只使用两个键而不是许多键.明显的缺点是你不能在每个用户对象上设置TTL(和其他东西),因为它只是Redis哈希中的一个字段而不是一个完整的Redis密钥.

    优点:JSON解析速度很快,尤其是当您需要同时访问此Object的许多字段时.减少主要名称空间的"污染". 缺点:当你有很多对象时,大约与#1相同的内存使用量.当您只需要访问单个字段时,比#2慢.可能不被认为是"良好做法".

  4. 将每个Object的每个属性存储在专用键中.

    INCR id:users
    SET user:{id}:name "Fred"
    SET user:{id}:age 25
    SADD users {id}
    
    Run Code Online (Sandbox Code Playgroud)

    根据上面的文章,这个选项几乎从不首选(除非Object的属性需要特定的TTL或其他东西).

    优点:对象属性是完整的Redis密钥,对您的应用程序来说可能不会过度. 缺点:速度慢,使用更多内存,而不是"最佳实践".很多污染主键名称空间.

总结

Option 4 is generally not preferred. Options 1 and 2 are very similar, and they are both pretty common. I prefer option 1 (generally speaking) because it allows you to store more complicated Objects (with multiple layers of nesting, etc.) Option 3 is used when you really care about not polluting the main key namespace (i.e. you don't want there to be a lot of keys in your database and you don't care about things like TTL, key sharding, or whatever).

If I got something wrong here, please consider leaving a comment and allowing me to revise the answer before downvoting. Thanks! :)

  • 对于选项#2,您说"当您需要访问对象中的所有/大多数字段时,可能会更慢".这已经过测试了吗? (4认同)
  • 如何将选项1和2与哈希相结合?对于不经常更新的数据使用选项1,对于频繁更新的数据使用选项2?比如说,我们正在存储文章,我们将标题,作者和网址等字段存储在一个JSON字符串中,其中包含一个像`obj`这样的通用键,并存储视图,投票和具有单独键的选民等字段?这种方式使用单个READ查询可以获得整个对象,并且仍然可以快速更新对象的动态部分?可以通过在事务中读取和写回整个对象来完成对JSON字符串中字段的相对不频繁的更新. (4认同)
  • [hmget](http://redis.io/commands/HMGET)对于*n*fields [get](http://redis.io/commands/GET)是O(n),选项1仍然是O( 1).理论上,是的,它更快. (3认同)
  • 根据这个:(http://instagram-engineering.tumblr.com/post/12202313862/storing-hundreds-of-millions-of-simple-key-value),建议在内存消耗方面存储多个哈希值.因此,在arun的优化之后,我们可以做到:1-将多个哈希存储json有效负载作为不经常更新的数据的字符串,并且2-制作多个哈希值,用于存储经常更新的数据的json字段 (2认同)
  • 对于option1,为什么要将其添加到集合中?为什么我们不能简单地使用Get命令并检查return是否为nil。 (2认同)
  • @sgon00 - 将用户 ID 添加到集合中可以让您跟踪哪些用户可用。从查询的角度考虑:您可能需要一种迭代整个用户集的方法。不鼓励使用 KEYS * 或其他选项;通常最好在集合中明确地进行跟踪。希望有帮助! (2认同)

The*_*ppo 150

这取决于您访问数据的方式:

选择方案1:

  • 如果您在大多数访问中使用大多数字段.
  • 如果可能的键有差异

选择2:

  • 如果您在大多数访问中仅使用单个字段.
  • 如果您始终知道哪些字段可用

PS:作为一个经验法则,请选择在大多数用例中需要较少查询的选项.

  • 如果期望`JSON`有效载荷的_concurrent modification_(**非原子**`read-modify-write`的经典问题),则选项1不是一个好主意. (21认同)

Але*_*щук 7

给定答案的一些补充:

首先,如果您要高效地使用Redis哈希,则必须知道键计数的最大数量和值的最大大小-否则,如果它们破坏了hash-max-ziplist-value或hash-max-ziplist-entries,Redis会将其转换为实际值常用的键/值对。(请参见hash-max-ziplist-value,hash-max-ziplist-entries),从哈希选项中破除确实是很糟糕的,因为Redis内部每个常用的键/值对每对都使用+90字节。

这意味着,如果您从选项二开始,而意外突破了max-hash-ziplist-value,则用户模型内部的每个属性将获得+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    
           ....
    }
Run Code Online (Sandbox Code Playgroud)

对于TheHippo的答案,对选项一的评论具有误导性:

如果需要所有字段或多个get / set操作,请使用hgetall / hmset / hmget进行救援。

对于BMiner的答案。

第三种选择实际上真的很有趣,对于max(id)<has-max-ziplist-value的数据集,此解决方案具有O(N)复杂度,因为令人惊讶的是,Reddis将小的散列存储为长度/键/值的数组式容器对象!

但是很多时候,哈希仅包含几个字段。当哈希较小时,我们可以将其编码为O(N)数据结构,例如带有长度前缀键值对的线性数组。由于我们仅在N较小时执行此操作,因此HGET和HSET命令的摊销时间仍为O(1):一旦包含的元素数量过多,哈希将转换为真实的哈希表

但是您不必担心,您将很快破坏hash-max-ziplist-entries,然后您实际上就已经在解决方案编号1上了。

第二种选择很可能会在第四个解决方案下获得解决,因为有疑问指出:

请记住,如果我使用哈希,则值长度是不可预测的。它们并不都是短的,例如上面的bio示例。

就像您已经说过的那样:第四个解决方案是每个属性最昂贵的+70字节。

我的建议是如何优化此类数据集:

您有两种选择:

  1. 如果您不能保证某些用户属性的最大大小,则可以使用第一个解决方案;如果内存问题至关重要,则可以在存储到Redis中之前压缩用户json。

  2. 如果可以强制所有属性的最大大小。比起您可以设置hash-max-ziplist-entries / value并将散列用作每个用户表示形式的一个散列,或将其用作Redis指南的以下主题中的散列内存优化:https : //redis.io/topics/memory-optimization和将用户存储为json字符串。无论哪种方式,您都可以压缩长用户属性。