Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。
命令
- hset
- 格式:hset key field value
- 功能:将哈希表 key 中的域 field 的值设为 value 。
- 说明:如果 key 不存在,一个新的哈希表被创建并进行 hset 操作。如果域 field 已经存在于哈希表中,旧值将被覆盖。如果 field 是哈希表中的一个新建域,并且值设置成功,返回 1 。如果哈希表中域 field 已经存在且旧值已被新值覆盖,返回 0 。如果hset一次性设置多个键值对,返回的是新增的field的个数。
- hget
- 格式:hget key field
- 功能:返回哈希表 key 中给定域 field 的值(只能得到一个field的值,不能返回多个field的值)。
- 说明:当给定域不存在或是给定 key 不存在时,返回 nil 。
- hmset
- 格式:hmset key field value [field value …]
- 功能:同时将多个 field-value (域-值)对设置到哈希表 key 中。
- 说明:此命令会覆盖哈希表中已存在的域。如果 key 不存在,一个空哈希表被创建并执行 hmset 操作。如果命令执行成功,返回 OK 。当 key 不是哈希表(hash)类型时,返回一个错误。
注意:在官方文档中关于hmset的描述如下:As per Redis 4.0.0, HMSET is considered deprecated. Please use HSET in new code.
(根据Redis 4.0.0,HMSET被视为已弃用。请在新代码中使用HSET。)
- hmget
- 格式:hmget key field [field …]
- 功能:按照给出顺序返回哈希表 key 中一个或多个域的值。
- 说明:如果给定的域不存在于哈希表,那么返回一个 nil 值。因为不存在的 key 被当作一个空哈希表来处理,所以对一个不存在的 key 进行 hmget 操作将返回一个只带有 nil 值的表。
- hsetnx
- 格式:hsetnx key field value
- 功能:将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在。(nx 表示的意思:不存在,就设置)
- 说明:若域 field 已经存在,该操作无效。如果 key 不存在,一个新哈希表被创建并执行 hsetnx 命令,且hsetnx一次只能设置一个不存在的键值对。
- hdel
- 格式:hdel key field [field …]
- 功能:删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。
- 说明:返回被成功移除的域的数量,不包括被忽略的域。
- hexists
- 格式:hexists key field
- 功能:查看哈希表 key 中给定域 field 是否存在。
- 说明:如果哈希表含有给定域,返回 1 。如果不含有给定域,或 key 不存在,返回 0 。
- hincrby 与 hincrbyfloat
- 格式:[ hincrby / hincrbyfloat ] key field increment
- 功能:为哈希表 key 中的域 field 的值加上增量 increment 。hincrby 命令只能增加整数值,而 hincrbyfloat 可以增加小数值。
- 说明:增量也可以为负数,相当于对给定域进行减法操作。如果 key 不存在,一个新的哈希表被创建并执行 hincrby 命令。如果域 field 不存在,那么在执行命令前,域的值被初始化为 0。对一个储存字符串值的域 field 执行 HINCRBY 命令将造成一个错误。
- hkeys 与 hvals
- 格式:[ hkeys / hvals ] key
- 功能:返回哈希表 key 中的所有域/值。
- 说明:当 key 不存在时,返回一个空表。
- hgetall
- 格式:hgetall key
- 功能:返回哈希表 key 中所有的域和值。
- 说明:在返回值里,紧跟每个域名(field name)之后是域的值(value),所以返回值的长度是哈希表大小的两倍。若 key 不存在,返回空列表。若 key 中包含大量元素,则该命令可能会阻塞 Redis 服务。所以生产环境中一般不使用该命令,而使用 hscan 命令代替。
- hlen
- 格式:hlen key
- 功能:返回哈希表 key 中域的数量。
- 说明:当 key 不存在时,返回 0 。
- hstrlen
- 格式:hstrlen key field
- 功能:返回哈希表 key 中, 与给定域 field 相关联的值的字符串长度(string length)。
- 说明:如果给定的键或者域不存在, 那么命令返回 0 。
散列与字符串
散列键的优点
从上图可以看出,为了存储4个数据项,程序须要使用4个字符串键或者一个散列键来存储。依此推算,如果须要存储100万篇文章,那么使用散列键的情况下,程序只须要在数据库中创建100万个散列键就可以了;如果使用字符串键,那么程序须要在数据库里面创建400万个字符串键。
数据库键数量增多带来的问题主要和资源有关:
-
为了对数据库以及数据库键的使用情况进行统计,Redis会为每个数据库键存储一些额外的信息,并因此带来一些额外的内存消耗,对于单个数据键件来说,这些额外的内存消耗几乎可以忽略不计,但是当数据库键的数量达到上百万,上千万甚至更多的时候,这些额外的内存消耗就会变得比较可观。
-
当散列包含的字段数量比较少的时候,Redis就会使用特殊的内存优化结构去存储散列中的字段和值与字符串键相比,这种内存优化结构,存储相同数据所需的内存要少得多。使用内存优化结构的散列越多,内存优化结构的效果也就越明显。在一定条件下对于相同的数据使用散列键进行存储,比使用字符串键存储要节约一半以上的内存,有时候甚至会更多。
-
除了需要消耗更多内存之外,更多的数据库键也需要占用更多的CPU,每当Redis需要对数据库中的键进行处理时,数据库包含的键越多,进行处理所需的CPU资源就会越多,处理所耗费的时间也会越长,典型的情况包括:
- 统计数据库和数据库键的使用情况;
- 对数据库执行持久化操作,或者根据持久化文件还原数据库;
- 通过模式匹配,在数据库中查找某个键或者执行类似的查找操作。
这些操作的执行时间都会受到数据库键数量的影响。
最后,除了资源方面的优势之外,散列键还可以有效地组织起相关的多项数据,让程序产生更容易理解的数据,使得针对数据的批量操作变得更方便。
字符串键的优点
虽然使用散列键可以有效的节约资源并更好的组织数据,但是字符串键也有自己的优点
-
虽然散列键命令和字符串键命令在部分功能上有重合的地方,但是字符串界命令提供的操作比散列键面料更为丰富。比如字符串能够使用setrange命令和getrange命令设置或者读取字符串值得其中一部分,或者使用命令append将新内容追加到字符串值的末尾,而散列键并不支持这些操作。
-
对于键值对过期操作,字符串键可以在指定时间到达时自动删除指定的键。因为键过期功能针对的是整个键用户,无法为散列中的不同字段设置不同的过期时间,所以当一个散列键过期的时候,它包含的所有字段和值都将被删除。相反如果用户使用字符串键存储信息就不会遇到这样的问题,用户可以为每个字符串键分别设置不同的过期时间,让他们根据实际的需要自动被删除。
字符串和散列的选择
比较的范畴 | 结果 |
---|---|
资源占用 | 字符串键在数量较多的情况下,将占用大量的内存和CPU时间。相反,将多个数据项存储到同一个散列中可以有效地减少内存和CPU消耗 |
支持的操作 | 散列键支持的所有命令,几乎都有相应的字符串键版本,但字符串键支持的setrange、getrange等操作散列键不具备 |
过期时间 | 字符串键可以为每个键单独设置过期时间,独立删除某个数据项;而散列键一旦到期,它包含的所有字段和值都会被删除 |
既然字符串键和散列键各有优点,那么我们在构建应用程序的时候,什么时候应该使用字符串键?什么时候应该使用散列键呢?对于这个问题以下总结了一些选择的条件和方法。
- 如果程序需要为每个数据项单独设置过期时间,那么使用字符串键。
- 如果程序需要对数据线执行诸如setrange、getrange或者append等操作,那么优先考虑使用字符串键。当然用户也可以选择把数据存储在散列中,然后将类似的操作交给客户端执行。
- 如果程序需要存储的数据项比较多,并且你希望尽可能的减少存储数据所需的内存,就应该优先考虑使用散列键。
- 如果多个数据线在逻辑上属于同一组或者同一类,那么应该优先考虑使用散列键。