您好,登錄后才能下訂單哦!
在Redis數(shù)據(jù)庫(kù)里,包含字符串值得鍵值對(duì)在底層都是由SDS實(shí)現(xiàn)的。
如:127.0.0.1:6379> set msg hello
鍵msg是一個(gè)字符串對(duì)象,其底層實(shí)現(xiàn)是一個(gè)值為"msg"的SDS。
值也是一個(gè)字符串對(duì)象,其底層實(shí)現(xiàn)是一個(gè)值為"hello"的SDS。
SDS結(jié)構(gòu)定義:
SDS遵循C字符串以‘\0’作為結(jié)尾,保存其的一個(gè)字節(jié)的額外空間不計(jì)入SDS的len屬性里,且添加空字符串到末尾等操作都是由SDS自動(dòng)完成的,遵循空字符串結(jié)尾的好處是可以重用C字符串函數(shù)庫(kù)里的函數(shù)。
SDS相對(duì)于C字符串的優(yōu)勢(shì):
常數(shù)復(fù)雜度獲取字符串長(zhǎng)度:
由于SDS在len屬性中保存了字符串長(zhǎng)度,因此不需要遍歷字符串計(jì)算長(zhǎng)度。
杜絕緩沖區(qū)溢出:
由于C字符串不記錄自身長(zhǎng)度,當(dāng)拼接一個(gè)長(zhǎng)度大于當(dāng)前字符數(shù)組剩余空間的字符串時(shí)則會(huì)出現(xiàn)緩沖區(qū)溢出;
而SDS再修改前會(huì)先判斷剩余空間是否能裝下修改后的字符串,若不能,則會(huì)先進(jìn)行擴(kuò)容,其擴(kuò)容規(guī)則為:
--若修改后的SDS長(zhǎng)度,即len屬性的值小于1MB,那么程序?qū)⒎峙浜蚻en屬性同樣大小的未使用空間,即屬性 free的值和len相同,此時(shí)buf數(shù)組實(shí)際長(zhǎng)度為:len + free + 1,多余的1用于保存結(jié)尾的空字符。
--若修改后的SDS長(zhǎng)度大于等于1MB,那么程序?qū)㈩~外分配1MB的未使用空間。
二進(jìn)制安全:
C字符以空字符作為結(jié)尾標(biāo)志,若字符串中包含空字符,則它會(huì)被誤以為是字符串的結(jié)尾,這限制了C字符串只能保存文本數(shù)據(jù),不能保存如圖片、視頻等二進(jìn)制數(shù)據(jù);
SDS以處理二進(jìn)制的方式處理buf數(shù)組中的數(shù)據(jù),不會(huì)對(duì)其做任何限制,數(shù)據(jù)寫入時(shí)什么樣子,讀取出來(lái)就是什么樣子。
鏈表在Redis中應(yīng)用十分廣泛,如列表的底層實(shí)現(xiàn)之一就是鏈表。
鏈表和鏈表節(jié)點(diǎn)結(jié)構(gòu)定義:
鏈表:
節(jié)點(diǎn):
包含三個(gè)節(jié)點(diǎn)的鏈表:
Redis鏈表特點(diǎn):
雙端:鏈表節(jié)點(diǎn)對(duì)prev和next指針;
無(wú)環(huán):表頭節(jié)點(diǎn)prev和表尾節(jié)點(diǎn)next都指向NULL;
帶表頭和表尾指針:通過(guò)list的head和tail指針獲取表頭和表尾節(jié)點(diǎn)時(shí)間復(fù)雜度為O(1);
長(zhǎng)度計(jì)數(shù)器:list屬性len記錄節(jié)點(diǎn)數(shù);
多態(tài):節(jié)點(diǎn)使用void*指針保存節(jié)點(diǎn)值,并且可通過(guò)list的dup,free,match為節(jié)點(diǎn)值設(shè)置特定函數(shù),所以鏈表可以保存不同類型的值;
保存鍵值對(duì),鍵不可以重復(fù),類似Java中的HashMap
字典結(jié)構(gòu)定義
字典:
其中ht[2]用戶保存哈希表,一個(gè)用于保存數(shù)據(jù),一個(gè)用于rehash時(shí)使用,其哈希表結(jié)構(gòu)為:
這個(gè)和Java中的HashMap很像,都是一個(gè)保存Entry的數(shù)組,當(dāng)hash沖突時(shí)使用鏈指法,其dictEntry結(jié)構(gòu)為:
這個(gè)和HashMap里那個(gè)內(nèi)部類Node也很像
來(lái)一個(gè)完成的字典結(jié)構(gòu)圖:
rehash
隨著不斷地操作,哈希表的鍵值對(duì)會(huì)逐漸增多或減少,為了維持哈希表的負(fù)載因子處在一個(gè)合理范圍內(nèi),當(dāng)哈希表保存的鍵值對(duì)太多或者太少時(shí),程序會(huì)對(duì)哈希表進(jìn)行擴(kuò)展或者收縮。進(jìn)行擴(kuò)展的目的是為了減少hash沖突,防止鏈表過(guò)長(zhǎng)導(dǎo)致查詢效率低,收縮的話就是為了節(jié)約內(nèi)存。
其中負(fù)載因子定義為:
load_factor = ht[0].used / ht[0].size
對(duì)于一個(gè)初始大小為4,包含四個(gè)鍵值對(duì)的哈希表來(lái)說(shuō):
load_factor = 4 / 4 = 1
當(dāng)滿足下面兩個(gè)條件之一時(shí),哈希表將進(jìn)行擴(kuò)展:
1)服務(wù)器未執(zhí)行BGSAVE或BGREWRITEAOF命令,且負(fù)載因子大于等于1
2)服務(wù)器正在執(zhí)行BGSAVE或BGREWRITEAOF命令,且負(fù)載因子大于等于5
當(dāng)負(fù)載因子小于0.1時(shí),程序自動(dòng)開始對(duì)哈希表進(jìn)行收縮操作。
漸進(jìn)式rehash
rehash時(shí)需要將所有ht[0]中的鍵值對(duì)全部移到ht[1]中,如果ht[0]中數(shù)據(jù)量非常龐大,那么一次性將這些鍵全部rehash到ht[1]中的話,龐大的計(jì)算量可能導(dǎo)致服務(wù)器在一段時(shí)間內(nèi)停止服務(wù)。因此,會(huì)分多次,漸進(jìn)式的將ht[0]中的數(shù)據(jù)慢慢的rehash到ht[1]中。
漸進(jìn)式rehash步驟:
1) 為ht[1]分配空間。
2) 將字典中的rehashidx置為0,表示rehash開始。
3) rehash期間,每次對(duì)字典的增刪改查操作時(shí),還會(huì)順帶將ht[0]哈希表在rehashidx索引上的所有數(shù)據(jù)rehash到ht[1]上,當(dāng)此rehashidx索引上的數(shù)據(jù)rehash完成后,程序會(huì)將rehashidx的值增加1。因?yàn)榇藭r(shí)字典會(huì)同時(shí)使用ht[0]和ht[1]兩個(gè)哈希表,所以刪除、查找、更新等操作會(huì)在兩個(gè)哈希表上進(jìn)行,先在ht[0]里查找,如果沒有找到則在ht[1]中查找,其中添加操作會(huì)直接保存到ht[1]中。
4) 所有數(shù)據(jù)rehash完成后,將rehashidx值設(shè)置為-1,表示rehash操作完成。
跳躍表是一種有序數(shù)據(jù)結(jié)構(gòu),查找平均時(shí)間復(fù)雜度為O(logN),最壞為O(N),可以通過(guò)順序性操作來(lái)批處理節(jié)點(diǎn)。Redis使用跳躍表作為有序集合的底層實(shí)現(xiàn)之一。
跳躍表結(jié)構(gòu)定義
跳躍表:
跳躍表節(jié)點(diǎn):
示例圖:
其中l(wèi)evel表示跳躍表內(nèi)層數(shù)最大的那個(gè)節(jié)點(diǎn)的層數(shù),length表示跳躍表內(nèi)節(jié)點(diǎn)數(shù);如上3個(gè)節(jié)點(diǎn)的分值分別為1.0、2.0、3.0
層:
跳躍表節(jié)點(diǎn)的level數(shù)據(jù)可以包含多個(gè)元素,每個(gè)元素都包含一個(gè)指向其他節(jié)點(diǎn)的指針,可以通過(guò)這些層來(lái)加快訪問其他節(jié)點(diǎn)的速度,一般來(lái)說(shuō),層數(shù)越多,訪問速度越快。
每創(chuàng)建一個(gè)跳躍表節(jié)點(diǎn)時(shí),程序會(huì)隨機(jī)生成一個(gè)介于1和32的值作為level數(shù)組的大小,這個(gè)大小就是高度。
前進(jìn)指針:
每個(gè)層都有一個(gè)指向表尾方向的前進(jìn)指針,用于從表頭向表尾方向訪問節(jié)點(diǎn)。
跨度:
層的跨度,即level[i].span 用于記錄兩個(gè)節(jié)點(diǎn)之間的距離,跨度是用來(lái)計(jì)算排位(rank)的,在查找某個(gè)節(jié)點(diǎn)的過(guò)程中,將沿途訪問過(guò)的所有層的跨度累加起來(lái),得到的結(jié)果就是目標(biāo)節(jié)點(diǎn)在跳躍表中的排位。
后退指針:
節(jié)點(diǎn)的后退指針backward,用于從表尾向表頭方向訪問節(jié)點(diǎn)。
分值和成員:
分值是一個(gè)double類型的浮點(diǎn)數(shù),跳躍表中所有節(jié)點(diǎn)都按分值的大小排序,分值相同時(shí),按照成員排序。
節(jié)點(diǎn)的成員對(duì)象是一個(gè)指針,指向一個(gè)使用SDS保存的字符串對(duì)象,同一個(gè)跳躍表中,成員對(duì)象不能重復(fù)。分值可以重復(fù),且分值相同時(shí)按成員對(duì)象字典順序進(jìn)行排序。
整數(shù)集合時(shí)集合的底層實(shí)現(xiàn)之一,當(dāng)一個(gè)集合只包含整數(shù)值元素且元素個(gè)數(shù)不多時(shí)將使用其作為底層實(shí)現(xiàn)。
結(jié)構(gòu)定義
contents數(shù)組保存集合元素,按從小到大排序且不能重復(fù)。
雖然contents屬性聲明為int8_t,但是它并不保存任何int8_t的值,contents數(shù)組真正類型取決于encoding的值,encoding取值可以是:INTSET_ENC_INT16、INTSET_ENC_INT32、INTSET_ENC_INT64,其中的16、32、64表示每個(gè)整數(shù)占用的位數(shù)。
升級(jí)
當(dāng)新加入的整數(shù)類型比所有現(xiàn)存的元素的類型都長(zhǎng)時(shí),整數(shù)集合將進(jìn)行升級(jí),將所有元素類型長(zhǎng)度升級(jí)到新加入元素的長(zhǎng)度。
整數(shù)集合不支持降級(jí),一旦升上去了就降不下來(lái)了。
壓縮列表是列表和哈希鍵的底層實(shí)現(xiàn)之一,當(dāng)一個(gè)列表只包含少量元素,且每個(gè)元素是小整數(shù)或者短字符串時(shí),則其將使用壓縮列表作為底層實(shí)現(xiàn)。
壓縮列表結(jié)構(gòu)定義
壓縮列表:
zlbytes:整個(gè)壓縮列表占用內(nèi)存字節(jié)數(shù)。
zltail:壓縮列表表尾節(jié)點(diǎn)距離起始地址字節(jié)數(shù),通過(guò)使用壓縮列表起始地址指針p + zltail 就能計(jì)算出最后一個(gè)節(jié)點(diǎn)的地址。
zlen:壓縮列表包含節(jié)點(diǎn)數(shù),當(dāng)這個(gè)值小于UINT16_MAX(65535)時(shí),這個(gè)值就是節(jié)點(diǎn)數(shù);當(dāng)這個(gè)值等于UINT16_MAX時(shí),需要遍歷壓縮列表才能計(jì)算出來(lái)。
entry:列表節(jié)點(diǎn)。
zlend:標(biāo)記壓縮列表末端。
壓縮列表節(jié)點(diǎn):
每個(gè)壓縮列表可以保存一個(gè)字節(jié)數(shù)組或者一個(gè)整數(shù)值。
previous_entry_length:記錄了前一個(gè)節(jié)點(diǎn)的長(zhǎng)度,根據(jù)所記錄長(zhǎng)度大小,其內(nèi)存占用大小可以是1字節(jié)或5字節(jié),單位字節(jié)??梢酝ㄟ^(guò)當(dāng)前節(jié)點(diǎn)的地址值減去這個(gè)值計(jì)算出前一個(gè)節(jié)點(diǎn)的地址,結(jié)合上述通過(guò)zltail計(jì)算出的最后一個(gè)節(jié)點(diǎn)地址值就可以實(shí)現(xiàn)從后向前遍歷整個(gè)壓縮列表。
encoding:記錄節(jié)點(diǎn)content屬性所保存數(shù)據(jù)的類型及長(zhǎng)度。
content:保存節(jié)點(diǎn)值,可以是字節(jié)數(shù)組或整數(shù)。
連鎖更新
前面說(shuō)過(guò)previous_entry_length根據(jù)前一個(gè)節(jié)點(diǎn)的長(zhǎng)度大小可以占用1字節(jié)或者5字節(jié),當(dāng)前一個(gè)節(jié)點(diǎn)長(zhǎng)度小于254字節(jié)時(shí),它占用1字節(jié);而前一個(gè)節(jié)點(diǎn)長(zhǎng)度大于等于254字節(jié)時(shí)它就占用5字節(jié)。
現(xiàn)考慮這么一種情況:
假設(shè)壓縮列表中保存若干節(jié)點(diǎn),它們的長(zhǎng)度都介于250到253字節(jié)之間,如圖:
現(xiàn)我們將一個(gè)長(zhǎng)度大于254字節(jié)的新節(jié)點(diǎn)設(shè)置為壓縮列表的頭節(jié)點(diǎn):
由于e1之前的previous_entry_length是1字節(jié),不足以保存長(zhǎng)度大于254的new節(jié)點(diǎn)長(zhǎng)度,因此它會(huì)擴(kuò)容至5字節(jié),使自己的長(zhǎng)度也大于或等于254,這樣e2也就得跟著擴(kuò)容了......如此直到最后一個(gè)節(jié)點(diǎn)。
前面我們介紹了Redis用到的所有主要的數(shù)據(jù)結(jié)構(gòu),但Redis并沒有直接使用這些數(shù)據(jù)結(jié)構(gòu)來(lái)實(shí)現(xiàn)鍵值對(duì)數(shù)據(jù)庫(kù),而是基于這些數(shù)據(jù)結(jié)構(gòu)創(chuàng)建了一個(gè)對(duì)象系統(tǒng),這個(gè)系統(tǒng)包含:字符串、列表、哈希、集合、有序集合。
Redis使用對(duì)象來(lái)表示數(shù)據(jù)庫(kù)中的鍵和值,每當(dāng)我們?cè)赗edis中創(chuàng)建一個(gè)鍵值對(duì)時(shí),我們至少會(huì)創(chuàng)建兩個(gè)對(duì)象,一個(gè)鍵對(duì)象,一個(gè)值對(duì)象;鍵總是一個(gè)字符串對(duì)象,而值則可以是字符串對(duì)象,列表對(duì)象,哈希對(duì)象,集合對(duì)象或者有序集合對(duì)象。
Redis中每個(gè)對(duì)象都有redisObject結(jié)構(gòu)表示:
type:記錄對(duì)象類型,它可以是下圖中的任何一種
TYPE命令可以返回?cái)?shù)據(jù)庫(kù)鍵對(duì)應(yīng)的值對(duì)象的類型:
127.0.0.1:6379> set msg hello
OK
127.0.0.1:6379> rpush list hello world
(integer) 2
127.0.0.1:6379> type msg
string
127.0.0.1:6379> type list
list
不同類型值對(duì)應(yīng)的type輸出:
encoding:記錄對(duì)象使用了什么數(shù)據(jù)結(jié)構(gòu)作為對(duì)象底層實(shí)現(xiàn),它可以是下圖中的任何一種
每種對(duì)象可以使用的編碼:
可以使用OBJECT ENCODING 命令查看一個(gè)數(shù)據(jù)庫(kù)鍵的值對(duì)象的編碼:
127.0.0.1:6379> object encoding msg
"embstr"
1,字符串對(duì)象
字符串對(duì)象的編碼可以是int,raw,embstr。
int:保存的是整數(shù)值且該整數(shù)可以用long類型表示。
embstr:保存的是字符串值且長(zhǎng)度小于等于32字節(jié),使用SDS保存。
raw:保存字符串值且長(zhǎng)度大于32字節(jié),使用SDS保存。
embstr和raw區(qū)別:
raw會(huì)調(diào)用兩次內(nèi)存分配函數(shù)分別創(chuàng)建redisObject結(jié)構(gòu)和sdshdr結(jié)構(gòu);而embstr只調(diào)用一次內(nèi)容分配函數(shù)來(lái)分配一塊連續(xù)的空間,空間依次包含redisObject結(jié)構(gòu)和sdshdr結(jié)構(gòu)。
編碼轉(zhuǎn)換:
int編碼的字符串被修改成不再是整數(shù)、embstr編碼的字符串執(zhí)行任何修改命令都會(huì)被轉(zhuǎn)換為raw
2,列表對(duì)象
列表對(duì)象的編碼可以是ziplist或者linkedlist。
ziplist:列表對(duì)象保存的所有字符串元素長(zhǎng)度小于64字節(jié)且元素?cái)?shù)量小于512個(gè)。
linkedlist:不滿足ziplist中的任一約束時(shí)。
64和512可以通過(guò)配置文件中l(wèi)ist-max-ziplist-value和list-max-ziplist-entries修改。
結(jié)構(gòu)圖:
補(bǔ)充:上兩圖中保存字符串"three"的對(duì)象的完×××式為:
3,哈希對(duì)象
哈希對(duì)象的編碼可以是ziplist或者h(yuǎn)ashtable。
ziplist:哈希對(duì)象保存的所有鍵值對(duì)的鍵和值的字符串長(zhǎng)度都小于64字節(jié)且鍵值對(duì)數(shù)量小于512個(gè)。
hashtable:不滿足ziplist中的任一約束時(shí)。
64和512可以通過(guò)配置文件中l(wèi)ist-max-ziplist-value和list-max-ziplist-entries修改。
127.0.0.1:6379> hmset profile name Tom age 25 career Programmer
.結(jié)構(gòu)圖:
4,集合對(duì)象
集合對(duì)象的編碼可以是intset或者h(yuǎn)ashtable。
intset:保存的所有元素都是整數(shù)值且元素?cái)?shù)量不超過(guò)512個(gè)。
hashtable:不滿足intset中任一約束時(shí)。
512可以通過(guò)配置文件中set-max-intset-entries修改。
127.0.0.1:6379> sadd Dfruits apple banana cherry
結(jié)構(gòu)圖:
5,有序集合對(duì)象
有序集合的編碼可以是ziplist或者skiplist。
ziplist:保存的所有元素成員的長(zhǎng)度都小于64字節(jié)且元素?cái)?shù)量小于128個(gè)。
skiplist:不滿足ziplist中任一約束時(shí)。
128和64可以銅牌配置文件中的zset-max-ziplist-entries和zset-max-ziplist-value修改。
127.0.0.1:6379> zadd price 8.5 apple 5.0 banana 6.0 cherry
結(jié)構(gòu)圖:
使用ziplist:
使用skiplist:
其中,zset結(jié)構(gòu)中的zsl為指向跳躍表的指針,dict為指向字典的指針。
zsl跳躍表按分值從小到大保存了所有集合元素,每個(gè)跳躍表節(jié)點(diǎn)都是一個(gè)集合元素,節(jié)點(diǎn)的object屬性保存了元素成員,score屬性保存分值。通過(guò)跳躍表,程序可以快速的對(duì)集合進(jìn)行范圍操作,如zrank、zrange命令就是基于跳躍表實(shí)現(xiàn)的。
dict字典為有序集合創(chuàng)建了一個(gè)從成員到分值的映射,字典中每個(gè)鍵值對(duì)保存一個(gè)元素,鍵保存元素成員,值保存分值。通過(guò)這個(gè)字典,程序可以一O(1)的時(shí)間復(fù)雜度查到給定成員的分值,如zscore命令就是基于此特性。
zsl和dict通過(guò)指針共享相同元素的成員和分值。
Redis提供了兩種不同的持久化方法。一個(gè)叫做快照,它可以將存在于某一時(shí)刻的所有數(shù)據(jù)都寫入硬盤里。另一種叫只追加文件,它會(huì)在執(zhí)行寫命令時(shí),將被執(zhí)行的命令復(fù)制到硬盤里。
1,快照持久化
Redis可以通過(guò)創(chuàng)建快照來(lái)獲得存儲(chǔ)在內(nèi)存里面的數(shù)據(jù)在某個(gè)時(shí)間點(diǎn)上的副本。創(chuàng)建快照后,用戶可以對(duì)快照進(jìn)行備份,可以將快照復(fù)制到其他服務(wù)器從而創(chuàng)建具有相同數(shù)據(jù)的服務(wù)器副本,也可留在本地以便重啟服務(wù)器時(shí)使用。
根據(jù)配置,快照將被寫入dbfilename指定的文件里,并存儲(chǔ)在dir指定的路徑上。
如果在新的快照創(chuàng)建完畢前,Redis、系統(tǒng)或硬件三者之中任一一個(gè)崩潰,Redis都將丟失上一次創(chuàng)建完快照之后的數(shù)據(jù)。
創(chuàng)建快照的方式:
客戶端發(fā)送BGSAVE命令來(lái)創(chuàng)建快照。對(duì)于支持BGSAVE命令的平臺(tái)來(lái)說(shuō),Redis會(huì)調(diào)用fork來(lái)創(chuàng)建一個(gè)子進(jìn)程,然后由子進(jìn)程負(fù)責(zé)將快照寫入硬盤,父進(jìn)程則繼續(xù)處理命令請(qǐng)求。
客戶端發(fā)送SAVE命令來(lái)創(chuàng)建快照。接到SAVE命令的Redis服務(wù)器在快照創(chuàng)建完畢之前不會(huì)響應(yīng)任何其他命令。SAVE命令并不常用,通常只在沒有足夠內(nèi)存執(zhí)行BGSAVE時(shí)才會(huì)使用。
配置文件中進(jìn)行了save配置,如 save 60 10000,那么從Redis上一次創(chuàng)建快照之后開始算起,當(dāng)60秒內(nèi)有10000次寫入時(shí),Redis就會(huì)自動(dòng)觸發(fā)BGSAVE命令。如果設(shè)置多個(gè)save選項(xiàng),當(dāng)任一一個(gè)滿足時(shí),Redis都會(huì)觸發(fā)BGSAVE。
當(dāng)Redis通過(guò)SHUTDOWN命令接收到關(guān)閉服務(wù)器的請(qǐng)求時(shí),或者接收到標(biāo)準(zhǔn)TERM信號(hào)時(shí),會(huì)執(zhí)行SAVE命令,并在SAVE命令執(zhí)行完畢后關(guān)閉服務(wù)器。
當(dāng)一個(gè)Redis服務(wù)器接收到另一個(gè)Redis服務(wù)器發(fā)來(lái)的SYNC命令時(shí),如果當(dāng)前Redis服務(wù)器還沒有執(zhí)行BGSAVE命令或并非剛剛執(zhí)行完BGSAVE命令,那么當(dāng)前服務(wù)器會(huì)執(zhí)行BGSAVE命令。
2,AOF持久化
AOF持久化會(huì)將被執(zhí)行的寫命令寫到AOF文件的末尾,因此,Redis只要從頭到尾執(zhí)行一次AOF文件包含的所有寫命令,就可以恢復(fù)數(shù)據(jù)集。
AOF可以通過(guò)在配置文件中設(shè)置 appendonly yes 選項(xiàng)來(lái)打開。
配置 同步頻率:
appendfsync always : 每個(gè)寫命令都要同步寫入硬盤,這樣做會(huì)嚴(yán)重降低Redis的速度。
appendfsync everysec :每秒執(zhí)行一次同步,顯示地將多個(gè)命令寫入硬盤。
appendfsync no :讓操作系統(tǒng)來(lái)決定何時(shí)進(jìn)行同步。
AOF存在的問題:
隨著Redis的不斷運(yùn)行,AOF文件體積也會(huì)不斷增長(zhǎng),甚至可能會(huì)用完硬盤所有空間。另一個(gè)問題,如果AOF文件過(guò)大,當(dāng)Redis重啟后執(zhí)行所有寫命令將會(huì)非常耗時(shí)。
AOF重寫:
用戶可以發(fā)送BGREWRITEAOF命令來(lái)重寫AOF文件,Redis接收到此命令后會(huì)fork一個(gè)子進(jìn)程進(jìn)行AOF文件重寫以使其體積更小。
配置文件中配置auto-aof-rewrite-percentage 和 auto-aof-rewrite-min-size 選項(xiàng)來(lái)自動(dòng)執(zhí)行BGREWRITEAOF。如auto-aof-rewrite-percentage = 100 和 auto-aof-rewrite-min-size = 64mb,并啟用AOF持久化,那么當(dāng)AOF文件的體積大于64mb并且AOF文件的體積比上一次重寫之后的體積大了一倍,即100%時(shí),Redis會(huì)執(zhí)行BGREWRITEAOF。
1,主從模式
在關(guān)系數(shù)據(jù)庫(kù)中,通常使用一個(gè)主服務(wù)器向多個(gè)從服務(wù)器發(fā)送更新,并使用從服務(wù)器來(lái)處理所有度請(qǐng)求。Redis也采用了同樣的方式來(lái)實(shí)現(xiàn)自己的復(fù)制特性。
用戶可以通過(guò)執(zhí)行SLAVEOF命令或者設(shè)置slaveof選項(xiàng),讓一個(gè)服務(wù)器去復(fù)制另一個(gè)服務(wù)器,我們稱被復(fù)制的服務(wù)器為主服務(wù)器,而對(duì)主服務(wù)器進(jìn)行復(fù)制的被稱為從服務(wù)器。
如使用:
127.0.0.1:6379> slaveof 127.0.0.1 6380
那么服務(wù)器 127.0.0.1:6379 將成為服務(wù)器127.0.0.1:6380 的從服務(wù)器, 127.0.0.1:6380將成為主服務(wù)器。
1.1 復(fù)制功能的實(shí)現(xiàn)
Redis的復(fù)制功能分為同步(sync/psync)和命令傳播兩個(gè)操作:
同步:同步操作用于將從服務(wù)器的數(shù)據(jù)庫(kù)庫(kù)狀態(tài)更新至主服務(wù)器當(dāng)前所處的狀態(tài)。其中sync為老版本中的,psync從2.8版本開始,用于代替sync命令。
命令傳播:同步完成后,主服務(wù)器將自己執(zhí)行的寫命令發(fā)送給從服務(wù)器執(zhí)行。
老板本復(fù)制過(guò)程:
新老版本主要差別是在斷線重連時(shí)的處理方式不同。老版本在斷線重連后需要重新發(fā)送sync命令,主服務(wù)器在收到命令后將重新執(zhí)行BGSAVE創(chuàng)建一個(gè)完整的RDB文件,如上圖中所示,斷線過(guò)程中,主服務(wù)器僅多增加了三條數(shù)據(jù)卻需要主服務(wù)器重新執(zhí)行BGSAVE,這是很不劃算的。
新版本復(fù)制過(guò)程:
psync命令具有完整同步和部分重同步兩種模式:
完整同步:用于初次復(fù)制情況,其步驟同sync基本一樣,都是通過(guò)讓主服務(wù)器創(chuàng)建并發(fā)送RDB文件,以及向從服務(wù)器發(fā)送保存在緩沖區(qū)里面的寫命令來(lái)進(jìn)行同步。
部分重同步:用于斷線后重復(fù)制情況,當(dāng)斷線重連后,如條件允許,主服務(wù)器僅需將斷線后的寫命令發(fā)送給從服務(wù)器,而無(wú)需主服務(wù)器創(chuàng)建完成RDB文件。
1.2 部分重同步的實(shí)現(xiàn)
部分重同步以一下三個(gè)部分構(gòu)成:
主服務(wù)器的復(fù)制偏移量和從服務(wù)器的復(fù)制偏移量。
主服務(wù)器的復(fù)制積壓緩沖區(qū)。
服務(wù)器的運(yùn)行ID。
1.2.1 復(fù)制偏移量
執(zhí)行復(fù)制的雙方都維護(hù)了一個(gè)復(fù)制偏移量。
主服務(wù)器每次向從服務(wù)器傳播N個(gè)字節(jié)的數(shù)據(jù)時(shí),就將自己的復(fù)制偏移量加N。
從服務(wù)器每次收到主服務(wù)器傳播來(lái)的N個(gè)字節(jié)的數(shù)據(jù)時(shí),就將自己的復(fù)制偏移量加N。
通過(guò)對(duì)比主從服務(wù)器的復(fù)制偏移量,就可以知道主從服務(wù)器是否處于一致狀態(tài)。
1.2.2 復(fù)制積壓緩沖區(qū)
復(fù)制積壓緩沖區(qū)是由主服務(wù)器維護(hù)的一個(gè)固定長(zhǎng)度的先進(jìn)先出隊(duì)列,默認(rèn)為1MB。當(dāng)主服務(wù)器進(jìn)行命令傳播時(shí),它不僅會(huì)將命令發(fā)送給所有從服務(wù)器,還會(huì)將命令入隊(duì)到復(fù)制積壓緩沖區(qū)里,并且復(fù)制積壓緩沖區(qū)還會(huì)為隊(duì)列中的每個(gè)字節(jié)記錄相應(yīng)的復(fù)制偏移量。
當(dāng)從服務(wù)器重新連上主服務(wù)器時(shí),會(huì)將自己的復(fù)制偏移量offset發(fā)送給主服務(wù)器,主服務(wù)器會(huì)根據(jù)這個(gè)偏移量來(lái)決定對(duì)從服務(wù)器執(zhí)行何種同步操作:
如果offset偏移量之后的數(shù)據(jù)還存在于復(fù)制積壓緩沖區(qū)里,那么執(zhí)行部分重同步操作。
如果offset偏移量之后的數(shù)據(jù)已經(jīng)不存在了,那么久執(zhí)行完整的重同步操作。
1.2.3 服務(wù)器運(yùn)行ID
每個(gè)Redis服務(wù)器都有自己的運(yùn)行ID,在服務(wù)器啟動(dòng)時(shí)自動(dòng)生成,由40個(gè)隨機(jī)的十六進(jìn)制字符組成。
當(dāng)從服務(wù)器對(duì)主服務(wù)器進(jìn)行初次復(fù)制時(shí),主服務(wù)器會(huì)將自己的ID發(fā)送給從服務(wù)器,從服務(wù)器會(huì)將其保存起來(lái)。當(dāng)從服務(wù)器斷線重連時(shí),會(huì)將這個(gè)保存的主服務(wù)器ID發(fā)送給主服務(wù)器。
如果從服務(wù)器發(fā)送的ID和主服務(wù)器自己的ID相同,那么說(shuō)明從服務(wù)器斷線前復(fù)制的主服務(wù)器就是當(dāng)前主服務(wù)器,主服務(wù)器可以繼續(xù)嘗試部分重同步操作。
如果從服務(wù)器發(fā)送的ID和主服務(wù)器自己的ID不同,主服務(wù)器將對(duì)從服務(wù)器執(zhí)行完整的重同步操作。
1.3 主從鏈
當(dāng)復(fù)制需要通過(guò)互聯(lián)網(wǎng)進(jìn)行或者需要在不同的數(shù)據(jù)中心之間進(jìn)行的時(shí)候,過(guò)多的從服務(wù)器可能會(huì)導(dǎo)致網(wǎng)絡(luò)不可用。而Redis的主從服務(wù)器并沒有什么特別的不同,所以從服務(wù)器也可以擁有自己的從服務(wù)器,像這樣:
2,哨兵模式
Sentinel是Redis的高可用解決方案,由一個(gè)或多個(gè)Sentinel實(shí)例組成的Sentinel系統(tǒng)可以監(jiān)視任意多個(gè)主服務(wù)器,以及這些主服務(wù)器的所有從服務(wù)器,當(dāng)主服務(wù)器下線時(shí),自動(dòng)將下線主服務(wù)器下的某個(gè)從服務(wù)器升級(jí)為新的主服務(wù)器。
Sentinel啟動(dòng)運(yùn)行主要流程:
1)根據(jù)載入的Sentinel配置文件獲取被監(jiān)視的主服務(wù)器信息;
2)創(chuàng)建連向主服務(wù)器的網(wǎng)絡(luò)連接:對(duì)于每個(gè)被監(jiān)視的主服務(wù)器,Sentinel會(huì)創(chuàng)建兩個(gè)連向主服務(wù)器的一部網(wǎng)絡(luò)連接
--- 一個(gè)命令連接,專門用于向主服務(wù)器發(fā)送命令,并接收回復(fù)。
--- 一個(gè)是訂閱連接,專門用于訂閱主服務(wù)器的sentinel:hello頻道。
Sentinel默認(rèn)會(huì)以每十秒一次的頻率,通過(guò)命令連接向被監(jiān)視的主服務(wù)器發(fā)送INFO命令,并通過(guò)命令回復(fù)獲取主服務(wù)器的當(dāng)前信息,包括主服務(wù)器本身的ID及服務(wù)器角色和其下的從服務(wù)器信息。
3)根據(jù)獲得的從服務(wù)器信息創(chuàng)建到從服務(wù)器的命令連接和訂閱連接。
Sentinel以每十秒一次的頻率向從服務(wù)器發(fā)送INFO命令,并從回復(fù)中獲得以下信息:包括該從服務(wù)器的主服務(wù)器的IP、端口,主從服務(wù)器的連接狀態(tài),以及從服務(wù)器的ID、角色、優(yōu)先級(jí)、復(fù)制偏移量并根據(jù)這些信息對(duì)保存在Sentinel中的從服務(wù)器信息進(jìn)行更新。
4)以每?jī)擅胍淮蔚念l率,通過(guò)命令連接向所有被監(jiān)視的主服務(wù)器和從服務(wù)器的sentinel:hello頻道發(fā)送以下格式的命令:
其中以s_開頭的是Sentinel自己的信息;m_開頭的是主服務(wù)器的信息,如果發(fā)送的目的是主服務(wù)器,那么就是該主服務(wù)器的信息,如果目的是從服務(wù)器,那么就是該從服務(wù)器所在的主服務(wù)器的信息。
5)接收來(lái)自主服務(wù)器和從服務(wù)器的頻道信息,上面的2,3步分別訂閱了主從服務(wù)器的sentinel:hello頻道,并在第4步對(duì)該頻道發(fā)送了信息,也就是或每個(gè)Sentinel既可以向該頻道發(fā)送信息也可以接收該頻道的信息,并能從接收到的信息中獲取到其他監(jiān)視了相同主服務(wù)器的Sentinel的信息。
6)根據(jù)上部獲得的其他Sentinel的信息與其他Sentinel建立命令連接,最終監(jiān)視相同主服務(wù)器的Sentinel將形成相互連接的網(wǎng)絡(luò)。
7)檢測(cè)主觀下線狀態(tài),Sentinel默認(rèn)以每秒一次的頻率向所有與它創(chuàng)建命令連接的實(shí)例(包括主從服務(wù)器及其他Sentinel)發(fā)送PING命令,并通過(guò)實(shí)例的回復(fù)來(lái)判斷實(shí)例是否在線。
Sentinel配置文件中的down-after-milliseconds指定了Sentinel判斷實(shí)例進(jìn)入主觀下線的時(shí)間長(zhǎng)度,如果一個(gè)實(shí)例在down-after-milliseconds毫秒內(nèi)連續(xù)向Sentinel返回?zé)o效回復(fù),那么Sentinel判定此實(shí)例下線。
8)當(dāng)Sentinel將一個(gè)主服務(wù)器主觀下線后,它會(huì)想其他同樣監(jiān)視此主服務(wù)器的Sentinel進(jìn)行詢問。當(dāng)從其他Sentinel那里接收到足夠數(shù)量的已下線判斷后,Sentinel就會(huì)將主服務(wù)器判定為客觀下線并對(duì)其進(jìn)行故障轉(zhuǎn)移。
9)當(dāng)一個(gè)主服務(wù)器被判定為客觀下線時(shí),監(jiān)視這個(gè)主服務(wù)器的Sentinel會(huì)進(jìn)行協(xié)商選出一個(gè)領(lǐng)頭Sentinel,并由領(lǐng)頭Sentinel執(zhí)行故障轉(zhuǎn)移。
10)故障轉(zhuǎn)移,從已下線主服務(wù)器的所有從服務(wù)器中選出一個(gè)將其轉(zhuǎn)換為主服務(wù)器;讓其他所有從服務(wù)器改為復(fù)制新的主服務(wù)器;將下線的主服務(wù)器設(shè)置為新主服務(wù)器的從服務(wù)器,當(dāng)它重新上線時(shí)就會(huì)成為新主服務(wù)器的從服務(wù)器。
新主服務(wù)器選擇依據(jù):
3,集群
Redis集群是Redis提供的分布式數(shù)據(jù)庫(kù)方案,集群通過(guò)分片來(lái)進(jìn)行數(shù)據(jù)共享,并提供復(fù)制和故障轉(zhuǎn)移功能。
1,節(jié)點(diǎn)
每個(gè)Redis服務(wù)器稱之為一個(gè)節(jié)點(diǎn),一個(gè)Redis集群通常由多個(gè)節(jié)點(diǎn)組成,通過(guò)命令:CLUSTER MEET ip port
將指定節(jié)點(diǎn)加入到當(dāng)前節(jié)點(diǎn)所在的集群中。
2,槽指派
Redis集群通過(guò)分片的方式來(lái)保存數(shù)據(jù)庫(kù)中的鍵值對(duì),集群的整個(gè)數(shù)據(jù)庫(kù)被分為16384個(gè)槽,數(shù)據(jù)庫(kù)中的每個(gè)鍵都屬于這16394個(gè)槽的其中一個(gè),集群中的每個(gè)節(jié)點(diǎn)可以處理0個(gè)或最多16384個(gè)槽。當(dāng)所有的16394個(gè)槽都有節(jié)點(diǎn)處理時(shí),集群處于上線狀態(tài),否則處于下線狀態(tài)。
通過(guò)向節(jié)點(diǎn)發(fā)送命令:CLUSTER ADDSLOTS slot ...
可以將一個(gè)或多個(gè)槽指派給節(jié)點(diǎn)負(fù)責(zé)。
一個(gè)節(jié)點(diǎn)除了會(huì)記錄自己處理的槽外還會(huì)向其他節(jié)點(diǎn)發(fā)送自己處理的槽并且也會(huì)記錄集群所有槽的指派信息。
3,執(zhí)行命令
當(dāng)客戶端向節(jié)點(diǎn)發(fā)送與數(shù)據(jù)庫(kù)相關(guān)的命令時(shí),接收命令的節(jié)點(diǎn)會(huì)計(jì)算出命令要處理的數(shù)據(jù)庫(kù)鍵屬于哪個(gè)槽,如果鍵所在的槽正好就指派給了當(dāng)前節(jié)點(diǎn),那么節(jié)點(diǎn)就直接處理這個(gè)命令;如果不是,那么節(jié)點(diǎn)就給客戶端返回一個(gè)MOVED錯(cuò)誤,并將命令轉(zhuǎn)發(fā)給正確的節(jié)點(diǎn)。
可以通過(guò)命令:
CLUSTER KEYSLOT key
獲取指定鍵屬于哪個(gè)槽。
4,重新分片
Redis集群的重新分片操作可以將任意數(shù)量已經(jīng)指派給某個(gè)節(jié)點(diǎn)的槽改為指派給另一個(gè)節(jié)點(diǎn),并且相關(guān)槽所屬的鍵值對(duì)也會(huì)被移動(dòng)到目標(biāo)節(jié)點(diǎn)。
5,復(fù)制與故障轉(zhuǎn)移
Redis中的節(jié)點(diǎn)分為主節(jié)點(diǎn)和從節(jié)點(diǎn),主節(jié)點(diǎn)用于處理槽,從節(jié)點(diǎn)用于復(fù)制主節(jié)點(diǎn),并在主節(jié)點(diǎn)下線時(shí),代替主節(jié)點(diǎn)處理請(qǐng)求成為新的主節(jié)點(diǎn),其具體步驟為:
1)從所有從節(jié)點(diǎn)中選擇一個(gè)成為新的主節(jié)點(diǎn)。
2)新的主節(jié)點(diǎn)會(huì)撤銷所有對(duì)已下線主節(jié)點(diǎn)的槽指派,并將這些槽都指派給自己。
3)新的主節(jié)點(diǎn)向集群廣播一條PONG消息,讓其他主節(jié)點(diǎn)知道自己接管了下線的主節(jié)點(diǎn)并負(fù)責(zé)處理下線主節(jié)點(diǎn)原來(lái)的槽。
選舉新的主節(jié)點(diǎn):
1)集群的配置紀(jì)元是一個(gè)自增計(jì)數(shù)器,初始為0,集群里的某個(gè)節(jié)點(diǎn)開始一次故障轉(zhuǎn)移操作時(shí),紀(jì)元增加1
2)對(duì)于每個(gè)配置紀(jì)元,集群里每個(gè)負(fù)責(zé)處理槽的主節(jié)點(diǎn)都有一次投票機(jī)會(huì),而第一個(gè)向主節(jié)點(diǎn)要求投票的從節(jié)點(diǎn)將會(huì)獲得主節(jié)點(diǎn)的投票。
3)當(dāng)從節(jié)點(diǎn)發(fā)現(xiàn)復(fù)制的主節(jié)點(diǎn)下線時(shí),它會(huì)廣播一條消息,要求所有接收到消息且有投票權(quán)(正在負(fù)責(zé)處理槽)的主節(jié)點(diǎn)投票給自己。
4)如果一個(gè)從節(jié)點(diǎn)收到的投票數(shù)大于具有投票權(quán)節(jié)點(diǎn)總數(shù)的一半時(shí),這個(gè)從節(jié)點(diǎn)就當(dāng)選為新的主節(jié)點(diǎn)。
5)如果沒有從節(jié)點(diǎn)收到足夠多的投票,那么集群進(jìn)入新的紀(jì)元,并在此選舉,直到新的主節(jié)點(diǎn)被選出。
可以通過(guò)命令:CLUSTER REPLICATE node_id
讓接收命令的節(jié)點(diǎn)成為node_id所指定的節(jié)點(diǎn)的從節(jié)點(diǎn)。
Redis通過(guò)multi、exec、watch、discard等命令來(lái)實(shí)現(xiàn)事務(wù)功能。事務(wù)提供了一種將多個(gè)命令請(qǐng)求打包,然后一次性、按順序地執(zhí)行多個(gè)命令的機(jī)制,并且在事務(wù)執(zhí)行期間,服務(wù)器不會(huì)中斷事務(wù)而改去執(zhí)行其他客戶端的命令請(qǐng)求,它會(huì)將事務(wù)中的所有命令都執(zhí)行完畢,然后才去處理其他客戶端的命令請(qǐng)求。
1,事務(wù)的實(shí)現(xiàn)
一個(gè)事務(wù)由multi命令開始,由exec命令將這個(gè)事務(wù)提交給服務(wù)器執(zhí)行。
1)multi命令可以將執(zhí)行該命令的客戶端從非事務(wù)狀態(tài)切換至事務(wù)狀態(tài)。
2)當(dāng)一個(gè)客戶端處于非事務(wù)狀態(tài)時(shí),這個(gè)客戶端發(fā)送的命令會(huì)立即被服務(wù)器執(zhí)行;當(dāng)處于事務(wù)狀態(tài)時(shí),服務(wù)器會(huì)根據(jù)這個(gè)客戶端發(fā)來(lái)的不同命令而執(zhí)行不同的操作:
如果客戶端發(fā)送的命令為exec、discard、watch、multi四個(gè)命令的其中一個(gè),那么服務(wù)器立即執(zhí)行這個(gè)命令。
如果發(fā)送的是其他命令,那么服務(wù)器將這個(gè)命令放入事務(wù)隊(duì)列里,然后向客戶端返回queued回復(fù)。
3)當(dāng)一個(gè)處于事務(wù)狀態(tài)的客戶端向服務(wù)器發(fā)送exec命令時(shí),這個(gè)exec命令會(huì)被立即執(zhí)行,服務(wù)器會(huì)遍歷這個(gè)客戶端的事務(wù)隊(duì)列,執(zhí)行隊(duì)列中保存的所有命令,并將執(zhí)行結(jié)果返回給客戶端。
2,watch命令的實(shí)現(xiàn)
watch命令是一個(gè)樂觀鎖,它可以在exec命令執(zhí)行之前,監(jiān)視任意數(shù)量的數(shù)據(jù)庫(kù)鍵,并在exec命令執(zhí)行時(shí),檢查被監(jiān)視的鍵是否至少有一個(gè)已經(jīng)被修改過(guò)了,若果是的話,服務(wù)器將拒絕執(zhí)行事務(wù),并向客戶端返回返回執(zhí)行失敗的空回復(fù)。
每個(gè)Redis數(shù)據(jù)庫(kù)都保存著一個(gè)watched_keys字典,這個(gè)字典的鍵是某個(gè)被watch命令監(jiān)視的數(shù)據(jù)庫(kù)鍵,而字典值則是一個(gè)鏈表,鏈表中記錄了所有所有監(jiān)視該鍵的客戶端。通過(guò)watched_keys字典,服務(wù)器可以清楚的知道哪些數(shù)據(jù)庫(kù)鍵正在被監(jiān)視,以及哪些客戶端正在監(jiān)視這些數(shù)據(jù)庫(kù)鍵。
所有對(duì)數(shù)據(jù)庫(kù)的修改命令,在執(zhí)行之后都會(huì)對(duì)watched_keys進(jìn)行檢查,查看是否有被監(jiān)視的鍵被修改,如果有,則會(huì)將客戶端的REDIS_DIRTY_CAS標(biāo)識(shí)打開,標(biāo)識(shí)客戶端的事務(wù)安全以及被破壞。
當(dāng)服務(wù)器接收到exec命令時(shí),服務(wù)器會(huì)根據(jù)這個(gè)客戶端是否打開了REDIS_DIRTY_CAS標(biāo)識(shí)來(lái)決定是否執(zhí)行事務(wù)。
參考:《Redis設(shè)計(jì)與實(shí)現(xiàn)》《Redis實(shí)戰(zhàn)》
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。