天天看點

一文帶你徹底掌握Redis為什麼這麼快?二、什麼是epoll三、什麼是select四、什麼是poll五、再談epoll六、阻塞/非阻塞七、再次分析Redis八、總結

一、Redis為什麼這麼快

因為Redis采取了epoll模型,IO多路複用。同步非阻塞。

二、什麼是epoll

介紹epoll之前必須要先介紹下select和poll。明白select和poll的缺點後才能知道為什麼epoll這麼快。

三、什麼是select

1、源碼

一文帶你徹底掌握Redis為什麼這麼快?二、什麼是epoll三、什麼是select四、什麼是poll五、再談epoll六、阻塞/非阻塞七、再次分析Redis八、總結

2、源碼解析

2.1、核心代碼

// select(xx,讀檔案描述符集合,寫檔案描述符集合,異常檔案描述符集合,逾時時間),我們隻需要關注讀檔案描述符集合,其他NULL不影響,因為有預設值。
select(max+1, &rset, NULL, NULL, NULL)
           

2.2、&rset是什麼?

rset其實就是fds(檔案描述符的集合)。而select模型存儲fds的方式是采取的bitmap,預設最大1024個。

2.3、執行流程

1.select模型每次都直接将rset(也就是fds)全部拷貝到核心态,因為核心态速度比使用者空間态快很多。

2.如果沒資料的話,select函數會阻塞,如果有資料的話會執行兩步

(1)将有資料的那個fd置位(也就是标記一下,代表這個fd有資料)

(2)select函數不在阻塞,将繼續往下執行。也就是整體周遊fds,找到有資料的那個fd讀取資料做處理。他的fd不能重用,每一次都需要重新建立新的fds且将使用者空間态的fds拷貝到核心态。(這個可以看上面while(1)的代碼)

一文帶你徹底掌握Redis為什麼這麼快?二、什麼是epoll三、什麼是select四、什麼是poll五、再談epoll六、阻塞/非阻塞七、再次分析Redis八、總結

3、缺點

  • fds最大支援1024個(可以更改,但是意義不大)
  • fd不可重用,每次核心态都給置位了,導緻每次都需要重新執行如下四句話建立一個新的rset(也就是fds)
    一文帶你徹底掌握Redis為什麼這麼快?二、什麼是epoll三、什麼是select四、什麼是poll五、再談epoll六、阻塞/非阻塞七、再次分析Redis八、總結
  • 使用者控件态拷貝rset到核心态也需要時間,雖然核心态執行比使用者态快,但是copy也需要開銷
  • O(n)再次周遊問題。因為rset裡的fd被置位後,select函數并不知道哪個被置位了,需要從頭周遊到尾,逐個對比。

四、什麼是poll

1.源碼

一文帶你徹底掌握Redis為什麼這麼快?二、什麼是epoll三、什麼是select四、什麼是poll五、再談epoll六、阻塞/非阻塞七、再次分析Redis八、總結

2、源碼解析

2.1、核心代碼

// 代表傳入了5個pollfd這個結構體,逾時時間是5000,poll整體就是圍繞他的pollfd這個結構體來展開的
poll(pollfds, 5, 5000)
           

2.2、結構體

poll的結構體是為了fd重複利用,不需要每次都拷貝到核心态用的。

一文帶你徹底掌握Redis為什麼這麼快?二、什麼是epoll三、什麼是select四、什麼是poll五、再談epoll六、阻塞/非阻塞七、再次分析Redis八、總結

revents:置位用的,如果某個fd有資料了,就把這個revents置位為POLLIN

2.3、執行流程

  • 有資料的時候流程如下
  • 将pollfd這個結構體裡的revents這個字段置位為POLLIN
  • 然後判斷這個結構體的revents字段是不是被置位為POLLIN了,是的話再将其結構體的這個字段恢複預設值0,然後取出資料,處理邏輯。
  • 恢複為預設值0的用途是不需要在像select一樣每次都需要重新建立fds,而是直接複用。但還是避免不了每個fd第一次複制一次。好的是不用每次都建立新的了。
    一文帶你徹底掌握Redis為什麼這麼快?二、什麼是epoll三、什麼是select四、什麼是poll五、再談epoll六、阻塞/非阻塞七、再次分析Redis八、總結

3、解決了select哪些問題

  • 采取的連結清單存儲,而不是bitmap,解決了1024長度限制問題
  • 采取結構體每次置位結構體内的revents字段,而不破壞fd本身,是以可重用,不需要每次都建立新的fd。

4、缺點

  • 使用者控件态拷貝rset到核心态也需要時間,雖然核心态執行比使用者态快,但是copy也需要開銷
  • O(n)再次周遊問題。因為rset裡的fd被置位後,select函數并不知道哪個被置位了,需要從頭周遊到尾,逐個對比。

五、再談epoll

0、概述

epoll的出現就是為了解決poll最後兩個缺點的。

1、源碼

一文帶你徹底掌握Redis為什麼這麼快?二、什麼是epoll三、什麼是select四、什麼是poll五、再談epoll六、阻塞/非阻塞七、再次分析Redis八、總結

2、源碼解析

2.1、核心代碼

epoll_create 相當于建立白闆
epoll_ctl相當于往白闆上寫字,寫的内容是一個fd、一個events結構體,events結構體跟pollfd的結構體很像。
epoll_ctl執行完後就得到了epfd,epfd就是循環了五個fd-events放裡面,每個fd都有events事件
epoll_wait
           

2.2、執行流程

  • epoll将fd放到了紅黑樹裡,且不需要拷貝到核心态,因為他采取了“共享記憶體”的概念。(其實還是複制,隻是複制采取了其他技術可以使開銷極其的小)
  • epoll的置位是重排,比如五個fd, 1 2 3 4 5,1 3 5這三個fd有資料了,那麼他會重排序,排成如下1 3 5 2 4。(也有的說是單獨放到新的數組裡)
  • 每一次置位nfds的值都+1。且會回調epoll_wait
  • 是以epoll_wait執行完會傳回有幾個fd有資料,那麼下面的for直接周遊nfds次即可。解決了前面的兩種O(n)。變成了O1
    一文帶你徹底掌握Redis為什麼這麼快?二、什麼是epoll三、什麼是select四、什麼是poll五、再談epoll六、阻塞/非阻塞七、再次分析Redis八、總結

六、阻塞/非阻塞

使用者程序通過調用select/poll/epoll去處理socket請求的時候, select/poll/epoll由于是阻塞的,是以導緻使用者程序阻塞;而select/poll/epoll處理的socket用的是非阻塞

七、再次分析Redis

比如三個redis-cli,假設2個redis-cli寫入指令,

select:那麼select模型是輪詢這三個redis-cli的fd,看哪個fd有消息,有的話讀取處理消息。當他下次再寫指令的時候還需要重新建立fd,然後複制到核心态然後再周遊全部。

poll:那麼poll模型是輪詢這三個redis-cli的fd,看哪個fd有消息,有的話讀取處理消息。下次再寫入的時候還是周遊全局fd,看哪個fd有消息進行處理。省去了每次都建立新的fd且複制的過程。

epoll:epoll就不輪詢了,有消息進來後你通知我,我去處理你的消息,那些沒消息的fd我不管。而且複制到核心态的過程我采取牛逼的技術讓開銷達到最小的極緻。

八、總結

  • select、poll、epoll必須懂,redis面試90%問

    -

    一文帶你徹底掌握Redis為什麼這麼快?二、什麼是epoll三、什麼是select四、什麼是poll五、再談epoll六、阻塞/非阻塞七、再次分析Redis八、總結

繼續閱讀