學完MySQL InnoDB以後,又開始學習和研究Redis。redis
首先介紹下書:《Redis設計與實現》第二版 黃健宏著,機械工業出版社,388頁,基於redis3.0版本。版本有點低,這個影響不大,基本面變化不大,而變化的部分網上查資料能夠彌補。算法
Redis服務器是一個鍵值對(key-value pair)類型數據庫服務器,屬於NoSQL。Redis源碼使用ANSI C語言編寫而成。數據庫
它最大的特色數據所有放緩存,主要用於讀寫操做頻繁的,如秒殺系統。我聯想的是咱們監控系統中的秒級回放,特別是分佈式系統中,或許能夠考慮。數組
包括動態字符串、鏈表、字典、跳躍表、整數集合、壓縮列表共六種。
redis雖然是C語言實現的,可是它的數據結構和平時用的不同,有些改進和封裝,更適用於數據庫;這應該也是它的創新之處。緩存
動態字符串SDS和Java StringBuffer類十分類似:帶緩衝區的字符串,值能夠被改變。服務器
印象最深仍是跳躍表,書上介紹說實現比平衡樹簡單,可是效率卻能夠和它媲美;從結構設計看,很是優秀。網絡
包括字符串對象、列表對象、哈希對象、集合對象、有序集合對象共五種。對象是對基礎數據結構的再封裝,從而供Redis直接使用,即Redis並不直接操做上述基本的數據結構。而每種對象都用到了至少一種上面的數據結構。數據結構
這個五大對象類型,才Redis數據庫中,對應五個常量,即對象類型常量。而上述六種的數據結構也有對應的常量,叫編碼常量。可是編碼常量共有八種,其餘兩種是整型字符串和短字符串。當字符串長度小於等於39字節時,採用embstr編碼,即短字符串編碼。架構
那麼對象類型常量和編碼常量是一個固定的對應關係,除了字符串對象類型常量對應三種編碼常量外,其餘都是對應兩種編碼常量。app
注:一個對象類型常量對應的 兩個或者說三個編碼常量在必定條件下系統會自動切換的,條件包括字符串長度變化、元素個數變化、追加不一樣類型等等;Redis稱爲編碼轉換。
Redis的鍵和值都是對象,因此Redis基於對象操做的,而對象的內存回收經過引用計數技術。
Redis服務器的全部數據庫都保存在redisServer.db數組中,而數據庫的數量則由redisServer.dbnum屬性保存,默認會建立16個數據庫。
數據庫由字典構成,咱們將這個字典稱爲鍵空間(key space);鍵老是一個字符串對象,而值能夠是任意一種對象類型,如上描述的五種對象類型之一。
數據庫主要由dict和exprires兩個字典構成,其中dict字典負責保存鍵值對,而 exprires字典負責鍵的過時時間。
Redis設置鍵的過時時間管理鍵的生存時間,設置過的鍵同時保存在exprires字典中,對鍵的生存時間的設置有以下命令。
expire------設置過時時間,第二個參數是TTL(單位秒)。
pexpire------設置過時時間(單位豪秒)。
TTL------查看剩下多少時間(單位秒),返回負數則key失效,key不存在了。
PTTL------功能同TTL,單位對應毫秒。
expireat------設置過時時間,第二個參數是時間戳(單位秒)。
pexpireat------功能同上(單位豪秒)。
persist ------取消過時時間,將鍵值對從exprires字典中移除。
刪除策略有三種方式:
(1)定時刪除:建立定時器,只要存在鍵過時則立刻刪除。
(2)惰性刪除:獲取鍵的時候才刪除。
(3)按期刪除:每隔一段時間檢測,刪除過時鍵。但不是全部過時鍵,隨機抽取的,由系統算法決定。
Redis實際採用第二種和第三種。
SAVE命令 和 BGSAVE命令所產生的新的RDB文件不會包含過時鍵。
BGREWRITEOF 命令所產生的AOF重寫文件不會包含過時鍵。
主從複製時,從節點不會主動刪除過時鍵,而是等待主節點發送DEL命令,以保證數據的一致性。
set------設置 key 對應的值爲 string 類型的 value。
setnx------設置 key 對應的值爲 string 類型的 value。若是 key 已經存在,返回 0,nx 是 not exist 的意思。
setex------設置 key 對應的值爲 string 類型的 value,並指定此鍵值對應的有效期。
del------刪除某個key,第一次返回1 刪除了 第二次返回0。
mset------一次設置多個 key 的值,成功返回 ok 表示全部的值都設置了,失敗返回 0 表示沒有任何值被設置。
getset------設置 key 的值,並返回 key 的舊值。
mget------一次獲取多個 key 的值,若是對應 key 不存在,則對應返回 nil。
append------給指定 key 的字符串值追加 value,返回新字符串值的長度。
strlen------取指定 key 的 value 值的長度。
randomkey------隨機返回一個key
rename------重命名
type ------返回數據類型
select 0 ------選擇數據庫(0-15庫)
move age 1------把age 移動到1庫
FLUSHDB------清空整個數據庫
思考:
Redis主要是內存資源使用多,但據網上資料,支持較短長度字符串時上百萬和千萬級不成問題。
分爲時間事件和文件事件兩類。
時間事件指服務器的一些定時操做。
文件事件是服務器與客戶端或者其餘服務器之間的通訊。文件事件處理器基於Reactor模型實現的網絡通訊,反應器模型;IO多路複用採用相對的還有proactor模型,主動器模型。
Redis服務器是典型的一對多程序,能夠同時與多個客戶端進行通訊,同時保存每一個客戶端相關信息。
客戶端結構體最重要的兩部分是一個輸入緩衝區和一個輸出緩衝區,用於保存接收和發送的內容。輸入緩衝區,大小不能超過1GB;輸出緩衝區分紅兩部分,固定的和可變的,固定的大小16KB。
客戶端分爲普通客戶端,僞客戶端。其中僞客戶端包括LUA腳本僞客戶端和AOF文件僞客戶端。普通客戶端的fd屬性值大於-1,僞客戶端該值爲-1。
服務器常見功能是處理客戶端輸入的命令外,還有serverCron函數,它是一個時間事件,每隔100毫秒運行一次。
serverCron函數的工做主要包括更新服務器狀態信息,處理服務器接收的SIGTERM信號,管理客戶端資源和數據庫狀態,檢查並執行持久化操做等。
SIGTERM信號主要做用是服務器關閉前不會因其餘事件如持久化阻塞而能及時退出。
補充:LRU緩存淘汰算法
在redis緩存機制中,提到了LRU,least recently used最近最少使用,這個在MySQL也有。可是二者描述的不是不少。
感受這個算法很好,在網上查資料,不少互聯網的技術也使用了該算法。其實對熱點數據,例如咱們的經常使用的攝像機的搜索、回放和即時回放能夠考慮。
最新版本Redis還有LFU(least frequently used最不常用)緩存技術。
相對MySQL很是簡單,ACID都沒有MySQL那麼強大;也不支持回滾。可能定位不同,redis自己是單線程。
對操做和消息而言,客戶端和服務器之間使用。
經過SORT命令對數據的查找和排序。
Redis內嵌的腳本語言,方便實現批量操做等。常見的有EVAL命令和EVALSHA命令,EVALSHA命令能夠直接執行由EVAL命令生成的SHA1校驗和。
補充:Lua是一種內嵌的腳本語言,輕量小巧,標準C語言編寫,開源。目前常(嵌入)用在遊戲開發中。
Redis支持二進制位數組的操做,這種類型能夠大大節約內存空間,並且查找和統計都很是方便。
補充:實際應用中,有不少的大型系統中將二進制位數組用於查詢和統計用戶的登陸狀態。
保存在結構體slowlogEntry爲元素的slowlog鏈表中。能夠設置超時時間單位毫秒,和保存條數。
客戶端可經過monitor命令將本身變成監視器,監視服務器處理的每一個命令。
redis數據都是放內存,持久化即將數據寫入磁盤,如下兩種都是以日誌的形式存放。
RDB是Redis DataBase縮寫,存的最終的真實的數據,二進制文件,用redis自帶命令能夠解析和讀取。
功能核心函數rdbSave(生成RDB文件)和rdbLoad(從文件加載內存)兩個函數。
SAVE命令 和 BGSAVE命令用於持久化操做,後者表示子進程在後臺運行。BGSAVE模式下,服務器根據配置文件或者save(小寫)命令設置得參數自動間隔執行一次BGSAVE。save參數第一個是間隔時間,第二個是修改次數;知足其中之一就會執行。
AOF是Append Only File縮寫,存的是增刪改的操做命令,ASCII字符格式,可讀。
數據修改時,先寫入AOF緩衝區,再WRITE寫入AOF文件(此時還在文件系統緩存中),最後由系統調用 fsync 或 fdatasync 函數將系統緩存數據保存到磁盤即同步。什麼時候同步,有三個選項:always立刻,everysec隔約一秒,no交給操做系統;默認值everysec。
BGREWRITEOF 命令實現AOF重寫的功能,即對最終數據讀取再添加的命令寫入文件,減小中間操做和最終文件的大小。一樣使用子進程執行。運行的同時新寫的數據放在AOF重寫緩衝區,即服務器父進程將寫命令同時寫入AOF緩衝區和AOF重寫緩衝區;待重寫工做完成後,再將AOF重寫緩衝區內容寫入新的AOF文件,繼續後面結尾工做。
注意:當服務器開啓了AOF功能時,服務器優先使用AOF文件來還原數據;而AOF默認是關閉的。
並且,BGSAVE 和 BGREWRITEOF 兩個命令不能同時運行。若是BGREWRITEOF 在運行,BGSAVE 會被拒絕;而BGSAVE 運行時,BGREWRITEOF 會被延遲到BGSAVE 完成以後。
(1)主從服務器架構中,從服務器發送SLAVEOF命令給主服務器啓動複製。從服務器收到OK返回消息後,再次發送SYNC命令表示請求同步數據。主服務器先發送RDB文件給從服務器,再將緩衝區全部寫命令也發送給從服務器,從而實現主從數據一致。可是若是同步後,在中間過程從服務器斷開,再鏈接上,將重複上面的過程,即徹底增量的同步。
(2)Redis從2.8版本開始用PSYNC代替SYNC,來實現部分重同步。
(3)部分重同步經過複製偏移量、複製積壓緩衝區、服務器運行ID三個部分來實現。
Sentinel哨兵模式是Redis的高可用解決方案。
(1)由一個或者多個Sentinel實例組成Sentinel系統,監視多個主服務器以及服務器下的全部從服務器。當一套主從服務器集羣中,主節點出現下線或者故障時,將從從服務器中選舉一個做爲該集羣的主服務器,若是之前的主服務器再次上線時,自動變成從服務器,將重新的主服務器複製數據保持同步。
(2)上述過程當中從服務器是如何選出的?領頭Sentinel先剔除離線的,較長時間沒有回覆INFO命令的,再根據優先級,複製偏移量來選擇的;即選擇狀態良好的、數據完整的從服務器。
(3)Sentinel是一個系統,自身每一個節點都是特定模式下的redis服務器,和普通Redis服務器使用的命令不一樣。
(4)Sentinel經過向主服務器發送INFO命令來獲取主服務器和從服務器的相關信息。再每十秒一次向全部記錄的主從服務器發送INFO命令,當集羣在主節點故障轉移過程當中,將改成一秒一次。
(5)Sentinel以每秒一次向全部實例(全部主從服務器和其餘Sentinel)發送PING命令,再根據回覆是否有效,若是必定時長都收到無效回覆,Sentinel認爲該實例主觀下線。隨後該Sentinel會向其餘Sentinel進行詢問,是否贊成該實例是否進入主觀下線,若是贊成的數量達到足夠時,將判斷該實例爲客觀下線。若是該實例是主服務器,即將開始故障轉移操做。
(6)故障轉移操做必須是領頭Sentinel來下發,當存在多個Sentinel時需選舉其中一個做爲領頭。
每一個發現主服務器進入客觀下線的sentinel均可以要求其餘sentinel選本身爲領頭sentinel,選舉是先到先得。同時每一個sentinel每次選舉都會自增配置紀元,每一個紀元中只會選擇一個領頭sentinel。若是全部超過一半的sentinel選舉某sentinel領頭sentinel。若是在給定時間內,未選出,將重來直到選出爲止。
補:選舉方法是參考的raft協議(一致性分佈式協議),被如今不少分佈式系統採用。
(7)存在多個Sentinel時,任意Sentinel都需每兩秒一次發現hello頻道消息給其餘Sentinel以宣告本身的存在。
Redis3.0上加入了cluster模式,實現的redis的分佈式存儲。
(1)每一個主節點還能夠再加入本身的從節點,主節點兼有Sentinel特性,從而也能夠實現複製和故障轉移的功能。若是主節點A和它的從節點A1都宕機了,那麼該集羣就沒法再提供服務了。
(2)集羣即主主之間經過分片(sharding)來分配和共享數據。集羣的整個數據庫被分爲16384個槽(slot),因此每一個主節點能夠處理0-16383個槽。
(3)槽被分配(槽指派)的信息存在一個數組下,數組下標爲16384/8=2048,即二進制位數組,每一個位表明被分配的狀態。二進制位數組前面已有講述,使用在這裏很是優秀。
(4)每一個鍵都屬於這16384個哈希槽的其中一個,公式 CRC16(key) % 16384來計算鍵key屬於哪一個槽。
(5)集羣的各個節點經過Gossip協議來交換各自信息,該協議由MEET、PING、PONG三種消息實現。
補充:Gossip來源於流行病學的研究(ps,當前正是疫情之下,讓人瑟瑟發抖)
Gossip簡單、高效,同時具備很好的可擴展性和魯棒性,很是適合大規模、動態、資源受限的網絡環境。
(6)節點的FAIL是經過集羣中超過半數的節點檢測失效時才生效。
集羣的演進:主從複製實現了讀寫分離,哨兵模式實現了故障轉移,集羣模式實現了分佈式存儲。
我的收穫總結:讀書除了能夠系統的瞭解一個key-value數據庫的實現外,還能夠看到版本的演進例如集羣。還能夠見識和學習鞏固不少策略和算法,例如跳躍表、二進制位數組、LRU、raft、LUA腳本、Gossip協議等等。開卷有益。若是再深一步,看源碼我想收穫會更大吧。