1:io多路復用epoll io多路復用簡單來說就是一個執行緒處理多個網路請求, 我們知道epoll in 的事件觸發是可讀了,這個比較好理解,比如一個連接過來,或者一個資料發送過來了,那么in事件就觸發了,那么out事件是如何觸發的呢?緩沖區可寫(有空的區域),就可以觸發,epoll有兩種模式LT(水平觸發)和ET(邊緣觸發),LT模式下,主要緩沖區資料一次沒有處理完,那么下次epoll_wait回傳時,還會回傳這個句柄;而ET模式下,緩沖區資料處理一次就結束,下次是不會再通知了,只在第一次回傳.所以在ET模式下,一般是通過while回圈,一次性讀完全部資料.epoll默認使用的是LT, socket的緩沖區已經滿了,此時無法繼續send,此時異步程式的正確處理流程是呼叫epoll_wait,當socket緩沖區中的資料被對方接收之后,緩沖區就會有空閑空間可以繼續往里面寫資料,此時epoll_wait就會回傳這個socket的EPOLLOUT事件,獲得這個事件時,你就可以繼續往socket中寫出資料, redis的epoll使用的是默認的LT模式,只要寫緩沖區可寫時,就會不斷的觸發可寫事件,為了避免一直觸發可寫事件,redis是在有資料可寫的時候注冊寫事件,寫完之后就取消寫事件的注冊 epoll內部資料結構為紅黑樹和鏈表,紅黑樹保存了所有socket和監聽的事件資訊,鏈表保存的是就緒的socket資訊,就是那些就緒socket已經幫你整理好了, 那么,這個準備就緒list鏈表是怎么維護的呢?當我們執行epoll_ctl時,除了把socket放到epoll檔案系統里file物件對應的紅黑樹上之外,還會給內核中斷處理程式注冊一個回呼函式,告訴內核,如果這個句柄的中斷到了,就把它放到準備就緒list鏈表里,所以,當一個socket上有資料到了,內核在把網卡上的資料copy到內核中后就來把socket插入到準備就緒鏈表里了, 如此,一顆紅黑樹,一張準備就緒句柄鏈表,少量的內核cache,就幫我們解決了大并發下的socket處理問題,執行epoll_create時,創建了紅黑樹和就緒鏈表,執行epoll_ctl時,如果增加socket句柄,則檢查在紅黑樹中是否存在,存在立即回傳,不存在則添加到樹干上,然后向內核注冊回呼函式,用于當中斷事件來臨時向準備就緒鏈表中插入資料,執行epoll_wait時立刻回傳準備就緒鏈表里的資料即可, 2:讀寫事件的注冊與洗掉 當一個新的連接建立后,redis會創建一個redisClient物件,然后為這個socket向epoll注冊一個讀事件,直到RedisClient物件銷毀時才洗掉讀事件,當redis讀到一個完整的命令并決議完成后,就會為socket向epoll注冊寫事件,將回復資訊發給client之后,就會從epoll洗掉剛注冊的寫事件,下個命令來了之后又會重復這個增刪寫事件的動作, 所以每個socket向epoll注冊銷毀一次讀事件,多次注冊銷毀寫事件,這樣做的目的:在我沒什么可寫的情況下你就別叫我寫了,我知道什么時候可寫 3:redis單執行緒是怎么做到高性能的呢? 以前我一直在想一個問題:如果一個redis命令很長,redis接收處理這個命令就要100毫秒,那么別的命令會不會延遲100毫秒呢?后續命令處理會不會像訊息佇列一樣積壓呢? 答案:不會, 上面我們已經說了epoll的原理,它不是讓我們一次處理完一個命令后,再去處理另一個命令,epoll是幫我們一次接收多個命令的部分資料(如果命令很短則是完整的資料),每個socket都有一個緩沖區,寫滿了就不能寫了,需要讀出來后才能繼續往里面寫,redis為每個client分配了一個變長緩沖區,從socket中讀出后存在緩沖區中,當接收到一個完整的命令,就決議并執行這個命令,然后把緩沖區后面的資料往前移動,反復利用這塊記憶體,當這塊記憶體超過一定值后就會釋放,在需要的時候重新分配一塊記憶體 也就是說epoll的水平觸發模式將一個較長的命令請求分成了多次接收,一次能接收多個命令的請求,天生就只支持高并發的,加上redis會將耗時的命令會分多次處理,保證了我們的讀寫操作都很快, 綜述單執行緒高性能的原因:
- 1:純記憶體操作本來就很快
- 2:redis使用epoll支持io多路復用,天生支持高并發請求
- 3:redis將耗時的操作分多次處理,保證每次處理的時間都很短,保證了讀寫性能,如果資料很長的話處理時間就會變長,所以redis不建議保存太長的資料
- 1:代碼簡潔又簡單
- 2:性能已經很好了
- 3:性能不夠我再搞多執行緒嗎
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/1138.html
標籤:NoSQL
上一篇:Redis常用命令
