引言:
上一節簡單介紹了redis的安裝與使用,與redis一樣的緩存組件還有memcached,大體接入方式類似,這里就不重復介紹了,大家可以google具體memcached安裝以及使用方式。那么這一節將圍繞redis以及memcached兩大分布式最常用的緩存組件,對它們各自使用的網絡IO模型進行分析。
簡介:
本篇博客主要針對redis的單線程模型,epoll事件驅動。以及memcached多線程模型,libevent事件驅動進行對比分析,了解他們各自的設計思想與應用層的利弊。
目錄:1.redis的單線程模型
? ? ? ? ? ?2.memcached的多線程模型
正文:?
引用一段CSDN博主對于redis架構的描述:
Redis單線程架構
1 單線程模型(linux只有輕量級進程,ps命令也會出線程,由于博主不搞底層開發,就把模擬進程稱為線程)
Redis客戶端對服務端的每次調用都經歷了發送命令,執行命令,返回結果三個過程。其中執行命令階段,由于Redis是單線程來處理命令的,所有每一條到達服務端的命令不會立刻執行,所有的命令都會進入一個隊列中,然后逐個被執行。并且多個客戶端發送的命令的執行順序是不確定的。但是可以確定的是不會有兩條命令被同時執行,不會產生并發問題,這就是Redis的單線程基本模型。
(1)純內存訪問。數據存放在內存中,內存的響應時間大約是100納秒,這是Redis每秒萬級別訪問的重要基礎。
(2)非阻塞I/O,Redis采用epoll做為I/O多路復用技術的實現,再加上Redis自身的事件處理模型將epoll中的連接,讀寫,關閉都轉換為了時間,不在I/O上浪費過多的時間。
(3)單線程避免了線程切換和競態產生的消耗。
(4)Redis采用單線程模型,每條命令執行如果占用大量時間,會造成其他線程阻塞,對于Redis這種高性能服務是致命的,所以Redis是面向高速執行的數據庫。
ok,看完了以上描述,我的總結是:
總體來說:
1)絕大部分請求是純粹的內存操作(非常快速)?
2)采用單線程,避免了不必要的上下文切換和競爭條件?
3)非阻塞IO 、epoll event loop。
Redis的單線程的行為主要是對內存的讀寫,這些操作其實用不了多少時間,因此瓶頸在網絡I/O上面,我們一般提供較好的網絡環境就可以提升Redis的吞吐量,比如提高網絡帶寬,除此之外還可以通過合并命令提交批處理請求(pipeline)來代替單條命令一次次請求從而減少網絡開銷,提高吞吐量。
我們來進一步研究redis的網絡模型,redis是封裝了下圖幾種多路復用器實現的EventLoop事件輪訓,redis沒有采用memcached采用的Libevent,Libevent為了迎合通用性造成代碼龐大及犧牲了在特定平臺的不少性能。Redis一直堅持設計小巧并去依賴庫的思路,提供Socket句柄事件的多路復用器,這部分分別對于不同平臺提供了不同的實現,比如epoll和select可以用于linux平臺、kqueue可以用于蘋果平臺、evpoll可以用于Solaris平臺,這里并沒有看到iocp,也就是Redis對于Windows支持并不是很好。
什么是多路I/O復用?
(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事件,則喚醒線程去處理。但是還是得遍歷所有的流,才能找出哪些流需要處理。
(3)Epoll代理:Select代理有一個缺點,線程在被喚醒后輪詢所有的Stream,還是存在無效操作。 Epoll會哪個流發生了怎樣的I/O事件通知處理線程,因此對這些流的操作都是有意義的。
Linux下epoll屬于IO多路復用,但他實際應用必須搭配no-blocking io ,執行epoll和具體業務都是在同個主進程中執行。雖然純內存的業務操作很快,但在執行業務時,有新的請求到來那么kernel中發現readylist正在被使用時,會把就緒事件放在ovflist中當處理完readylist后,會檢查ovflist是否有事件。
首先,Redis服務器中有兩類事件,文件事件和時間事件。
文件事件(file event):Redis客戶端通過socket與Redis服務器連接,而文件事件就是服務器對套接字操作的抽象。例如,客戶端發了一個GET命令請求,對于Redis服務器來說就是一個文件事件。
時間事件(time event):服務器定時或周期性執行的事件。例如,定期執行RDB持久化。
在這里我們主要關注Redis處理文件事件的模型:Reactor模型
Handles?:表示操作系統管理的資源,我們可以理解為fd。
Synchronous Event Demultiplexer?:同步事件分離器,阻塞等待Handles中的事件發生。
Initiation Dispatcher?:初始分派器,作用為添加Event handler(事件處理器)、刪除Event handler以及分派事件給Event handler。也就是說,Synchronous Event Demultiplexer負責等待新事件發生,事件發生時通知Initiation Dispatcher,然后Initiation Dispatcher調用event handler處理事件。
Event Handler?:事件處理器的接口
Concrete Event Handler?:事件處理器的實際實現,而且綁定了一個Handle。因為在實際情況中,我們往往不止一種事件處理器,因此這里將事件處理器接口和實現分開,與C++、Java這些高級語言中的多態類似。
2.memcached網絡IO模型
Memcached是多線程,非阻塞IO復用的網絡模型,分為監聽主線程和worker子線程,監聽線程監聽網絡連接,接受請求后,將連接描述字pipe 傳遞給worker線程,進行讀寫IO, 網絡層使用libevent封裝的事件庫,多線程模型可以發揮多核作用,但是引入了cache coherency和鎖的問題,比如,Memcached最常用的stats 命令,實際Memcached所有操作都要對這個全局變量加鎖,進行計數等工作,帶來了性能損耗。
Libevent應該算是包含了上述一些功能,總體來說也是走事件驅動+no blocking io。會有一個fd和event的mapping關系,至于linux內核對于喚醒的細節就沒有多研究了,那總體對比下來,io層面的話,redis有單線程的優勢、批量pipeline的優勢,memcached有多核優勢,但是多了一些同步的損耗,但是在內存管理方面以及集群方面,redis是動態分配,memcached是分配比較固定的chunk,那對于某些字節比chunk小的情況,就有浪費的空間,集群方面memcached服務端是沒有分布式的,redis在3.0提供了cluster,這個后續討論,具體性能對比還需要區分數據量 數據格式 集群方案等,下篇文章主要分析兩者在內存管理上的區別。