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