Redis之雜湊物件原始碼閱讀
阿新 • • 發佈:2018-11-24
hashTypeTryConversion:對傳入的引數進行檢查是否需要從ziplist轉換成hashtable
void hashTypeTryConversion(robj *o, robj **argv, int start, int end) { int i; // 如果物件不是 ziplist 編碼,那麼直接返回 if (o->encoding != REDIS_ENCODING_ZIPLIST) return; // 檢查所有輸入物件,看它們的字串值是否超過了指定長度 for (i = start; i <= end; i++) { if (sdsEncodedObject(argv[i]) && sdslen(argv[i]->ptr) > server.hash_max_ziplist_value) { // 將物件的編碼轉換成 REDIS_ENCODING_HT hashTypeConvert(o, REDIS_ENCODING_HT); break; } } }
hashTypeTryObjectEncoding:當subject為hashtable編碼時,嘗試將o1,o2進行編碼
void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2) {
if (subject->encoding == REDIS_ENCODING_HT) {
if (o1) *o1 = tryObjectEncoding(*o1);
if (o2) *o2 = tryObjectEncoding(*o2);
}
}
hashTypeGetFromZiplist:從ziplist找到field對應的值
int hashTypeGetFromZiplist(robj *o, robj *field, unsigned char **vstr, unsigned int *vlen, long long *vll) { unsigned char *zl, *fptr = NULL, *vptr = NULL; int ret; // 確保編碼正確 redisAssert(o->encoding == REDIS_ENCODING_ZIPLIST); // 取出未編碼的域 field = getDecodedObject(field); // 遍歷 ziplist ,查詢域的位置 zl = o->ptr; fptr = ziplistIndex(zl, ZIPLIST_HEAD); if (fptr != NULL) { // 定位包含域的節點 fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1); if (fptr != NULL) { /* Grab pointer to the value (fptr points to the field) */ // 域已經找到,取出和它相對應的值的位置 vptr = ziplistNext(zl, fptr); redisAssert(vptr != NULL); } } decrRefCount(field); // 從 ziplist 節點中取出值 if (vptr != NULL) { ret = ziplistGet(vptr, vstr, vlen, vll); redisAssert(ret); return 0; } // 沒找到 return -1; }
hashTypeGetFromHashTable:從hashtable中找到鍵對應的值
int hashTypeGetFromHashTable(robj *o, robj *field, robj **value) {
dictEntry *de;
// 確保編碼正確
redisAssert(o->encoding == REDIS_ENCODING_HT);
// 在字典中查詢域(鍵)
de = dictFind(o->ptr, field);
// 鍵不存在
if (de == NULL) return -1;
// 取出域(鍵)的值
*value = dictGetVal(de);
// 成功找到
return 0;
}
hashTypeGetObject:這是一個多型函式,根據雜湊物件是ziplist還是hashtable編碼採用不同的獲取方式
robj *hashTypeGetObject(robj *o, robj *field) {
robj *value = NULL;
// 從 ziplist 中取出值
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *vstr = NULL;
unsigned int vlen = UINT_MAX;
long long vll = LLONG_MAX;
if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) {
// 建立值物件
if (vstr) {
value = createStringObject((char*)vstr, vlen);
} else {
value = createStringObjectFromLongLong(vll);
}
}
// 從字典中取出值
} else if (o->encoding == REDIS_ENCODING_HT) {
robj *aux;
if (hashTypeGetFromHashTable(o, field, &aux) == 0) {
incrRefCount(aux);
value = aux;
}
} else {
redisPanic("Unknown hash encoding");
}
// 返回值物件,或者 NULL
return value;
}
hashTypeExists:檢查給定的鍵是否在當前雜湊物件中
int hashTypeExists(robj *o, robj *field) {
// 檢查 ziplist
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *vstr = NULL;
unsigned int vlen = UINT_MAX;
long long vll = LLONG_MAX;
if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) return 1;
// 檢查字典
} else if (o->encoding == REDIS_ENCODING_HT) {
robj *aux;
if (hashTypeGetFromHashTable(o, field, &aux) == 0) return 1;
// 未知編碼
} else {
redisPanic("Unknown hash encoding");
}
// 不存在
return 0;
}
hashTypeSet:將指定的鍵值對新增到雜湊物件中
int hashTypeSet(robj *o, robj *field, robj *value) {
int update = 0;
// 新增到 ziplist
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *zl, *fptr, *vptr;
// 解碼成字串或者數字
field = getDecodedObject(field);
value = getDecodedObject(value);
// 遍歷整個 ziplist ,嘗試查詢並更新 field (如果它已經存在的話)
zl = o->ptr;
fptr = ziplistIndex(zl, ZIPLIST_HEAD);
if (fptr != NULL) {
// 定位到域 field
fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1);
if (fptr != NULL) {
/* Grab pointer to the value (fptr points to the field) */
// 定位到域的值
vptr = ziplistNext(zl, fptr);
redisAssert(vptr != NULL);
// 標識這次操作為更新操作
update = 1;
/* Delete value */
// 刪除舊的鍵值對
zl = ziplistDelete(zl, &vptr);
/* Insert new value */
// 新增新的鍵值對
zl = ziplistInsert(zl, vptr, value->ptr, sdslen(value->ptr));
}
}
// 如果這不是更新操作,那麼這就是一個新增操作
if (!update) {
/* Push new field/value pair onto the tail of the ziplist */
// 將新的 field-value 對推入到 ziplist 的末尾
zl = ziplistPush(zl, field->ptr, sdslen(field->ptr), ZIPLIST_TAIL);
zl = ziplistPush(zl, value->ptr, sdslen(value->ptr), ZIPLIST_TAIL);
}
// 更新物件指標
o->ptr = zl;
// 釋放臨時物件
decrRefCount(field);
decrRefCount(value);
/* Check if the ziplist needs to be converted to a hash table */
// 檢查在新增操作完成之後,是否需要將 ZIPLIST 編碼轉換成 HT 編碼
if (hashTypeLength(o) > server.hash_max_ziplist_entries)
hashTypeConvert(o, REDIS_ENCODING_HT);
// 新增到字典
} else if (o->encoding == REDIS_ENCODING_HT) {
// 新增或替換鍵值對到字典
// 新增返回 1 ,替換返回 0
if (dictReplace(o->ptr, field, value)) { /* Insert */
incrRefCount(field);
} else { /* Update */
update = 1;
}
incrRefCount(value);
} else {
redisPanic("Unknown hash encoding");
}
// 更新/新增指示變數
return update;
}
hashTypeDelete:從雜湊物件中刪除指定的鍵值對
int hashTypeDelete(robj *o, robj *field) {
int deleted = 0;
// 從 ziplist 中刪除
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *zl, *fptr;
field = getDecodedObject(field);
zl = o->ptr;
fptr = ziplistIndex(zl, ZIPLIST_HEAD);
if (fptr != NULL) {
// 定位到域
fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1);
if (fptr != NULL) {
// 刪除域和值
zl = ziplistDelete(zl,&fptr);
zl = ziplistDelete(zl,&fptr);
o->ptr = zl;
deleted = 1;
}
}
decrRefCount(field);
// 從字典中刪除
} else if (o->encoding == REDIS_ENCODING_HT) {
if (dictDelete((dict*)o->ptr, field) == REDIS_OK) {
deleted = 1;
/* Always check if the dictionary needs a resize after a delete. */
// 刪除成功時,看字典是否需要收縮
if (htNeedsResize(o->ptr)) dictResize(o->ptr);
}
} else {
redisPanic("Unknown hash encoding");
}
return deleted;
}
hsetCommand:hset命令
void hsetCommand(redisClient *c) {
int update;
robj *o;
// 取出或新建立雜湊物件
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
// 如果需要的話,轉換雜湊物件的編碼
hashTypeTryConversion(o,c->argv,2,3);
// 編碼 field 和 value 物件以節約空間
hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
// 設定 field 和 value 到 hash
update = hashTypeSet(o,c->argv[2],c->argv[3]);
// 返回狀態:顯示 field-value 對是新新增還是更新
addReply(c, update ? shared.czero : shared.cone);
// 傳送鍵修改訊號
signalModifiedKey(c->db,c->argv[1]);
// 傳送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hset",c->argv[1],c->db->id);
// 將伺服器設為髒
server.dirty++;
}
hsetnxCommand:hsetnx命令
void hsetnxCommand(redisClient *c) {
robj *o;
// 取出或新建立雜湊物件
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
// 如果需要的話,轉換雜湊物件的編碼
hashTypeTryConversion(o,c->argv,2,3);
// 如果 field-value 對已經存在
// 那麼回覆 0
if (hashTypeExists(o, c->argv[2])) {
addReply(c, shared.czero);
// 否則,設定 field-value 對
} else {
// 對 field 和 value 物件編碼,以節省空間
hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
// 設定
hashTypeSet(o,c->argv[2],c->argv[3]);
// 回覆 1 ,表示設定成功
addReply(c, shared.cone);
// 傳送鍵修改訊號
signalModifiedKey(c->db,c->argv[1]);
// 傳送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hset",c->argv[1],c->db->id);
// 將資料庫設為髒
server.dirty++;
}
}
hmsetCommand:新增多個鍵值對的命令
void hmsetCommand(redisClient *c) {
int i;
robj *o;
// field-value 引數必須成對出現
if ((c->argc % 2) == 1) {
addReplyError(c,"wrong number of arguments for HMSET");
return;
}
// 取出或新建立雜湊物件
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
// 如果需要的話,轉換雜湊物件的編碼
hashTypeTryConversion(o,c->argv,2,c->argc-1);
// 遍歷並設定所有 field-value 對
for (i = 2; i < c->argc; i += 2) {
// 編碼 field-value 對,以節約空間
hashTypeTryObjectEncoding(o,&c->argv[i], &c->argv[i+1]);
// 設定
hashTypeSet(o,c->argv[i],c->argv[i+1]);
}
// 向客戶端傳送回覆
addReply(c, shared.ok);
// 傳送鍵修改訊號
signalModifiedKey(c->db,c->argv[1]);
// 傳送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hset",c->argv[1],c->db->id);
// 將資料庫設為髒
server.dirty++;
}
hincrbyCommand:為指定鍵的值進行增
void hincrbyCommand(redisClient *c) {
long long value, incr, oldvalue;
robj *o, *current, *new;
// 取出 incr 引數的值,並建立物件
if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return;
// 取出或新建立雜湊物件
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
// 取出 field 的當前值
if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) {
// 取出值的整數表示
if (getLongLongFromObjectOrReply(c,current,&value,
"hash value is not an integer") != REDIS_OK) {
decrRefCount(current);
return;
}
decrRefCount(current);
} else {
// 如果值當前不存在,那麼預設為 0
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 = createStringObjectFromLongLong(value);
// 編碼值物件
hashTypeTryObjectEncoding(o,&c->argv[2],NULL);
// 關聯鍵和新的值物件,如果已經有物件存在,那麼用新物件替換它
hashTypeSet(o,c->argv[2],new);
decrRefCount(new);
// 將計算結果用作回覆
addReplyLongLong(c,value);
// 傳送鍵修改訊號
signalModifiedKey(c->db,c->argv[1]);
// 傳送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hincrby",c->argv[1],c->db->id);
// 將資料庫設為髒
server.dirty++;
}
hgetCommand:hget命令
void hgetCommand(redisClient *c) {
robj *o;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,o,REDIS_HASH)) return;
// 取出並返回域的值
addHashFieldToReply(c, o, c->argv[2]);
}
hdelCommand:hdel方法
void hdelCommand(redisClient *c) {
robj *o;
int j, deleted = 0, keyremoved = 0;
// 取出物件
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_HASH)) return;
// 刪除指定域值對
for (j = 2; j < c->argc; j++) {
if (hashTypeDelete(o,c->argv[j])) {
// 成功刪除一個域值對時進行計數
deleted++;
// 如果雜湊已經為空,那麼刪除這個物件
if (hashTypeLength(o) == 0) {
dbDelete(c->db,c->argv[1]);
keyremoved = 1;
break;
}
}
}
// 只要有至少一個域值對被修改了,那麼執行以下程式碼
if (deleted) {
// 傳送鍵修改訊號
signalModifiedKey(c->db,c->argv[1]);
// 傳送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hdel",c->argv[1],c->db->id);
// 傳送事件通知
if (keyremoved)
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],
c->db->id);
// 將資料庫設為髒
server.dirty += deleted;
}
// 將成功刪除的域值對數量作為結果返回給客戶端
addReplyLongLong(c,deleted);
}