1、前言
欢迎大家来到通俗易懂Redis,先从redis的基础数据结构说起,给大家简单的进行剖析。Redis有5种基础数据类型,分别为:string (字符串)、list (列表)、set (集合)、hash (哈希) 和 zset (有序集合)。Redis 底层的数据结构一共有6种,如下图第二排部分,它和数据类型对应关系也如下图:
今天主要给大家分享我对Hash数据类型的一点见解,有错误的地方请大家指正。
2、Hash介绍
Redis的hash结构类似于Java语言里面的HashMap,它是无序字典。内部实现结构上同Java的HashMap一样,同样的数组 + 链表二维结构。第一维hash的数组位置碰撞时,就会将碰撞的元素使用链表串接起来。
Hash 类型的底层数据结构是由压缩列表或哈希表实现的:
- 如果哈希类型元素个数小于
512个,所有值小于64字节的话,Redis会使用压缩列表作为Hash类型的底层数据结构; - 如果哈希类型元素不满足上面条件,Redis 会使用哈希表作为Hash类型的底层数据结构。
3、Hash的基本使用
3.1、存储/获取 hash
//存储一个哈希表key=animal1的键值 (格式:HSET key field value [field value ...] )
127.0.0.1:6379> HSET animal1 name cat
(integer) 1
127.0.0.1:6379> HSET animal1 name cat age 5
(integer) 1
// 获取哈希表 key 对应的 field 键值 (格式:HGET key field)
127.0.0.1:6379> HGET animal1 name
"cat"
//返回哈希表 key 中所有的键值 (格式:HGETALL key)
127.0.0.1:6379> HGETALL animal1
"name"
"cat"
"age"
"5"
//在一个哈希表 key 中存储多个键值对 (格式:HMSET key field value [field value...])
127.0.0.1:6379> HMSET animal2 name dog age 3
OK
//批量获取哈希表 key 中多个 field 键值 (格式:HMGET key field [field ...])
127.0.0.1:6379> HMGET animal2 name age
1) "dog"
2) "3"
3.2、删除/获取长度
//删除哈希表 key 中的 field 键值 (格式:HDEL key field [field ...])
127.0.0.1:6379> HDEL animal1 name
(integer) 1
127.0.0.1:6379> HDEL animal2 name age
(integer) 2
//返回哈希表 key 中 field 的数量 (格式:HLEN key )
127.0.0.1:6379> HLEN animal1
(integer) 1
127.0.0.1:6379> HLEN animal2
(integer) 0
3.3、增量/判断操作
//为哈希表key中field键的值加上增量increment(格式:HINCRBY key field increment)
127.0.0.1:6379> HINCRBY animal1 age 1
(integer) 6
//判断哈希表中指定的field是否存在(格式:HEXISTS key field)
127.0.0.1:6379> HEXISTS animal1 age
(integer) 1
127.0.0.1:6379> HEXISTS animal1 type
(integer) 0
3.4、获取哈希表中的所有field/value
//获取哈希表中的所有field(格式:HKEYS key)
127.0.0.1:6379> HKEYS animal1
1) "name"
2) "age"
127.0.0.1:6379> HKEYS animal2
(empty array)
//获取哈希表中的所有value(格式:HVALS key )
127.0.0.1:6379> HVALS animal1
1) "cat"
2) "5"
127.0.0.1:6379> HVALS animal2
(empty array)
4、Hash原理
上面提到了Hash类型的底层数据结构是由压缩列表或哈希表实现的,对于压缩列表,在介绍list数据类型的时候已经详细的介绍过了,大家可以看一下这篇文章的压缩列表部分通俗易懂Redis - list数据类型详解。本篇主要介绍一下哈希表的原理。
哈希表是一种保存键值对(key-value)的数据结构。哈希表中的每一个key都是独一无二的,程序可以根据key查找到与之关联的value,或者通过key来更新value,又或者根据key来删除整个key-value等等。
4.1、Hash优缺点
Hash表优点是能以 O(1) 的复杂度快速查询数据。主要是通过Hash函数的计算,就能定位数据在表中的位置,紧接着可以对数据进行操作,这就使得数据操作非常快。
Hash表缺点也比较明显,在哈希表大小固定的情况下,随着数据不断增多,那么哈希冲突的可能性也会越高。解决哈希冲突的方式有很多种,Redis 采用了链式哈希。链式哈希也有缺点,随着链表长度的增加,在查询这一位置上的数据的耗时就会增加,因为定位到具体的数据上是遍历链表时间复杂度是O(n)。如下图所示:
a、b、c、d、e 5个值,其中a和b经过哈希计算后,都落在同一个哈希桶1上,链式哈希的话,a就会通过next指针指向 b,形成一个单向链表。其中c、d和e经过哈希计算后,都落在另一个哈希桶4上,链式哈希的话,c通过next指针指向d,d通过next指针指向e,同样形成另一个单向链表。
上面说到了链式hash的一些缺陷,为了解决这个问题,需要进行rehash,就是对哈希表的大小进行扩展。Redis的rehash策略和Java中的Hashmap有些不同,因为在字典很大时,rehash是个耗时的操作,需要一次性全部rehash。redis为了高性能,不能堵塞服务,它采用了渐进式rehash策略。
4.2、redis中的rehash
对于redis中每个hash结构,redis都会使用两个全局哈希表进行维护,一个存放数据,一个为rehash做准备。其实整个rehash操作可以分为下面几步(假设现在又hash表1和hash表2,表1用来存储数据,表2用来准备rehash):
- 插入的数据,都会写入到哈希表1,随着数据逐步增多,触发了 rehash 操作
- 给哈希表 2分配空间,一般会比哈希表 1大2倍
- 将哈希表1的数据根据hash表2的长度再次进行hash,把数据迁移到哈希表2中
- 迁移完成后,哈希表1的空间会被释放,并把哈希表2设置为哈希表1,然后再创建一个空白的哈希表,为下次rehash 做准备。
上面也提到了,如果数据量过大的话,将哈希表1的数据根据hash表2的长度再次进行hash,把数据迁移到哈希表2中,这个过程是非常消耗时间的。Redis为了不影响性能,采用了渐进式rehash,也就是将数据的迁移的工作分多次迁移。
4.3、rehash 触发条件
rehash的触发条件跟*负载因子(load factor)** 有关系,负载因子=哈希表中保存的节点数量/哈希表的大小。
- 当负载因子大于等于1,并且Redis没有执行RDB快照或者没有进行 AOF重写的时候,就会进行rehash操作。
- 当负载因子大于等于5时,此时说明哈希冲突非常严重了,不管有没有有在执行RDB快照或AOF重写,都会强制进行rehash操作。
负载因子可以通过下面这个公式计算:
4.4、redis中的渐进式rehash
渐进式rehash会在rehash的同时,保留新旧两个hash结构,查询时会同时查询两个hash结构,然后在后续的定时任务中以及hash操作指令中,循序渐进地将旧hash的内容一点点迁移到新的hash结构中。渐进式 rehash 进行期间,哈希表元素的删除、查找、更新等操作都会在这两个哈希表进行。
比如,查找一个key的值的话,先会在哈希表1里面进行查找,如果没找到,就会继续到哈希表2里面进行找到。
当搬迁完成了,就会使用新的hash结构取而代之。当hash移除了最后一个元素之后,该数据结构自动被删除,内存被回收。这样就巧妙地把一次性大量数据迁移工作的开销,分摊到了多次处理请求的过程中,避免了一次性rehash的耗时操作。
以上就是今天全部的内容了,主要目的是让大家了解redis的hash基本数据类型,后续有新的知识点会继续补充!!!感谢大家!!!