Redis的t_hash.c
是对hash
字典数据结构的实现。
主要是基于ziplist
、dict
、sds
实现。
当元素较少的时候,使用ziplist
来实现字典数据结构。
当元素较多的时候,使用dict
来实现字典数据结构。
每次添加元素的时候,都会去检查是否需要转换类型。
在redis.conf中存在如下配置:
# Hashes are encoded using a memory efficient data structure when they have a
# small number of entries, and the biggest entry does not exceed a given
# threshold. These thresholds can be configured using the following directives.
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
在t_hash.c
存在如下转换ziplist和dict的代码:
/* Check the length of a number of objects to see if we need to convert a
* ziplist to a real hash. Note that we only check string encoded objects
* as their string length can be queried in constant time. */
//当元素较少的时候,用ziplist存储,数量较多的时候转为Hash存储。
void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
int i;
if (o->encoding != OBJ_ENCODING_ZIPLIST) return;
//达到hash_max_ziplist_value的时候,会转为dict
for (i = start; i <= end; i++) {
if (sdsEncodedObject(argv[i]) &&
sdslen(argv[i]->ptr) > server.hash_max_ziplist_value)
{
hashTypeConvert(o, OBJ_ENCODING_HT);
break;
}
}
}
1. 新增元素
//hset命令
void hsetCommand(client *c) {
int i, created = 0;
robj *o;
if ((c->argc % 2) == 1) {
addReplyError(c,"wrong number of arguments for HMSET");
return;
}
//根据key查找
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
//尝试转换类型ziplist -> dict
hashTypeTryConversion(o,c->argv,2,c->argc-1);
for (i = 2; i < c->argc; i += 2)
created += !hashTypeSet(o,c->argv[i]->ptr,c->argv[i+1]->ptr,HASH_SET_COPY);
/* HMSET (deprecated) and HSET return value is different. */
char *cmdname = c->argv[0]->ptr;
if (cmdname[1] == 's' || cmdname[1] == 'S') {
/* HSET */
addReplyLongLong(c, created);
} else {
/* HMSET */
addReply(c, shared.ok);
}
//事务 watch key
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
server.dirty++;
}
//hsetnx命令
void hsetnxCommand(client *c) {
robj *o;
//根据key查找
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
//类型转换
hashTypeTryConversion(o,c->argv,2,3);
//判断field是否存在
if (hashTypeExists(o, c->argv[2]->ptr)) {
addReply(c, shared.czero);
} else {
//添加
hashTypeSet(o,c->argv[2]->ptr,c->argv[3]->ptr,HASH_SET_COPY);
addReply(c, shared.cone);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
server.dirty++;
}
}
static void addHashFieldToReply(client *c, robj *o, sds field) {
int ret;
//如果为空,返回"$-1\r\n"
if (o == NULL) {
addReply(c, shared.nullbulk);
return;
}
//ziplist
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
unsigned char *vstr = NULL;
unsigned int vlen = UINT_MAX;
long long vll = LLONG_MAX;
//根据field获取
ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll);
if (ret < 0) {
//如果为空,返回"$-1\r\n"
addReply(c, shared.nullbulk);
} else {
if (vstr) {
addReplyBulkCBuffer(c, vstr, vlen);
} else {
addReplyBulkLongLong(c, vll);
}
}
}
//dict
else if (o->encoding == OBJ_ENCODING_HT) {
sds value = hashTypeGetFromHashTable(o, field);
if (value == NULL)
addReply(c, shared.nullbulk);
else
addReplyBulkCBuffer(c, value, sdslen(value));
} else {
serverPanic("Unknown hash encoding");
}
}
2. 删除元素
//hdel命令
void hdelCommand(client *c) {
robj *o;
int j, deleted = 0, keyremoved = 0;
//根据key查找并校验类型
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,OBJ_HASH)) return;
//删除
for (j = 2; j < c->argc; j++) {
if (hashTypeDelete(o,c->argv[j]->ptr)) {
deleted++;
if (hashTypeLength(o) == 0) {
dbDelete(c->db,c->argv[1]);
keyremoved = 1;
break;
}
}
}
if (deleted) {
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_HASH,"hdel",c->argv[1],c->db->id);
if (keyremoved)
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],
c->db->id);
server.dirty += deleted;
}
//响应client
addReplyLongLong(c,deleted);
}
3. 查找元素
//hget命令
void hgetCommand(client *c) {
robj *o;
//根据key查找,并检验类型
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,o,OBJ_HASH)) return;
addHashFieldToReply(c, o, c->argv[2]->ptr);
}
//hmget命令
void hmgetCommand(client *c) {
robj *o;
int i;
/* Don't abort when the key cannot be found. Non-existing keys are empty
* hashes, where HMGET should respond with a series of null bulks. */
//根据key查找
o = lookupKeyRead(c->db, c->argv[1]);
if (o != NULL && o->type != OBJ_HASH) {
addReply(c, shared.wrongtypeerr);
return;
}
addReplyMultiBulkLen(c, c->argc-2);
//返回
for (i = 2; i < c->argc; i++) {
addHashFieldToReply(c, o, c->argv[i]->ptr);
}
}
4. 增加value值
//hincrby命令
void hincrbyCommand(client *c) {
long long value, incr, oldvalue;
robj *o;
sds new;
unsigned char *vstr;
unsigned int vlen;
//增加的值,类型转换,值处理
if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != C_OK) return;
//根据key查找
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
//获取当前value
if (hashTypeGetValue(o,c->argv[2]->ptr,&vstr,&vlen,&value) == C_OK) {
if (vstr) {
if (string2ll((char*)vstr,vlen,&value) == 0) {
addReplyError(c,"hash value is not an integer");
return;
}
} /* Else hashTypeGetValue() already stored it into &value */
} else {
value = 0;
}
oldvalue = value;
//判断是否越域
if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
(incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
addReplyError(c,"increment or decrement would overflow");
return;
}
//赋值
value += incr;
new = sdsfromlonglong(value);
//修改value,HASH_SET_TAKE_VALUE
hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE);
//响应client
addReplyLongLong(c,value);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_HASH,"hincrby",c->argv[1],c->db->id);
server.dirty++;
}
//hincrbyfloat命令
void hincrbyfloatCommand(client *c) {
long double value, incr;
long long ll;
robj *o;
sds new;
unsigned char *vstr;
unsigned int vlen;
//增加的值,类型转换,值处理
if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != C_OK) return;
//根据key查找
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
//获取原来的值
if (hashTypeGetValue(o,c->argv[2]->ptr,&vstr,&vlen,&ll) == C_OK) {
if (vstr) {
if (string2ld((char*)vstr,vlen,&value) == 0) {
addReplyError(c,"hash value is not a float");
return;
}
} else {
value = (long double)ll;
}
} else {
value = 0;
}
//增加
value += incr;
if (isnan(value) || isinf(value)) {
addReplyError(c,"increment would produce NaN or Infinity");
return;
}
char buf[MAX_LONG_DOUBLE_CHARS];
int len = ld2string(buf,sizeof(buf),value,1);
new = sdsnewlen(buf,len);
//修改value
hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE);
addReplyBulkCBuffer(c,buf,len);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_HASH,"hincrbyfloat",c->argv[1],c->db->id);
server.dirty++;
/* Always replicate HINCRBYFLOAT as an HSET command with the final value
* in order to make sure that differences in float pricision or formatting
* will not create differences in replicas or after an AOF restart. */
//新值引用次数增加,老值引用次数减少
robj *aux, *newobj;
aux = createStringObject("HSET",4);
newobj = createRawStringObject(buf,len);
rewriteClientCommandArgument(c,0,aux);
decrRefCount(aux);
rewriteClientCommandArgument(c,3,newobj);
decrRefCount(newobj);
}
5. 长度统计
//hlen命令
void hlenCommand(client *c) {
robj *o;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,OBJ_HASH)) return;
addReplyLongLong(c,hashTypeLength(o));
}
//hstrlen命令
void hstrlenCommand(client *c) {
robj *o;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,OBJ_HASH)) return;
addReplyLongLong(c,hashTypeGetValueLength(o,c->argv[2]->ptr));
}
6. 获取所有
//hkeys命令 OBJ_HASH_KEY
void hkeysCommand(client *c) {
genericHgetallCommand(c,OBJ_HASH_KEY);
}
//hvals命令 OBJ_HASH_VALUE
void hvalsCommand(client *c) {
genericHgetallCommand(c,OBJ_HASH_VALUE);
}
//hgetall命令 OBJ_HASH_KEY|OBJ_HASH_VALUE
void hgetallCommand(client *c) {
genericHgetallCommand(c,OBJ_HASH_KEY|OBJ_HASH_VALUE);
}
void genericHgetallCommand(client *c, int flags) {
robj *o;
hashTypeIterator *hi;
int multiplier = 0;
int length, count = 0;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
|| checkType(c,o,OBJ_HASH)) return;
if (flags & OBJ_HASH_KEY) multiplier++;
if (flags & OBJ_HASH_VALUE) multiplier++;
length = hashTypeLength(o) * multiplier;
addReplyMultiBulkLen(c, length);
hi = hashTypeInitIterator(o);
//遍历dict
while (hashTypeNext(hi) != C_ERR) {
if (flags & OBJ_HASH_KEY) {
addHashIteratorCursorToReply(c, hi, OBJ_HASH_KEY);
count++;
}
if (flags & OBJ_HASH_VALUE) {
addHashIteratorCursorToReply(c, hi, OBJ_HASH_VALUE);
count++;
}
}
hashTypeReleaseIterator(hi);
serverAssert(count == length);
}