日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

redis源码剖析(三)——基础数据结构

發(fā)布時(shí)間:2023/11/30 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 redis源码剖析(三)——基础数据结构 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

    • SDS
    • 鏈表
    • 字典

這篇文章關(guān)于 Redis 的基礎(chǔ)數(shù)據(jù):

SDS

SDS (Simple Dynamic String)是 Redis 最基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)。直譯過來就是”簡單的動(dòng)態(tài)字符串“。Redis 自己實(shí)現(xiàn)了一個(gè)動(dòng)態(tài)的字符串,而不是直接使用了 C 語言中的字符串。

sds 的數(shù)據(jù)結(jié)構(gòu):

struct sdshdr {// buf 中已占用空間的長度int len;// buf 中剩余可用空間的長度int free;// 數(shù)據(jù)空間char buf[]; };

所以一個(gè) SDS 的就如下圖:

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-t2YCOSKC-1573627290412)(media/15663750838907/15663751675407.jpg)]
所以我們看到,sds 包含3個(gè)參數(shù)。buf 的長度 len,buf 的剩余長度,以及buf。

為什么這么設(shè)計(jì)呢?

  • 可以直接獲取字符串長度。
    C 語言中,獲取字符串的長度需要用指針遍歷字符串,時(shí)間復(fù)雜度為 O(n),而 SDS 的長度,直接從len 獲取復(fù)雜度為 O(1)。

  • 杜絕緩沖區(qū)溢出。
    由于C 語言不記錄字符串長度,如果增加一個(gè)字符傳的長度,如果沒有注意就可能溢出,覆蓋了緊挨著這個(gè)字符的數(shù)據(jù)。對于SDS 而言增加字符串長度需要驗(yàn)證 free的長度,如果free 不夠就會(huì)擴(kuò)容整個(gè) buf,防止溢出。

  • 減少修改字符串長度時(shí)造成的內(nèi)存再次分配。
    redis 作為高性能的內(nèi)存數(shù)據(jù)庫,需要較高的相應(yīng)速度。字符串也很大概率的頻繁修改。 SDS 通過未使用空間這個(gè)參數(shù),將字符串的長度和底層buf的長度之間的額關(guān)系解除了。buf的長度也不是字符串的長度。基于這個(gè)分設(shè)計(jì) SDS 實(shí)現(xiàn)了空間的預(yù)分配和惰性釋放。

    • 預(yù)分配
      如果對 SDS 修改后,如果 len 小于 1MB 那 len = 2 * len + 1byte。 這個(gè) 1 是用于保存空字節(jié)。
      如果 SDS 修改后 len 大于 1MB 那么 len = 1MB + len + 1byte。
    • 惰性釋放
      如果縮短 SDS 的字符串長度,redis并不是馬上減少 SDS 所占內(nèi)存。只是增加 free 的長度。同時(shí)向外提供 API 。真正需要釋放的時(shí)候,才去重新縮小 SDS 所占的內(nèi)存
    • 二進(jìn)制安全。
      C 語言中的字符串是以 ”\0“ 作為字符串的結(jié)束標(biāo)記。而 SDS 是使用 len 的長度來標(biāo)記字符串的結(jié)束。所以SDS 可以存儲字符串之外的任意二進(jìn)制流。因?yàn)橛锌赡苡械亩M(jìn)制流在流中就包含了”\0“造成字符串提前結(jié)束。也就是說 SDS 不依賴 “\0” 作為結(jié)束的依據(jù)。

    • 兼容C語言
      SDS 按照慣例使用 ”\0“ 作為結(jié)尾的管理。部分普通C 語言的字符串 API 也可以使用。

    鏈表

    C語言中并沒有鏈表這個(gè)數(shù)據(jù)結(jié)構(gòu)所以 Redis 自己實(shí)現(xiàn)了一個(gè)。Redis 中的鏈表是:

    typedef struct listNode {// 前置節(jié)點(diǎn)struct listNode *prev;// 后置節(jié)點(diǎn)struct listNode *next;// 節(jié)點(diǎn)的值void *value;} listNode;

    非常典型的雙向鏈表的數(shù)據(jù)結(jié)構(gòu)。

    同時(shí)為雙向鏈表提供了如下操作的函數(shù):

    /** 雙端鏈表迭代器*/ typedef struct listIter {// 當(dāng)前迭代到的節(jié)點(diǎn)listNode *next;// 迭代的方向int direction;} listIter;/** 雙端鏈表結(jié)構(gòu)*/ typedef struct list {// 表頭節(jié)點(diǎn)listNode *head;// 表尾節(jié)點(diǎn)listNode *tail;// 節(jié)點(diǎn)值復(fù)制函數(shù)void *(*dup)(void *ptr);// 節(jié)點(diǎn)值釋放函數(shù)void (*free)(void *ptr);// 節(jié)點(diǎn)值對比函數(shù)int (*match)(void *ptr, void *key);// 鏈表所包含的節(jié)點(diǎn)數(shù)量unsigned long len;} list;

    鏈表的結(jié)構(gòu)比較簡單,數(shù)據(jù)結(jié)構(gòu)如下:
    [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-pwkSNd6w-1573627290413)(media/15663750838907/15663752964435.jpg)]

    總結(jié)一下性質(zhì):

    • 雙向鏈表,某個(gè)節(jié)點(diǎn)尋找上一個(gè)或者下一個(gè)節(jié)點(diǎn)時(shí)間復(fù)雜度 O(1)。
    • list 記錄了 head 和 tail,尋找 head 和 tail 的時(shí)間復(fù)雜度為 O(1)。
    • 獲取鏈表的長度 len 時(shí)間復(fù)雜度 O(1)。

    字典

    字典數(shù)據(jù)結(jié)構(gòu)極其類似 java 中的 Hashmap。

    Redis的字典由三個(gè)基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)組成。最底層的單位是哈希表節(jié)點(diǎn)。結(jié)構(gòu)如下:

    typedef struct dictEntry {// 鍵void *key;// 值union {void *val;uint64_t u64;int64_t s64;} v;// 指向下個(gè)哈希表節(jié)點(diǎn),形成鏈表struct dictEntry *next;} dictEntry;

    實(shí)際上哈希表節(jié)點(diǎn)就是一個(gè)單項(xiàng)列表的節(jié)點(diǎn)。保存了一下下一個(gè)節(jié)點(diǎn)的指針。 key 就是節(jié)點(diǎn)的鍵,v是這個(gè)節(jié)點(diǎn)的值。這個(gè) v 既可以是一個(gè)指針,也可以是一個(gè) uint64_t或者 int64_t 整數(shù)。*next 指向下一個(gè)節(jié)點(diǎn)。

    通過一個(gè)哈希表的數(shù)組把各個(gè)節(jié)點(diǎn)鏈接起來:

    typedef struct dictht {// 哈希表數(shù)組dictEntry **table;// 哈希表大小unsigned long size;// 哈希表大小掩碼,用于計(jì)算索引值// 總是等于 size - 1unsigned long sizemask;// 該哈希表已有節(jié)點(diǎn)的數(shù)量unsigned long used;} dictht;

    通過圖示我們觀察:

    [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-FVEBYd5O-1573627290413)(media/15663750838907/15663753610286.jpg)]

    實(shí)際上,如果對java 的基本數(shù)據(jù)結(jié)構(gòu)了解的同學(xué)就會(huì)發(fā)現(xiàn),這個(gè)數(shù)據(jù)結(jié)構(gòu)和 java 中的 HashMap 是很類似的,就是數(shù)組加鏈表的結(jié)構(gòu)。

    字典的數(shù)據(jù)結(jié)構(gòu):

    typedef struct dict {// 類型特定函數(shù)dictType *type;// 私有數(shù)據(jù)void *privdata;// 哈希表dictht ht[2];// rehash 索引// 當(dāng) rehash 不在進(jìn)行時(shí),值為 -1int rehashidx; /* rehashing not in progress if rehashidx == -1 */// 目前正在運(yùn)行的安全迭代器的數(shù)量int iterators; /* number of iterators currently running */} dict;

    其中的dictType 是一組方法,代碼如下:

    /** 字典類型特定函數(shù)*/ typedef struct dictType {// 計(jì)算哈希值的函數(shù)unsigned int (*hashFunction)(const void *key);// 復(fù)制鍵的函數(shù)void *(*keyDup)(void *privdata, const void *key);// 復(fù)制值的函數(shù)void *(*valDup)(void *privdata, const void *obj);// 對比鍵的函數(shù)int (*keyCompare)(void *privdata, const void *key1, const void *key2);// 銷毀鍵的函數(shù)void (*keyDestructor)(void *privdata, void *key);// 銷毀值的函數(shù)void (*valDestructor)(void *privdata, void *obj);} dictType;

    字典的數(shù)據(jù)結(jié)構(gòu)如下圖:
    [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-PI20viEC-1573627290414)(media/15663750838907/15663754115428.jpg)]

    這里我們可以看到一個(gè)dict 擁有兩個(gè) dictht。一般來說只使用 ht[0],當(dāng)擴(kuò)容的時(shí)候發(fā)生了rehash的時(shí)候,ht[1]才會(huì)被使用。

    當(dāng)我們觀察或者研究一個(gè)hash結(jié)構(gòu)的時(shí)候偶我們首先要考慮的這個(gè) dict 如何插入一個(gè)數(shù)據(jù)?

    我們梳理一下插入數(shù)據(jù)的邏輯。

    • 計(jì)算Key 的 hash 值。找到 hash 映射到 table 數(shù)組的位置。

    • 如果數(shù)據(jù)已經(jīng)有一個(gè) key 存在了。那就意味著發(fā)生了 hash 碰撞。新加入的節(jié)點(diǎn),就會(huì)作為鏈表的一個(gè)節(jié)點(diǎn)接到之前節(jié)點(diǎn)的 next 指針上。

    • 如果 key 發(fā)生了多次碰撞,造成鏈表的長度越來越長。會(huì)使得字典的查詢速度下降。為了維持正常的負(fù)載。Redis 會(huì)對 字典進(jìn)行 rehash 操作。來增加 table 數(shù)組的長度。所以我們要著重了解一下 Redis 的 rehash。步驟如下:

      • 根據(jù)ht[0] 的數(shù)據(jù)和操作的類型(擴(kuò)大或縮小),分配 ht[1] 的大小。
      • 將 ht[0] 的數(shù)據(jù) rehash 到 ht[1] 上。
      • rehash 完成以后,將ht[1] 設(shè)置為 ht[0],生成一個(gè)新的ht[1]備用。
    • 漸進(jìn)式的 rehash 。

    其實(shí)如果字典的 key 數(shù)量很大,達(dá)到千萬級以上,rehash 就會(huì)是一個(gè)相對較長的時(shí)間。所以為了字典能夠在 rehash 的時(shí)候能夠繼續(xù)提供服務(wù)。Redis 提供了一個(gè)漸進(jìn)式的 rehash 實(shí)現(xiàn)
    rehash的步驟如下:

  • 分配 ht[1] 的空間,讓字典同時(shí)持有 ht[1] 和 ht[0]。
  • 在字典中維護(hù)一個(gè) rehashidx,設(shè)置為 0 ,表示字典正在 rehash。
  • 在rehash期間,每次對字典的操作除了進(jìn)行指定的操作以外,都會(huì)根據(jù) ht[0] 在 rehashidx 上對應(yīng)的鍵值對 rehash 到 ht[1]上。
  • 隨著操作進(jìn)行, ht[0] 的數(shù)據(jù)就會(huì)全部 rehash 到 ht[1] 。設(shè)置ht[0] 的 rehashidx 為 -1,漸進(jìn)的 rehash 結(jié)束。
    這樣保證數(shù)據(jù)能夠平滑的進(jìn)行 rehash。防止 rehash 時(shí)間過久阻塞線程。
    • 在進(jìn)行 rehash 的過程中,如果進(jìn)行了 delete 和 update 等操作,會(huì)在兩個(gè)哈希表上進(jìn)行。如果是 find 的話優(yōu)先在ht[0] 上進(jìn)行,如果沒有找到,再去 ht[1] 中查找。如果是 insert 的話那就只會(huì)在 ht[1]中插入數(shù)據(jù)。這樣就會(huì)保證了 ht[1] 的數(shù)據(jù)只增不減,ht[0]的數(shù)據(jù)只減不增。

    總結(jié)

    以上是生活随笔為你收集整理的redis源码剖析(三)——基础数据结构的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。