哈希
幾乎所有的編程語言都提供了哈希(hash)類型,它們的叫法可能是哈希、字典、關(guān)聯(lián)數(shù)組在Redis中,哈希類型是指鍵值本身又是一個(gè)鍵值對結(jié)構(gòu),形如value={{field1,value1},...{fieldN,valueN}}/
命令
1.hset key field value 設(shè)置值
hset user:1 name tom
2.hget key field 獲取值,如果鍵或field不存在,會(huì)返回nil:
3.hdel key field [field ...] 刪除field,會(huì)刪除一個(gè)或多個(gè)field,返回結(jié)果為成功刪除field的個(gè)數(shù)
4.hlen key 計(jì)算field個(gè)數(shù)
5.hmget key field [field...] hmset key field value [field value..]批量設(shè)置或獲取field-value
hmset和hmget分別是批量設(shè)置和獲取field-value,hmset需要的參數(shù)是key 和多對field-value,hmget需要的參數(shù)是key和多個(gè)field。
6.hexists key field 判斷field是否存在
7.hkeys key 獲取所有field
8.hvals key 獲取所有value
在使用hgetall時(shí),如果哈希元素個(gè)數(shù)比較多,會(huì)存在阻塞Redis的可能。如果開發(fā)人員只需要獲取部分field,可以使用hmget,如果一定要獲取全部 field-value,可以使用hscan命令,該命令會(huì)漸進(jìn)式遍歷哈希類型,
9. hincrby key field
hincrbyfloat key field
incrby和incrbyfloat命令一樣,但是它們的作用域是filed
10.hstrlen key field 計(jì)算value的字符串長度(需要Redis3.2以上
內(nèi)部編碼
哈希類型的內(nèi)部編碼有兩種:
·ziplist(壓縮列表):當(dāng)哈希類型元素個(gè)數(shù)小于hash-max-ziplist-entries 配置(默認(rèn)512個(gè))、同時(shí)所有值都小于hash-max-ziplist-value配置(默認(rèn)64 字節(jié))時(shí),Redis會(huì)使用ziplist作為哈希的內(nèi)部實(shí)現(xiàn),ziplist使用更加緊湊的結(jié)構(gòu)實(shí)現(xiàn)多個(gè)元素的連續(xù)存儲(chǔ),所以在節(jié)省內(nèi)存方面比hashtable更加優(yōu)秀。
·hashtable(哈希表):當(dāng)哈希類型無法滿足ziplist的條件時(shí),Redis會(huì)使用hashtable作為哈希的內(nèi)部實(shí)現(xiàn),因?yàn)榇藭r(shí)ziplist的讀寫效率會(huì)下降,而 hashtable的讀寫時(shí)間復(fù)雜度為O(1)
數(shù)據(jù)結(jié)構(gòu)
提供兩種結(jié)構(gòu)來存儲(chǔ),一種是hashtable、另一種是前面講的ziplist,數(shù)據(jù)量小的時(shí)候用ziplist. 在redis中,哈希表分為三層,分別是,源碼地址【dict.h】
dictEntry
管理一個(gè)key-value,同時(shí)保留同一個(gè)桶中相鄰元素的指針,用來維護(hù)哈希桶的內(nèi)部鏈;
typedef struct dictEntry {
void *key;
union { //因?yàn)関alue有多種類型,所以value用了union來存儲(chǔ)
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;//下一個(gè)節(jié)點(diǎn)的地址,用來處理碰撞,所有分配到同一索引的元素通過next指針
鏈接起來形成鏈表key和v都可以保存多種類型的數(shù)據(jù)
} dictEntry;
dictht
實(shí)現(xiàn)一個(gè)hash表會(huì)使用一個(gè)buckets存放dictEntry的地址,一般情況下通過hash(key)%len得到的值就是buckets的索引,這個(gè)值決定了我們要將此dictEntry節(jié)點(diǎn)放入buckets的哪個(gè)索引里,這個(gè)buckets實(shí)際上就是我們說的hash表。dict.h的dictht結(jié)構(gòu)中table存放的就是buckets的地址.
typedef struct dictht {
dictEntry **table;//buckets的地址
unsigned long size;//buckets的大小,總保持為 2^n
unsigned long sizemask;//掩碼,用來計(jì)算hash值對應(yīng)的buckets索引
unsigned long used;//當(dāng)前dictht有多少個(gè)dictEntry節(jié)點(diǎn)
} dictht;
dict
dictht實(shí)際上就是hash表的核心,但是只有一個(gè)dictht還不夠,比如rehash、遍歷hash等操作,所以redis定義了一個(gè)叫dict的結(jié)構(gòu)以支持字典的各種操作,當(dāng)dictht需要擴(kuò)容/縮容時(shí),用來管理dictht的遷移,以下是它的數(shù)據(jù)結(jié)構(gòu),源碼在
typedef struct dict {
dictType *type;//dictType里存放的是一堆工具函數(shù)的函數(shù)指針,
void *privdata;//保存type中的某些函數(shù)需要作為參數(shù)的數(shù)據(jù)
dictht ht[2];//兩個(gè)dictht,ht[0]平時(shí)用,ht[1] rehash時(shí)用
long rehashidx; //當(dāng)前rehash到buckets的哪個(gè)索引,-1時(shí)表示非rehash狀態(tài)
int iterators; //安全迭代器的計(jì)數(shù)。
} dict;
比如我們要講一個(gè)數(shù)據(jù)存儲(chǔ)到hash表中,那么會(huì)先通過murmur計(jì)算key對應(yīng)的hashcode,然后根據(jù)hashcode取模得到bucket的位置,再插入到鏈表中/
整體結(jié)構(gòu)如圖所示:

使用場景
存儲(chǔ)關(guān)系型數(shù)據(jù)表的數(shù)據(jù)
用戶的屬性作為表的列,每條用戶信息作為行。如果將其用哈希類型存儲(chǔ)相比于使用字符串序列化緩存用戶信息,哈希類型變得更加直觀,并且在更新操作上會(huì)更加便捷??梢詫⒚總€(gè)用戶的id定義為鍵后綴,多對fieldvalue對應(yīng)每個(gè)用戶的屬性。哈希類型是稀疏的,而關(guān)系型數(shù)據(jù)庫是完全結(jié)構(gòu)化的,例如哈希類型每個(gè)鍵可以有不同的field,而關(guān)系型數(shù)據(jù)庫一旦添加新的列,所有行都要為其設(shè)置值(即使為NULL),如圖2-17所示。
·關(guān)系型數(shù)據(jù)庫可以做復(fù)雜的關(guān)系查詢,而Redis去模擬關(guān)系型復(fù)雜查詢開發(fā)困難,維護(hù)成本高。
哈希類型:每個(gè)用戶屬性使用一對field-value,但是只用一個(gè)鍵保存。
hmset user:1 name tomage 23 city beijing
優(yōu)點(diǎn):簡單直觀,如果使用合理可以減少內(nèi)存空間的使用。
缺點(diǎn):要控制哈希在ziplist和hashtable兩種內(nèi)部編碼的轉(zhuǎn)換,hashtable會(huì)消耗更多內(nèi)存。
待續(xù)。。。
來自《Redis運(yùn)維與開發(fā)》