Redis設計與實現(二) 分散式部分 Sentinel(哨兵) 和 叢集模式
Redis設計與實現
第一部分 資料結構與物件
Redis物件
-
首先key value,key是固定的字串物件,value可以是那5種中的一種,而那5種根據場景的不同,每種都有至少兩種編碼方式,也就是資料結構
-
資料結構有linkedlist 雙端連結串列
-
ziplist壓縮列表
- 這個用的太多了 以至於我有深刻的印象
-
skiplist
- 跳躍表 類似於平衡樹的作用 但是實現方式太友好了
-
raw
- 都是sds simply dynamic string
-
embstr
- 都是sds simply dynamic string 區別是這個更短嘍 壓縮過的 是連續的記憶體 所以速度比raw的快
-
hashtable
- 兩個表嘛ht[0]和ht[1]
- 嗯 衝突就是在同一個雜湊值組成連結串列 然後有一個負載因子 預設好像是1 進行rehash 然後rehasn的話會在另外一個大於當前數量number的最小的2的n次方那麼大的擴容 擴容期間 伺服器空閒就轉移 當然 新插入的都是在這個新表裡 hashtable陣列ht[1] 然後全部拷到ht[1]後 就會把ht[0]換成ht[1] 然後h[1]又變成空
-
linkedlist
- 就是一個簡單的雙鏈表
-
intset
- int_8 int_16 int_32 int_64
- 會儲存編碼方式
- 一旦有東西需要往上提 比如從int_32到int_64了 這個過程是不可逆的 即使把64的都刪了 也不會降編碼回32了
第八章 物件
-
字串
-
int
- redis預設有0~9999的int字串
- 順便說一下 整數都是先轉字串 然後用的時候再轉回數字
-
raw
-
embstr
-
-
列表
- linkedlist
- ziplist
-
雜湊物件
- ziplist
- dict 或者叫hashtable
-
集合
- intset
- hashtable
-
有序集合
-
ziplist
- score key
-
資料結構叫zset
-
skiplist
- 子主題 1
-
dict
- 子主題 1
-
同時用這兩個的原因是因為 既要單點查詢的速度 也要範圍查詢的速度吧
-
-
第二部分 單機資料庫的實現
第9章 資料庫
-
型別檢查 命令多型
-
先看key是不是符合那個命令的
- 再看看編碼方式是什麼
-
-
物件回收
- 計數唄
-
物件共享
-
不共享包含字串的物件
-
對整數檢查O(1)
-
對字串O(N)
-
物件包含了多個值物件
- O(N^2)
-
-
空轉時長
- 有個lru時間 記錄最後一次被程式訪問的時間
第10章 RDB持久化
第11章 AOF持久化
第12章 事件
第13章 客戶端
第14章 伺服器
第三部分 多機資料庫的實現
第15章 複製
-
Redis的複製分為兩步, 同步(sync)和傳播(command propagate)
- 在我理解無非一個是先獲取一個伺服器的快照
- 另外一個是追趕式恢復
-
1.同步
-
客戶端向伺服器傳送SYNC命令來完成
- 1.從伺服器向主伺服器傳送SYNC命令
- 2.收到SYNC的master執行 BGSAVE命令, 在後臺生成一個RDB檔案, 並用一個緩衝區來記錄現在開始的所有命令
- 3.主伺服器把RDB檔案傳給從伺服器,從服務區更新至 主伺服器執行BGSAVE的命令狀態
- 4.主伺服器把緩衝區的所有命令傳送給從伺服器, 從伺服器進行追趕式恢復
-
SYNC命令非常號資源,
- 生成RDB檔案需要CPU 記憶體, 磁碟I/O
- 傳送RDB檔案需要網路頻寬
- 從節點載入的時候也無法處理請求
-
-
2.命令傳播
- 無非就是主伺服器執行命令 , 然後傳送給 從伺服器 來保持一致性
-
舊版複製功能的缺陷
-
每次都是把完整的RDB檔案進行傳輸
- 如果斷線時間不是很長也要 全部複製一遍
- 所有要引入部分複製(PSYNC)
-
-
新版複製功能
-
部分重同步的實現
-
1.主伺服器的複製偏移量(replication offset)
-
主伺服器和從伺服器都維護一個偏移量
- 每次主伺服器向從伺服器傳播N個位元組的資料,就把位元組的偏移量加上N
- 從伺服器收到N個位元組的資料, 也把自己的偏移量加上N
- 如果二者偏移量不一樣, 那就是資料不一致
-
-
2.主伺服器的複製擠壓緩衝區(replication backlog)
-
是一個固定長度的先進先出佇列
-
如果從伺服器連線上主伺服器的時候, 從伺服器會把自己的偏移量發給伺服器
-
如果已經不在了
- 執行完整的重同步
-
如果這個偏移量還在複製積壓緩衝區之內
- 執行部分同步
-
-
預設1MB
- 一般設成= 2(冗餘)* 平均重連時間*每秒寫入命令的位元組數
-
-
-
3.伺服器的執行ID
-
主伺服器會把自己的ID傳送給客戶端
- 如果從伺服器儲存的執行ID和當前的主伺服器發過來的一樣, 說明之前連的就是這臺, 嘗試部分重同步
- 如果不一樣, 執行完整重同步
-
-
-
-
複製功能的實現
-
步驟1 設定主伺服器的ip和埠
- 加上倆機子伺服器
- 非同步的, 返回ok, 然後後臺執行復制
-
步驟2 建立socket連線
-
如果成功連線
-
從伺服器會被主伺服器看成特殊的客戶端
-
對於普通的客戶端來說
- 從伺服器還是個伺服器
-
-
-
步驟3 傳送ping命令
- 如果不成功會回到第2步的
-
步驟4 身份驗證
- 主要是看主伺服器和從伺服器有沒有設定密碼, 常識
- 失敗就重試
- 成功就準備複製
-
步驟5 傳送埠資訊
- 從伺服器向主伺服器傳送從伺服器的監聽埠號
-
步驟6 同步
- 互為客戶端
-
步驟7 命令傳播
-
進入命令傳播狀態
- 一直接受主伺服器的寫命令
-
-
-
心跳檢測
-
命令傳播階段, 每1s ,從伺服器會向主伺服器傳送replication ack [offset]
-
作用
-
1.檢測主從伺服器的連線狀態
-
2.輔助實現min-slaves選項
- 比如min-slave-to-write 3
- min-salves-max-lag 10
- 如果從伺服器少於3 , 拒絕寫
- 如果三個伺服器延遲都大於等於10, 拒絕寫的請求
-
3.檢測命令丟失
- 如果主伺服器發現落後於自己, 就會從複製擠壓緩衝區中重新發給從伺服器
-
-
第16章 Sentinel哨兵
-
現在我知道了,哨兵就是負責監視主伺服器和從伺服器 然後如果主伺服器下線了 它會選出新的從伺服器作為主伺服器, 然後在原來的主伺服器上線後把它降級成從伺服器
-
哨兵是Redis的高可用性解決方案
- 由一個或多個哨兵組成的哨兵系統可用監視任意多的伺服器
-
啟動並初始化Sentinel
-
初始化伺服器
-
使用Sentinel專用程式碼
-
初始化Sentinel狀態
-
初始化Sentinel狀態的masters屬性
-
建立連線向主伺服器的"非同步"網路連線
-
一個是命令連線
- 用來發送命令 和 接受回覆
-
一個是 訂閱連線
- sentinel:hello頻道
-
-
-
獲取主伺服器資訊
-
每10s一次 傳送INFO命令
- 可以獲取主伺服器和 它的從伺服器的資訊
-
-
獲取從伺服器資訊
- 會建立到從伺服器的命令連線和訂閱連線
-
向主伺服器和從伺服器傳送資訊
- 向__sentinel__:hello 頻道傳送資訊 s_開頭的是 sentinel本身的資訊, m_開頭的是 主伺服器的資訊
-
接受來自主伺服器和從伺服器的頻道資訊
-
如果有3個sentinel監視同一個伺服器 , 三個sentinel都會收到 sentinelA傳送的頻道資訊
-
如果發現是自己的資訊, 就丟棄
-
如果發現是其他sentinel, 就更新資訊, 也會儲存其他sentinel
- 然後會建立連向其他sentinel的命令連線
-
-
檢測主觀下線狀態
-
預設情況,sentinel 會向 所有的 "命令連線" 傳送PING命令(包括主伺服器, 從伺服器, 其他Sentinel
-
有效回覆
- +PONG
- -LOADING
- -MASTERDOWN
-
無效回覆
- 除那三個以外的
- 或者無回覆
-
50000ms
- 會用來判斷 主伺服器, 從伺服器 ,以及所有監視主伺服器的Sentinel的狀態, 這些人 都是50s沒回復, 本寶寶就認為你們下線了
-
-
檢測客觀下線狀態
- 會問其他Sentinel,看他們是否認為下線了
- 當認為下線的Sentinel達到一定數量, 我們會標記成客觀下線狀態
-
選舉領頭Sentinel
- 當Amaster下線後, 監視他的所有Sentinel會 選出一個領頭Sentinel, 並由這個領頭Sentinel來負責故障轉移操作
- 然後會在一個配置紀元裡選出領頭Sentinel
- 選不出就繼續選
- Raft演算法
-
故障轉移
-
選出新的主伺服器
- 領頭Sentinel向它選擇的從伺服器 傳送SALVE no one, 然後每秒發一次INFO, 直到從slave變成 master
-
修改從伺服器的複製目標
- 向其他的從伺服器傳送SLAVEOF命令
-
將舊的主伺服器變成從伺服器
-
第17章 叢集
-
Redis叢集是Redis提供的分散式資料庫方案, 叢集通過分片(sharding)來進行資料共享, 並提供複製和故障轉移功能
-
節點
-
啟動節點
-
一個節點就是一個執行在叢集模式下的Redis伺服器
-
一個節點會繼續做所有單機模式中使用的伺服器元件
-
1,會繼續使用檔案事件處理器來處理命令請求和返回命令回覆
-
2,繼續使用時間事件處理器來執行serverCron函式, 還會呼叫clusterCron函式
-
clusterCron函式負責執行在叢集模式下的常規操作
- 1,向其他節點發Gossip資訊
- 2,檢查節點是否斷線, 檢查是否需要對下線節點進行自動故障轉移等等
-
-
3,繼續資料庫
-
4, 繼續RDB和AOF持久化模組
-
5,繼續使用釋出/訂閱模組
-
6,繼續使用複製模組
-
7,繼續使用Lua指令碼
-
-
CLUSTER MEET命令的實現
-
客戶端發 CLUSTER MEET命令給節點A, 讓節點A和節點B握手
- 節點A傳送MEET訊息
- 節點B傳送PONG訊息
- 節點A傳送PING訊息
-
-
-
-
槽指派
-
叢集通過分片的方式來儲存 資料庫的 鍵值對
- 整個資料庫被分為16384個槽, 只有這16384個槽都有節點處理 的時候, 叢集才是上線狀態的
-
記錄節點的槽指派資訊
-
slots屬性是一個二進位制位陣列(bit array)
- 如果索引i上的二進位制位的值位1, 表示處理槽i
- 如果索引i上的二進位制值位0 , 表示不處理i
-
所以檢查是否負責處理某個槽, 或者任命某個槽
- 時間複雜度為O(1)
-
-
傳播節點的槽指派資訊
- 節點會向其他節點發送自己的slots陣列
-
記錄叢集所有槽的指派資訊
- clusterState.slots陣列記錄了所有 具體的槽 map到具體節點的資訊
- clusterNode記錄了某個節點的槽指派資訊
-
CLUSTER ADDSLOTS命令的實現
-
-
在叢集中執行命令
-
客戶端向節點發送資料庫的命令時, 接受命令的節點會算出 key屬於哪個槽 ,這個槽又是否屬於自己
-
計算鍵屬於哪個槽
-
CLUSTER KEYSLOT
- 可以看給定的key屬於哪個槽
- CRC16(key)& 16383
-
-
判斷槽是否由當前節點負責處理
- 看hashtable是不是指向自己
-
MOVED錯誤
- MOVED
:
- MOVED
-
節點資料庫的實現
- 會開一個跳躍表來儲存 槽與鍵的 關係
- 便於對屬於某個或者某些槽的所有資料庫鍵進行批量操作
-
-
重新分片
-
重新分片可以將任意數量 已經分派給A的 槽 改成 分派給B的
-
可以線上進行, 並且可以繼續處理命令請求
-
重新分片的實現原理
-
由Redis叢集管理軟體redis-trib負責進行的
-
相當於一個controller
- 對目標節點發送準備匯入
- 源節點準備遷移
- 遷移
-
-
-
ASK錯誤
-
具體步驟
-
客戶端 向源節點 傳送關於key的命令
-
在 源上, 執行命令
-
不在源上 正在遷移?
- 在遷移, 返回ASK錯誤
- 不在遷移 返回key不存在
-
-
ASK錯誤和MOVED錯誤的區別
- 都會導致客戶端轉向
- MOVED 以後都會發給新的
- ASK 只是臨時的
-
-
複製與故障轉移
-
Redis中的節點分為主節點(master)和從節點(slave), 主節點用於處理槽 , 從節點用於複製某個主節點
-
設定從節點
- CLUSTER REPLICATE <node_id>
-
故障檢測
- 子主題 1
-
選舉新的主節點
- Raft演算法
-
-
訊息
-
主要有5種訊息
- MEET
- PING
- PONG
- FAIL
- PUBLISH
-
第四部分 獨立功能的實現
第18章 釋出與訂閱
-
釋出與訂閱的核心命令是兩組
-
Publish
-
Subsctibe
- Subscribe
- UnSubscribe
- PSubscribe
- PUnSubscribe
-
-
1.頻道的訂閱與退訂
-
訂閱頻道
-
伺服器有個字典叫pubsub_channels
- 鍵是 某個被訂閱的頻道
- 值是 一個連結串列, 這裡面存了該頻道的訂閱者
-
-
退訂頻道
-
根據頻道的名字在字典中找
- 把客戶端從連結串列中刪除
- 如果恰好是最後一個, 把這個頻道也刪除
-
-
-
2.模式的訂閱與退訂
-
伺服器有個list叫pubsub_patterns
- <client, pattern>作為list連結串列的元素
- list<client,pattern>
-
訂閱模式
- 新建一個node, 把這個node新增到list後面
-
退訂模式
- 遍歷這個list, 找到node然後刪掉
-
-
3.傳送訊息
-
傳送訊息publish channelA "message"有兩步,
- 1.首先把訊息發給channelA的所有訂閱者
- 2.然後把訊息發給所有模式匹配channelA的
-
1.很簡單啦, 找到key對應的連結串列, 然後全部發一遍
-
2.先查詢所有list裡的pattern, 如果匹配, 就把這個pattern對應的client都發一遍
-
-
4.檢視訂閱訊息
-
pubsub channels
-
pubsub numsub [channel]
- 返回channel的訂閱者數量
-
pubsub numpat
- 返回被訂閱模式的數量
- number of pattern
-
第19章 事務
第23章 慢查詢日誌
第24章 監視器
- 客戶端可以通過MONITOR命令 ,將客戶端轉換成監視器 ,接受並列印每個命令請求的相關資訊
- 把客戶端的REDIS_MONITOR標識開啟, 就可以從普通客戶端變成監視器了
- 伺服器把所有監視器都存在一個連結串列裡
- 每次都會遍歷連結串列, 挨個傳送
實踐
Redis的入門應用
-
Redis是key-value型資料庫
-
Redis 裡的單行命令都是原子的 是為了同時有多個使用者對同一個資料修改
-
string
-
set key value
-
子主題 1
-
SET server:name "fido"
-
GET server:name => "fido"
-
EXISTS server:name => 1
-
EXISTS server:blabla => 0
-
SET connections 10
-
INCR connections => 11
-
INCR connections => 12
-
DEL connections
-
INCR connections => 1
-
It is also possible to increment the number contained inside a key by a specific amount:
-
INCRBY connections 100 => 101
-
And there are similar commands in order to decrement the value of the key.
-
DECR connections => 100
-
DECRBY connections 10 => 90
-
-
-
get key
-
exists key
-
DEL key
-
EXPIRE key 100
-
100秒後刪除
-
TTL key
- 顯示還有幾秒刪除
- 只要set key 一次 之前的計時失效 TTL key 返回-1
- 返回-2說明計時過了 這東西被刪了
-
PERSIST key
-
讓這個計時結束
- 永久儲存
-
-
-
INCR key
- INCRBY key 100
-
DECR key
- DECRBY key 100
-
不能簡單的get key \ value=value+1 \set key value
-
-
list
-
對頭尾操作較快 且 有序號的
-
lpush friend "yanhao"
-
rpush friend "yanhao"
-
lpush friend 1 2 3 4
-
執行順序是
- lpush friend 1
- lpush friend 2
- lpush friend 3
- lpush friend 4
-
所以結果是 4 3 2 1
-
-
lrange friend 0 -1
-
切片
-
example
-
LRANGE friends 0 -1 => 1) "Sam", 2) "Alice", 3) "Bob"
-
LRANGE friends 0 1 => 1) "Sam", 2) "Alice"
-
LRANGE friends 1 2 => 1) "Alice", 2) "Bob"
-
lrange friend 0 10
-
- "Sam", 2) "Alice", 3) "Bob"
-
-
lrange friend -3 -1
-
- "Sam", 2) "Alice", 3) "Bob"
-
-
-
-
lset friend 0 "yanhao"
- 把friend這個list裡面的第0個元素改成"yanhao"
-
lpop friend
- rpop friend
- 子主題 2
-
llen
-
-
set
-
sadd setname "yanhao"
-
return 0
- 沒加成功,因為本來就有這個key了
-
return 1
- 加成功了
-
-
sismember setname "yanhao"
-
smembers friend
-
sincr
-
sdecr
-
srem friend "yanhao"
- return 0
- return 1
-
srandmeber friend 2
- 隨機返回資料成員
-
spop
- 隨機刪
-
sunion set1 set2
-
-
sorted set
-
在set的同時加上一個用來排序的東西
-
ZADD fruit 1 apple
-
ZADD fruit 2 banano
-
Zrange fruit 0 1
- 切片
-
-
hashes
-
hset user:37 name "yanhao"
-
hgetall user:37
-
hmset user:37 name "yanhao" age 17
- 同時設定多個
-
hget user:37 name
-
hincrby user:37 name 2
- name會加2
-
hdel
-
XMind: ZEN - Trial Version