Redis 数据类型之(底层解析)
Redis 數(shù)據(jù)類型之(底層解析)
Redis 提供了5種數(shù)據(jù)類型:String(字符串)、Hash(哈希)、List(列表)、Set(集合)、Zset(有序集合),理解每種數(shù)據(jù)類型的特點(diǎn)對(duì)于redis的開發(fā)和運(yùn)維非常重要。
二、疑問(wèn)與解析#
結(jié)構(gòu)圖上顯示,String類型有三種實(shí)現(xiàn)方式:
使用整數(shù)值實(shí)現(xiàn)的字符串對(duì)象
使用 embstr 編碼的動(dòng)態(tài)字符串實(shí)現(xiàn)的字符串對(duì)象
動(dòng)態(tài)字符串實(shí)現(xiàn)的字符串對(duì)象
1、Redis中定義的對(duì)象的結(jié)構(gòu)體#
/** Redis 對(duì)象*/ typedef struct redisObject {// 類型 4bitsunsigned type:4;// 編碼方式 4bitsunsigned encoding:4;// LRU 時(shí)間(相對(duì)于 server.lruclock) 24bitsunsigned lru:22;// 引用計(jì)數(shù) Redis里面的數(shù)據(jù)可以通過(guò)引用計(jì)數(shù)進(jìn)行共享 32bitsint refcount;// 指向?qū)ο蟮闹?64-bitvoid *ptr; } robj;// 16bytes注釋:type表示該對(duì)象的類型,即上面 [String,List,Hash,Set,Zset] 中的一個(gè),但為了提高存儲(chǔ)效率與程序執(zhí)行效率,每種對(duì)象的底層數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)都可能不止一種,encoding 表示對(duì)象底層所使用的編碼
2、Redis對(duì)象底層八種數(shù)據(jù)結(jié)構(gòu)
REDIS_ENCODING_INT(long 類型的整數(shù))REDIS_ENCODING_EMBSTR embstr (編碼的簡(jiǎn)單動(dòng)態(tài)字符串)REDIS_ENCODING_RAW (簡(jiǎn)單動(dòng)態(tài)字符串)REDIS_ENCODING_HT (字典)REDIS_ENCODING_LINKEDLIST (雙端鏈表)REDIS_ENCODING_ZIPLIST (壓縮列表)REDIS_ENCODING_INTSET (整數(shù)集合)REDIS_ENCODING_SKIPLIST (跳躍表和字典)3、embstr與動(dòng)態(tài)字符串
embstr :是專門用于保存短字符串的一種優(yōu)化編碼方式,跟正常的字符編碼相比,字符編碼會(huì)調(diào)用兩次內(nèi)存分配函數(shù)來(lái)分別創(chuàng)建 redisObject 和 sdshdr 結(jié)構(gòu)(動(dòng)態(tài)字符串結(jié)構(gòu)),而 embstr 編碼則通過(guò)調(diào)用一次內(nèi)存分配函數(shù)來(lái)分配一塊連續(xù)的內(nèi)存空間,空間中包含 redisObject 和 sdshdr(動(dòng)態(tài)字符串)兩個(gè)結(jié)構(gòu),兩者在同一個(gè)內(nèi)存塊中。從 Redis 3.0 版本開始,字符串引入了 embstr 編碼方式,長(zhǎng)度小于 OBJ_ENCODING_EMBSTR_SIZE_LIMIT(39) 的字符串將以EMBSTR方式存儲(chǔ)。
注意: 在Redis 3.2 之后,就不是以 39 為分界線,而是以 44 為分界線,主要與 Redis 中內(nèi)存分配使用的是 jemalloc 有關(guān)。( jemalloc 分配內(nèi)存的時(shí)候是按照 8、16、32、64 作為 chunk 的單位進(jìn)行分配的。為了保證采用這種編碼方式的字符串能被 jemalloc 分配在同一個(gè) chunk 中,該字符串長(zhǎng)度不能超過(guò)64,故字符串長(zhǎng)度限制
OBJ_ENCODING_EMBSTR_SIZE_LIMIT = 64 - sizeof('0')為1 - sizeof(robj) 為16 - sizeof(struct sdshdr)為8 = 39)動(dòng)態(tài)字符串 :Redis 自己構(gòu)建的一種名為 簡(jiǎn)單動(dòng)態(tài)字符串(simple dynamic string,SDS)的抽象類型,并將 SDS 作為 Redis 的默認(rèn)字符串表示。先簡(jiǎn)單了解概念,后面看詳細(xì)解析
4、帶著疑問(wèn)來(lái)細(xì)品下面一段話
字符串的編碼可以是 int,raw 或者 embstr。如果一個(gè)字符串內(nèi)容可轉(zhuǎn)為 long,那么該字符串會(huì)被轉(zhuǎn)化為 long 類型,對(duì)象 ptr 指向該 long,并且對(duì)象類型也用 int 類型表示。普通的字符串有兩種 embstr 和 raw。如果字符串對(duì)象的長(zhǎng)度小于 39 字節(jié),就用 embstr,否則用 raw。
也就是說(shuō),Redis 會(huì)根據(jù)當(dāng)前值的類型和長(zhǎng)度決定使用內(nèi)部編碼實(shí)現(xiàn):恍然大悟
int:8個(gè)字節(jié)的長(zhǎng)整型 embstr:小于等于39個(gè)字節(jié)的字符串 raw:大于39個(gè)字節(jié)的字符串5、實(shí)踐驗(yàn)證
命令:object encoding key ,獲取數(shù)據(jù)底層的數(shù)據(jù)結(jié)構(gòu)
1)整數(shù)類型示例如下:
2)短字符串示例如下:
3)長(zhǎng)字符串示例如下:
?疑問(wèn):至此,我們知道了embstr、字符串對(duì)象, 但是動(dòng)態(tài)字符串的結(jié)構(gòu)還是沒(méi)說(shuō)清楚啊,你是不是在逗我?
靚仔疑問(wèn),再一次出現(xiàn),別急,繼續(xù)往下看
三、動(dòng)態(tài)字符串#
眾所周知,Redis 是用 C 語(yǔ)言寫的,但是對(duì)于 Redis 的字符串,卻不是 C 語(yǔ)言中的字符串(即以空字符 ’\0’ 結(jié)尾的字符數(shù)組),它是自己構(gòu)建了一種名為 簡(jiǎn)單動(dòng)態(tài)字符串(simple dynamic string,SDS)的抽象類型,并將 SDS 作為 Redis 的默認(rèn)字符串表示。
1、動(dòng)態(tài)字符串結(jié)構(gòu)分析#
SDS 定義:
用 SDS 保存字符串 “Redis” 具體結(jié)構(gòu)如下圖
對(duì)于 SDS 數(shù)據(jù)類型的定義:
len 保存了SDS保存字符串的長(zhǎng)度
buf[] 數(shù)組用來(lái)保存字符串的每個(gè)元素
free 記錄了 buf 數(shù)組中未使用的字節(jié)數(shù)量
上面的定義相對(duì)于 C 語(yǔ)言對(duì)于字符串的定義,多出了 len 屬性以及 free 屬性。為什么不直接使用 C 語(yǔ)言字符串實(shí)現(xiàn),而是要使用 SDS 呢?有什么特別的優(yōu)勢(shì)呢?
2、SDS結(jié)構(gòu)與C語(yǔ)言字符串結(jié)構(gòu)比較分析
1)獲取字符串長(zhǎng)度復(fù)雜度#
?sdshdr 中由于 len 屬性的存在,獲取 SDS 字符串的長(zhǎng)度只需要讀取 len 屬性,時(shí)間復(fù)雜度為 O(1),而對(duì)于 C 語(yǔ)言來(lái)說(shuō),獲取字符串的長(zhǎng)度通常是遍歷字符串計(jì)數(shù)來(lái)實(shí)現(xiàn)的,時(shí)間復(fù)雜度為 O(n)。
2)API安全性與緩沖區(qū)溢出#
? 緩沖區(qū)溢出(buffer overflow):是這樣的一種異常,當(dāng)程序?qū)?shù)據(jù)寫入緩沖區(qū)時(shí),會(huì)超過(guò)緩沖區(qū)的邊界,并覆蓋相鄰的內(nèi)存位置。在 C 語(yǔ)言中使用 strcat 函數(shù)來(lái)進(jìn)行兩個(gè)字符串的拼接,一旦沒(méi)有分配足夠長(zhǎng)度的內(nèi)存空間,就會(huì)造成緩沖區(qū)溢出,如
?
s1 = ‘Redis’,s2 = ‘MongoDB’,當(dāng)執(zhí)行strcat(s1, " Cluster")時(shí),未給 s1 分配足夠內(nèi)存空間,s1 的數(shù)據(jù)將溢出到 s2 所在的內(nèi)存空間,導(dǎo)致 s2 保存的內(nèi)容被意外地修改。
于 SDS 記錄了自身長(zhǎng)度,同時(shí)在修改時(shí),API 會(huì)按照如下步驟進(jìn)行:
(1)先檢查SDS的空間是否滿足修改所需的要求;
(2)如果不滿足要求的話,API 會(huì)自動(dòng)將 SDS 的空間擴(kuò)展至執(zhí)行修改所需的大小(realloc);
(3)然后才執(zhí)行實(shí)際的修改操作;
所以SDS不會(huì)造成緩沖區(qū)溢出情況3)字符串的內(nèi)存重分配次數(shù)#
?C 語(yǔ)言由于不記錄字符串的長(zhǎng)度,所以如果要修改字符串,必須要重新分配內(nèi)存。
?SDS 實(shí)現(xiàn)了空間預(yù)分配和惰性釋放兩種策略:
(1)空間預(yù)分配:當(dāng) SDS 的 API 對(duì)一個(gè) SDS 進(jìn)行修改,并且需要對(duì) SDS 進(jìn)行空間擴(kuò)展的時(shí)候,程序不僅會(huì)為 SDS 分配修改所必須的空間,還會(huì)為 SDS 分配額外的未使用空間,這樣可以減少連續(xù)執(zhí)行字符串增長(zhǎng)操作所需的內(nèi)存重分配次數(shù)。
(2)惰性釋放:當(dāng) SDS 的 API 需要對(duì) SDS 保存的字符串進(jìn)行縮短時(shí),程序并不立即使用內(nèi)存重分配來(lái)回收縮短后多出來(lái)的字節(jié),而是使用 free 屬性將這些字節(jié)的數(shù)量記錄起來(lái),并等待將來(lái)使用,如
4)二進(jìn)制數(shù)據(jù)安全#
二進(jìn)制安全(binary-safe):指能處理任意的二進(jìn)制數(shù)據(jù),包括非 ASCII 和 null 字節(jié)。
?C 字符串以空字符 ‘\0’,作為字符串結(jié)束的標(biāo)識(shí),而對(duì)于一些二進(jìn)制文件(如圖片等),內(nèi)容可能包括空字符串’\0’,導(dǎo)致程序讀入的空字符會(huì)被誤認(rèn)為是字符串的結(jié)尾,因此C字符串無(wú)法正確存取二進(jìn)制數(shù)據(jù);
?SDS 的 API 都是以處理二進(jìn)制的方式來(lái)處理 buf 里面的元素,并且 SDS 不是以空字符串’\0’來(lái)判斷是否結(jié)束,而是以 len 屬性表示的長(zhǎng)度來(lái)判斷字符串是否結(jié)束,
因此 Redis 不僅可以保存文本數(shù)據(jù),還可以保存任意格式的二進(jìn)制數(shù)據(jù)。
5)C字符串函數(shù)兼容#
SDS 的buf數(shù)組會(huì)以’\0’結(jié)尾,這樣可以重用 C 語(yǔ)言庫(kù)<string.h> 中的一部分函數(shù),避免了不必要的代碼重復(fù)。
四、要點(diǎn)總結(jié)#
String 類型對(duì)象三種實(shí)現(xiàn)方式,int,embstr,raw
字符串內(nèi)容可轉(zhuǎn)為 long,采用 int 類型,否則長(zhǎng)度<39(3.2版本前39,3.2版本后分界線44) 用 embstr,其他用 raw
SDS 是Redis自己構(gòu)建的一種簡(jiǎn)單動(dòng)態(tài)字符串的抽象類型,并將 SDS 作為 Redis 的默認(rèn)字符串表示
SDS 與 C 語(yǔ)言字符串結(jié)構(gòu)相比,具有五大優(yōu)勢(shì)
總結(jié)
以上是生活随笔為你收集整理的Redis 数据类型之(底层解析)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Spring-data-jpa和myba
- 下一篇: SpringBoot 45个注解