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