Redis基础篇–hash
hash
内部实现: 类似于JAVA的HashMap。也是数组+链表的二维结构。rehash不同,是渐进式rehash策略。
常见命令:
hset books java "think in java" #设置key为books,field为java,value为"think in java"
hgetall books #entries(),key和value间隔出现
hlen books# 获取map的长度
hmset books java "effective java" python "learning python"#批量set
hset user age 18#设置age为18
hincrby user age 1 #执行自增操作
探索字典内部
字典是Redis服务器中出现最为频繁的复合型数据结构,除了hash结构的数据会用到字典(dict)外,整个redis数据库的所有key和value也组成了一个全局字典,还有带过期时间的key集合也是一个字典。zset集合中存储value和score值的映射关系也是通过字典结构实现的。
struct RedisDb {
dict * dict; /* all keys key=>value */
dict * expires; /* all expired keys key=>long(timestamp) */
...
}
struct zset {
dict *dict; /* all values value=>score */
zskiplist *zsl;
}
内部实现
struct dict {
...
dictht ht[2];
}
struct dictEntry {
void * key;
void * val;
dictEntry * next; /* 链接下一个entry */
}
struct dictht {
dictEntry ** table; /* 二维 */
long size; /* 第一维数组的长度 */
long used; /* hash表中的元素个数 */
...
}
如上图所示,字典结构内部包含两个hashtable,通常情况下只有一个hashtable是有值的,但是在字典扩缩容时,需要分配新的hashtable,然后进行渐进式搬迁,这时候两个hashtable存储的分别是旧的hashtable和新的hashtable。待搬迁结束后,旧的hashtable被删除,新的hashtable取而代之。
hashtable的结构与Java的HashMap几乎是一样的,都是通过分桶的方式解决hash冲突。第一维是数组,第二维是链表。如上图所示。
hash函数
Redis的字典默认的hash函数是siphash.
hash攻击
如果hash函数存在偏向性,黑客就可能利用这种偏向性对服务器进行攻击。存在偏向性的hash函数在特定模式下的输入会导致hash第二维链表长度极为不均匀,甚至所有的元素都集中到个别链表中,直接导致查询效率急剧下降,从O(1)退化到O(n),有限的服务器计算能力将会被hashtable的查找效率彻底拖垮。这就是所谓的hash攻击。
扩容条件
正常情况下,当hash表中的元素的个数等于第一维数组的长度时,就会开始扩容,扩容的新数组是原数组大小的2倍。不过如果redis正在做bgsave,为了减少内存页的过多分离(Copy On Write),Redis尽量不去扩容(dict_can_resize),但是如果hash表已经非常满了,元素的个数已经达到了第一维数组长度的5倍(dict_force_resize_ratio),说明hash表已经过于拥挤了,这个时候就会强制扩容。
缩容条件
当hash表因为元素逐渐被删除变得越来越稀疏时,Redis就会hash表进行缩容来减少hash表的第一维数组占用空间。缩容的条件是元素个数低于数组长度的10%。缩容不会考虑redis是否正在做bgsave。