Redis是目前熟知的內存數據庫。Redis6.0版本推出了多線程模型。
Redis不是已經采用了多路復用技術嗎?不是號稱很高的性能了嗎?為啥還要采用多線程模型呢?
一、Redis為什么最初設計成單線程?

Redis的單線程指的是"其網絡IO和鍵值對讀寫是由一個線程完成的"。也就是只有網絡請求模塊和數據操作模塊是單線程的,而其他持久化存儲模塊、集群支撐模塊等是多線程的。
二、多線程適用場景?
計算機執行過程操作:讀寫操作和計算操作。
讀寫主要涉及IO操作,包括網絡IO和磁盤IO。計算操作主要涉及CPU。
多線程的目的:通過並發的方式提高IO利用率和CPU利用率。
Redis為什么不需要通過多線程的方式來提升IO利用率和CPU利用率?
- 首先,不需要提高CPU利用率,因為redis的操作是基於內存的,CPU不是Redis的性能瓶頸。
- Redis確實是一個IO密集型框架,數據操作過程中確實會有大量的網絡IO和磁盤IO,提高IO利用率毋庸置疑。
但是提高IO利用率,不是只有多線程一條路。
多線程的弊端:線程安全、共享資源的並發控制;線程切換帶來的性能開銷。
redis采用的多路復用IO。
小結
Redis並沒有在網絡請求模塊和數據操作模塊中使用多線程模型,主要是基於以下四個原因:
1、Redis 操作基於內存,絕大多數操作的性能瓶頸不在 CPU
2、使用單線程模型,可維護性更高,開發,調試和維護的成本更低
3、單線程模型,避免了線程間切換帶來的性能開銷
4、在單線程中使用多路復用 I/O技術也能提升Redis的I/O利用率
還是要記住:Redis並不是完全單線程的,只是有關鍵的網絡IO和鍵值對讀寫是由一個線程完成的。
三、redis的多路復用
linux的多路復用:通過一個線程來處理多個IO流。多個進程的IO可以注冊到同一管道上,這個管道會統一個內核進行交互。當管道中某一個請求需要的數據准備好之后,進程再把對應的數據拷貝到用戶空間中。
下圖是網上找的一個IO復用模型的圖:
IO多路復用在Linux下包括了三種,select、poll、epoll,抽象來看,他們功能是類似的,但具體細節各有不同。
其實,Redis的IO多路復用程序的所有功能都是通過包裝操作系統的IO多路復用函數庫來實現的。
在Redis 中,每當一個套接字准備好執行連接應答、寫入、讀取、關閉等操作時,就會產生一個文件事件。因為一個服務器通常會連接多個套接字,所以多個文件事件有可能會並發地出現。
一旦有請求到達,就會交給 Redis 線程處理,這就實現了一個 Redis 線程處理多個 IO 流的效果。
四、為什么redis6.0引入多線程?
Redis6.0中的多線程,也只是針對網絡請求過程采用了多線程,而數據的讀寫命令仍然是單線程處理的。
為什么呢?不是多路復用技術已經大大提高IO利用率了嗎?
主要對Redis有着更高的要求。
根據測算,Redis 將所有數據放在內存中,內存的響應時長大約為 100 納秒,對於小數據包,Redis 服務器可以處理 80,000 到 100,000 QPS,這么高的對於 80% 的公司來說,單線程的 Redis 已經足夠使用了。
但隨着越來越復雜的業務場景,有些公司動不動就上億的交易量,因此需要更大的 QPS。
為了提升QPS,很多公司的做法是部署Redis集群,並且盡可能提升Redis機器數。但是這種做法的資源消耗是巨大的。
而經過分析,限制Redis的性能的主要瓶頸出現在網絡IO的處理上,雖然之前采用了多路復用技術。但是我們前面也提到過,多路復用的IO模型本質上仍然是同步阻塞型IO模型。
下面是多路復用IO中select函數的處理過程:
從上圖我們可以看到,在多路復用的IO模型中,在處理網絡請求時,調用 select (其他函數同理)的過程是阻塞的,也就是說這個過程會阻塞線程,如果並發量很高,此處可能會成為瓶頸。
雖然現在很多服務器都是多個CPU核的,但是對於Redis來說,因為使用了單線程,在一次數據操作的過程中,有大量的CPU時間片是耗費在了網絡IO的同步處理上的,並沒有充分的發揮出多核的優勢。
如果能采用多線程,使得網絡處理的請求並發進行,就可以大大的提升性能。多線程除了可以減少由於網絡 I/O 等待造成的影響,還可以充分利用 CPU 的多核優勢。
所以,Redis 6.0采用多個IO線程來處理網絡請求,網絡請求的解析可以由其他線程完成,然后把解析后的請求交由主線程進行實際的內存讀寫。提升網絡請求處理的並行度,進而提升整體性能。
但是,Redis 的多 IO 線程只是用來處理網絡請求的,對於讀寫命令,Redis 仍然使用單線程來處理。
那么,在引入多線程之后,如何解決並發帶來的線程安全問題呢?
這就是為什么我們前面多次提到的"Redis 6.0的多線程只用來處理網絡請求,而數據的讀寫還是單線程"的原因。
Redis 6.0 只有在網絡請求的接收和解析,以及請求后的數據通過網絡返回給時,使用了多線程。而數據讀寫操作還是由單線程來完成的,所以,這樣就不會出現並發問題了。