1. 程式人生 > 實用技巧 >Redis設計與實現(二) 分散式部分 Sentinel(哨兵) 和 叢集模式

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 :
    • 節點資料庫的實現

      • 會開一個跳躍表來儲存 槽與鍵的 關係
      • 便於對屬於某個或者某些槽的所有資料庫鍵進行批量操作
  • 重新分片

    • 重新分片可以將任意數量 已經分派給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

            1. "Sam", 2) "Alice", 3) "Bob"
        • lrange friend -3 -1

            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