当前位置: 首页 > 科技观察

Redis存储数据是选择Hash还是String?

时间:2023-03-15 17:31:18 科技观察

在stackoverflow上看到一个问题,RedisstringsvsRedishashestorepresentJSON:efficiency?内容如下:我想存一个JSONpayload到redis中。我真的有两种方法可以做到这一点:一种使用简单的字符串键和值。键:用户,值:有效负载(整个JSONblob可以是100-200KB)SETuser:1payloadUsinghashesHSETuser:1username"someone"HSETuser:1location"NY"HSETuser:1bio"STRINGWITHOVER100lines"请记住,如果我使用散列,值长度是不可预测的。它们并不都像上面的生物示例那样短。哪个内存效率更高?使用字符串键和值,或使用哈希?字符串和哈希直观测试首先,让我们先测试数据。测试数据结构如下:values={"name":"gs","age":1}用于生成10w个key,key生成规则为:foriinrange(100000):key="object:%d"%i将数据以hash和string的形式存储在redis中(values使用json编码为string)。结果如下:hash占用10.16Mstring占用10.15M,这似乎与我们印象中hash占用比较大的空间不符。为什么是这样?这里是因为Redishash对象有两种编码方式:ziplist(2.6之前是zipmap)hashtable当hash对象可以同时满足以下两个条件时,hash对象使用ziplist编码:哈希对象中存储的所有键值对小于64字节;哈希对象保存的键值对数量小于512;不能满足这两个条件的哈希对象需要使用哈希表编码。上面的测试数据满足这两个条件,所以这里使用ziplist中存储的数据,而不是hashtable。请注意,这两个条件的上限是可以修改的。具体请参考配置文件中hash-max-ziplist-value选项和hash-max-ziplist-entries选项的说明。hash-max-ziplist-entriesforRedis>=2.6hash-max-ziplist-valueforRedis>=2.6ziplistziplist编码后的数据底层使用压缩列表作为底层数据结构,结构如下:当使用ziplist保存hash对象时,程序会将持有key的ziplist节点推送到链表的末尾,再将持有value的ziplist节点推送到链表的末尾。这样保存的时候不需要申请额外的内存空间,而且每个Key都要存储一些相关的系统信息(比如过期时间,LRU等),所以相对于String类型的Key/Value,Hash类型的key的数量大大减少(大部分key以Hash字段的形式表示和存储),从而进一步优化了存储空间的使用效率。在这篇redis内存优化官方文章中,作者强烈推荐使用散列存储数据尽可能使用散列小散列被编码在一个非常小的空间中,所以你应该尽可能地尝试使用散列来表示你的数据。例如,如果您在Web应用程序中有代表用户的对象,不要对姓名、姓氏、电子邮件、密码使用不同的键,而是使用包含所有必填字段的单个散列。但很多时候散列只包含几个字段。当哈希值很小时,我们可以改为将它们编码为O(N)数据结构,例如具有长度前缀键值对的线性数组。由于我们仅在N较小时才这样做,因此HGET和HSET命令的摊销时间仍然是O(1):一旦哈希包含的元素数量增长太多,哈希将被转换为真正的哈希表(你可以在redis.conf中配置limit)。单从时间复杂度的角度来看,这样是不行的,从cons的角度也是不行的tant时间,因为键值对的线性数组恰好与CPU缓存配合得很好(它具有比哈希表更好的缓存局部性)。hashtable哈希表编码的哈希对象使用字典作为底层实现,并且hash对象中的每个键值对使用字典键值对保存:字典的每个键都是一个字符串对象,键值对的键保存在对象中;字典的每个值都是一个字符串对象,该对象存储键值对值的hashtable编码对象如下:第二个testvalues={"name":"gs","age":1,"intro":"long..long..long..string"}第二次测试方法同第一次,只是在测试数据中加入了一个大字符串,保证hash使用hashtable来存储数据。结果如下:hashtable:1.13Gstring:1.13G基本一样,应该主要是因为Hash类型大大减少了key的数量(大部分key都以Hash字段的形式表示和存储),从而进一步优化存储空间使用效率。注意:读写速度基本一致,差别不大。回到这个问题,string和hash怎么取舍?我同意以下回答:使用哪种数据结构实际上取决于您要存储的数据和使用场景。如果你存储的是结构化数据,比如用户数据缓存,或者你经常需要操作其中的一个或几个数据,尤其是一个数据中有很多文件,但你每次只需要使用其中的一个或几个,使用hash是一个不错的选择,因为它提供了hget和hmget而无需取出所有数据并在代码中进行处理。反之,如果数据相差较大,在运行过程中往往需要读取所有数据再进行处理。使用字符串是一个不错的选择。当然,最简单的还是听从官方的建议,放心使用hash。还有一种场景:如果一个hash中有大量的字段(上千个),需要考虑使用字符串单独存储是否是更好的选择。参考链接[1]RedisstringsvsRedishashestorepresentJSON:efficiency?:https://stackoverflow.com/questions/16375188/redis-strings-vs-redis-hashes-to-represent-json-efficiency[2]redis内存优化:https://redis.io/topics/memory-optimization[3]Redis设计与实现:http://redisbook.com/preview/object/hash.html