1.服務端處理網絡請求過程
IO分為:
網絡IO:本質是socket文件讀取;上圖中紅線部分
磁盤IO:上圖中藍線部分
由上圖可知:IO都分為了兩個階段
1.將數據從文件先加載至內核內存空間(緩沖區(qū))--時間長
2.將數據從內核緩沖區(qū)復制到用戶空間的進程的內存中--時間短
服務端處理網絡請求的過程
1.客戶端發(fā)起請求,數據發(fā)送到網卡
2.將網卡中數據通過DMA機制復制到內核緩沖區(qū)
3.內核緩沖區(qū)復制到用戶空間(比如要:GET index.html)
-------------------
4.web服務器收到請求數據,因為應用程序無法與硬件打交道,所以向內核空間發(fā)起系統(tǒng)調用
5.內核空間收到信息,cpu發(fā)出指令,讓DMA設備去磁盤獲取index.html,并拷貝到內核緩沖區(qū)
6.內核緩沖區(qū)再復制到用戶空間
-------------------
7.web服務器收到數據,然后構建響應報文,再發(fā)送給內核空間的socket buffer
8.socket buffer 在發(fā)送給網卡
9.通過網卡把響應報文發(fā)送給客戶端
2.什么是DMA
DMA:直接內存訪問
PIO 程序的輸入輸出模型,--所有操作都需要CPU參與
DMA 直接內存訪問;只需要cpu發(fā)送指令,讓DMA設備去把磁盤數據拷貝到內存,降低CPU的使用率
3.IO模型
同步,異步,阻塞,非阻塞的理解
比如上圖:A是領導 B是員工
現在A讓B去做一件事情
同步就是:領導自己去詢問事情是否做完
異步就是:員工主動告知領導事情的進度
阻塞就是:事情非常緊急需要馬上解決,領導必須盯著員工把事情做完,不能干其他的
非阻塞:領導在員工做事情時,可以去喝喝茶,打打高爾夫...等等
A對應:應用程序
B對應:內核
只有讓內核多做事情,應用程序少做,這樣才能實現高并發(fā)
衍生出
同步阻塞,同步非阻塞,異步阻塞,異步非阻塞
場景:用燒水壺燒水
同步阻塞就是:買的燒水壺是無聲的,為了把水燒開,并且不能讓水噴出來,就只能守著燒
同步非阻塞:買的燒水壺也是無聲的,只需要把水燒開就行,不管開水會不會噴出,所以我就可以在燒水時,去看看電視
偶爾去看看水是否燒開就行
異步阻塞:這次買的燒水壺可以語音提示,燒開了會提醒,如果我此時還守著,不是有病嘛,所以這沒意義
異步非阻塞:水燒開自動提醒我,在燒水時我可以開心看電視
4.五中IO模型
阻塞型、非阻塞型、復用型、信號驅動型、異步
4.1.阻塞型
理解場景:釣魚
去釣魚,那人必須守著
數據準備好:就好比魚兒上鉤,這個過程是很漫長的
復制完成:好比當魚兒上鉤,把魚兒放到桶里,這個過程很快
在釣魚的過程中,為了不讓魚兒跑掉,你必須一直守著,不能干其他的;所以一直是阻塞的
4.2.非阻塞型
理解場景: 釣魚,釣魚旁邊有個麻將館
釣魚時,把魚竿搞好,就去麻將館打麻將了...
打一局,我就去看看魚兒上鉤沒有...
當魚兒上鉤了,這個時候我必須在場把魚兒放在桶里
在魚兒上鉤過程中,我都跑去打麻將了,只是偶爾去看看魚兒是否上鉤,也只有把魚放在桶里這階段我在場,所以
只有這段時間是阻塞的
4.3.復用型
理解場景:我雇了一只貓去守著釣魚,而且這次拿了很多釣竿
讓貓在河邊守著,但是呢,我又怕貓偷吃,所以我也只能盯著貓不能去打麻將了,這階段我是阻塞的
當有釣竿魚上鉤了,貓就叫我,然后我就過去把魚放在桶里,所以我從頭到尾一直都是阻塞的....
4.4.信號驅動型
理解場景:我買了一個帶語音提示的魚竿
這次,我把魚竿搞好,我也依然去旁邊的麻將館打麻將了,
當魚兒上鉤了,就語音提示我,主人,主人,魚上鉤了,然后我就去把魚兒放在桶里
只有把魚放在桶里,我需要去河邊,這段時間我是阻塞的
4.5.異步
理解場景:由于前幾次都是空軍,這次我就聘請了一個釣魚達人去釣
釣魚達人在釣魚,我就去打麻將..
我麻將打完了,釣魚達人,就把魚兒給我了
5.五種I/O模型比較
阻塞型:一直都阻塞
非阻塞:只有魚兒放桶里是阻塞
復用型:也是一直阻塞
信號驅動:只有魚兒放桶里是阻塞
異步:完全不阻塞
6.模型實現的方式
Select: #Linux實現 對應I/O復用模型 BSD4.2最早實現,POSIX標準,一般操作系統(tǒng)均有實現
Poll: #Linux實現, 對應I/O復用模型 System V unix最早實現
Epoll: #Linux特有, 對應I/O復用模型 具有信號驅動I/O模型的某些特性
Kqueue: #FreeBSD實現, 對應I/O復用模型 具有信號驅動I/O模型某些特性
/dev/poll:#SUN的Solaris實現 對應I/O復用模型 具有信號驅動I/O模型的某些特性
Iocp #Windows實現, 對應第5種(異步I/O)模型
7.select/poll/epoll
7.1.select
Select:POSIX所規(guī)定,目前幾乎在所有的平臺上支持,其良好跨平臺支持也是它的一個優(yōu)點,本質上是通過設置或
者檢查存放fd標志位的數據結構來進行下一步處理
#缺點
1)單個進程能夠監(jiān)視的文件描述符的數量存在最大限制,在Linux上一般為1024,可以通過修改宏定義FD_SETSIZE,
再重新編譯內核實現,但是這樣也會造成效率的降低
2)單個進程可監(jiān)視的fd數量被限制,默認是1024,修改此值需要重新編譯內核
3)對socket是線性掃描,即采用輪詢的方法,效率較低
4)select 采取了內存拷貝方法來實現內核將 FD 消息通知給用戶空間,這樣一個用來存放大量fd的數據結構,
這樣會使得用戶空間和內核空間在傳遞該結構時復制開銷大
7.2.poll
1)本質上和select沒有區(qū)別,它將用戶傳入的數組拷貝到內核空間,然后查詢每個fd對應的設備狀態(tài)
2)其沒有最大連接數的限制,原因是它是基于鏈表來存儲的
3)大量的fd的數組被整體復制于用戶態(tài)和內核地址空間之間,而不管這樣的復制是不是有意義
4)poll特點是"水平觸發(fā)",如果報告了fd后,沒有被處理,那么下次poll時會再次報告該fd
7.3.epoll
epoll:在Linux 2.6內核中提出的select和poll的增強版本
支持水平觸發(fā)LT和邊緣觸發(fā)ET,最大的特點在于邊緣觸發(fā),它只告訴進程哪些fd剛剛變?yōu)榫托钁B(tài),并且只會通知一次
使用"事件"的就緒通知方式,通過epoll_ctl注冊fd,一旦該fd就緒,內核就會采用類似callback的回調機制來激
活該fd,epoll_wait便可以收到通知
#優(yōu)點:
1)沒有最大并發(fā)連接的限制:能打開的FD的上限遠大于1024(1G的內存能監(jiān)聽約10萬個端口),具體查看
/proc/sys/fs/file-max,此值和系統(tǒng)內存大小相關
2)效率提升:非輪詢的方式,不會隨著FD數目的增加而效率下降;只有活躍可用的FD才會調用callback函數,
即epoll最大的優(yōu)點就在于它只管理"活躍"的連接,而跟連接總數無關
3)內存拷貝,利用mmap(Memory Mapping)加速與內核空間的消息傳遞;即epoll使用mmap減少復制開銷
邊緣觸發(fā):只通知一次
8.什么是零拷貝
傳統(tǒng)Linux中 I/O 的問題
傳統(tǒng)的Linux系統(tǒng)的標準 I/O 接口(read、write)是基于數據拷貝的,也就是數據都是 copy_to_user 或者
copy_from_user,這樣做的好處是,通過中間緩存的機制,減少磁盤 I/O 的操作,
但是壞處也很明顯,大量數據的拷貝,用戶態(tài)和內核態(tài)的頻繁切換,會消耗大量的 CPU 資源,嚴重影響數據傳輸的
性能
統(tǒng)計表明,在Linux協議棧中,數據包在內核態(tài)和用戶態(tài)之間的拷貝所用的時間甚至占到了數據包整個處理流
程時間的57.1%
零拷貝就是上述問題的一個解決方案,盡量避免拷貝操作來緩解 CPU 的壓力。
零拷貝并沒有真正做到"0"拷貝,它更多是一種思想,很多的零拷貝技術都是基于這個思想去做的優(yōu)化
8.1.原始數據拷貝操作
8.2.MMAP:Memory Mapping
8.3.SENDFILE
8.4.DMA 輔助的 SENDFILE
9.Httpd MPM
HTTP中的三種請求處理模式(MPM)的區(qū)別
(1)prefork(預派生模式):多進程I/o模型,每個進程相應一個請求 #-->>進程模型,兩級結構
主進程--子進程--線程--請求
|--子進程--線程--請求
|--子進程--線程--請求
優(yōu)點:穩(wěn)定
缺點:慢,占用資源(內存),不適用于高并發(fā)場景
使用select 模型,最大并發(fā)1024
(2)worker:復用的多進程I/O模型,多進程多線程 #-->>線程模型,三級結構
主進程--子進程--線程--請求
| --線程--請求
| --線程--請求
|--子進程--線程--請求
| --線程--請求
| --線程--請求
優(yōu)點:相比prefork,占用內存較少,可以同時處理更多的請求
缺點:使用keep-alive的長連接方式,某個線程會一直被占據,即使沒有傳輸數據,也需要一直等待到超時才會被釋放。
(3)event 事件驅動模型(epoll),增加了一個監(jiān)聽線程 #-->>線程模型,三級結構
監(jiān)聽線程:用于向工作線程分配任務并和客戶端保持會話連接,超時之后監(jiān)聽線程會刪除socket,工作線程只處理用戶請求,處理完之后將會話保持交于監(jiān)聽線程,自己去處理新的請求,不再負責會話保持
主進程--子進程--監(jiān)聽線程--請求1 空請求不被分配 請求3
| |--工作線程--請求1
| |--工作線程--請求3
| |--工作線程 處理完成請求,將keep-alived交給監(jiān)聽線程,自己開始等待處理新請求
與worker進程很像,最大的區(qū)別在于,它解決了keep-alive場景下,長期被占用的線程的資源浪費問題
優(yōu)點:單線程響應多請求,占據更少的內存,高并發(fā)下表現更優(yōu)秀
缺點:沒有線程安全控制
本文摘自 :https://blog.51cto.com/t