之前面試時被面試官問了一個問題,Redis是多線程還是單線程的?依稀記得Redis為單線程,其更深層次的技術原理完全懵逼。所以此篇文章旨在解讀Redis為什么為單線程。
1、基本原理
采用多路 I/O 復用技術可以讓單個線程高效的處理多個連接請求(盡量減少網絡IO的時間消耗)
(1)為什么不采用多進程或多線程處理?
多線程處理可能涉及到鎖
多線程處理會涉及到線程切換而消耗CPU
(2)單線程處理的缺點?
無法發揮多核CPU性能,不過可以通過在單機開多個Redis實例來完善
2、Redis不存在線程安全問題?
Redis采用了線程封閉的方式,把任務封閉在一個線程,自然避免了線程安全問題,不過對於需要依賴多個redis操作的復合操作來說,依然需要鎖,而且有可能是分布式鎖
3、什么是多路I/O復用(Epoll)
(1) 網絡IO都是通過Socket實現,Server在某一個端口持續監聽,客戶端通過Socket(IP+Port)與服務器建立連接(ServerSocket.accept),成功建立連接之后,就可以使用Socket中封裝的InputStream和OutputStream進行IO交互了。針對每個客戶端,Server都會創建一個新線程專門用於處理
(2) 默認情況下,網絡IO是阻塞模式,即服務器線程在數據到來之前處於【阻塞】狀態,等到數據到達,會自動喚醒服務器線程,着手進行處理。阻塞模式下,一個線程只能處理一個流的IO事件
(3) 以下三種思路
(1)非阻塞【忙輪詢】:采用死循環方式輪詢每一個流,如果有IO事件就處理,這樣可以使得一個線程可以處理多個流,但是效率不高,容易導致CPU空轉
(2)Select代理(無差別輪詢):可以觀察多個流的IO事件,如果所有流都沒有IO事件,則將線程進入阻塞狀態,如果有一個或多個發生了IO事件,則喚醒線程去處理。但是還是得遍歷所有的流,才能找出哪些流需要處理。如果流個數為N,則時間復雜度為O(N)
(3)Epoll代理:Select代理有一個缺點,線程在被喚醒后輪詢所有的Stream,還是存在無效操作。 Epoll會哪個流發生了怎樣的I/O事件通知處理線程,因此對這些流的操作都是有意義的,復雜度降低到了O(1)
Redis的高並發和快速原因很多,總結一下幾點:
1. Redis是純內存數據庫,一般都是簡單的存取操作,線程占用的時間很多,時間的花費主要集中在IO上,所以讀取速度快。
2. 再說一下IO,Redis使用的是非阻塞IO,IO多路復用,使用了單線程來輪詢描述符,將數據庫的開、關、讀、寫都轉換成了事件,減少了線程切換時上下文的切換和競爭。
3. Redis采用了單線程的模型,保證了每個操作的原子性,也減少了線程的上下文切換和競爭。
4. 另外,數據結構也幫了不少忙,Redis全程使用hash結構,讀取速度快,還有一些特殊的數據結構,對數據存儲進行了優化,如壓縮表,對短數據進行壓縮存儲,再如,跳表,使用有序的數據結構加快讀取的速度。
5. 還有一點,Redis采用自己實現的事件分離器,效率比較高,內部采用非阻塞的執行方式,吞吐能力比較大。