類型檢查和多態(tài)命令的實(shí)現(xiàn)
redis中用于鍵操作的命令基本上可以分為兩類:
可以對任何類型的鍵執(zhí)行, eg.
del
,expire
,rename
,type
,object
只能對特定命令執(zhí)行的鍵,
eg.
set
瞳筏、get
腥例、append
、strlen
等命令只能對字符串鍵執(zhí)行?
hdel
、hset
、hget
、hlen
等命令只能對hash鍵執(zhí)行?
rpush
虐先、lpop
、linsert
派敷、llen
等只能對列表鍵執(zhí)行?
sadd
蛹批、spop
、sinter
篮愉、scard
等命令只能對集合鍵執(zhí)行?
zadd
腐芍、zcard
、zrank
试躏、zcore
等命令只能對有序集合鍵執(zhí)行
類型檢查的實(shí)現(xiàn)
類型特定命令所進(jìn)行的類型檢查是通過redisObject
結(jié)構(gòu)的type屬性
來實(shí)現(xiàn)的.
- 在執(zhí)行一個(gè)類型特定命令之前, 服務(wù)器先檢查輸入數(shù)據(jù)庫鍵的值對象是否為執(zhí)行命令所需要的類型, 是猪勇、就執(zhí)行
- 否則, server拒絕執(zhí)行、并向client返回一個(gè)類型錯(cuò)誤
eg. 對于llen命令:
在執(zhí)行l(wèi)len命令前冗酿、server會(huì)先檢查輸入數(shù)據(jù)庫鍵的值對象是否為列表類型
, 即: 檢查redisObject
的type屬性
是否為redis_list
, 是的話埠对、執(zhí)行 llen命令, 否則返回類型錯(cuò)誤
多態(tài)命令的實(shí)現(xiàn)
Redis除了會(huì)根據(jù)值對象的類型來判斷是否能執(zhí)行特定命令外络断、還會(huì)根據(jù)值對象的編碼方式裁替、選擇正確的命令實(shí)現(xiàn)代碼來執(zhí)行命令
eg. 對一個(gè)鍵執(zhí)行 llen
命令, 則服務(wù)器除了要確保執(zhí)行命令的是列表鍵之外, 還要根據(jù)鍵的值對象所使用的編碼來選擇正確的llen命令實(shí)現(xiàn)
- 若列表對象的編碼為 ziplist, 那么說明列表對象的實(shí)現(xiàn)為壓縮列表, 程序?qū)⑹褂?ziplistLen 函數(shù)來返回列表的長度
- 若列表對象的編碼為 linkedlist, 說明列表對象的實(shí)現(xiàn)為雙端鏈表, 程序?qū)⑹褂?listLength 函數(shù)來返回列表的長度
借用面向?qū)ο蟮男g(shù)語來說、可以認(rèn)為llen命令的實(shí)現(xiàn)是多態(tài)的, 只要執(zhí)行 llen 命令的是列表鍵貌笨、無論值對象是 ziplist 還是 linkedlist 編碼弱判、命令都可以正常執(zhí)行
del
、expire
等命令和llen
命令的區(qū)別在于锥惋、前者是基于類型的多態(tài), 一個(gè)命令可以同時(shí)處理多種不同類型的鍵昌腰、而后者是基于編碼的多態(tài): 一個(gè)命令可以同時(shí)用于處理多種不同的編碼
內(nèi)存回收
因?yàn)镃語言并不具備內(nèi)存回收功能, redis 在自己的對象系統(tǒng)中構(gòu)建了一個(gè)引用計(jì)數(shù)(reference counting) 技術(shù)來實(shí)現(xiàn)內(nèi)存回收機(jī)制, 通過引用計(jì)數(shù)機(jī)制开伏、程序可以通過跟蹤對象的引用計(jì)數(shù)信息、在適當(dāng)?shù)臅r(shí)候自動(dòng)釋放對象并進(jìn)行內(nèi)存回收
每個(gè)對象的引用計(jì)數(shù)信息由 RedisObject 結(jié)構(gòu)的 refcount屬性
記錄:
typedef struct redisObject {
// ...
int refcount; // 引用計(jì)數(shù)
// ...
} robj;
對象的引用技術(shù)信息會(huì)隨著對象的使用狀態(tài)不斷變化
- 創(chuàng)建一個(gè)新的對象時(shí)遭商、引用計(jì)數(shù)初始化為1
- 對象被一個(gè)新的程序引用時(shí)固灵、引用計(jì)數(shù)值 +1
- 對象不再被一個(gè)程序引用時(shí)、引用計(jì)數(shù)值 -1
- 對象的引用計(jì)數(shù)值變?yōu)?時(shí)劫流、對象所占用的內(nèi)存會(huì)被釋放
下邊是修改對象引用計(jì)數(shù)的API
函數(shù) | 作用 |
---|---|
incrRefCount | 將對象的引用計(jì)數(shù)值+1 |
decrRefCount | 將對象的引用計(jì)數(shù)值-1, 當(dāng)對象的引用計(jì)數(shù)值=0時(shí)巫玻、釋放對象 |
resetRefCount | 將對象的引用計(jì)數(shù)值設(shè)為0, 但不釋放對象、需要重設(shè)對象引用值是使用 |
其它不同類型的對象也會(huì)經(jīng)歷類似的過程
共享對象
除了實(shí)現(xiàn)引用計(jì)數(shù)內(nèi)存回收機(jī)制外祠汇、對象的引用計(jì)數(shù)屬性還帶有對象共享的作用.
eg. A鍵創(chuàng)建了一個(gè)包含整數(shù)值100的字符串對象作為值對象, 此時(shí)若B鍵也想要?jiǎng)?chuàng)建一個(gè)同樣保存了整數(shù)值100的字符串對象作為值對象仍秤、那么Server有兩種做法:
- 為鍵B創(chuàng)建一個(gè)包含整數(shù)值100的字符串對象
- 讓鍵A和鍵B共享同一個(gè)字符串對象
明顯, 第二種方式更節(jié)約內(nèi)存, 在Redis中、多個(gè)鍵共享同一個(gè)值需要執(zhí)行以下步驟:
- 將數(shù)據(jù)庫鍵的值指向一個(gè)現(xiàn)有的值對象
- 將被共享的值對象的引用計(jì)數(shù)+1
**注意: **
創(chuàng)建共享字符串對象的數(shù)量可以通過修改 redis.h/redis_shared_integers
常量來修改
eg, 創(chuàng)建一個(gè)值為100的鍵a, 使用object refcount
命令查看a的引用計(jì)數(shù), 會(huì)發(fā)現(xiàn)值為2
redis> set a 100
OK
redis> object refcount a
(integer) 2
引用這個(gè)值對象的兩個(gè)程序分表是持有這個(gè)值對象的服務(wù)器程序, 及共享這個(gè)值對象的鍵A
另外: 這些共享對象不單單只有字符串鍵可以使用, 那些在數(shù)據(jù)結(jié)構(gòu)中嵌套了字符串對象的對象(linkedlist編碼的列表對象可很、hashtable編碼的hash對象诗力、hashtable編碼的集合對象及zset編碼的有序集合對象)等都可以使用這些共享對象
思考
為什么redis不共享包含字符串的對象?
當(dāng)服務(wù)器考慮將一個(gè)共享對象設(shè)置為鍵的值對象時(shí)、程序需要檢查給定的共享對象和鍵想創(chuàng)建的目標(biāo)對象是否完全相同, 只有在共享對象和目標(biāo)對象完全相同的情況下我抠、程輝才會(huì)將共享對象的用作鍵的值對象苇本、而一個(gè)共享對象保存的值越復(fù)雜、驗(yàn)證兩者相同的復(fù)雜度就會(huì)越高, 消耗的CPU時(shí)間也會(huì)越多
- 若共享對象保存整數(shù)值的字符串對象屿良、那么驗(yàn)證操作的復(fù)雜度為 O(1)
- 若共享對象是保存字符串值的字符串對象圈澈、那么驗(yàn)證操作的復(fù)雜度為 O(N)
- 若共享對象是包含了多個(gè)值(或者對象)的對象, 比如列表對象或者h(yuǎn)ash對象、驗(yàn)證的復(fù)雜度將是O(N2)
因此尘惧、盡管共享更復(fù)雜的對象可以節(jié)約更多內(nèi)存康栈、但受到CPU時(shí)間的限制、redis只對包含整數(shù)值的字符串對象進(jìn)行共享
對象的空轉(zhuǎn)時(shí)長
除了前邊介紹過的type
喷橙、encoding
啥么、ptr
和 refcount
4個(gè)屬性外, redisObject結(jié)構(gòu)包含的最后一個(gè)屬性為 lru屬性
, 它記錄了對象最后一次被命令訪問的時(shí)間
typedef struct redisObject {
// ...
unsigned lru:22;
// ...
} robj;
object idletime
命令可以打印出給定鍵的空轉(zhuǎn)時(shí)長, 就是通過當(dāng)前時(shí)間 - 鍵的lru時(shí)間得到的
注意:
Object idletime
的實(shí)現(xiàn)是特殊的, 它在訪問鍵的時(shí)候、不會(huì)修改值對象的lru屬性
除了使用 命令打印鍵的空轉(zhuǎn)時(shí)長, lru屬性還用于回收內(nèi)存, 當(dāng)設(shè)置了 maxmemory 選項(xiàng), 且服務(wù)器用于回收內(nèi)存的算法為 volatile-lru
或者 allkeys-lru
時(shí)贰逾、當(dāng)服務(wù)器占用內(nèi)存超過了 maxmemory設(shè)置的上限值時(shí), 空轉(zhuǎn)時(shí)長較高的鍵會(huì)優(yōu)先被服務(wù)器釋放.
重點(diǎn)回顧
- redis數(shù)據(jù)庫的中每個(gè)鍵值對的鍵和值都是一個(gè)對象
- redis共有字符串悬荣、列表、hash疙剑、結(jié)合氯迂、有序集合五種類型的對象, 每種類型的對象至少有2種或以上的編碼方式, 不同的編碼可以在不同的場景上優(yōu)化對象的使用概率
- 服務(wù)器在執(zhí)行某些命令之前、會(huì)先檢查給定鍵的類型能否執(zhí)行
- redis的對象系統(tǒng)帶有引用計(jì)數(shù)實(shí)現(xiàn)的內(nèi)存回收機(jī)制, 當(dāng)一個(gè)對象不再被使用時(shí)言缤、該對象占用的內(nèi)存會(huì)被自動(dòng)釋放
- redis會(huì)共享值為 0 到 9999 的字符串對象
- 對象會(huì)記錄自己最后一次被訪問的時(shí)間, 這個(gè)時(shí)間還可以用于計(jì)算對象的空轉(zhuǎn)時(shí)長