第一遍略讀筆記
字符串
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期間,字段的刪改查操作都會執行兩次,新增只會在新哈希表執行
- 爲ht[1]分配空間,讓字典同時持有兩個哈希表
- 設置rehashidx=0表示正在rehash
- 在rehash期間,每次對字段執行增刪改,除了執行指定操作外,還會將ht[0]哈希表在rehashidx索引上所有鍵值對都複製到ht[1]上,最後rehashidx屬性自增
- 隨着字典操作不斷進行,最終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使用壓縮列表實現
- 哈希對象所有鍵和值都小於64字節
- 哈希對象鍵值對數量小於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。
- 保存元素成員長度都小於64字節
- 有序集合元素數量小於128個
redis爲何同時使用跳躍表和字典來實現有序集合呢,而且還會保存兩次???
理論上可以單獨使用一種數據結構實現,但同時使用的性能會更高
- 如果我們只使用字段保存,保留了以0(1)時間複雜度查詢成員的優點,但是zrank,zrange等排序命令,則需要再次進行排序,完成排序至少0(NlogN)。
- 如果我們只使用跳躍表實現有序集合,排序可以很快完成,但是查找又從0(1)上升到0(lonN)
- 最終字典和跳躍表會共享元素成員和分值,不會造成任何內存浪費
類型檢查與多態命令
llen命令:確保執行命令的是列表鍵,而且要好根據值對象的編碼,使用正確的實現
- 類型檢查:爲了確保只有指定類型的鍵可以執行某種特定操作,根據值對象類型進行檢查
- 多態命令:爲了同一條命令可以處理不同值類型,可以根據值對象編碼使用正確的實現
內存回收和共享
內存回收
c語言不具備自動內存回收功能。
解決:redis使用引用計數實現了內存自動回收機制。
- 創建新對象時,引用計數初始化爲1
- 被調用時自增1
- 不被使用時自減1
- 當引用計數變爲0時,對象佔用內存將被釋放
內存共享
引用計數器還能實現對象共享功能。
redis會共享值爲0到9999的字符串對象