hash类型,又叫作散列类型,它类似hashmap,通过一定的hash算法得到对应的索引位置,然后将数据保存在该索引所在的地方。本章讲述的东西,重点不在于应用场景,因为hash能做的事情,string也都能做。所以本章分享的是,试图揣测redis官方推出hash的意义,以及实现原理。
api
针对字符串的操作
命令 | 说明 |
---|---|
HSET key fieId value | 存储一个散列键 |
HMSETNX key fieId valuefieId value ... | 批量一个key的多个fieId |
HSETNX key fieId value | 存入一个不存在的散列键 |
HGET key fieId | 获取key的一个键的值 |
HMGET key fieIdfieId ... | 批量获取key的值 |
HDEL key fieId | 删除一个散列键 |
针对数字的操作
命令 | 说明 |
---|---|
HINCRBY key fieId increment | 对key散列中的fieId进行{increment}的增加 |
应用场景
缓存
像string类型一样,它的第一个应用场景作为缓存,我们考虑一下该表存在redis中怎么存储合适。
代码语言:txt复制-- 约定key生成规则为
{table} {id}::name {id}::age
-- 写入缓存
HMSET user 1::name ally 1::age 18
-- 读取缓存
HMGET user 1::name 1::age
hash键意义何在?
看完hash键的api,我们看到hash类型存在的命令,string类型都提供了,就连上面举例的缓存的应用场景,string类型也可以实现,那么这个时候hash类型的意义存在哪里?
- hash键可以将信息凝聚在一起,而不是直接分散的存储在整个redis中,方便管理数据,还可以避免一定的误操作
- 避免键名冲突
- 减少内存/cpu的消耗
这里仅解释第三点,第三点怎么理解,当你给key设置一个过期时间的时候,redis会对所有的key进行扫描它有没有过期,而这种机制只会对hash键的key进行扫描,它的fieId层是不会被扫描的,所以减少的消耗。当你有一批key它们的过期时间一致,你使用string类型,他会扫描所有的key,而使用hash类型,redis只需扫描hash的第一层。
哪些情况不适用hash?
- 需要使用过期功能,过期功能只能使用在key上
- 二进制操作命令不适用hash,如:SETBIT,GETBIT,BITOP
- 需要考虑数据量分布问题
这里仅解释第三点,第三点怎么理解,需要先了解redis集群存储方式(预分配hash槽),redis会将集群分为16384个槽,16384不会根据集群的数量而改变。然后将16384个槽平均分配给每一个redis节点去管理。如图所示
当一个key需要存储时,会对key进行取模计算,得到一个槽的位置,假如这个槽由redis的节点1管理,那么这个key的数据就会真正落在节点1的物理内存上。
这个时候来解释第三点的原因,假如我们使用hash类型,缓存的key约定为以上举例的情况,那么user表的所有数据都会落在某一个节点上,假如这个表的数据有一千万,那么一千万都落在一个节点上,其他的节点上没有数据,这肯定是不合适的。这个节点内存是会撑爆的。
那么回过头来,为什么redis要采用预分配hash槽作为集群管理的方案?
主要是为了解决快速扩缩容的问题。假如我现在新加入一个节点,根据预分配hash槽的方案,该新加入的节点管理的槽为16384,16284...那么redis要做的操作只需将这些槽的数据迁移到新节点上就行了,不用像hashmap那样rehash的操作。