哈希
- 几乎所有的编程语言都提供了哈希(hash)类型,它们的叫法可能是哈希、字
典、关联数组。在Redis中,哈希类型是指键值本身又是一个键值对结构,形如
value={{field,value},...{fieldN,valueN}}。
-
命令
(1)设置值
hset key field value
下面为user:1添加一堆field-value:
127.0.0.1:6379> hset user:1 name tom (integer) 1
如果设置成功会返回1,反之会返回0。此外Redis提供了hsetnx命令,它们的
关系就像set和setnx命令一样,只不过作用域由键变为field。(2)获取值
hget key field
例如,下面操作获取user:1的那么域(属性)对应的值:
127.0.0.1:6379> hget user:1 name "tom"
如果键或field不存在,会返回null
127.0.0.1:6379> hget user:2 name (nil) 127.0.0.1:6379> hget user:1 age (nil)
(3)删除field
hdel key field [field...]
hdel会删除一个或多个field,返回结果为成功删除field的个数,例如:
127.0.0.1:6379> hdel user:1 name (integer) 1 127.0.0.1:6379> hdel user:1 age (integer) 0
(4)计算field个数
hlen key
例如user:1有3个field
127.0.0.1:6379> hset user:1 name tom (integer) 1 127.0.0.1:6379> hset user:1 age 23 (integer) 1 127.0.0.1:6379> hset user:1 city tianjin (integer) 1 127.0.0.1:6379> hlen user:1 (integer) 3
(5)批量设置或获取field-value
hmget key field [field ...] hmset key field value [field value ...]
hmset和hmget分别是批量设置和获取field-value,hmset需要的参数是key和
多对field-value,hmget需要的参数是key和多个field。例如:127.0.0.1:6379> hmset user:1 name mike age 12 city tianjin OK 127.0.0.1:6379> hmget user:1 name city 1) "mike" 2) "tianjin"
(6)判断field是否存在
hexists key filed
例如,user:1包含name域,所以返回结果为1,不包含时返回0:
127.0.0.1:6379> hexists user:1 name (integer) 1
(7)获取所有filed
hkeys key
hkeys命令应该叫hfields更为恰当,他返回制定哈希键所有的field,例如:
127.0.0.1:6379> hkeys user:1 1) "name" 2) "age" 3) "city"
(8)获取所有value
hvals key
下面操作获取user:1全部value:
127.0.0.1:6379> hvals user:1 1) "mike" 2) "12" 3) "tianjin"
(9)获取所有的field-value
hgetall key
下面操作获取user:1所有的field-value:
127.0.0.1:6379> hgetall user:1 1) "name" 2) "mike" 3) "age" 4) "12" 5) "city" 6) "tianjin"
(10)hinerby hincybyfloat
hincrby key field
hincrbyfloat key field
hincrby和hincrbyfloat,就像incrbyhy和incrbyfloat命令一样,但是它们
的作用域是field。(11)计算value的字符串长度
hstrlen key field
例如hget user:1 name的value是tom,那么hstrlen的返回结果是3:
127.0.0.1:6379> hstrlen user:1 name (integer) 3
下表是哈希类型命令的时间复杂度
命令 时间复杂度 hset key field value O(1) hget key field O(1) hdel key field [field ...] O(k),k是field的个数 hlen key O(1) hgetall key O(n),n是field的总数 hmget field [field ...] O(k),k是field的个数 hmset field value [field value ...] O(k),k是field的个数 hexists key field O(1) hkeys key O(n),n是field的总数 hvals key O(n),n是field的总数 hstenx key field value O(1) hincrby key field increment O(1) hincrbyfloat key filed increment O(1) hstrlen key field O(1)
-
内部编码
哈希类型的编码有两种:
ziplist(压缩列表):当哈希类型元素个数小于
hash-max-ziplist-entries配置(默认512个)、同时所有值都小于
hash-max-ziplist-value配置(默认64字节)是,Redis会使用ziplist作为
哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,所以
在节省内存方面比hashtable更加优秀。hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis会使
用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而
hashtable的读写时间复杂度是O(1)。
下面的实例演示了哈希类型的内部编码,以及相应的变化。
1)当field个数比较少且没有大的value时,内部编码为ziplist:
127.0.0.1:6379> hmset hashkey f1 v1 f2 v2 OK 127.0.0.1:6379> objet encoding hashkey "ziplist"
2.1)当有value大于64字节,内部编码会由ziplist变为hashtable:
127.0.0.1:6379> hset hashkey f3 "one string is bigger than 64 byte ...忽略..." OK 127.0.0.1:6379> object encoding hashkey "hashtable"
2.2)当field个数超过512,内部编码也会有ziplist变为hashtable:
127.0.0.1:6379> hmset hashkey f1 v1 f2 v2 f3 v3 ...忽略... f513 v513 OK 127.0.0.1:6379> object encoding hashkey "hashtable"
-
使用场景
下表为关系型数据表记录的两条用户信息,用户的属性作为表的列,每条用户信
息作为行id name age city 1 tom 23 beijing 2 mike 30 tianjin 如果将其用哈希类型存储,如下表
user:1
field value id 1 name tom age 23 city beijing user:2
field value id 2 name mike age 30 city tianjing 相比于使用字符串序列化缓存用户信息,哈希类型变得更加直观,并且在更新操
作上会更加便捷。可以将每个用户的id定义为键后缀,多对field-value对应每
个用户的属性,类似如下伪代码:UserInfo getUserInfo(long id){ //用户id作为key后缀 userRedisKey = "user:info" + id; //使用hgetall获取所有用户信息映射关系 userInfoMap = redis.hgetAll(userRedisKey); UserInfo userInfo; if(userInfoMap != null){ //将映射关系转换为UserInfo userInfo = transferMapToUserInfo(userInfoMap); }else{ //从MySQL中获取用户信息 userInfo = mysql.get(id); //将userInfo变为映射关系使用hmset保存到Redis中 redis.hmset(userRedisKey, transferUserInfoToMap(userInfo)); //添加过期时间 redis.expire(userRedisKey, 3600); } return usreInfo; }
但是需要注意的是哈希类型和关系型数据库有两点不同之处:
- 哈希类型是稀疏的,而关系型数据库是完全结构化的,例如哈希类型每个键可
以有不同的field,而关系型数据库一旦添加新的列,所有行都要为其设置值
(即使为NULL),如下表:
id name favor city age gender 1 tom sprot beijing NULL NULL 2 mike NULL NULL 30 male user:1
field value id 1 name tom favor sport city beijing user:2
field value id 2 name mike age 30 gender male 开发人员需要将两者的特点搞清楚,才能在适合的场景使用适合的技术。到目前
为止,我们已经能够用三种方法缓存用户信息,下面给出三种方案的实现方法和
优缺点分析。1)原声字符串类型:每个属性一个键。
set user:1:name tom set user:1:age 23 set user:1:city beijing
优点:简单直观,每个属性都支持更新操作。
缺点:占用过多的键,内存占用量较大,同时用户信息内聚性比较差,所以此种
方案一般不会在生产环境使用。2)序列化字符串类型:将用户信息序列化后用一个键保存。
set user:1 serialize(usreInfo)
优点:简化编程,如果合理的使用序列化可以提高内存的使用效率。
缺点:序列化和反序列化有一定的开销,同时每次更新属性都需要把全部数据取
出进行反序列化,更新后在序列化到Redis中。3)哈希类型:每个用户属性使用一堆field-value,但是只用一个键保存。
hmset user:1 name tom age 23 city beijing
优点:简单直观,如果使用合理可以减少内存空间的使用。
缺点:要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable会消耗更多内存。 - 哈希类型是稀疏的,而关系型数据库是完全结构化的,例如哈希类型每个键可