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






