Redis
為什麼用 Redis 作為 MySQL 的快取
Redis 具備「高效能」和「高併發」兩種特性。
Redis 資料型別
String(字串),Hash(雜湊),List(列表),Set(集合)、Zset(有序集合)
String 型別的應用場景:快取物件、常規計數、分散式鎖、共享 session 資訊等。
List 型別的應用場景:訊息佇列(但是有兩個問題:1. 生產者需要自行實現全域性唯一 ID;2. 不能以消費組形式消費資料)等。
Hash 型別:快取物件、購物車等。
Set 型別:聚合計算(並集、交集、差集)場景,比如點贊、共同關注、抽獎活動等。
Zset 型別:排序場景,比如排行榜、電話和姓名排序等。
String 型別內部實現
String 型別的底層的資料結構實現主要是 SDS(簡單動態字串
SDS 不僅可以儲存文字資料,還可以儲存二進位制資料
SDS 獲取字串長度的時間複雜度是 O(1)
Redis 的 SDS API 是安全的,拼接字串不會造成緩衝區溢位
List 型別內部實現
由雙向連結串列或壓縮列表實現
元素個數小於 512 個,Redis 會使用壓縮列表
Redis 3.2 版本之後,List 資料型別底層資料結構就只由 quicklist 實現
Hash 型別內部實現
Hash 型別的底層資料結構是由壓縮列表或雜湊表實現的
元素個數小於 512 使用壓縮列表
Redis 7.0 中,壓縮列表資料結構已經廢棄了,交由 listpack 資料結構來實現了
Set 型別內部實現
雜湊表或整數集合實現的:
小於512用整數集合
ZSet 型別內部實現
由壓縮列表或跳錶實現的
Redis 7.0 中,壓縮列表資料結構已經廢棄了,交由 listpack 資料結構來實現了
Redis 是單執行緒
Redis 是單執行緒的,
但是,Redis 程式並不是單執行緒的
Redis 在啟動的時候,是會啟動後臺執行緒(BIO)
Redis 為「關閉檔案、AOF 刷盤、釋放記憶體」
Redis 單執行緒模式
首先,呼叫 epoll_create() 建立一個 epoll 物件和呼叫 socket() 建立一個服務端 socket
然後,呼叫 bind() 繫結埠和呼叫 listen() 監聽該 socket;
然後,將呼叫 epoll_ctl() 將 listen socket 加入到 epoll,同時註冊「連線事件」處理函式如果是連線事件到來,則會呼叫連線事件處理函式,該函式會做這些事情:呼叫 accpet 獲取已連線的 socket -> 呼叫 epoll_ctl 將已連線的 socket 加入到 epoll -> 註冊「讀事件」處理函式;
如果是讀事件到來,則會呼叫讀事件處理函式,該函式會做這些事情:呼叫 read 獲取客戶端傳送的資料 -> 解析命令 -> 處理命令 -> 將客戶端物件新增到傳送佇列 -> 將執行結果寫到傳送快取區等待傳送;
如果是寫事件到來,則會呼叫寫事件處理函式,該函式會做這些事情:透過 write 函式將客戶端傳送快取區裡的資料傳送出去,如果這一輪資料沒有傳送完,就會繼續註冊寫事件處理函式,等待 epoll_wait 發現可寫後再處理
Redis 採用單執行緒為什麼還這麼快
Redis 的大部分操作都在記憶體中完成
Redis 採用單執行緒模型可以避免了多執行緒之間的競爭
I/O 多路複用機制+事件分派
Redis 6.0 之前為什麼使用單執行緒
CPU 並不是制約 Redis 效能表現的瓶頸所在
Redis 持久化
AOF 日誌,RDB 快照,混合持久化方式
為什麼先執行命令,再把資料寫入日誌呢
避免額外的檢查開銷,不會阻塞當前寫操作命令的執行
風險:
資料可能會丟失,可能阻塞其他操作
寫操作->命令寫入aof緩衝區->寫入aof檔案(還在核心緩衝區)->核心決定實機寫入硬碟
AOF 寫回策略有幾種
Always,同步寫
Everysec,每秒寫入硬碟
No,交給系統決定
AOF 日誌過大
觸發AOF 重寫機制,讀取當前資料庫中的所有鍵值對,然後將每一個鍵值對用一條命令記錄到「新的 AOF 檔案」,等到全部記錄完後,就將新的 AOF 檔案替換掉現有的 AOF 檔案
重寫 AOF 日誌的過程
重寫 AOF 過程是由後臺子程序bgrewriteaof 來完成的
因為寫時複製導致資料不一致,所以使用AOF 重寫緩衝區,期間執行的命令會也寫入該緩衝區
RDB 快照
save 和 bgsave
透過配置檔案的選項來實現每隔一段時間自動執行一次 bgsave 命令
寫時複製技術
執行 bgsave 命令的時候,會透過 fork() 建立子程序,此時子程序和父程序是共享同一片記憶體資料的,因為建立子程序的時候,會複製父程序的頁表,但是頁表指向的實體記憶體還是一個,此時如果主執行緒執行讀操作,則主執行緒和 bgsave 子程序互相不影響
如果主執行緒執行寫操作,則被修改的資料會複製一份副本,然後 bgsave 子程序會把該副本資料寫入 RDB 檔案,在這個過程中,主執行緒仍然可以直接修改原來的資料
一個切片叢集共有 16384 個雜湊槽
根據鍵值對的 key,按照 CRC16 演算法 (opens new window)計算一個 16 bit 的值。
再用 16bit 值對 16384 取模,得到 0~16383 範圍內的模數,每個模數代表一個相應編號的雜湊槽
叢集腦裂導致資料丟失
當主節點發現從節點下線或者通訊超時的總數量小於閾值時,那麼禁止主節點進行寫資料,直接把錯誤返回給客戶端,可以設定:
min-slaves-to-write x,主節點必須要有至少 x 個從節點連線,如果小於這個數,主節點會禁止寫資料。
min-slaves-max-lag x,主從資料複製和同步的延遲不能超過 x 秒,如果超過,主節點會禁止寫資料
Redis 使用的過期刪除策略
Redis 會把該 key 帶上過期時間儲存到一個過期字典
Redis 使用的過期刪除策略是「惰性刪除+定期刪除」
定期,惰性刪除策略的優缺點
定期刪除:在規定時間內不斷隨機取20個key,看過期比例是否超過約定來刪除或結束
Redis 持久化時,對過期鍵會如何處理的
RDB 檔案生成階段,過期的鍵「不會」被儲存到新的 RDB 檔案中
RDB 載入階段,過期鍵「不會」被載入到主資料庫中,會載入從資料庫,但同步後就被刪除了
AOF 檔案寫入階段,會寫入,刪除時會加一條del
AOF 重寫階段,已過期的鍵不會被儲存到重寫後的 AOF 檔案中
Redis 主從模式中,對過期鍵會如何處理
主庫負責刪過期,加aof,從庫等同步
Redis 記憶體滿了,會發生什麼
記憶體淘汰機制
Redis 記憶體淘汰策略有哪些
不進行資料淘汰的策略,返回錯誤
在過期鍵中淘汰或全鍵中淘汰
隨機淘汰,淘汰最早過期的,最少最近使用,最少使用
如何避免快取雪崩、快取擊穿、快取穿透
雪崩:打散過期,設定不過期
擊穿:鎖或不過期
穿透:設null,布隆過濾器,非法請求限制
大 key 會帶來以下四種影響
客戶端超時阻塞,引發網路阻塞,阻塞工作執行緒,叢集記憶體分佈不均
找到大 key ?
redis-cli --bigkeys 查詢大key,只能返回每種型別中最大的那個 bigkey,無法得到大小排在前 N 位的 bigkey;對於集合類只統計個數,不統計大小
使用 SCAN 命令對資料庫掃描,然後用 TYPE 命令獲取返回的每一個 key 的型別。
刪除大 key
分批次刪除
非同步刪除
Redis 管道有什麼用
使用管道技術可以解決多個命令執行時的網路等待
Redis 事務支援回滾嗎
Redis 中並沒有提供回滾機制
Redis 實現分散式鎖
setnx
SET lock_key unique_value NX PX 10000
Redis 主從複製模式中的資料是非同步複製的,這樣導致分散式鎖的不可靠性
Redis 如何解決叢集情況下分散式鎖的可靠性
Redis 官方已經設計了一個分散式鎖演算法 Redlock(紅鎖)
Redlock 演算法的基本思路,是讓客戶端和多個獨立的 Redis 節點依次請求申請加鎖,如果客戶端能夠和半數以上的節點成功地完成加鎖操作,那麼我們就認為,客戶端成功地獲得分散式鎖,否則加鎖失敗。