redis源碼剖析(五)—— 字符串,列表,哈希,集合,有序集合

文章目錄

    • 對象
    • REDIS_STRING (字符串)
    • REDIS_LIST 列表
    • REDIS_SET (集合)
    • REDIS_ZSET (有序集合)
    • REDIS_HASH (hash表)
    • int refcount(引用計數器)
    • unsigned lru:REDIS_LRU_BITS

對象

對于 Redis 來說使用了 redisObject 來對所有的對象進行了封裝:

typedef struct redisObject {// 對象類型unsigned type:4;// 編碼unsigned encoding:4;// 對象最后一次被訪問的時間unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */// 引用計數int refcount;// 指向實際值的指針void *ptr;} robj;

我們先關注兩個參數

type 和 encoding :

/* Object types */
// 對象類型
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4
/* Objects encoding. Some kind of objects like Strings and Hashes can be* internally represented in multiple ways. The 'encoding' field of the object* is set to one of this fields for this object. */
// 對象編碼
#define REDIS_ENCODING_RAW 0     /* Raw representation */
#define REDIS_ENCODING_INT 1     /* Encoded as integer */
#define REDIS_ENCODING_HT 2      /* Encoded as hash table */
#define REDIS_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
#define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define REDIS_ENCODING_INTSET 6  /* E  dncoded as intset */
#define REDIS_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define REDIS_ENCODING_EMBSTR 8  /* Embedded sds string encoding */

所以通過這段代碼我們可以知道 Redis 支持的數據類型如下:

type	類型
REDIS_STRING	字符串
REDIS_LIST	列表
REDIS_SET	集合
REDIS_ZSET	有序集合
REDIS_HASH	哈希表

Redis 的 Object 通過 ptr 指向具體的底層數據。Redis 的底層數據:

編碼	類型
REDIS_ENCODING_RAW	SDS 實現的動態字符串對象
REDIS_ENCODING_INT	整數實現的動態字符串對象
REDIS_ENCODING_HT	字典實現的 hash 對象
REDIS_ENCODING_ZIPMAP	壓縮map實現對對象,(3.0)版本未使用
REDIS_ENCODING_LINKEDLIST	雙向鏈表實現的對象
REDIS_ENCODING_ZIPLIST	壓縮列表實現的對象
REDIS_ENCODING_INTSET	整數集合實現的對象
REDIS_ENCODING_SKIPLIST	跳躍表實現的對象
REDIS_ENCODING_EMBSTR	使用 embstr 實現的動態字符串的對象

PS:下文會解釋 RAW 和 EMBSTR 的區別。

我就按照類型的順序看看 Redis 是怎么利用底層的數據結構實現不同的對象類型的。

REDIS_STRING (字符串)

Redis 的字符串 String,主要由 int、raw 和 emstr 底層數據實現的。 Redis 遵循以下的原則來決定使用底層數據結構的使用。

  • 如果數據是可以用 long 表示的整數,那就直接使用將ptr 的類型設置為long。將RedisObject 的 encoding 設置為 REDIS_ENCODING_INT。
  • 如果是一個字符串,那就需要考察字符串的字節數。如果字節數小于 39 就是使用 emstr,encoding 就使用 REDIS_ENCODING_EMBSTR,底層依然是我們之前介紹的 SDS 。
  • 如果字符串的長度超過 39 那就使用 raw,encoding 就是 REDIS_ENCODING_RAW。

問題來了:

  1. 為什么是 39 個字符?
    我們所String對象是由一個 RedisObject 和 sdshdr 組成的。所以我們如下公式在在64位的系統中,一個 emstr 最大占用 64bite。
    RedisObject(16b) + sds header(8b) + emstr + “\0”(1b) <= 64
    簡單的 四則運算 emstr <= 39。
  2. 一直都是 39 么?
    在 3.2 的版本的時候,作者對 sdshdr 做了修改,從 39 改成了 44。為什么?
    之前我們說過一個 sdshdr 包含三個參數,len、free 還有 buf,在3.2之前 len 和 free 的數據類型都是 unsigned int。 這個就是為什么上面的公式 sds header 是 8個字節了。新版本的 sdshdr 變成了 sdshdr8, sdshdr16 和 sdshdr32還有 sdshdr64。優化的地方就在于如果 buf 小,使用更小位數的數據類型來描述 len 和 free 減少他們占用的內存,同時增加了一個char flags。emstr使用了最小的 sdshdr8。 這個時候 sds header 就變成了(len(1b) + free(1b) + flags(1b)) 3個字節, 比之前的實現少了5個字節。 所以新版本的 emstr 的最大字節變成了 44。 還是那句話 Redis 對內存真是 “斤斤計較”
  3. SDS 是動態的為什么要區分 emstr 和 raw?
    區別在于生產 raw 的時候,會有兩步操作,分別產生 redisObject 和 sdshdr。而 emstr 一次成型,同時生成 redisObject 和 sdshdr 。就是為了高效。同時注意 emstr 是不可變的。
  4. 他們之間是什么關系?
    如果不能用 long 表示的數據,double 也是使用 raw 或者 emstr 來保存的。
    按照 Redis 的套路這三個底層數據在條件滿足的是是會發生裝換的。REDIS_ENCODING_INT 的數據如果不是整數了,那就會變成 raw 或者 emstr。emstr 發生了變化就會變成 raw。

REDIS_LIST 列表

Reids 的列表,底層是一個 ziplist 或者 linkedlist。

當列表對象保存的字符串元素的長度都小于64字節。
保存的元素數量小于512個。
兩個條件都滿足使用ziplist編碼,兩個條件任意一個不滿足時,ziplist會變為linkedlist。

3.2 以后使用 quicklist 保存。這個數據結構之前沒有講解過。

實際上 quicklist 是 ziplist 和雙向鏈表結合的產物。我們這樣理解,每個雙向鏈表的節點上是一個ziplist。之所以這么設計,應該是空間和時間之間的取舍或者一個折中的方案。 具體的實現我會在以后的文章里面具體分析。

REDIS_SET (集合)

Redis 的集合底層是一個 intset 或者 一個字典(hashtable)。

這個比較容易理解:

當集合都是整數且不超過512個的時候,就使用intset。
剩下都是用字典。
使用字典的時候,字典的每一個 key 就是集合的一個元素,對應的 value 就是一個 null。

REDIS_ZSET (有序集合)

Redis 的有序集合使用 ziplist 或者 skiplist 實現的。

元素小于 128 個
每個元素長度 小于 64 字節。
同時滿足以上條件使用ziplist,否則使用skiplist。

對于 ziplist 的實現,redis 使用相鄰的兩個 entity 分別保存對象以及對象的排序因子。這樣對于插入和查詢的復雜度都是 O(n) 的。直接看圖:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-oaNn00zx-1573628572107)(media/15663772797131/15663782632938.jpg)]

元素開發工程師,排序的因子就是月薪。(好吧php是世界上最好的語言)。

對于skiplist 的實現:

typedef struct zset{zskiplist *zsl;dict *dict}zset;

skiplist 的有序鏈表的實現不只是只有一個 skiplist ,還有一個字典存儲對象的key 和 排序因子的映射,這個是為了保證按照key 查詢的時候時間負責度為 O(1)。同時有序性依賴 skiplist 維護。大家可以看我之前的教程。所以直接看圖:

在這里插入圖片描述

REDIS_HASH (hash表)

Redis 的 hash 表 使用 ziplist 和 字典 實現的。

鍵值對的鍵和值都小于 64 個字節
鍵值對的數量小于 512。
都滿足的時候使用 ziplist,否則使用字典。

ziplist 的實現類似,類似 zset 的實現。兩個entity成對出現。一個存儲key,另一個存儲 velue。

ziplist
還是可以使用上面使用過的圖。這個時候 entity 不用排序。key 是職位名稱,velue 是對應的月薪。(好吧php還是世界上最好的語言)。與zset實現的區別就是查詢是 O(n) 的,插入直接往tail后面插入就行時間復雜度O(1)。

使用字典實現一個 hash表。好像沒有什么可以多說的。

int refcount(引用計數器)

這個參數是引用計數。Redis 自己管理內存,所以就使用了最簡單的內存管理方式–引用計數。

創建對象的時候計數器為1
每被一個地方引用,計數器加一
每被取消引用,計數器減一
計數器為0的時候,就說明沒有地方需要這個對象了。內存就會被 Redis 回收。

unsigned lru:REDIS_LRU_BITS

這個參數記錄了對象的最后一次活躍時間。

如果 Redis 開啟了淘汰策略,且淘汰的方式是 LRU 的時候,這個參數就派上了用場。Redis 會優先回收 lru 最久的對象。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/382368.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/382368.shtml
英文地址,請注明出處:http://en.pswp.cn/news/382368.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

函數sscanf小結

1.sscanf用于處理固定格式的字符串&#xff0c;包含在頭文件<cstdio>中&#xff0c;函數原型為&#xff1a; int sscanf(const char *buffer,const char*format,[]argument ]...); 其中:buffer代表著要存儲的數據&#xff0c;format 代表格式控制字符串&#xff0c;arg…

redis源碼剖析(六)—— Redis 數據庫、鍵過期的實現

文章目錄數據庫的實現數據庫讀寫操作鍵的過期實現數據庫的實現 我們先看代碼 server.h/redisServer struct redisServer{...//保存 db 的數組redisDb *db;//db 的數量int dbnum;... }再看redisDb的代碼&#xff1a; typedef struct redisDb {dict *dict; /*…

多益網絡 視頻面試面試總結20180816

1.首先是自我介紹&#xff1a;因為等了半個小時&#xff0c;所以有點兒緊張&#xff0c;只說了一下自己的學校&#xff0c;愛好和興趣&#xff1b; 2.介紹了一個自己的最成功的項目&#xff1a;我介紹了一個關于GPS導航的項目&#xff0c;介紹了項目的內容和項目的一些工作&am…

redis源碼剖析(七)—— Redis 數據結構dict.c

文章目錄dict.hdict.cdict.h //定義錯誤相關的碼 #define DICT_OK 0 #define DICT_ERR 1//實際存放數據的地方 typedef struct dictEntry {void *key;void *val;struct dictEntry *next; } dictEntry;//哈希表的定義 typedef struct dict {//指向實際的哈希表記錄(用數組開鏈的…

簡述linux中動態庫和靜態庫的制作調用流程

假設現在有這些文件&#xff1a;sub.c add.c div.c mul.c mainc head.h&#xff08;前4個.C文件的頭文件&#xff09; 1.靜態庫制作流程 gcc -c sub.c add.c div.c mul.c -->生成 .o目標文件文件 ar rcs libmycal.a *.o …

redis源碼剖析(八)—— 當你啟動Redis的時候,Redis做了什么

文章目錄啟動過程初始化server結構體main函數會調用initServer函數初始化服務器狀態載入持久化文件&#xff0c;還原數據庫開始監聽事件流程圖啟動過程 初始化server結構體從配置文件夾在加載參數初始化服務器載入持久化文件開始監聽事件 初始化server結構體 服務器的運行ID…

linux中錯誤總結歸納

1.使用gcc編譯C文件&#xff0c;C文件在for循環語句中出現變量定義 編譯器提示錯誤&#xff1a;“for”loop initial declarations are only allowed in C99 mode. note:use option -stdc99or-stdgnu99 to compile; 原因&#xff1a;gcc的標準是基于c89的&#xff0c;c89不能在…

redis源碼剖析(十一)—— Redis字符串相關函數實現

文章目錄初始化字符串字符串基本操作字符串拼接操作other獲取指定范圍里的字符串將字符串中的所有字符均轉為小寫的形式將字符串中所有字符均轉為大寫的形式字符串比較other#define SDS_ABORT_ON_OOM#include "sds.h" #include <stdio.h> #include <stdlib.…

makefile內容小結

makefile中每個功能主要分為三部分&#xff1a;目標&#xff0c;依賴條件和命令語句 1.支持對比更新的Makefile寫法&#xff08;只會編譯文件時.o文件和.c文件時間不一致的文件&#xff09; 2.使用makefile自動變量和自定義變量的makefile寫法 其中&#xff1a;這三個符號為ma…

事務隔離級別動圖演示

事務的基本要素&#xff08;ACID&#xff09; 原子性&#xff08;Atomicity&#xff09; 事務開始后所有操作&#xff0c;要么全部做完&#xff0c;要么全部不做&#xff0c;不可能停滯在中間環節。事務執行過程中出錯&#xff0c;會回滾到事務開始前的狀態&#xff0c;所有的…

C/C++的優點和缺點

1.C/C語言的優點 C語言是面向過程的語言&#xff0c;常用來編寫操作系統。C語言是從C語言發展過來的&#xff0c;是一門面向對象的語言&#xff0c;它繼承了C語言的優勢&#xff0c;同時也添加了三個主要的內容&#xff1a;Oriented-Object class,Template,STL. 1)C/C可以潛入…

C/C++命令行參數那點事

int main(int argc, char *argv[ ]); 1.命令行參數&#xff1a;在命令行中給定的參數&#xff1b; 2.命令行參數在對函數main的調用時&#xff0c;主要有兩個參數送到main,一個是argc(argument count),命令行參數的個數&#xff0c;另外一個是argv,命令行參數的數組,命令行參…

mysql row_id為什么是6字節?為什么是8字節

mysql row_id是幾個字節&#xff1f; row_id InnoDB表中在沒有默認主鍵的情況下會生成一個6字節空間的自動增長主鍵 row_id是整型還是字符型&#xff1f; 源代碼中 row_id 是 ib_uint64_t 這是 8字節 uint64_t 是整形 為什么是6個字節&#xff1f; P.S. Base64編碼說明 B…

linux中的man文檔結構

使用命令 man chapter章節號查找的內容

偽隨機數和真隨機數

偽隨機數小項目 猜數字游戲 //C語言 猜數字游戲 https://blog.csdn.net/csdn_kou/article/details/79785709 C語言之隨機數生成超詳解 https://blog.csdn.net/csdn_kou/article/details/79788815 在上面的文章中&#xff0c;使用固定函數就一直是生成固定的隨機結果&#…

linux中的IO函數

1)open函數&#xff1a;以特定的方式打開一個文件&#xff1b; 頭文件&#xff1a;sys/type.h sys/stat.h fcntl.h 返回值&#xff1a;錯誤則返回-1&#xff0c;正確則返回文件描述符&#xff08;int類型&#xff0c;范圍為3~1023,文件的標號&#xff09; 函數原型&#xff…

ps -ef和ps aux

ps -ef和ps aux ps -ef unix風格 -e 列出所有進程 -f 完整格式 UID PID PPID C STIME TTY TIME CMD root 1 0 0 8月27 ? 00:25:08 /usr/lib/systemd/systemd --switched-root --system --deserialize 22 root 2 0 0 8月…

Linux中screen的用法

screen 查看當前有多少窗口 [rootpython ~]# screen -ls There are screens on:20706.khz (Attached)20679.khz (Attached)20453.khz (Attached)20143.khz (Detached)16993.pts-2.python (Attached) 5 Sockets in /var/run/screen/S-root.新建一…

linux文件操作相關函數

&#xff08;1&#xff09;stat函數&#xff1a;顯示文件的相關信息&#xff08;類似于 ls -l的感覺&#xff09; 頭文件及函數原型&#xff1a; 函數參數:path:文件的路徑&#xff0c;buf是指待寫入的文件信息&#xff0c;fd:表示文件描述符&#xff1b; stat,fstat,lstat三者…

linux查看硬盤是不是ssd固態硬盤

linux查看硬盤是不是ssd固態硬盤 sdb是ssd、sr0是SATA [root 01 ~]# cat /sys/block/sdb/queue/rotational 0 [root 01 ~]# cat /sys/block/sr0/queue/rotational 1