Redis做爲一個基於key-value的NoSQL數據庫,最顯著的特色存取速度很是快,官方說能夠達到10W OPS,可是Redis爲什麼這麼快?算法
Redis使用C語言進行編寫的,而Unix系統也是C語言實現,因此C語言是很是貼近操做系統的語言數據庫
基於內存讀寫是Redis速度快的主要緣由,不進行數據同步的狀況下,不從磁盤讀取數據,沒有IO。內存響應時間大約100ns數組
1).單線程避免了線程上下文切換以及同步加鎖、解鎖帶來的消耗。服務器
2).單線程簡化算法的實現網絡
3).單線程也帶來一個問題,阻塞,對於一個長命令來講,會阻塞不少命令的執行響應數據結構
這裏的單線程,不包含fork()產生的子進程。除了Redis以外,Node.js、Nginx都是單線程,都屬於高性能的組件框架併發
因爲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),一旦某個描述符就緒,可以通知程序進行相應的操做。
一、select:
本質是採用32個整數的32位,即32*32 = 1024來標識,fd值爲1-1024。當fd的值超過1024限制時,就必須修改FD_SETSIZE的大小。這個時候就
能夠標識32*max值範圍的fd。
二、poll:
poll與select不一樣,經過一個pollfd數組向內核傳遞須要關注的事件,故沒有描述符個數的限制,pollfd中的events字段和revents分別用於
標示關注的事件和發生的事件,故pollfd數組只須要被初始化一次。
三、epoll:
是poll的一種優化,返回後不須要對全部的fd進行遍歷,在內核中維持了fd的列表。select和poll是將這個內核列表維持在用戶態,而後傳遞
到內核中。與poll/select不一樣,epoll再也不是一個單獨的系統調用,而是由epoll_create/epoll_ctl/epoll_wait三個系統調用組成,後面將會看到
這樣作的好處。epoll在2.6之後的內核才支持。
select/poll的幾大缺點:
一、每次調用select/poll,都須要把fd集合從用戶態拷貝到內核態,這個開銷在fd不少時會很大
二、同時每次調用select/poll都須要在內核遍歷傳遞進來的全部fd,這個開銷在fd不少時也很大
三、針對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內容原文地址:http://www.javashuo.com/article/p-ocfrejfu-ng.html