1、數據結構與對象
簡單動態字符串(SDS)
- 相比C字符串增長記錄字符串長度的,獲取字符串長度複雜度爲O(1)
- 相比C字符串增長記錄已分配內存空間,能夠避免緩衝區溢出
- 空間預分配和空間惰性釋放
- 二進制安全,不是以空字符(0)來判斷字符串是否結束
- 遵循C字符串以空字符結尾的慣例,能夠兼容部分C字符串函數
關於空間預分配和空間惰性釋放redis
- 字符串增加操做時,若是修改後長度小於1M則分配該字符串長度2倍的內存空間,若是修改後長度大於等於1M則分配該字符串長度+1M的內存空間。(預分配,避免每次增加操做都須要進行內存重分配執行系統調用)
- 字符串縮短操做時,程序不會當即釋放縮短後多出來的字節,而是在須要時再釋放。(惰性釋放,避免之後須要增加操做時重分配內存,會在較短的時間內形成內存浪費,文中未說起什麼時候是「須要時」)
最佳實踐:由於對字符串的增加或縮短操做都有可能須要執行內存重分配,因此修改相同鍵使用SDS類型保存的值時保持修改先後長度一致。
鏈表
- 雙端,獲取某節點先後置節點對複雜度爲O(1)
- 無環,表頭prev指針和表尾next指針都指向NULL
- 記錄表頭尾節點,獲取表頭尾節點的複雜度爲O(1)
- 記錄鏈表長度,獲取鏈表長度複雜度爲O(1)
- 空指針保存值,能夠保存各類不一樣類型的值
字典
- 使用鏈地址法解決衝突,當多個鍵被分配到相同哈希索引時將新鍵添加到節點鏈表表頭
- 字典包含ht[0]和ht[1](ht[1]僅爲rehash時使用)兩個哈希表,當哈希表保存的鍵值對數量太多或太少時使用從新散列(rehash)維持哈希表負載因子在合理範圍以內
- rehash操做採用漸進式,份量將ht[0]中的鍵值對rehash到ht[1],新鍵值對統一保存到ht[1]中
rehash步驟算法
- 擴展操做(沒有執行BGSAVE或BGREWRITEAOF且負載因子大於等於1;正在執行BGSAVE或BGREWRITEAOF且負載因子大於等於5),爲ht[1]分配第一個大於等於當前包含鍵值對數量(ht[0].used)*2的<math><msubsup><mi>2</mi><mi></mi><mi>n</mi></msubsup></math>內存空間
- 收縮操做(負載因子小於0.1時),爲ht[1]分配第一個大於等於當前包含鍵值對數量的<math><msubsup><mi>2</mi><mi></mi><mi>n</mi></msubsup></math>內存空間
- 將保存在ht[0]中的全部鍵值對rehash到ht[1]
- 釋放ht[0],將ht[1]設置爲ht[0],建立新的空白哈希表ht[1]
負載因子=哈希表已保存節點數量/哈希表大小sql
Redis使用MurmurHash2算法來計算鍵的哈希值
跳躍表
- 有序集合的底層實現之一
- 每一個節點能夠保存一個字節數組或整數值
- 鏈表中的節點按照分值大小排序,分值相同時按對象大小排序
整數集合
- 能夠保存int16_t(-32768至32767)、int32_t(-2147483648至2147483647)、int64_t(-9223372036854775808至9223372036854775807)三種類型的整數集
- 爲節約內存,集合類型使用最小類型保存整數,僅當新添加的整數大於當前所能容納的值範圍時進行升級操做
- 由於每次添加新元素都有可能引發升級,因此添加新元素的時間複雜度爲O(N)
- 不支持降級操做
升級步驟數據庫
- 根據新元素的類型擴展底層數組空間,併爲新元素分配空間
- 轉換現有元素至新的類型,保持有序性放置元素
- 添加新元素,當新元素小於全部先有元素時放置在索引0,當新元素大於全部先有元素師放置在索引length-1
最佳實踐:爲了不添加新元素時產生升級操做,應向同一整數集合添加相同類型的整數
壓縮列表
- 做爲列表鍵和哈希鍵的底層實現之一
- 添加或刪除節點均可能形成連鎖更新,連鎖更新最壞時間複雜度爲O(<math><msubsup><mi>N</mi><mi></mi><mi>2</mi></msubsup></math>)
對象
- 字符串對象(REDIS_STRING即string)
- 列表對象(REDIS_LIST即list)
- 哈希對象(REDIS_HASH即hash)
- 集合對象(REDIS_SET即set)
- 有序集合對象(REDIS_ZSET即zset)
<div align=center>不一樣類型和編碼的對象</div>
類型 |
編碼 |
對象 |
REDIS_STRING |
REDIS_ENCODING_INT(整數值) |
使用整數值實現的字符串對象 |
REDIS_STRING |
REDIS_ENCODING_EMBSTR(小於32字節字符串) |
使用embstr編碼的簡單動態字符串實現的字符串對象 |
REDIS_STRING |
REDIS_ENCODING_RAW(大於32字節字符串) |
使用簡單字動態字符串實現的字符串對象 |
REDIS_LIST |
REDIS_ENCODING_ZIPLIST(默認配置下,全部元素長度小於64字節且元素數量小於513,查看命令:CONFIG GET list-max-ziplist*) |
使用壓縮列表實現的列表對象 |
REDIS_LIST |
REDIS_ENCODING_LINKEDLIST |
使用雙端鏈表實現的列表對象 |
REDIS_HASH |
REDIS_ENCODING_ZIPLIST (默認配置下,全部元素長度小於64字節且元素數量小於513,查看命令:CONFIG GET hash-max-ziplist*) |
使用壓縮列表實現的列表對象 |
REDIS_HASH |
REDIS_ENCODING_HT |
使用字典實現的哈希對象 |
REDIS_SET |
REDIS_ENCODING_INTSET(默認配置下,全部元素都是整數值且元素數量小於513,查看命令:CONFIG GET set-max-intset-entries) |
使用整數集合實現的集合對象 |
REDIS_SET |
REDIS_ENCODING_HT |
使用字典實現的集合對象 |
REDIS_ZSET |
REDIS_ENCODING_ZIPLIST(默認配置下,全部元素長度小於64字節且元素數量小於128,查看命令:CONFIG GET zset-max-ziplist*) |
使用壓縮列表實現的有序集合對象 |
REDIS_ZSET |
REDIS_ENCODING_SKIPLIST |
使用跳躍鏈表和字典實現的有序集合對象 |
備註數組
- TYPE KEY(獲取鍵的對應值對象類型)
- OBJECT ENCODING KEY(獲取鍵的對應值對象編碼)
內存回收、對象共享、空轉時長度
- 每一個對象都有引用計數器,當引用計數爲0時對象所佔用的內存將被釋放
- Redis初始化服務時自動建立0-9999的字符串對象(包括數據結構中嵌套了字符串對象的:linkedlist的列表對象、hashtable的哈希對象、hashtable的集合對象、zset的有序集合對象),值在對應範圍內的字符串對象將共享同一對象
- 每一個對象記錄有最後一次被命令程序訪問的時間,當maxmemory且回收內存算法爲volatile-lru或allkeys-lru時內存一旦超過maxmemory上限則優先釋放空轉時長較高的鍵值對
最佳實踐:爲了最大程度的節省內存,應將簡單字符或重複率較高的字符串對應成0-9999範圍內的數字。
2、單機數據庫的實現
數據庫
- Redis有多個數據庫,默認值爲16(查看命令:CONFIG GET databases)
- 過時鍵有惰性刪除和按期刪除兩種策略
- 從服務器不會自主刪除過時鍵
惰性刪除:當讀取的鍵是一個過時鍵時纔會將該鍵刪除並返回空。安全
按期刪除:在規定的時間內分屢次遍歷每一個數據庫,從expires字典中隨機檢查一部分鍵的過時時間(也即每次執行按期刪除並不必定能把全部的過時鍵都刪除)。服務器
最佳實踐:主從模式下從服務器在讀取到過時鍵時不會主動刪除且會當成正常鍵返回數據,當數據中包含較多的過時鍵時主服務器的按期刪除策略可能須要較長時間才能將該過時鍵刪除,所以Redis的主從模式不一樣於Mysql的主從模式(主寫從讀),若是使用相似Mysql主從的用法時需注意過時數據在必定時間內多是髒數據。
RDB持久化
- RDB文件用於保存和還原Redis服務器全部數據庫中的數據
- SAVE由服務器進程執行,所以會阻塞服務器
- BGSAVE由子進程執行,所以不會阻塞服務器
- RDB是一個通過壓縮的二進制文件
AOF持久化
- AOF文件經過保存全部修改數據庫的寫命令請求來記錄服務器的數據庫狀態
- AOF文件中全部命令均以Redis命令請求協議保存
- 命令請求會先保存到AOF緩衝區中,再按期保存到AOF文件
- AOF重寫經過讀取數據庫中的鍵值對來從新產生一個AOF文件,該文件減小了不少再也不須要的命令所以文件體積更小
事件
- Redis是由時間事件和文件事件組成的事件驅動程序
- 文件事件處理器是基於Reactor模式實現的網絡通訊程序,事件分爲讀事件、寫事件
- 時間事件分爲定時事件、週期事件
- serverCron是一個週期性事件,它是Redis週期性事件的主要函數
- 由於事件處理在時間事件和文件事件中輪訓,且不會搶佔,時間事件不必定在設定的時間當即執行
客戶端
- 客戶端發送的請求記錄在服務端的輸入緩衝區,該緩衝區大小爲1G。
- 服務端的輸出緩衝區分固定緩衝區(16KB)和可變緩衝區(查看命令:CONFIG GET client-output-buffer-limit)。
- Redis默認最大鏈接數爲10000(查看命令:CONFIG GET maxclients)
- 網絡鏈接關閉、發送不合協議格式的命令請求、CLIENT KILL、空轉時間超時、輸入、輸出緩衝區大小超過限制時,客戶端將被關閉。
- 客戶端的空轉時間超過timeout設置的值時將被關閉(查看命令:CONFIG GET timeout)。
- 可變輸出緩衝區分普通客戶端、pubsub(發佈/訂閱模式)、slave三種客戶端限制。默認狀況下,普通客戶端無限制(阻塞式的消息應答模式一般不會形成輸出緩衝區堆積),pubsub客戶端超過32m或持續60s超高8m,slave客戶端超高256m或持續60s超過64m,對於超過限制的客戶端Redis將關閉鏈接。
- 最大鏈接數受系統當前文件描述符數量限制,最大隻能取文件描述符數量限制-32(Redis最多會佔用32個文件描述符)。
- 若是客戶端是主服務器、從服務器、被BLPOP等命令阻塞、正在執行SUBSCRIBE等訂閱命令,將不受timeout設置影響。
服務器
- serverCron函數默認每100毫秒執行一次,主要包括更新服務器狀態信息、處理服務接收的SIGTERM信號、管理客戶端資源和數據庫狀態、檢查執行持久化等。
命令請求步驟網絡
- 客戶端將命令請求發送給服務器
- 服務器讀取命令請求並解析出命令參數
- 命令執行器根據參數查找命令的實現函數,執行實現函數並得出命令回覆
- 服務器將命令回覆返回給客戶端
服務器啓動步驟數據結構
- 初始化服務器狀態
- 載入服務器配置
- 初始化服務器數據結構
- 還原數據庫狀態
- 執行事件循環
3、多機數據庫的的實現
複製
- Reids 2.8之前沒有部分重同步功能,命令丟失沒法檢測,斷線後須要從新執行一次完整同步
- 部分重同步經過複製偏移量、複製擠壓緩衝區、服務器運行ID三部分實現
- 從服務器默認以1s一次的頻率向主服務器發送REPLCONF ACK <replication_offset>(從服務器當前複製偏移量) 以完成心跳檢測、命令丟失檢測
Sentinel(哨兵)
- Sentinel是運行在特殊模式下的Redis服務器,使用不一樣的命令表
- Sentinel向被監視的主服務器以及其屬下的從服務器建立命令鏈接和訂閱鏈接,命令鏈接用於向主服務器發送命令,訂閱鏈接用於接收__sentinel__:hello頻道的訂閱信息(感知其餘Sentinel的存在)
- 監視同一主服務器的Sentinel感知到其餘Sentinel的存在後相互創建命令鏈接,用於主服務器主觀下線後相互詢問是否贊成主觀下線,當收集夠足夠多票數(大於1/2)後判斷爲客觀下線並進行故障轉移
集羣
- 集羣的整個數據庫(集羣模式下只能使用一個數據庫)被分爲16384個槽,每一個節點會記錄指派給本身的槽以及哪些槽指派給了其餘哪一個節點
- 節點在收到命令請求時先檢查所需處理的鍵是否位於本身的槽中,不是則返回MOVED錯誤引導客戶端跳轉正確節點
- 從新分片工做由redis-trib負責,用於將已指派的槽從源節點轉移到目標節點
- 從新分片過程當中若是客戶端請求一個已經轉移到新節點的鍵則返回ASK錯誤引導客戶端跳轉新節點
- 集羣中的從節點用於複製主節點並在主節點下線後從中選舉出新的主節點
MOVED錯誤表示所請求的鍵負責權已經轉移到另外一節點,ASK錯誤則只是槽正在轉移時的一種臨時性錯誤
4、獨立功能的實現
發佈與訂閱
- 發佈訂閱分爲頻道發佈訂閱和模式發佈訂閱兩種
- 服務器狀態在pubsub_channels字典保存全部頻道訂閱關係,在pubsub_patterns鏈表保存全部模式訂閱關係
事務
- 事務是提供了一種將多個命令打包而後一次性按先進先出順序執行的機制,並不具有回滾功能
- 事務執行過程當中不會中斷,直到全部命令都被執行完以後纔會結束事務
- 帶有WATCH命令的事務能夠監視某個鍵是否被修改,若是事務執行過程當中被修改則客戶端的REDIS_DIRTY_CAS標誌將被打開,事務將被服務器拒絕提交
Lua腳本
- Redis內嵌Lua執行環境,並對環境中的函數進行一些修改以適應Redis,當須要執行Redis命令時使用僞客戶端
- Redis使用腳本字典來保存全部執行或載入過的Lua腳本,腳本的SHA1校驗和做爲鍵名
- Lua腳本在執行前服務器會爲其設置一個超時處理鉤子,腳本運行超時時可使用SCRIPT KILL來停止腳本或SHUTDOWN nosave關閉整個服務器
Redis建立Lua執行環境步驟dom
- 建立基礎Lua環境
- 載入函數庫到Lua環境中
- 建立包含對Redis進行操做的函數的全局表格
- 使用自制隨機函數替代Lua原有帶反作用的隨機函數(自制隨機函數具備如下特徵:①對於相同seed,math.random總產生相同的隨機數序列;②除非顯示修改math.randomseed中的seed,不然均使用math.randomseed(0)初始化seed)
- 建立排序輔助函數,Lua環境使用該函數對一部分Redis命令的結果進行排序
- 建立能夠提供更多詳細錯誤信息的錯誤報告輔助函數redis.pcall
- 保護Lua環境的全局變量,防止執行腳本過程當中修改全局變量
- 將修改完成後的Lua環境保存到服務器狀態的Lua屬性中
排序
- SORT命令由快速排序算法實現
- SORT命令經過將元素保存在數組中,再對數組進行排序
慢查詢日誌
- Redis默認記錄執行超過10000us的命令(查看命令:CONFIG GET slowlog-log-slower-than)
- Redis默認保留128條慢查詢日誌,超事後舊的日誌將被優先刪除(查看命令:CONFIG GET slowlog-max-len)
源地址 By佐柱
轉載請註明出處,也歡迎偶爾逛逛個人小站,謝謝 :)