《Redis設計與實現》之數據結構與對象

第一遍略讀筆記

字符串

SDS用法:字符串值,AOF緩衝區,輸入緩衝區
sds={
int len; //判定字符串是否結束
int free; //表示空閒長度
char buf[]; //保存二進制數據
}
容易獲取長度
不會造成緩衝區溢出
減少修改字符串長度所需內存分配次數
二進制安全
兼容c字符串安徽唸書

鏈表

在這裏插入圖片描述
列表鍵,發佈訂閱,慢查詢,監視器
node={prev,next,value} 雙端隊列
list={head,tail,len}
dup fee match

字典

普通哈希表結構

Node[] table;
Node{hash,key,value,next}

字典結構 一個哈希表有多個哈希表

## 哈希表節點
dictEntry={key,value,dictEntry next}   
## 哈希表
dictht={dictEntry[],size,mask,used} 哈希表節點,size是哈希表大小,mask=size-1,used哈希表已有的節點
## 字典,有兩個哈希表
dict={type,data,dictht[2],rehashinx}  字典:data和type爲針對不同類型鍵值對設計,dictht[1]擴容使用

在這裏插入圖片描述

擴容
沒執行bgsave,bgrewriteaof命令,load_factor>=1
執行bgsave,bgrewriteaof命令,load_factor>=5
在子進程存在期間,提高擴容所需負載因子,避免再子進程存在期間進行擴容。

漸進式rehash
若有成千上萬個鍵值對,一次重rehash回使得服務在一段時間停止服務。

總體思路:分而治之,將rehash工作量分攤到對字段的其他操作中,避免集中rehash帶來大量計算。但是在rehash期間,字段的刪改查操作都會執行兩次,新增只會在新哈希表執行

  1. 爲ht[1]分配空間,讓字典同時持有兩個哈希表
  2. 設置rehashidx=0表示正在rehash
  3. 在rehash期間,每次對字段執行增刪改,除了執行指定操作外,還會將ht[0]哈希表在rehashidx索引上所有鍵值對都複製到ht[1]上,最後rehashidx屬性自增
  4. 隨着字典操作不斷進行,最終ht[0]哈希表所有鍵值對都重哈希到ht[1]上

有序集合

zskiplistNode={
	level={next,span},
	score,
	object
}
zskiplist={header,tail,level,length}   //level最大層數,length個數

跳躍表是一種有序數據結構。它通過每個節點維持多個指向其他節點的指針,達到快速訪問節點的目的。
支持平均0(lonN),最壞0(N)複雜度進行查找節點。還可以通過順序操作批量處理節點。
跳躍表的效率可以和平衡樹相媲美,而且跳躍表實現比平衡樹更加簡單,有很多程序使用跳躍表來代替平衡樹。

redis使用跳躍表作爲有序集合鍵的底層實現之一,若有序集合較多,有序集合元素成員是較長字符串,就會選用跳躍表

舉例
fruit-price是一個有序集合鍵,以水果爲value,水果價格爲score
zrange fruit-price 0 2  順序查詢前3個水果的價格
分析:
fruit-price有序集合鍵都保存在一個跳躍表中。

壓縮列表

是爲了節約內存開發得,是由連續內存塊組成得順序型數據結構。
底層很複雜待學習

整數集合

redis對象

Redis並沒有直接使用這些數據結構實現鍵值對數據庫,而是基於這些數據結構創建了一個對象系統,這個系統包含五種類型對象:字符串對象,列表對象,集合對象,字典對象,有序列表對象
redis爲何要構建一個對象系統呢??

  • 執行命令前,可根據對象類型判斷此對象是否可執行該命令
  • 針對不同應用場景,爲對象設置多種不同得數據結構實現。
  • 實現基於引用計數技術得內存回收機制,當不使用對象時自動回收
  • 攜帶訪問時間記錄信息,可計算數據庫鍵的空轉時長,從而優化回收

對象類型和編碼

redis使用對象表示數據庫的鍵和值,每當每次插入一個鍵值對時,至少創建兩個對象**{鍵對象+值對象}**

typedef struct redisObject{
  unsigned type;           // 定義類型
  unsigned encoding;       //定義編碼
  void *ptr;               //指向底層數據結構的執行
}
  • 類型
    type命令返回的是值對象的類型。
    在這裏插入圖片描述
  • 編碼
    記錄對象使用哪種數據結構,每種數據結構有自己的編碼
    每種對象至少使用兩種編碼
    object encoding key 查看編碼
    在這裏插入圖片描述
    幾大提升了redis的靈活率,比如列表對象元素比較少時使用壓縮列表,元素比較多時使用雙端隊列。

字符串對象

編碼可以是int,raw,embstr
int: 整形值
raw:字符串值且長度大於32字節,簡單動態sds
embstr:字符串值且長度小於32字節,簡單動態sds 所有數據都存儲在一塊連續內存,只讀

列表對象

編碼可以是ziplist和linkedlist

rpush numbers 1 "three" 5
  • ziplist使用壓縮列表作爲底層實現
    使用情況:
  • 列表對象所有字符串長度都小於64字節
  • 列表對象元素數量小於512個
    在這裏插入圖片描述
  • linkedlist使用雙端隊列實現
    在這裏插入圖片描述

哈希對象

編碼可以是ziplist或者hashtable

  • ziplist使用壓縮列表實現
  1. 哈希對象所有鍵和值都小於64字節
  2. 哈希對象鍵值對數量小於512個
    在這裏插入圖片描述

hashtable使用字典實現
哈希對象的每個鍵值對 都使用字典的鍵值對來保存下圖有點問題
在這裏插入圖片描述

集合對象

編碼可以是intset或者hashtable
sadd numbers 1 3 5

  • intset編碼使用整數集合實現
  • 集合對象保存的都是整數值
  • 集合對象元素個數不超過512個
    在這裏插入圖片描述
  • hashtable使用字段實現
    在這裏插入圖片描述

有序集合對象

zadd price apple 8.5 banana 5.0 cherry 6.0
編碼可以是ziplist或者skplist

  • ziplist使用壓縮列表實現
    在這裏插入圖片描述
  • ziplist使用跳躍表+字典實現
    dict字典的哈希表都保存了一個集合元素,字典的鍵保存了value,字典的值保存了score。
  1. 保存元素成員長度都小於64字節
  2. 有序集合元素數量小於128個
    在這裏插入圖片描述
    redis爲何同時使用跳躍表和字典來實現有序集合呢,而且還會保存兩次???
    理論上可以單獨使用一種數據結構實現,但同時使用的性能會更高
  • 如果我們只使用字段保存,保留了以0(1)時間複雜度查詢成員的優點,但是zrank,zrange等排序命令,則需要再次進行排序,完成排序至少0(NlogN)。
  • 如果我們只使用跳躍表實現有序集合,排序可以很快完成,但是查找又從0(1)上升到0(lonN)
  • 最終字典和跳躍表會共享元素成員和分值,不會造成任何內存浪費

類型檢查與多態命令

llen命令:確保執行命令的是列表鍵,而且要好根據值對象的編碼,使用正確的實現

  • 類型檢查:爲了確保只有指定類型的鍵可以執行某種特定操作,根據值對象類型進行檢查
  • 多態命令:爲了同一條命令可以處理不同值類型,可以根據值對象編碼使用正確的實現

內存回收和共享

內存回收

c語言不具備自動內存回收功能。
解決:redis使用引用計數實現了內存自動回收機制。

  1. 創建新對象時,引用計數初始化爲1
  2. 被調用時自增1
  3. 不被使用時自減1
  4. 當引用計數變爲0時,對象佔用內存將被釋放

內存共享

引用計數器還能實現對象共享功能。
redis會共享值爲0到9999的字符串對象

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章