每次面試都要被問:為什麼採用單線程的Redis也會如此之快?

每次面試都要被問:為什麼採用單線程的Redis也會如此之快?

眾所周知,Redis在內存庫資料庫領域非常地火熱,它

眾所周知,Redis在內存庫資料庫領域非常地火熱,它極高的性能和豐富的數據結構為我們的開發提供了極大的便利。

但我們也聽說了,Redis是單線程的,為什麼採用單線程的Redis也會如此之快呢?這篇文章我們來分析一下其中的緣由。

其實,嚴格來說,Redis Server是多線程的,只是它的請求處理整個流程是單線程處理的。 這一點我們一定要清楚了解到,不要單純地認為Redis Server是單線程的!

我們平時說的Redis單線程快是指它的請求處理過程非常地快!

下面我們就來分析一下為什麼請求處理使用單線程,依舊可以達到這麼高的性能。

Redis的性能非常之高,每秒可以承受10W+的QPS,它如此優秀的性能主要取決於以下幾個方面:

  • 純內存操作
  • 使用IO多路復用技術
  • 非CPU密集型任務
  • 單線程的優勢

純內存操作

Redis是一個內存資料庫,它的數據都存儲在內存中,這代表著我們讀寫數據都是在內存中完成,這個速度是非常快的。

Redis是一個KV內存資料庫,它內部構建了一個哈希表,根據指定的KEY訪問時,只需要O(1)的時間複雜度就可以找到對應的數據。同時,Redis提供了豐富的數據類型,並使用高效的操作方式進行操作,這些操作都在內存中進行,並不會大量消耗CPU資源,所以速度極快。

使用IO多路復用技術

Redis採用單線程,那麼它是如何處理多個客戶端連接請求呢?

Redis採用了IO多路復用技術和非阻塞IO,這個技術由作業系統實現提供,Redis可以方便地作業系統的API即可。Redis可以在單線程中監聽多個Socket的請求,在任意一個Socket可讀/可寫時,Redis去讀取客戶端請求,在內存中操作對應的數據,然後再寫回到Socket中。

整個過程非常高效,Redis利用了IO多路復用技術的事件驅動模型,保證在監聽多個Socket連接的情況下,只針對有活動的Socket採取反應。

非CPU密集型任務

採用單線程的缺點很明顯,無法使用多核CPU。Redis作者提到,由於Redis的大部分操作並不是CPU密集型任務,而Redis的瓶頸在於內存和網絡帶寬。

在高並發請求下,Redis需要更多的內存和更高的網絡帶寬,否則瓶頸很容易出現在內存不夠用和網絡延遲等待的情況。

當然,如果你覺得單個Redis實例的性能不足以支撐業務,Redis作者推薦部署多個Redis節點,組成集群的方式來利用多核CPU的能力,而不是在單個實例上使用多線程來處理。

單線程的優勢

基於以上特性,Redis採用單線程已足夠達到非常高的性能,所以Redis沒有採用多線程模型。

另外,單線程模型還帶了以下好處:

  • 沒有了多線程上下文切換的性能損耗
  • 沒有了訪問共享資源加鎖的性能損耗
  • 開發和調試非常友好,可維護性高

所以Redis正是基於以上這些方面,所以採用了單線程模型來完成請求處理的工作。

多線程優化

在文章開頭已經特別說明,Redis Server本身是多線程的,除了請求處理流程是單線程處理之外,Redis內部還有其他工作線程在後台執行,它負責異步執行某些比較耗時的任務,例如AOF每秒刷盤、AOF文件重寫都是在另一個線程中完成的。

而在Redis 4.0之後,Redis引入了lazyfree的機制,提供了unlinkflushall ayscflushdb async等命令和lazyfree-lazy-evictionlazyfree-lazy-expire等機制來異步釋放內存,它主要是為了解決在釋放大內存數據導致整個redis阻塞的性能問題。

在刪除大key時,釋放內存往往都比較耗時,所以Redis提供異步釋放內存的方式,讓這些耗時的操作放到另一個線程中異步去處理,從而不影響主線程的執行,提高性能。

到了Redis 6.0,Redis又引入了多線程來完成請求數據的協議解析,進一步提升性能。它主要是解決高並發場景下,單線程解析請求數據協議帶來的壓力。請求數據的協議解析由多線程完成之後,後面的請求處理階段依舊還是單線程排隊處理。

可見,Redis並不是保守地認為單線程有多好,也不是為了使用多線程而引入多線程。Redis作者很清楚單線程和多線程的使用場景,針對性地優化,這是非常值得我們學習的。

缺點

上面介紹了單線程可以達到如此高的性能,並不是說它就沒有缺點了。

單線程處理最大的缺點就是,如果前一個請求發生耗時比較久的操作,那麼整個Redis就會阻塞住,其他請求也無法進來,直到這個耗時久的操作處理完成並返回,其他請求才能被處理到。

我們平時遇到Redis變慢或長時間阻塞的問題,90%也都是因為Redis處理請求是單線程這個原因導致的。

所以,我們在使用Redis時,一定要避免非常耗時的操作,例如使用時間複雜度過高的方式獲取數據、一次性獲取過多的數據、大量key集中過期導致Redis淘汰key壓力變大等等,這些場景都會阻塞住整個處理線程,直到它們處理完成,勢必會影響業務的訪問。

我會在後期的文章中專門介紹具體有哪些場景會引發Redis阻塞的問題,並提供規避問題的方法和優化方案。

總結

Redis使用單線程,配合IO多路復用技術,可以完成多個連接的請求處理。而且正是由於它的使用定位是內存資料庫,這樣幾乎所有的操作都在內存中完成,它的性能可以達到非常之高。

同時,單線程沒有了線程上下文切換和訪問共享資源加鎖的性能損耗,而且單線程模型對程序的開發和調試非常友好,因此Redis使用單線程模型也就在情理之中了。

Redis在最近的版本也對多線程進行了優化,用於解決釋放大內存數據和請求數據協議解析對Redis產生的性能影響,進一步提升了Redis的性能。

單線程結合上述場景可以達到非常高的性能,同時也存在耗時操作阻塞整個線程的問題,我們在使用Redis時要避免耗時過長的操作,才能更好地發揮Redis的性能。

聲明:文章觀點僅代表作者本人,PTTZH僅提供信息發布平台存儲空間服務。
喔!快樂的時光竟然這麼快就過⋯
繼續其他精彩內容吧!
more