Redis的執行緒模型
為了接下來一篇部落格,能使讀者更加完整地學習執行緒模型,所以本文對Redis的執行緒模型進行必要的講解。
由於《Redis設計與實現》對於Redis的講解簡練優雅,所以本文大部分內容節選自本書,也歡迎對Redis有更多底層瞭解需求的讀者,自行購買學習。
檔案事件處理器
Redis基於Reactor模式開發了網路事件處理器,這個處理器被稱為檔案事件處理器。它的組成結構為4部分:多個套接字、IO多路複用程式、檔案事件分派器、事件處理器。因為檔案事件分派器佇列的消費是單執行緒的,所以Redis才叫單執行緒模型。
訊息處理流程
- 檔案事件處理器使用I/O多路複用(multiplexing)程式來同時監聽多個套接字,並根據套接字目前執行的任務來為套接字關聯不同的事件處理器。
- 當被監聽的套接字準備好執行連線應答(accept)、讀取(read)、寫入(write)、關閉(close)等操作時,與操作相對應的檔案事件就會產生,這時檔案事件處理器就會呼叫套接字之前關聯好的事件處理器來處理這些事件。
儘管多個檔案事件可能會併發地出現,但I/O多路複用程式總是會將所有產生事件的套接字都推到一個佇列裡面,然後通過這個佇列,以有序(sequentially)、同步(synchronously)、每次一個套接字的方式向檔案事件分派器傳送套接字:當上一個套接字產生的事件被處理完畢之後(該套接字為事件所關聯的事件處理器執行完畢), I/O多路複用程式才會繼續向檔案事件分派器傳送下一個套接字。
I/O 多路複用程式的實現
Redis的I/O多路複用程式的所有功能是通過包裝select、epoll、evport和kqueue這些I/O多路複用函式庫來實現的,每個I/O多路複用函式庫在Redis原始碼中都對應一個單獨的檔案,比如ae_select.c、ae_epoll.c、ae_kqueue.c等。
因為Redis為每個I/O多路複用函式庫都實現了相同的API,所以I/O多路複用程式的底層實現是可以互換的,如下圖所示。
有關epoll的詳細講解,可以點選檢視,徹底搞懂epoll高效執行的原理
Redis在I/O多路複用程式的實現原始碼中用#include巨集定義了相應的規則,程式會在編譯時自動選擇系統中效能最好的I/O多路複用函式庫來作為Redis的I/O多路複用程式的底層實現:
/* Include the best multiplexing layer supported by this system.
* The following should be ordered by performances, descending. */
#ifdef HAVE_EVPORT
#include "ae_evport.c"
#else
#ifdef HAVE_EPOLL
#include "ae_epoll.c"
#else
#ifdef HAVE_KQUEUE
#include "ae_kqueue.c"
#else
#include "ae_select.c"
#endif
#endif
#endif
檔案事件的型別
I/O 多路複用程式可以監聽多個套接字的ae.h/AE_READABLE事件和ae.h/AE_WRITABLE事件,這兩類事件和套接字操作之間的對應關係如下:
- 當套接字變得可讀時(客戶端對套接字執行write操作,或者執行close操作),或者有新的可應答(acceptable)套接字出現時(客戶端對伺服器的監聽套接字執行connect操作),套接字產生AE_READABLE 事件。
- 當套接字變得可寫時(客戶端對套接字執行read操作),套接字產生AE_WRITABLE事件。I/O多路複用程式允許伺服器同時監聽套接字的AE_READABLE事件和AE_WRITABLE事件,如果一個套接字同時產生了這兩種事件,那麼檔案事件分派器會優先處理AE_READABLE事件,等到AE_READABLE事件處理完之後,才處理AE_WRITABLE 事件。這也就是說,如果一個套接字又可讀又可寫的話,那麼伺服器將先讀套接字,後寫套接字。
檔案事件的處理器
Redis為檔案事件編寫了多個處理器,這些事件處理器分別用於實現不同的網路通訊需求,常用的處理器如下:
- 為了對連線伺服器的各個客戶端進行應答, 伺服器要為監聽套接字關聯連線應答處理器。
- 為了接收客戶端傳來的命令請求, 伺服器要為客戶端套接字關聯命令請求處理器。
- 為了向客戶端返回命令的執行結果, 伺服器要為客戶端套接字關聯命令回覆處理器。
連線應答處理器
networking.c中acceptTcpHandler函式是Redis的連線應答處理器,這個處理器用於對連線伺服器監聽套接字的客戶端進行應答,具體實現為sys/socket.h/accept函式的包裝。
當Redis伺服器進行初始化的時候,程式會將這個連線應答處理器和伺服器監聽套接字的AE_READABLE事件關聯起來,當有客戶端用sys/socket.h/connect函式連線伺服器監聽套接字的時候, 套接字就會產生AE_READABLE 事件, 引發連線應答處理器執行, 並執行相應的套接字應答操作,如圖所示。
命令請求處理器
networking.c中readQueryFromClient函式是Redis的命令請求處理器,這個處理器負責從套接字中讀入客戶端傳送的命令請求內容, 具體實現為unistd.h/read函式的包裝。
當一個客戶端通過連線應答處理器成功連線到伺服器之後, 伺服器會將客戶端套接字的AE_READABLE事件和命令請求處理器關聯起來,當客戶端向伺服器傳送命令請求的時候,套接字就會產生 AE_READABLE事件,引發命令請求處理器執行,並執行相應的套接字讀入操作,如圖所示。
在客戶端連線伺服器的整個過程中,伺服器都會一直為客戶端套接字的AE_READABLE事件關聯命令請求處理器。
命令回覆處理器
networking.c中sendReplyToClient函式是Redis的命令回覆處理器,這個處理器負責將伺服器執行命令後得到的命令回覆通過套接字返回給客戶端,具體實現為unistd.h/write函式的包裝。
當伺服器有命令回覆需要傳送給客戶端的時候,伺服器會將客戶端套接字的AE_WRITABLE事件和命令回覆處理器關聯起來,當客戶端準備好接收伺服器傳回的命令回覆時,就會產生AE_WRITABLE事件,引發命令回覆處理器執行,並執行相應的套接字寫入操作, 如圖所示。
當命令回覆傳送完畢之後, 伺服器就會解除命令回覆處理器與客戶端套接字的 AE_WRITABLE 事件之間的關聯。
一次完整的客戶端與伺服器連線事件示例
假設Redis伺服器正在運作,那麼這個伺服器的監聽套接字的AE_READABLE事件應該正處於監聽狀態之下,而該事件所對應的處理器為連線應答處理器。
如果這時有一個Redis客戶端向Redis伺服器發起連線,那麼監聽套接字將產生AE_READABLE事件, 觸發連線應答處理器執行:處理器會對客戶端的連線請求進行應答, 然後建立客戶端套接字,以及客戶端狀態,並將客戶端套接字的 AE_READABLE 事件與命令請求處理器進行關聯,使得客戶端可以向主伺服器傳送命令請求。
之後,客戶端向Redis伺服器傳送一個命令請求,那麼客戶端套接字將產生 AE_READABLE事件,引發命令請求處理器執行,處理器讀取客戶端的命令內容, 然後傳給相關程式去執行。
執行命令將產生相應的命令回覆,為了將這些命令回覆傳送回客戶端,伺服器會將客戶端套接字的AE_WRITABLE事件與命令回覆處理器進行關聯:當客戶端嘗試讀取命令回覆的時候,客戶端套接字將產生AE_WRITABLE事件, 觸發命令回覆處理器執行, 當命令回覆處理器將命令回覆全部寫入到套接字之後, 伺服器就會解除客戶端套接字的AE_WRITABLE事件與命令回覆處理器之間的關聯。
Redis疑問快答
詳細的解答,請檢視筆者之前的幾篇Redis部落格。
是否使用過Redis叢集,叢集的原理是什麼?
Redis Sentinal著眼於高可用,在master宕機時會自動將slave提升為master,繼續提供服務。
Redis Cluster著眼於擴充套件性,在單個redis記憶體不足時,使用Cluster進行分片儲存。
如何使用過Redis做非同步佇列?
一般使用list結構作為佇列,rpush生產訊息,lpop消費訊息。當lpop沒有訊息的時候,要適當sleep一會再重試。
如果不用sleep,list還有個指令叫blpop,在沒有訊息的時候,它會阻塞住直到訊息到來。
如果想要生產一次消費多次,可以使用pub/sub主題訂閱者模式,可以實現1:N的訊息佇列,但在消費者下線後,生產的訊息會丟失,想要持久化的話,需要使用訊息佇列如rabbitmq等。
redis如何實現延時佇列?
使用sortedset,拿時間戳作為score,訊息內容作為key呼叫zadd來生產訊息,消費者用zrangebyscore指令獲取N秒之前的資料輪詢進行處理。
如果有大量的key需要設定同一時間過期,需要注意什麼?
如果大量的key過期時間設定的過於集中,到過期的那個時間點,redis可能會出現短暫的卡頓現象。一般需要在過期時間上加一個隨機值,使得過期時間分散一些。
Redis單點吞吐量
單點TPS達到8萬/秒,QPS達到10萬/秒,補充下TPS和QPS的概念
- QPS: 應用系統每秒鐘最大能接受的使用者訪問量。每秒鐘處理完請求的次數,注意這裡是處理完,具體是指發出請求到伺服器處理完成功返回結果。可以理解在server中有個counter,每處理一個請求加1,1秒後counter=QPS。
- TPS:每秒鐘最大能處理的請求數。每秒鐘處理完的事務次數,一個應用系統1s能完成多少事務處理,一個事務在分散式處理中,可能會對應多個請求,對於衡量單個介面服務的處理能力,用QPS比較合理。
Redis雜湊槽
Redis叢集沒有使用一致性hash,而是引入了雜湊槽的概念,當需要在 Redis 叢集中放置一個 key-value 時,根據 CRC16(key) mod 16384的值,決定將一個key放到哪個桶中。
Redis叢集最大節點個數是多少?
Redis叢集預分好16384個桶(雜湊槽),(2^14)
Redis事務是什麼?
Redis事務可以一次執行多個命令,有以下特點:
- 批量操作在傳送 EXEC 命令前被放入佇列快取。
- 收到 EXEC 命令後進入事務執行,事務中任意命令執行失敗,其餘的命令依然被執行。
- 在事務執行過程,其他客戶端提交的命令請求不會插入到事務執行命令序列中。
事務可以理解為一個打包的批量執行指令碼,但批量指令並非原子化的操作,中間某條指令的失敗不會導致前面已做指令的回滾,也不會造成後續的指令不做。