Redis作為一個基於key-value的NoSQL數據庫,最顯著的特點存取速度非常快,官方說可以達到10W OPS,但是Redis為何這么快?
1、開發語言
Redis使用C語言進行編寫的,而Unix系統也是C語言實現,所以C語言是非常貼近操作系統的語言
2、基於內存讀寫
基於內存讀寫是Redis速度快的主要原因,不進行數據同步的情況下,不從磁盤讀取數據,沒有IO。內存響應時間大約100ns
3、單線程
1).單線程避免了線程上下文切換以及同步加鎖、解鎖帶來的消耗。
2).單線程簡化算法的實現
3).單線程也帶來一個問題,阻塞,對於一個長命令來說,會阻塞很多命令的執行響應
這里的單線程,不包含fork()產生的子進程。除了Redis之外,Node.js、Nginx都是單線程,都屬於高性能的組件框架
4、多路I/O復用模型
由於Redis是單線程的,所有的操作都是串行執行,但是由於讀寫操作等待用戶輸入或輸出都是阻塞的,所以 I/O 操作在一般情況下往往不能直
接返回,這會導致某一文件的 I/O 阻塞導致整個進程無法對其它客戶提供服務,而 I/O 多路復用就是為了解決這個問題而出現的。
Redis的I/O模型基於epoll實現,也提供select和kqueue的實現,默認epoll
epoll相對於其他多路復用技術,具有的優點:
1. epoll沒有最大並發連接的限制,上限是最大可以打開文件的數目,這個數字一般遠大於 2048
2. 效率提升,epoll最大的優點就在於它只管你“活躍”的連接,而跟連接總數無關,因此在實際的網絡環境中, epoll的效率就會遠遠高於
select和poll
3. 內存拷貝,epoll在這點上使用了“共享內存 ”,這個內存拷貝也省略了。
epoll與select/poll的區別:
I/O多路復用:通過一種機制,可以監視多個描述符(File Descriptor,簡稱fd),一旦某個描述符就緒,能夠通知程序進行相應的操作。
1、select:
本質是采用32個整數的32位,即32*32 = 1024來標識,fd值為1-1024。當fd的值超過1024限制時,就必須修改FD_SETSIZE的大小。這個時候就
可以標識32*max值范圍的fd。
2、poll:
poll與select不同,通過一個pollfd數組向內核傳遞需要關注的事件,故沒有描述符個數的限制,pollfd中的events字段和revents分別用於
標示關注的事件和發生的事件,故pollfd數組只需要被初始化一次。
3、epoll:
是poll的一種優化,返回后不需要對所有的fd進行遍歷,在內核中維持了fd的列表。select和poll是將這個內核列表維持在用戶態,然后傳遞
到內核中。與poll/select不同,epoll不再是一個單獨的系統調用,而是由epoll_create/epoll_ctl/epoll_wait三個系統調用組成,后面將會看到
這樣做的好處。epoll在2.6以后的內核才支持。
select/poll的幾大缺點:
1、每次調用select/poll,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大
2、同時每次調用select/poll都需要在內核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大
3、針對select支持的文件描述符數量太小了,默認是1024
4.select返回的是含有整個句柄的數組,應用程序需要遍歷整個數組才能發現哪些句柄發生了事件;
5.select的觸發方式是水平觸發,應用程序如果沒有完成對一個已經就緒的文件描述符進行IO操作,那么之后每次select調用還是會將這些文
件描述符通知進程。相比select模型,poll使用鏈表保存文件描述符,因此沒有了監視文件數量的限制,但其他三個缺點依然存在。
epoll IO多路復用模型實現機制:
由於epoll的實現機制與select/poll機制完全不同,上面所說的 select的缺點在epoll上不復存在。
epoll沒有這個限制,它所支持的FD上限是最大可以打開文件的數目,這個數字一般遠大於2048,舉個例子,在1GB內存的機器上大約是10萬左右
設想一下如下場景:有100萬個客戶端同時與一個服務器進程保持着TCP連接。而每一時刻,通常只有幾百上千個TCP連接是活躍的(事實上大部
分場景都是這種情況)。
如何實現這樣的高並發?
在select/poll時代,服務器進程每次都把這100萬個連接告訴操作系統(從用戶態復制句柄數據結構到內核態),讓操作系統內核去查詢這些套
接字上是否有事件發生,輪詢完后,再將句柄數據復制到用戶態,讓服務器應用程序輪詢處理已發生的網絡事件,這一過程資源消耗較大,因此,
select/poll一般只能處理幾千的並發連接。
如果沒有I/O事件產生,我們的程序就會阻塞在select處。但是依然有個問題,我們從select那里僅僅知道了,有I/O事件發生了,但卻並不知
道是那幾個流(可能有一個,多個,甚至全部),我們只能無差別輪詢所有流,找出能讀出數據,或者寫入數據的流,對他們進行操作。但是使用
select,我們有O(n)的無差別輪詢復雜度,同時處理的流越多,每一次無差別輪詢時間就越長
epoll的設計和實現與select完全不同。epoll通過在Linux內核中申請一個簡易的文件系統(文件系統一般用什么數據結構實現?B+樹)。把原先的
select/poll調用分成了3個部分:
1)調用epoll_create()建立一個epoll對象(在epoll文件系統中為這個句柄對象分配資源)
2)調用epoll_ctl向epoll對象中添加這100萬個連接的套接字
3)調用epoll_wait收集發生的事件的連接
如此一來,要實現上面說是的場景,只需要在進程啟動時建立一個epoll對象,然后在需要的時候向這個epoll對象中添加或者刪除連接。同時,
epoll_wait的效率也非常高,因為調用epoll_wait時,並沒有一股腦的向操作系統復制這100萬個連接的句柄數據,內核也不需要去遍歷全部的連接。
epoll內容原文地址:https://blog.csdn.net/wxy941011/article/details/80274233