Redis字符串与Redis散列来表示JSON:效率?

imzjd6km  于 2022-12-22  发布在  Redis
关注(0)|答案(6)|浏览(175)

我想把一个JSON有效载荷存储到redis中,有两种方法可以做到:
1.一个使用简单的字符串键和值。
键:用户,值:有效负载(整个JSON blob,大小可以是100-200 KB)
第一个月
1.使用散列
HSET user:1 username "someone" HSET user:1 location "NY" HSET user:1 bio "STRING WITH OVER 100 lines"
请记住,如果我使用散列,值的长度是不可预测的。它们并不都像上面的生物示例那样短。
哪一种内存效率更高?使用字符串键和值,还是使用哈希?

4nkexdtk

4nkexdtk1#

这篇文章可以提供很多见解:http://redis.io/topics/memory-optimization
在Redis中有很多方法可以存储对象数组(spoiler:对于大多数使用情形,我喜欢选项1):
1.将整个对象作为JSON编码的字符串存储在单个键中,并使用集合(或列表,如果更合适)跟踪所有对象。例如:

INCR id:users
SET user:{id} '{"name":"Fred","age":25}'
SADD users {id}

一般来说,这可能是大多数情况下最好的方法。如果对象中有很多字段,您的对象没有与其他对象嵌套,并且您倾向于一次只访问字段的一个小子集,那么选择2可能更好。

    • 优点**:每个对象都是一个完整的Redis密钥。JSON解析速度很快,特别是当你需要同时访问该对象的多个字段时。缺点:如果只需要访问单个字段,则速度会较慢。

1.将每个对象的属性存储在Redis散列中。

INCR id:users
HMSET user:{id} name "Fred" age 25
SADD users {id}
    • 优点**:被认为是一个"好习惯"。每个对象都是一个完整的Redis密钥。不需要解析JSON字符串。缺点:当你需要访问一个对象中的所有/大部分字段时可能会更慢。而且,嵌套对象(对象中的对象)不能很容易地存储。

1.将每个对象存储为Redis散列中的JSON字符串。

INCR id:users
HMSET users {id} '{"name":"Fred","age":25}'

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

    • 优点**:JSON解析速度很快,尤其是当您需要同时访问此对象的多个字段时。主键名称空间的"污染"更少。缺点:当你有很多对象时,内存使用和#1差不多。当你只需要访问一个字段时,比#2慢。可能不被认为是一个"好习惯"。

1.将每个对象的每个属性存储在专用键中。

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、密钥分片等问题)。
如果我在这里做错了什么,请考虑留下评论,并允许我在下投之前修改答案。谢谢!:)

hmtdttj4

hmtdttj42#

这取决于您访问数据的方式:
选择选项1:

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

选择选项2:

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

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

332nm8kg

332nm8kg3#

对给定答案的一些补充:
首先,如果你想有效地使用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,你将在用户模型中得到每个属性+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)〈has-max-ziplist-value的数据集,这个解决方案有O(N)的复杂度,因为令人惊讶的是,Reddis将小哈希存储为类似数组的长度/键/值对象的容器!
但是很多时候散列只包含几个字段,当散列很小时,我们可以用O(N)的数据结构来编码它们,就像一个带有长度前缀的键值对的线性数组,因为我们只在N很小时才这样做,所以HGET和HSET命令的摊余时间仍然是O(1):一旦散列表包含的元素数量增长太多,散列表就会被转换成真实的散列表
但是你不用担心,你会很快打破hash-max-ziplist-entries,现在你实际上已经到了第一个解决方案。
第二种选择很可能会在幕后使用第四种解决方案,因为正如问题所述:
请记住,如果我使用散列,值的长度是不可预测的。它们并不都像上面的生物示例那样短。
就像你说的:第四种解决方案肯定是每个属性最昂贵的+70字节。
我的建议如何优化这样的数据集:
你有两个选择:
1.如果你不能保证一些用户属性的最大大小,那么你会选择第一个解决方案,如果内存问题很重要,那么在存储到redis之前压缩用户json。
1.如果你可以强制所有属性的最大值,那么你可以设置hash-max-ziplist-entries/value,并将哈希值作为每个用户表示的一个哈希值,或者作为哈希内存的优化。https://redis.io/topics/memory-optimization并将用户存储为json字符串。无论哪种方式,您都可以压缩长用户属性。

tzdcorbm

tzdcorbm4#

我们在生产环境中遇到了类似的问题,我们提出了一个想法,即如果负载超过某个阈值KB,则对负载进行gzip压缩。
我有一个专用于这个Redis客户端库here的存储库
其基本思想是如果有效载荷的大小大于某个阈值则检测该有效载荷,然后将其以及base-64进行Gzip压缩,然后将压缩后的串作为正常串保存在REDIS中。在检索时检测该串是否是有效的base-64串,如果是,则将其解压缩。
整个压缩和解压缩过程将是透明的,而且您可以获得接近50%的网络流量

压缩基准测试结果

BenchmarkDotNet=v0.12.1, OS=macOS 11.3 (20E232) [Darwin 20.4.0]
Intel Core i7-9750H CPU 2.60GHz, 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.201
  [Host] : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT DEBUG

| 方法|平均值|错误|标准差|第0代|第1代|第2代|已分配|
| - ------| - ------| - ------| - ------| - ------| - ------| - ------| - ------|
| 压缩基准|668.2毫秒|十三点三四毫秒|二十七点二四毫秒|- -| - -| - -| 4.88兆字节|
| 无压缩基准|1,387.1毫秒|二十六点九二毫秒|37.74毫秒|- -| - -| - -| 2.39兆字节|

crcmnpdw

crcmnpdw5#

要在Redis中存储JSON,您可以使用Redis JSON模块。
这将为您提供:

  • 完全支持JSON标准
  • 用于选择/更新文档内元素的JSONPath语法
  • 文档以二进制数据形式存储在树结构中,允许快速访问子元素
  • 所有JSON值类型的类型化原子操作

https://redis.io/docs/stack/json/
https://developer.redis.com/howtos/redisjson/getting-started/
https://redis.com/blog/redisjson-public-preview-performance-benchmarking/

jk9hmnmh

jk9hmnmh6#

您可以使用json模块:https://redis.io/docs/stack/json/它是完全支持的,允许你在Redis中使用json作为数据结构。Redis也有一些语言的对象Map器:https://redis.io/docs/stack/get-started/tutorials/

相关问题