简介
字典是redis最常用的数据结构,是用来保存键值对的一种特殊性数据结构,与java中的Map结构类似,字典在redis被使用的场景非常多,例如全局的key和value就存在字典中、有过期时间的key也组成了一个字典以及zset集合中的value和score也是通过字典去实现的。
数据结构
redis字典底层使用哈希表实现的,在源码中可查看dict.h文件,该文件定义了字典所需要的数据结构,其中dict为字典、dictht为哈希表、dictEntry为哈希节点,具体数据结构如下图所示。
- dict字典,该字典数据结构里面有两个特殊的数据需要特别注意下,一个是dictht[2],定义两个哈希表主要是为了哈希扩容时候进行渐进式扩容,通常情况下只有一个哈希表存在值。另外一个是rehashidx,该值记录了当前rehash的进度,未进行rehash的时候为-1。
- dictht哈希表,该数据结构没什么特殊的,哈希表数组指针、哈希表大小size、sizemask、已使用节点数量used,这里的sizemask是size-1,主要是为了计算新进来一个key值的下标的,这里算是一个小小的性能优化点,以免计算key所在的哈希数组下标时都需要进行size-1的计算。
- dictEntry哈希节点,key、value值就是存在这个里面的,然后还有下一个节点的指针,这个主要是因为哈希算法存在撞桶的可能性,一旦撞桶就会存在多个哈希节点挂在在同一个桶下标上,这个时候就会由一个节点变为链表形式。
扩容与缩容策略
127.0.0.1:6379> INFO memory
# Memory
used_memory:1025248
......(添加数据)
127.0.0.1:6379> INFO memory
# Memory
used_memory:1025264
...... (添加数据)
127.0.0.1:6379> INFO memory
# Memory
used_memory:1025296
上面是我自己实战的截图,第一个memory节点数量小于4,第二个memory节点超过4小于8,第三个memory节点超过8小于16,由结果可以看出哈希表是成倍增长的,每一次都会扩充为之前size的两倍。
哈希表的扩容与缩容条件:
- 如果redis当前没有进行bgsave的情况下,扩容的条件是当前哈希表元素大于等于一维数组的数量时候就会进行扩容,扩容为旧哈希表大小的两倍。
- 如果redis当前正在进行bgsave,只有当前哈希表元素超过一维数组数量的五倍的时候才会进行扩容,扩容大小为旧哈希表的两倍。
- 缩容不受bgsave影响,只要当前哈希表元素个数小于一位数组的10%的时候就会进行缩容。
注意:如果哈希表正处于扩容中,该哈希表的删除、更新、查询都会在两个哈希表中进行,先在第一个表中查询,如果存在进行操作,如果不存在去第二个表进行查询并操作。新增操作只会在第二个表中进行,这样是为了保证表1的数据量只增不减,减缓扩容的工作量。
hash攻击
所为的hash攻击就是指如果hash函数存在某种偏向性,黑客利用这种偏向性去对服务器进行攻击,在这种场景下redis的哈希表就会存在超长的二维链表,这对于查询来说是致命的,如果查询频率稍微高一点redis的性能将会被拖垮,以至于不能对其他服务请求不能正常的响应。
参考文献:
1.Redis 深度历险:核心原理与应用实践