后端学习 - Redis
文章目錄
- 一 Redis 概述
- Redis 為什么是單線程,單線程為什么這么快?
- 數據存儲結構
- 二 常用數據類型
- 1 String
- 2 Hash
- Hash 的擴容機制:漸進式 rehash*
- 3 List
- 4 Set
- 5 Zset
- 三 Redis 事務
- 1 樂觀鎖與 watch 命令
- 2 事務的三個特性
- 四 Redis 持久化
- 1 RDB(Redis Database)與寫時復制
- 2 AOF(Append Only File)
- 五 主從復制
- 1 復制原理
- 2 一主二從
- 3 薪火相傳
- 4 哨兵模式
- 六 常見問題及解決
- 1 緩存穿透
- 2 緩存擊穿
- 3 緩存雪崩
- 4 解決緩存穿透:布隆過濾器
一 Redis 概述
- 屬于一種 NoSQL(非關系型數據庫),另一種常用的非關系型數據庫是 MongoDB
- Redis 的數據都在內存中,并且支持持久化,主要用作備份恢復
- 除了支持簡單的 key-value 模式,還支持多種數據結構的存儲: string、list、set、hash、zset 等,這些數據類型都支持 push / pop、add / remove 及取交集并集和差集等操作,而且這些操作都是原子性的,但 Redis 事務不具有原子性
- 一般是作為緩存數據庫輔助持久化的數據庫
- Redis 不支持自定義數據庫的名字,每個數據庫都以編號命名,開發者必須自己記錄哪些數據庫存儲了哪些數據;Redis 也不支持為每個數據庫設置不同的訪問密碼,所以一個客戶端要么可以訪問全部數據庫,要么連一個數據庫也沒有權限訪問;多個數據庫之間并不是完全隔離的,這些數據庫更像是一種命名空間,而不適宜存儲不同應用程序的數據(比如可以使用0號數據庫存儲某個應用生產環境中的數據,使用1號數據庫存儲測試環境中的數據,但不適宜使用0號數據庫存儲A應用的數據而使用1號數據庫B應用的數據,不同的應用應該使用不同的Redis實例存儲數據)
Redis 為什么是單線程,單線程為什么這么快?
- 因為 Redis 是基于內存的操作,CPU 不是 Redis 的瓶頸,Redis 的瓶頸最有可能是內存大小或者網絡帶寬。既然單線程容易實現,而且 CPU 不會成為瓶頸,那就順理成章地采用單線程的方案了
- 在單線程的情況下,處理邏輯更簡單,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導致的性能消耗,不存在多進程或者多線程導致的切換而消耗 CPU
- Redis 采用網絡 I/O 多路復用技術,來保證在多連接的時候系統的高吞吐量
數據存儲結構
- Redis 的 Db 默認情況下有16個,每個 redisDb 內部包含一個 dict 的數據結構
- dict 內部包含 ht 的數組,數組個數為2,元素類型為 dictht,主要用于 hash 擴容使用(參考下面的 Hash 擴容)
- dictht 內部包含 dictEntry 的數組,可以理解就是 hash 桶,然后使用拉鏈法解決沖突
- dictEntry 當中的 key 和 v 的指針指向的是 redisObject,redisObject 是 Redis server 存儲最原子數據的數據結構,其中的void *ptr 會指向真正的存儲數據結構
二 常用數據類型
1 String
參考鏈接
- 具有二進制安全(binary safe)特性,這意味著它的長度是已知的,不由任何其他終止字符決定的,一個字符串類型的值最多能夠存儲 512 MB 的內容。所有 SDS API 都會以處理二進制的方式來處理 SDS 存放在 buf 數組里的數據,程序不會對其中的數據做任何限制、過濾、或者假設(保證數據在寫入時是什么樣的, 它被讀取時就是什么樣),這也是 SDS 的 buf 屬性稱為字節數組的原因,Redis 不是用這個數組來保存字符, 而是用它來保存一系列二進制數據
- Redis 實現了SDS(Simple Dynamic String,簡單動態字符串)的抽象類型,它通過存儲額外數據能簡單地得到自身信息:總長度、可用長度等
- SDS 遵循 C 字符串以空字符結尾的慣例, 保存空字符的 1 字節空間不計算在 SDS 的 len 屬性里面, 并且為空字符分配額外的 1 字節空間(遵循空字符結尾這一慣例的好處是, SDS 可以直接重用一部分 C 字符串函數庫里面的函數)
- SDS 還被用作緩沖區,比如 AOF 模塊中的 AOF 緩沖區
2 Hash
- 底層存儲使用 ziplist 或 hashtable(再底層是字典)
當一個哈希對象可以滿足以下兩個條件時,哈希對象會選擇使用ziplist編碼來進行存儲:
1 哈希對象中的所有鍵值對總長度(包括鍵和值)小于64字節(這個閾值可以通過參數hash-max-ziplist-value 來進行控制)
2 哈希對象中的鍵值對數量小于512個(這個閾值可以通過參數hash-max-ziplist-entries 來進行控制)
一旦不滿足這兩個條件中的任意一個,哈希對象就會選擇使用hashtable來存儲
- 使用拉鏈法解決哈希沖突
- 可以理解成一個包含了多個鍵值對的集合,一般用于存儲對象
Hash 的擴容機制:漸進式 rehash*
將 rehash 的操作分攤在每一個的訪問中,避免集中式 rehash 可能會導致服務器在一段時間內停止服務
參考鏈接
Hash 底層有兩個數組 ht[0] 和 ht[1] ,還有一個 rehashidx 用來控制 rehash 過程
初始默認長度為4,當元素個數與 Hash 表長度一致時(負載因子為1)發生擴容,長度變為原來的二倍;同時 rehashindex 的值設置為0,表示 rehash 工作正式開始
在 rehash 期間,每次對字典執行增刪改查時,還會順帶將 ht[0] 哈希表在rehashindex 索引上的所有鍵值對 rehash 到 ht[1] ,當 rehash 工作完成以后,rehashindex 的值 +1
隨著字典操作的不斷執行,最終會在某一時間段上 ht[0] 的所有鍵值對都會被 rehash 到 ht[1],這時將 rehashindex 的值設置為 -1,表示 rehash 操作結束
在漸進式 rehash 的過程中,如果有增刪改查操作,index 大于 rehashindex,訪問 ht[0] ,否則訪問 ht[1]
3 List
- 單鍵多值,內容按照插入的順序排序(lpush 和 rpush 的順序不同),可以向頭部或尾部添加數據
- 底層是快速鏈表 QuickList,每個部分是壓縮鏈表 ZipList(內部是一段連續的內存),所以只需要額外存儲 ZipList 之間的指針
4 Set
- 單鍵多值,集合中的元素無序不重復
- 底層實現為 intset 或 hashtable
- intset 實現為數組,這個數組以有序、無重復的方式保存集合元素,在有需要時,程序會根據新添加元素的類型,改變這個數組的類型
5 Zset
- ZSet 基于跳表實現,是一種有序的數據結構,支持平均 O(logn) 的查詢效率
- 每個元素都關聯了一個 score,被用來按照從最低分到最高分的方式排序集合中的成員;集合里的成員是唯一的,但是 score 可以重復
- 底層實現為 ziplist 或 跳表:
跳表的作用是根據 score 給元素排序,方便獲取指定 score 范圍的元素列表
每次創建一個新跳表節點的時候,程序都根據冪次定律(power law,越大的數出現的概率越小)隨機生成一個1和32之間的值作為新節點所在的層的高度
跳表的插入/刪除首先需要執行查找,平均時間復雜度 O(logn) - 跳表詳解
三 Redis 事務
- Redis 事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。主要作用就是串聯多個命令防止別的命令插隊。
- 事務操作通過命令 multi - discard / exec 執行
multi 過程中某個命令出現錯誤,執行時整個的所有命令都會被取消
exec 階段某個命令出現錯誤,則只有報錯的命令不會被執行
1 樂觀鎖與 watch 命令
- 在讀數據時不認為該數據會被更新,不會上鎖
- 在更新的時候會判斷:在此期間該數據是否被更新,如果被更新則不能執行,需要獲取最新的版本??梢允褂冒姹咎柕葯C制
- 樂觀鎖適用于多讀的應用類型,可以提高吞吐量
- 如果在事務執行之前被 watch 的 key 被其他命令改動,那么事務將被打斷
2 事務的三個特性
- 單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷
- 沒有隔離級別的概念:隊列中的命令沒有提交之前都不會實際被執行
- 不保證原子性:事務中除了執行失敗的命令,其它的命令仍然會被執行
四 Redis 持久化
1 RDB(Redis Database)與寫時復制
- 在指定的時間間隔內將內存中的數據集快照寫入磁盤,恢復時將快照文件直接讀到內存里
- RDB 持久化的流程:主進程不進行任何IO操作,而是創建子進程,子進程將快照先寫入臨時文件,再用這個臨時文件替換上次持久化好的文件
- 優點:
適合大規模的數據恢復
節省磁盤空間,恢復速度快
對數據完整性和一致性要求不高時,更適合使用 - 缺點:
如果 Redis 意外停止,會丟失最后一次快照后的數據
Redis持久化時可以進行寫操作嗎?
- BGSAVE 命令的保存工作是由子進程執行的,所以在子進程下創建RDB文件的過程中,Redis 服務器仍然可以繼續處理客戶端的命令請求
- 子進程是通過 fork 系統調用創建的,剛創建時由于 CopyOnWrite 機制會與父進程共享同一塊地址空間,此時如果父進程收到寫請求,CopyOnWrite 機制就會創建新頁面存放修改的數據,不影響持久化的 RDB
- 寫時復制,通俗來說是多個調用者同時去請求一個資源數據的時候,有一個調用者需要對當前的數據源進行修改,這個時候系統將會復制一個當前數據源的副本給調用者修改
- 寫時復制的優勢是,在并發的場景下進行讀操作不需要加鎖
2 AOF(Append Only File)
- AOF 的策略是增量保存,以日志的形式來記錄每個寫操作(不記錄讀操作)
- 對于 AOF 文件,只允許追加但不可以改寫
- AOF 的持久化流程:
客戶端的請求寫命令會被追加到 AOF 緩沖區內
AOF 緩沖區根據 AOF 同步頻率的設置 always / everysec / no 將操作同步到磁盤的 AOF 文件中
AOF 文件大小超過重寫策略或手動重寫時,對 AOF 文件 Rewrite ,壓縮 AOF 文件容量
Redis 服務重啟時,會重新加載 AOF 文件中的寫操作,達到數據恢復的目的 - AOF 同步頻率:
| everysec | 每秒同步,每秒記入日志一次 |
| no | 不主動進行同步,把同步時機交給操作系統 |
- Rewrite:AOF 文件的大小超過所設定的閾值時,Redis 就會啟動 AOF 文件的內容壓縮, 只保留可以恢復數據的 最小指令集。重寫也是 fork 子進程完成的,類似 RDB
- AOF 優點:
丟失數據概率更低 - AOF 缺點:
比起 RDB 占用更多的磁盤空間,恢復備份速度更慢
五 主從復制
- 可以實現:讀寫分離(Master 以寫為主,Slave 以讀為主)、容災恢復
1 復制原理
2 一主二從
- 如果 Master 停止,Slave 不做任何事,仍然承認 Master 但會標記狀態為 down
- 如果 Slave 停止,重啟后成為另一個 Master,除非再次命令其成為原 Master 的 Slave
3 薪火相傳
- 上一個 Slave 可以是下一個 Slave 的 Master,Slave 同樣可以接收其他 Slaves 的連接和同步請求,形成鏈式結構
- 可以有效減輕 Master 的寫壓力,去中心化降低風險
- 如果 Master 停止:
Slave 不做任何事,仍然承認 Master 但會標記狀態為 down
或者用 slaveof no one 將從機變為主機,“反客為主”成為新的 Master
4 哨兵模式
- “反客為主”的自動版:當 Master 停機,從 Slave 中選出新的 Master(根據優先級別:slave-priority 等因素)
- 需要有 n 個哨兵都檢測到 Master 停機才會執行 Master 更新,n 需要手動設定
- 原 Master 重啟后會變為 Slave
六 常見問題及解決
1 緩存穿透
- 問題:
key 對應的數據在數據源并不存在,每次針對此 key 的請求從 Redis 緩存獲取不到,請求都會傳遞到數據源,從而可能壓垮數據源 - 解決:
1、對空值緩存,如果一個查詢返回的數據為空(不管是數據是否不存在),仍然把空結果進行緩存,設置過期時間相對短
2、白名單、布隆過濾器
3、實時監控,當發現 Redis 的命中率開始急速降低,排查訪問對象和訪問的數據,設置黑名單限制服務
2 緩存擊穿
- 問題:
Redis 中的 某個 key 過期了,但這個 key 又被超高并發地訪問,從后端 DB 加載數據并返回到緩存,這個時候大并發的請求可能會瞬間把后端 DB 壓垮 - 解決:
1、實時監控
2、預先設置熱門數據,在 Redis 高峰訪問之前,把一些熱門數據提前存入到 Redis 里面,加大這些熱門數據 key 的時長
3、使用鎖,保證 DB 不會承受過高的訪問壓力,但是了降低效率
3 緩存雪崩
- 問題:
和緩存擊穿類似,區別在于,緩存雪崩針對很多 key 緩存,緩存擊穿則是某一個 key - 解決:
1、構建多級緩存架構:nginx 緩存 + redis 緩存 +其他緩存(ehcache 等)
2、使用鎖或隊列降低 DB 壓力,但降低效率,不適于高并發的情況
3、記錄緩存數據是否過期(可以在此設置提前量),如果過期(或快過期)會觸發通知另外的線程,后臺更新 key 的緩存
4、將緩存失效時間分散開
4 解決緩存穿透:布隆過濾器
- 由 一個 二進制向量(或者說位數組)和 一系列 隨機映射函數(哈希函數)兩部分組成的數據結構
- 對于一個字符串,用所有的哈希函數對其進行計算,將得到的一系列值作為下標,并將位數組對應位置的值設置為1;如果該字符串再次插入,用相同的方法驗證出位數組的對應位置都是1,則說明重復加入
- 理論情況下添加到集合中的元素越多,誤報的可能性就越大
- 不同的字符串可能哈希出來的位置相同,這種情況可以適當增加位數組大小,或者調整哈希函數
- 布隆過濾器認為某個元素存在,小概率會誤判。布隆過濾器認為某個元素不在,那么這個元素一定不在
總結
以上是生活随笔為你收集整理的后端学习 - Redis的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SAI绘画软件怎么用电脑软件如何绘图
- 下一篇: 在线教育后端开发项目总结