承接上文深入redis源碼,了解數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)思想
本地調(diào)試redis源碼
啟動(dòng)redis源碼
mac下載C語(yǔ)言編譯器CLion,拉取redis源碼并導(dǎo)入CLion
編譯redis源碼并啟動(dòng)
編譯成功之后,在src目錄中就會(huì)出現(xiàn)可執(zhí)行文件
啟動(dòng)redis-server
啟動(dòng)成功
在set方法中打個(gè)斷點(diǎn)
redis的所有命令都在redisCommand里面定義,找到set命令,并找到對(duì)應(yīng)的觸點(diǎn)函數(shù)setCommand
在setCommand函數(shù)中打個(gè)端點(diǎn)
通過(guò)redis-cli執(zhí)行set命令,就可以斷點(diǎn)跟蹤了
看下編碼的過(guò)程
tryObjectEncoding
len是value的長(zhǎng)度,長(zhǎng)度小于等于20的話,才可以被轉(zhuǎn)換成整數(shù)。
為什么整數(shù)范圍最大是十進(jìn)制數(shù)20?
在64位系統(tǒng)下C語(yǔ)言中的int還是占4字節(jié),32位,與32位系統(tǒng)中沒(méi)有區(qū)別!
64位操作系統(tǒng)下,采用64位編譯器進(jìn)行編譯處理時(shí),發(fā)生變化的變量類型是long。
32位系統(tǒng)下,long占4字節(jié),32位,與int相同。
64位系統(tǒng)下,long占8字節(jié),64位,有符號(hào)數(shù)取值范圍是-9223372036854775808至9223372036854775807。
JAVA的long型整數(shù)最大值:9223372036854775807,即19位十進(jìn)制數(shù),64位二進(jìn)制數(shù),16位16進(jìn)制數(shù)。
long占8個(gè)字節(jié),64位二進(jìn)制數(shù),19位十進(jìn)制數(shù),算上符號(hào)的話就是20位十進(jìn)制數(shù)。
如果value string可以轉(zhuǎn)換成long,然后將value值賦值給redisObject的ptr指針,強(qiáng)制把整型值轉(zhuǎn)換成指針類型,而不讓ptr存內(nèi)存地址,這樣即節(jié)省了內(nèi)存,還減少了一次內(nèi)存io。
字符串編碼
短字符串編碼
短字符串(長(zhǎng)度小于等于44)編碼是embstr
長(zhǎng)字符串(長(zhǎng)度大于44)編碼是raw
為什么是44?
要充分理解這個(gè)問(wèn)題,需要先科普下cpu、地址總線、內(nèi)存之間的關(guān)系:
- • 64位處理器比32位處理器速度更快
32位處理器一次性只能處理32位,也就是4個(gè)字節(jié)的數(shù)據(jù),而64位處理器一次性能處理64位即8個(gè)字節(jié)的數(shù)據(jù),64位處理器的處理速度比32位更快。
- • 64位處理器比32位能處理的地址范圍更大
除了運(yùn)算能力之外,與32位處理器相比,64位處理器的優(yōu)勢(shì)還體現(xiàn)在系統(tǒng)對(duì)內(nèi)存的控制上。由于地址使用的是特殊的整數(shù),而64位處理器的一個(gè)ALU(算術(shù)邏輯運(yùn)行器)和寄存器可以處理更大的整數(shù),也就是更大的地址。傳統(tǒng)的32位處理器的尋址空間最大為4GB,而64位處理器在理論上則可以達(dá)到1800萬(wàn)個(gè)TB。
- • 地址總線
要存取數(shù)據(jù)或指令就要知道數(shù)據(jù)或指令存放的位置,地址寄存器存儲(chǔ)的就是cpu當(dāng)前要存取的數(shù)據(jù)或指令的地址,該地址是由地址總線傳輸?shù)降刂芳拇嫫魃系?/p>
假設(shè)地址總線有n位,即共有n位二進(jìn)制位來(lái)表示地址,那么最多可以表示2^n個(gè)地址,另外計(jì)算機(jī)以一個(gè)字節(jié)為尋址單位,所以cpu的尋址能力或者最大的尋址范圍是2^n個(gè)字節(jié)。綜上,地址總線的位數(shù)決定了cpu的尋址能力。
- • 數(shù)據(jù)總線的寬度與字長(zhǎng)及cpu位數(shù)
字長(zhǎng)指cpu同一時(shí)間內(nèi)可以處理的二進(jìn)制數(shù)的位數(shù),數(shù)據(jù)總線傳輸?shù)臄?shù)據(jù)或指令的位數(shù)要與字長(zhǎng)一致。否則,如果數(shù)據(jù)總線長(zhǎng)度大于字長(zhǎng)則一條數(shù)據(jù)或指令要分多次傳輸,則分開(kāi)傳輸幾組數(shù)據(jù)也就沒(méi)有意義;如果數(shù)據(jù)總線寬度小于字長(zhǎng),則cpu的利用率降低,對(duì)資源是種浪費(fèi)。另外如果字長(zhǎng)是n位,一般稱cpu是n位的,所以說(shuō)數(shù)據(jù)總線的寬度和字長(zhǎng)及cpu的位數(shù)是一致的。
結(jié)合上面的問(wèn)題,繼續(xù)分析:
64位系統(tǒng)緩存行(字長(zhǎng))大小是64字節(jié)即64位處理器一次性讀取64字節(jié)的緩存數(shù)據(jù)。基于緩存局部性原理,一次性要讀取的數(shù)據(jù)不足64字節(jié),也會(huì)順便獲取相鄰的其他數(shù)據(jù)共64字節(jié)。
從內(nèi)存中獲取到redisObject對(duì)象之后,根據(jù)ptr指向的內(nèi)存空間獲取數(shù)據(jù)。
分析下這個(gè)redisObject對(duì)象占多少個(gè)字節(jié):
type 4bit,encoding4bit,LRU_BITS占24bit即3字節(jié),int類型的refcount占4字節(jié),ptr指針類型占8字節(jié),共16個(gè)字節(jié),那么64字節(jié)的緩存行還剩48個(gè)字節(jié)的剩余空間,那么這48字節(jié)的緩存行空間存放什么呢?
存放redisObject ptr指針指向的內(nèi)存空間的數(shù)據(jù),即cpu一次性讀取的64字節(jié)的緩存行中包含redisObject對(duì)象以及該對(duì)象ptr指針?biāo)赶虻膬?nèi)存空間的數(shù)據(jù),以免再根據(jù)redisObject的ptr指針再去內(nèi)存中獲取對(duì)應(yīng)的數(shù)據(jù),減少了一次內(nèi)存io。
那48字節(jié)在哪個(gè)字符串類型的范圍內(nèi)呢?
sdshdr8 對(duì)應(yīng)的字符串范圍是32-64字節(jié)
c語(yǔ)言中的char類型占1個(gè)字節(jié),java中的char類型占2個(gè)字節(jié)。
redis字符串為了兼容c語(yǔ)言函數(shù)庫(kù)在末尾加上的字符占1個(gè)字節(jié)。
uint8_t len 占一個(gè)字節(jié),uint8_t alloc占一個(gè)字節(jié),char flags占一個(gè)字節(jié),占一個(gè)字節(jié)
所以sdshdr8數(shù)據(jù)結(jié)構(gòu)需要4個(gè)字節(jié)的元數(shù)據(jù)
除去這4個(gè)字節(jié),緩存行還有48-4=44個(gè)字節(jié)的剩余空間,如果字符串?dāng)?shù)據(jù)范圍小于等于44,把redisObject對(duì)象ptr指針?biāo)赶騼?nèi)存中的這個(gè)數(shù)據(jù)和redisObject對(duì)象放在一起即嵌入式字符串,在內(nèi)存空間分配的時(shí)候,這兩部分?jǐn)?shù)據(jù)是分配在一起的,這樣就可以直接適應(yīng)緩存行的大小,這樣就減少了一次內(nèi)存io。
List數(shù)據(jù)類型
- • LPUSH
從左邊開(kāi)始往alist中放入元素
- • LPOP
從左邊獲取元素
依次是c、b、a
list如果沒(méi)有元素的話,對(duì)應(yīng)的key也會(huì)被清除
- • RPUSH
從右邊放入元素
從左邊放入元素,從左邊拿出元素,則是棧的數(shù)據(jù)結(jié)構(gòu)即a,b,c的數(shù)據(jù)放入,c、b、a順序的拿出,a最開(kāi)始進(jìn),最后出,先進(jìn)后出,
同一端,從這邊進(jìn),也從這邊出,這就是棧的數(shù)據(jù)結(jié)構(gòu)。
一邊進(jìn),另外一邊出,是隊(duì)列的數(shù)據(jù)結(jié)構(gòu)即右邊放a,b,c,左邊拿a,b,c,這是隊(duì)列的數(shù)據(jù)結(jié)構(gòu)。
阻塞api
如果沒(méi)有元素就一直等待,直到有元素或者設(shè)置超時(shí)時(shí)間
LPOP從list中彈出元素之后就會(huì)從list中刪除,如果應(yīng)用程序沒(méi)有正確處理的話,就會(huì)造成元素丟失的情況,BRPOPLPUSH就是為了應(yīng)對(duì)這場(chǎng)場(chǎng)景,彈出元素之后,push到另外一個(gè)list中,做一個(gè)數(shù)據(jù)備份,防止數(shù)據(jù)丟失。
List數(shù)據(jù)類型底層設(shè)計(jì)
List是一個(gè)有序(按加入的時(shí)序排序)的數(shù)據(jù)結(jié)構(gòu),Redis采用quicklist(雙端鏈表)和ziplist作為L(zhǎng)ist的底層實(shí)現(xiàn)。
可以通過(guò)設(shè)置每個(gè)ziplist的最大容量,quicklist的數(shù)據(jù)壓縮范圍,提升數(shù)據(jù)存取效率。
- • list-max-ziplist-size -2
單個(gè)ziplist節(jié)點(diǎn)最大能存儲(chǔ)8kb,超過(guò)則進(jìn)行分裂,將數(shù)據(jù)存儲(chǔ)在新的ziplist節(jié)點(diǎn)中
- • list-compress-depth 1
0代表所有節(jié)點(diǎn),都不進(jìn)行壓縮,1代表從頭節(jié)點(diǎn)往后走一個(gè),尾節(jié)點(diǎn)往前走一個(gè)不用壓縮,其他的全部壓縮,2,3,4...依次類推
雙端鏈表需要兩個(gè)指針:pre,next,而指針占8個(gè)字節(jié)的內(nèi)存空間,所以存儲(chǔ)一個(gè)元素就至少占16個(gè)字節(jié),如果元素比較多的情況下,內(nèi)存開(kāi)銷就會(huì)非常大,所以redis并沒(méi)有用quicklist 雙端鏈表,而是用更加緊湊的ziplist。