03 單執行緒
Redis的單執行緒指的是網路IO和鍵值對讀寫是由一個執行緒來完成的;其他功能如持久化、非同步刪除、叢集同步等是由額外的執行緒執行的。
注:Redis6.0網路IO已改為多執行緒執行,以解決單執行緒的效能瓶頸。
多執行緒的開銷
併發訪問控制問題:
系統中通常會存在被多執行緒同時訪問的共享資源,比如一個共享的資料結構。
當有多個執行緒要修改這個共享資源時,為了保證共享資源的正確性,就需要有額外的機制進行保證,而這個額外的機制,就會帶來額外的開銷。
Redis高效原因
Redis使用單執行緒模型能達到每秒十萬級的處理能力,原因有:
- 大部分操作在記憶體上完成
- 高效的資料結構,如雜湊表和跳錶
- 多路複用機制,使其在網路IO操作中能併發處理大量的客戶端請求,實現高吞吐
IO模型和阻塞點
Redis工作流程:
網路IO處理:
- 監聽客戶端請求:bind/listen
- 和客戶端建立連線:accept
- 從socket中讀取請求:recv
- 解析客戶端傳送請求:parse
- 鍵值資料讀寫:get
- 網路IO處理:send
阻塞點:accept和recv - 當 Redis 監聽到一個客戶端有連線請求,但一直未能成功建立起連線時,會阻塞在 accept() 函式這裡,導致其他客戶端無法和 Redis 建立連線。
- 當 Redis 透過 recv() 從一個客戶端讀取資料時,如果資料一直沒有到達,Redis 也會一直阻塞在 recv()。
非阻塞模型
套接字
簡單的說就是通訊的兩方的一種約定,用套接字中的相關函式來完成通訊過程。
主要有3個引數:通訊的目的IP地址、使用的傳輸層協議(TCP或UDP)和使用的埠號。
socket模型
在 socket 模型中,不同操作呼叫後會返回不同的套接字型別。
socket() 方法會返回主動套接字,然後呼叫 listen() 方法,將主動套接字轉化為監聽套接字,此時,可以監聽來自客戶端的連線請求。
最後,呼叫 accept() 方法接收到達的客戶端連線,並返回已連線套接字。
非阻塞模式
當 Redis 呼叫 accept() 但一直未有連線請求到達時,Redis 執行緒可以返回處理其他操作,而不用一直等待。
Redis 呼叫 recv() 後,如果已連線套接字上一直沒有資料到達,Redis 執行緒同樣可以返回處理其他操作。
IO多路複用機制
Linux 中的 IO 多路複用機制是指一個執行緒處理多個 IO 流,就是我們經常聽到的 select/epoll 機制。
簡單來說,在 Redis 只執行單執行緒的情況下,該機制允許核心中,同時存在多個監聽套接字和已連線套接字。核心會一直監聽這些套接字上的連線請求或資料請求。一旦有請求到達,就會交給 Redis 執行緒處理,這就實現了一個 Redis 執行緒處理多個 IO 流的效果。
Redis 網路框架呼叫 epoll 機制,讓核心監聽這些套接字。此時,Redis 執行緒不會阻塞在某一個特定的監聽或已連線套接字上,也就是說,不會阻塞在某一個特定的客戶端請求處理上。正因為此,Redis 可以同時和多個客戶端連線並處理請求,從而提升併發性。
為了在請求到達時能通知到 Redis 執行緒,select/epoll 提供了基於事件的回撥機制,即針對不同事件的發生,呼叫相應的處理函式。
事件回撥機制
- select/epoll 一旦監測到 FD 上有請求到達時,就會觸發相應的事件。
所有新增到epoll中的事件都會與裝置(網路卡)驅動程式建立回撥關係,也就是說,一旦監測到fd上有請求到達時,就會觸發相應的事件,當相應的事件發生時會呼叫回撥方法。這個回撥方法在核心中叫ep_poll_callback,它會將發生的事件新增到rdlist雙連結串列中。 這些事件會被放進一個事件佇列,Redis 單執行緒對該事件佇列不斷進行處理。
- Redis 無需一直輪詢是否有請求實際發生,這就可以避免造成 CPU 資源浪費。
- Redis 在對事件佇列中的事件進行處理時,會呼叫相應的處理函式,這就實現了基於事件的回撥。
- 因為 Redis 一直在對事件佇列進行處理,所以能及時響應客戶端請求,提升 Redis 的響應效能。
04 AOF日誌
AOF(Append Only File)是寫後日志,Redis先執行命令把資料寫入記憶體,然後才記錄日誌。
實現原理
AOF 裡記錄的是 Redis 收到的每一條命令,這些命令是以文字形式儲存的。
示例:
set testkey testvalue
對應AOF檔案
*3
$3
set
$7
testkey
$9
testvalue
- “*3”表示當前命令有三個部分,每部分都是由“$+數字”開頭,後面緊跟著具體的命令、鍵或值。
- 數字”表示這部分中的命令、鍵或值一共有多少位元組。
注意:
為了避免額外的檢查開銷,Redis 在向 AOF 裡面記錄日誌的時候,並不會先去對這些命令進行語法檢查。
所以,如果先記日誌再執行命令的話,日誌中就有可能記錄了錯誤的命令,Redis 在使用日誌恢復資料時,就可能會出錯。
寫後日志這種方式,就是先讓系統執行命令,只有命令能執行成功,才會被記錄到日誌中,否則,系統就會直接向客戶端報錯。
AOF的好處:
- 可以避免出現記錄錯誤命令的情況
- 它是在命令執行後才記錄日誌,所以不會阻塞當前的寫操作。
風險:
- 如果剛執行完一個命令,還沒有來得及記日誌就當機了,那麼這個命令和相應的資料就有丟失的風險。
- AOF 雖然避免了對當前命令的阻塞,但可能會給下一個操作帶來阻塞風險。
這兩個風險都是和 AOF 寫回磁碟的時機相關的。
三種寫回策略
AOF 機制給我們提供了三個選擇,也就是 AOF 配置項 appendfsync 的三個可選值。
- Always,同步寫回:每個寫命令執行完,立馬同步地將日誌寫回磁碟;
- Everysec,每秒寫回:每個寫命令執行完,只是先把日誌寫到 AOF 檔案的記憶體緩衝區,每隔一秒把緩衝區中的內容寫入磁碟;
- No,作業系統控制的寫回:每個寫命令執行完,只是先把日誌寫到 AOF 檔案的記憶體緩衝區,由作業系統決定何時將緩衝區內容寫回磁碟。
策略分析: - 同步寫回:可靠性高,資料基本不丟失;但是每個寫命令都要落盤,效能影響較大
- 每秒寫回:效能適中;當機時丟失1秒內的資料
- 作業系統控制寫回:效能好;當機時丟失資料較多
檔案過大問題
隨著接收的寫命令越來越多,AOF 檔案會越來越大。這也就意味著,我們一定要小心 AOF 檔案過大帶來的效能問題。
- 檔案系統限制,無法儲存過大檔案
- 檔案太大,追加命令記錄效率變低
- 發生當機故障恢復時,AOF記錄要一個個重新執行,檔案太大恢復過程很慢
AOF重寫機制
AOF重寫機制指的是,對過大的AOF檔案進行重寫,以此來壓縮AOF檔案的大小。
實現方法
檢查當前鍵值資料庫中的鍵值對,記錄鍵值對的最終狀態,從而實現對某個鍵值對重複操作後產生的多條操作記錄壓縮成一條的效果。
也就是說,舊日誌檔案中的多條命令,在重寫後的新日誌中變成了一條命令。
是否阻塞
重寫過程是由主執行緒 fork 出後臺的子程式 bgrewriteaof 來完成的
重寫過程
一個複製,兩處日誌:
- 每次執行重寫時,fork 會把主執行緒的記憶體(頁表:虛擬對映關係)複製一份給 bgrewriteaof 子程式,這裡面就包含了資料庫的最新資料。
- 因為主執行緒未阻塞,仍然可以處理新來的操作。此時,如果有寫操作,第一處日誌就是指正在使用的 AOF 日誌,Redis 會把這個操作寫到它的緩衝區。這樣一來,即使當機了,這個 AOF 日誌的操作仍然是齊全的,可以用於恢復。
- 這個操作也會被寫到重寫日誌的緩衝區。這樣,重寫日誌也不會丟失最新的操作。等到複製資料的所有操作記錄重寫完成後,重寫日誌記錄的這些最新操作也會寫入新的 AOF 檔案,以保證資料庫最新狀態的記錄。此時,我們就可以用新的 AOF 檔案替代舊檔案了。
小結
- 系統設計中的一個重要原則 ,即 trade-off,或者稱為“取捨”,指的就是在效能和可靠性保證之間做取捨。
- 最新持久化方案:前置RDB快照+AOF檔案
重新過程潛在阻塞風險:
- fork會消耗大量CPU資源,複製完成之前整個程式是會阻塞的
- bigkey重新申請大塊記憶體耗時會變長,可能會產阻塞風險(所以需要關閉Huge Page機制)