Hash详解

[toc]

Hash的特点和ZSet比较接近:

  • 键值对存储
  • 根据键获得值
  • 键唯一

区别

  • ZSet的值score必须是数字,因为要用score进行排序
  • ZSet要排序,而Hash不需要

因此,Hash底层只需要去掉ZSet中负责排序的SkipList即可。

以下基于Redis 6:

Hash结构默认采用ZipList编码,以节约内存。同ZSet,ZipList的两个相邻Entry分别保存field和value

image-20240922215227979

同样,当数据量较大时,会转为HT编码,也就是Dict

  • ZipList元素数量超过了hash-max-ziplist-entries(默认值:512)
  • ZipList中任意Entry大小超过hash-max-ziplist-value(默认值:64字节)

image-20240922220350412

源码分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// t_hash.c
void hsetCommand(client *c) { // 执行hset命令的函数
int i, created = 0;
robj *o;

if ((c->argc % 2) == 1) { // 检查参数个数是否正确
addReplyErrorFormat(c,"wrong number of arguments for '%s' command",c->cmd->name);
return;
}
// 判断hash的key是否存在,如果不存在创建一个新的,默认用ZipList
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; // 该函数见下一段代码
hashTypeTryConversion(o,c->argv,2,c->argc-1); // 判断是否需要转为HT编码,代码见最后一段

for (i = 2; i < c->argc; i += 2) // 循环遍历每一个键值对,进行set操作
// HashTypeSet会检查ZipList中元素数目是否达到上限
created += !hashTypeSet(o,c->argv[i]->ptr,c->argv[i+1]->ptr,HASH_SET_COPY);
// 后面省略 ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// t_hash.c
robj *hashTypeLookupWriteOrCreate(client *c, robj *key) {
robj *o = lookupKeyWrite(c->db,key); // 查找hash的key是否存在
if (o == NULL) { // key不存在,创建一个ZipList
o = createHashObject(); // 该函数见下一段代码
dbAdd(c->db,key,o);
} else { // key存在
if (o->type != OBJ_HASH) {
addReply(c,shared.wrongtypeerr);
return NULL;
}
}
return o;
}
1
2
3
4
5
6
7
// object.c
robj *createHashObject(void) {
unsigned char *zl = ziplistNew(); // 申请了一个ZipList
robj *o = createObject(OBJ_HASH, zl);
o->encoding = OBJ_ENCODING_ZIPLIST; // 设置编码为ZipList
return o;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// t_hash.c 
void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
int i;
size_t sum = 0;

if (o->encoding != OBJ_ENCODING_ZIPLIST) return; // 原本不是ZipList编码,直接返回
// 遍历key,value
for (i = start; i <= end; i++) {
if (!sdsEncodedObject(argv[i]))
continue;
size_t len = sdslen(argv[i]->ptr);
if (len > server.hash_max_ziplist_value) { // 单个Entry大小达到上限,转为HT编码
hashTypeConvert(o, OBJ_ENCODING_HT); // 检查数目是否到达上限,是在逐个插入的hashTypeSet函数中
return;
}
sum += len;
}
if (!ziplistSafeToAdd(o->ptr, sum)) // 如果ZipList总大小过大(默认1G),也转为HT编码
hashTypeConvert(o, OBJ_ENCODING_HT);
}