Redis常用的基本數(shù)據(jù)類型
激勵:人人都有一個大廠的心,堅持自己的夢想逢捺,你就是世界。
乏味:筆記很無聊,需要去品味笤受。
堅持:每天進步一點點喻犁,當(dāng)知道的越多陆错,才發(fā)現(xiàn)不知道的也越多捞蚂。
String
最基本也是最常用的數(shù)據(jù)類型,也被叫做Binary-safe strings需了。
- 可以用來存儲字符串跳昼、正數(shù)、浮點數(shù)肋乍。
操作命令
-
批量操作(原子性)
mset key1 val1 key2 val2
-
設(shè)置值鹅颊,如果key存在,則不成功
setnx key
說明:基于該操作可以實現(xiàn)分布式鎖墓造,然后用del key來釋放鎖堪伍。
存在問題:如果del key失敗了,會導(dǎo)致其它節(jié)點永遠獲取不到鎖觅闽。
解決方法:給key加上過期時間帝雇,可以使用expire命令,單是這樣不是原子性操作蛉拙。
最好的辦法就是使用setnx key value [expriation EX seconds | PX milliseconds] [NX|XX]尸闸。
用例:
set lock 1 EX 10 NX
-
整數(shù)值遞增/減
遞增:
- incr key
- incrby key num
遞減:
- decr key
- decrby key num
浮點數(shù):
- Incrbyfloat key num
備注:這里的num是要增加/減少的值。
-
批量獲取
mget key1 key2
-
獲取長度
strlen key
-
字符串追加
append key val
-
獲取指定范圍的字符
getrange key start end
備注:start:開始位置孕锄, end:結(jié)束位置吮廉。
實現(xiàn)原理
結(jié)構(gòu)圖
備注:SDS:Simple Dynamic String,redis中字符串的實現(xiàn)畸肆。
-
SDS結(jié)構(gòu)宦芦,下邊源碼是sdshdr8, SDS又分為:sdshdr5(2^5 = 32byte)、sdshdr8(2^8 = 256byte)恼除、sdshdr16(2^16 = 65536byte = 64KB)踪旷、sdshdr32、sdshdr64豁辉。
struct __attribute__ ((__packed__)) sdshdr8 { uint8_t len; /* 當(dāng)前字符數(shù)組的長度 */ uint8_t alloc; /*當(dāng)前字符數(shù)組總共分配的內(nèi)存大小 */ unsigned char flags; /* 當(dāng)前字符數(shù)組的屬性令野、用來標(biāo)識到底是 sdshdr8 還是 sdshdr16 等 */ char buf[]; /* 字符串真正的值 */ };
-
redis為什么要用SDS來實現(xiàn)字符串?
原因:c語言本身是沒有字符串類型的徽级,只能用字符數(shù)組char[]來實現(xiàn)气破。
- 使用字符數(shù)組必須先給目標(biāo)變量分配足夠的空間,否則會有溢出的情況餐抢;
- 如果要獲取字符數(shù)據(jù)的長度现使,必須遍歷整個字符數(shù)組,比較耗時(時間復(fù)雜度是O(n))旷痕;
- 字符串長度如果發(fā)生變更碳锈,需求重新分配內(nèi)存空間;
- c語言中字符串的尾部是以‘\0’來標(biāo)示的欺抗,因此不能存儲圖片售碳、音視頻、壓縮文件等二進制保存的內(nèi)容绞呈,二進制不安全贸人,這里也解釋了redis為什么是Binary-safe strings。
SDS特點:
- 不用擔(dān)心內(nèi)存溢出問題佃声,會自動擴容艺智;
- 內(nèi)部結(jié)構(gòu)存儲了字符串長度(定義了len屬性),獲取字符串長度時間復(fù)雜度是O(1)的圾亏;
- 通過空間預(yù)分配和惰性空間釋放來防止多次重復(fù)分配內(nèi)存十拣;
- 判斷是否結(jié)束的標(biāo)示是len屬性,它同樣以‘\0’結(jié)尾召嘶,是因為這樣可以使用c語言中的函數(shù)庫父晶;
數(shù)據(jù)模型
Redis是KV形式的數(shù)據(jù)庫,它是通過hashtable實現(xiàn)的弄跌,所以每個鍵值對都有一個dictEntry(源碼參考:dict.h)甲喝。
typedef struct dictEntry {
void *key; /* key 關(guān)鍵字定義 */
union {
void *val;
uint64_t u64; /* value 定義 */
int64_t s64;
double d;
} v;
struct dictEntry *next; /* 指向下一個鍵值對節(jié)點 */
} dictEntry;
key是字符串,但是redis沒有直接使用C語言中國呢的字符數(shù)組铛只,而是存儲在自定定義的SDS中埠胖。
value既不是直接作為字符串存儲,也不是直接使用SDS淳玩,而是存儲在redisObject中直撤。實際上五種常用的數(shù)據(jù)類型的任何一種都是通過redisObject來存儲的。
-
redisObject定義在src/server.h文件中蜕着。
typedef struct redisObject { unsigned type:4; /* 對象的類型谋竖,包括:OBJ_STRING红柱、OBJ_LIST、OBJ_HASH蓖乘、OBJ_SET锤悄、OBJ_ZSET */ unsigned encoding:4; /* 具體的數(shù)據(jù)結(jié)構(gòu) */ unsigned lru:LRU_BITS; /* 24 位,對象最后一次被命令程序訪問的時間嘉抒,與內(nèi)存回收有關(guān) */ int refcount; /* 引用計數(shù)零聚。當(dāng) refcount 為 0 的時候,表示該對象已經(jīng)不被任何對象引用些侍,則可以進行垃圾回收了*/ void *ptr; /* 指向?qū)ο髮嶋H的數(shù)據(jù)結(jié)構(gòu) */ } robj;
內(nèi)部編碼
int:存儲8個字節(jié)的長整型(long隶症,2^63 - 1);
Embstr:代表embstr格式的SDS岗宣,存儲小于44個字節(jié)的字符串蚂会。
-
raw:存儲大于44個字節(jié)的字符串(3.2版本以前是39個字節(jié))。
embstr和raw的區(qū)別:
- embstr只分配一次內(nèi)存空間(因為redisObject和SDS是連續(xù)的)耗式,raw需要分配兩次內(nèi)存空間(為redisObject和SDS分別分配空間)颂龙;
- embstr相對于raw的好處是在于創(chuàng)建時少分配一次空間,刪除的時候少釋放一次空間纽什,所有的數(shù)據(jù)都是連在一起的措嵌,尋找方便;
- embstr的缺點也很明顯芦缰,如果字符串的長度增加需要重新分配內(nèi)存時企巢,整個redisObject和SDS都需要重新分配空間,因此redis中的embstr實現(xiàn)為只讀让蕾。
int和embstr與raw之間的轉(zhuǎn)換:
- 當(dāng)int數(shù)據(jù)不再是整數(shù)時浪规,或者大小超過了long的范圍時會將int自動轉(zhuǎn)化為embstr;
- 對于embstr來說探孝,由于實現(xiàn)時只讀的笋婿,因此在對embstr對象進行修改時,會先轉(zhuǎn)化成raw后再進行修改顿颅。只要是對embstr類型的對象進行修改缸濒,修改后的對象類型一定是raw,無論是否超過了44個字節(jié)的長度限制粱腻;
- int和embstr與raw之間轉(zhuǎn)換的過程是不可逆的庇配,只能從小內(nèi)存編碼轉(zhuǎn)換成大內(nèi)存編碼(重新set的情況除外);
備注long的范圍為:2^63 - 1
redis通過這種封裝绍些,可以根據(jù)對象的類型動態(tài)的選擇存儲的結(jié)構(gòu)和可使用的命令捞慌,實現(xiàn)節(jié)省空間和優(yōu)化查詢。
應(yīng)用場景
string類型:熱點數(shù)據(jù)的緩存(例如:新聞內(nèi)容柬批、報表數(shù)據(jù))啸澡、對象緩存袖订、全頁緩存,可以提升熱點數(shù)據(jù)的訪問速度嗅虏。
分布式系統(tǒng)中的共享數(shù)據(jù):分布式的session著角、分布式鎖、全局的ID旋恼、計數(shù)器、限流操作奄容、位統(tǒng)計等冰更。
Hash哈希
操作命令
-
常用的操作:
- hset key field val
- hmset key field1 val1 field2 val2 field3 val3
- hget key field
- hmget key field1 field2
- hkeys key
- hvals key
- hgetall key
-
key操作:
- hget exists key
- hdel key
- hlen key
實現(xiàn)原理
結(jié)構(gòu)示例圖
存儲類型
包含鍵值的無序散列表,val只能是字符串且不能嵌套其他類型昂勒。
-
hash與string的區(qū)別
- 把所有相關(guān)的值聚集到一個key中蜀细,節(jié)省內(nèi)存空間;
- 只使用一個key戈盈,減少key之間的沖突奠衔;
- 存儲的是一個完整的對象信息,減少了將對象信息分開存儲的I/O和cpu的消耗塘娶;
-
hash不適合的場景
- Field不能單獨設(shè)置過期時間归斤;
- 需要考慮數(shù)據(jù)量分布問題,value值非常大的時候刁岸,無法分布到多個節(jié)點脏里;
- 沒有bit操作;
存儲原理
redis的hash本身也是一個kv的結(jié)構(gòu)虹曙,類似于java中的HashMap迫横。
外層的哈希(redis kv的實現(xiàn))只用到了hashtable,當(dāng)存儲hash數(shù)據(jù)類型時酝碳,一般叫做內(nèi)層的哈希矾踱。
內(nèi)層的哈希底層可以使用兩種數(shù)據(jù)結(jié)構(gòu)實現(xiàn):ziplist:OBJ_ENCODING_ZIPLIST(壓縮列表),hashtable:OBJ_ENCODING_HT(哈希表)疏哗。
- ziplist壓縮列表: 是一個經(jīng)過特殊編碼的雙向鏈表呛讲,它不存儲指向上一個鏈表節(jié)點和指向下一個鏈表節(jié)點的指針,而是存儲上一個節(jié)點長度和當(dāng)前節(jié)點長度返奉,通過犧牲部分讀寫性能來換取高效的內(nèi)存空間利用率圣蝎,是一種時間換空間的思想。只用在字段個數(shù)少衡瓶、字段值小的場景徘公。當(dāng)hash對象同時滿足以下兩個條件的時候,使用ziplist編碼:
- 所有的鍵值對的鍵和值的字符串長度都小于等于64byte(一個英文字母一個字節(jié))哮针;
- 哈希對象保存的鍵值對數(shù)量小于512個关面;
// src/redis.conf配置 hash-max-ziplist-value 64 //ziplist中最大能存放的值長度 hash-max-ziplist-entries 512 //ziplist中最多能存放的entry節(jié)點數(shù)量
一個哈希對象超過配置的閾值(鍵和值的長度大于64byte坦袍,鍵值對個數(shù)大于512個)時,會轉(zhuǎn)換成哈希表hashtable等太。
- hashtable(dict):hashtable被稱為dictionary捂齐,它是一個數(shù)組+鏈表的結(jié)構(gòu)。
redis的hash默認(rèn)使用的是ht[0]缩抡,ht[1]不會初始化和分配空間奠宜。
哈希表dictht是用鏈地址法來解決碰撞的問題,在這種情下瞻想,哈希表的性能取決于它的大醒拐妗(size屬性)和它所保存的節(jié)點的數(shù)量(used屬性)之間的比率;
- 比率在1:1時(一個哈希表ht只存儲一個節(jié)點entry)蘑险,哈希表的性能最好滴肿;
- 如果節(jié)點數(shù)量比哈希表的大小要大很多的話(比例用ratio表示,5表示平均一個ht存儲5個entry)佃迄,那么哈希表就會退化成個多個鏈表泼差,哈希表本身的性能優(yōu)勢就不存在了。在這個情況下需要擴容呵俏。redis里面的這種操作叫做rehash堆缘。
- rehash的步驟:
- 為字符ht[1]哈希表分配空間,這個哈希表的空間大小取決于要執(zhí)行的操作和ht[0]當(dāng)前包含的鍵值對的數(shù)量普碎。(ht[1]的大小為第一個大于等于ht[0].used*2);
- 將所有的ht[0]上的節(jié)點rehash到ht[1]上套啤,重新計算hash值和索引,然后放到指定的位置随常;
- 當(dāng)ht[0]全部遷移到ht[1]后潜沦,釋放ht[0]的空間,將ht[1]設(shè)置為ht[0]表绪氛,并創(chuàng)建新的ht[1]為下次rehash做準(zhǔn)備唆鸡。
應(yīng)用場景
- String:String 類型的hash都可以做。
- 存儲對象:一個對象或一張表的數(shù)據(jù)(比string節(jié)省更多的key空間枣察,集中管理)争占。
List列表
操作命令
-
元素增減
lpush key val
lpush key val1 val2
rpush key val1
lpop key
rpop key
blpop key
brpop key
-
取值
lindex key index
lrange key start stop
存儲類型
存儲有序的字符串(從左到右),元素可以重復(fù)序目”酆郏可以當(dāng)簡單的隊列和棧使用.
結(jié)構(gòu)圖
存儲原理
3.2版本之前,數(shù)據(jù)量較小的時候用ziplist存儲猿涨,達到閾值時轉(zhuǎn)換為linkedlist進行存儲握童,分別對應(yīng)OBJ_ENCODING_ZIPLIST和OBJ_ENCODING_LINKEDLIST。
3.2版本之后統(tǒng)一使用了quicklist來存儲叛赚。quicklist存儲了一個雙向鏈表澡绩,每個節(jié)點都是一個ziplist稽揭。
quicklist
- quicklist(快速列表)是ziplist和linkedlist的結(jié)合體。
- quicklist.h肥卡,head和tail指向雙向鏈表的表頭和表尾溪掀。
typedef struct quicklist { quicklistNode *head; /* 指向雙向列表的表頭 */ quicklistNode *tail; /* 指向雙向列表的表尾 */ unsigned long count; /* 所有的 ziplist 中一共存了多少個元素 */ unsigned long len; /* 雙向鏈表的長度,node 的數(shù)量 */ int fill : 16; /* fill factor for individual nodes */ unsigned int compress : 16; /* 壓縮深度步鉴,0:不壓縮揪胃; */ } quicklist;
配置參數(shù)(redis.conf):
- List-max-ziplist-size(fill):正數(shù)表示單個ziplist最多包含的entry個數(shù)。負(fù)數(shù)代表單個ziplist的大小氛琢,默認(rèn)8k喊递。(-1:4KB;-2:8KB艺沼;-3:16KB;-4:32KB蕴掏;-5:64KB)
- List-compress-depth(compress):壓縮深度障般,默認(rèn)是0。(1:首尾的ziplist不壓縮盛杰;2:首尾第一第二個ziplist不壓縮挽荡,以此類推)
應(yīng)用場景
用戶消息的時間線(timeline)
-
消息隊列:list提供兩個阻塞的彈出操作:blpop/brpop,可以設(shè)置超時時間即供。
blpop:blpop key1 timeout移除并獲取列表的第一個元素定拟,如果列表沒有元素會阻塞列表直到等待超時或發(fā)現(xiàn)可彈出的元素為止;
brpop:brpop key1 timeout移除并獲取列表的最后一個元素逗嫡,超時機制同blpop青自;
隊列:先進先出:rpush、blpop驱证,左頭右尾延窜,右邊進入隊列,左邊出隊列抹锄;
棧:先進后出:rpsuh和brpop
Set集合
操作命令
-
添加一個/多個元素
sadd key member
sadd key member1 member2
-
獲取所有元素
smembers key
-
統(tǒng)計元素個數(shù)
scard key
-
隨機獲取一個元素
srandmember key
-
隨機彈出一個元素
spop key
-
移除一個/多個
srem key member
srem key member1 member2
-
查看元素是否存在
sismember key member
存儲類型
String類型的無序集合逆瑞,最大存儲數(shù)量為2^32 - 1。
結(jié)構(gòu)圖
存儲原理
redis用intset或hashtable來存儲set伙单。如果元素都是整數(shù)類型获高,就用intset存儲,如果不是整數(shù)類型吻育,就用hashtable存儲念秧,如果元素個數(shù)超過512個,也會用hashtable存儲布疼。
應(yīng)用場景
- 抽獎:隨機獲取元素
- 點贊出爹、簽到庄吼、打卡
- 數(shù)據(jù)的標(biāo)簽,數(shù)據(jù)的篩選
- 用戶關(guān)注严就、推薦模型:可以用set來取并集总寻,差集,交集梢为。
ZSet有序集合
操作命令
-
添加元素
zadd key [NX|XX] [CH] [INCR] score member [score member ...]
-
獲取全部元素
zrange key start stop [WITHSCORES]
zrevrange key start stop [WITHSCORES]
-
根據(jù)分值區(qū)間獲取元素
zrangebyscore key min max [WITHSCORES] [LIMIT offset count]
-
移除元素
zrem key member [member ...]
-
統(tǒng)計元素個數(shù)
zcard key
-
分值遞增
zincrby key increment member
-
根據(jù)分值統(tǒng)計個數(shù)
zcount key min max
-
獲取元素rank
zrank key member
-
獲取元素score
zscore key member
存儲類型
sorted set:有序的set渐行,每個元素都有一個score。如果score相同铸董,按照key的ASCII碼排序祟印。
結(jié)構(gòu)圖
存儲原理
同時滿足以下條件使用ziplist:
- 元素個數(shù)小于128;
- 所有member的長度都小于64字節(jié)粟害;
在ziplist的內(nèi)部蕴忆,按照score排序遞增來存儲,插入的時候要移動之后的數(shù)據(jù)悲幅。超過閾值之后使用skiplist+dict存儲套鹅。
- skiplist(跳躍表)
假設(shè)我們要查找22這個值,
- 22首先和5比較汰具,然后再和17比較卓鹿,22比它們都大,所以繼續(xù)向后比較留荔;
- 當(dāng)22和27比較時吟孙,發(fā)現(xiàn)22小于27,則會進入下面的鏈表聚蝶,開始比較杰妓;
- 22正好時下一個鏈表的值,這樣就順利找到了碘勉。
假如要查找25稚失,根據(jù)上邊流程第三步發(fā)現(xiàn)25比22大,會繼續(xù)向后查詢恰聘,然后又比27小句各,說明要查詢的25在原鏈表中不存在。
在整個查詢過程中晴叨,由于新增加了指針凿宾,查詢的時候就不需要遍歷整個鏈表的節(jié)點,這樣查詢的次數(shù)大概減少了一半兼蕊,這個就叫做跳躍表初厚。
應(yīng)用場景
- 熱搜排行榜:例如視頻網(wǎng)站需要用戶上傳的視頻做排行榜,榜單維護可能是多方面,按時間产禾、按照播放量排作、按照獲得的點贊次數(shù)等。
- 帶權(quán)重的隊列:比如普通消息的sorce為1亚情,重要消息的score為2妄痪,然后工作線程可以選擇按score的倒序來獲取工作任務(wù),讓重要的任務(wù)優(yōu)先執(zhí)行楞件。
BitMaps
BitMaps時在字符串類型上面定義的位操作衫生,一個字節(jié)又8個二進制位組成。
操作命令
-
獲取value在offset處的值
getbit key offset
-
修改二進制數(shù)據(jù)
setbit key offset value
-
統(tǒng)計二進制位中1的個數(shù)
bitcount key
bitcount key [start end] //獲取start到end的位置的1
應(yīng)用場景
- 用戶訪問統(tǒng)計
- 在線用戶統(tǒng)計
- 布隆過濾器
其他數(shù)據(jù)類型
Hyperloglogs:提供了一種不精準(zhǔn)的基數(shù)統(tǒng)計方法土浸,比較適合用來做大規(guī)模數(shù)據(jù)的去重統(tǒng)計罪针。例如:網(wǎng)站的UV。
Geospatial:可以用來保存地理位置黄伊,并作位置距離計算或者根據(jù)半徑計算位置等泪酱,例如:用redis來實現(xiàn)附近的人或者計算最優(yōu)地圖路徑。
Streams:5.0版本以后推出的數(shù)據(jù)類型还最,支持多播的可持久化的消息隊列墓阀,用于實現(xiàn)發(fā)布訂閱功能(借鑒了kafka的設(shè)計)。
本文由博客一文多發(fā)平臺 OpenWrite 發(fā)布憋活!