操作系統(tǒng)
直接IO與緩沖IO
- 緩沖io又稱作標準I/O,大多數(shù)文件系統(tǒng)的默認IO操作都是緩沖IO樱溉。在linux的緩沖IO機制中挣输,數(shù)據(jù)先從磁盤復(fù)制到內(nèi)核空間的緩沖區(qū),然后從內(nèi)核空間緩沖區(qū)復(fù)制到應(yīng)用程序的地址空間福贞。內(nèi)核緩沖區(qū)即pagecache撩嚼,一個page一般為4K。
- 直接io是由應(yīng)用程序直接訪問磁盤數(shù)據(jù)挖帘,而不經(jīng)過內(nèi)核緩沖區(qū)完丽,這樣做的目的是減少一次從內(nèi)核緩沖區(qū)到用戶程序緩存的數(shù)據(jù)復(fù)制。比如說數(shù)據(jù)庫管理系統(tǒng)這類應(yīng)用拇舀,它們更傾向于選擇它們自己的緩存機制逻族,因為數(shù)據(jù)庫管理系統(tǒng)往往比操作系統(tǒng)更了解數(shù)據(jù)庫中存放的數(shù)據(jù),數(shù)據(jù)庫管理系統(tǒng)可以提供一種更加有效的緩存機制來提高數(shù)據(jù)庫中數(shù)據(jù)的存取性能骄崩。通常直接IO與異步IO結(jié)合使用瓷耙,會得到比較好的性能。
內(nèi)存映射文件mmap與sendfile
內(nèi)存映射文件mmap:用戶空間不再有物理內(nèi)存刁赖,直接拿應(yīng)用程序的邏輯內(nèi)存地址映射到了linux操作系統(tǒng)的內(nèi)核緩沖區(qū)搁痛,應(yīng)用程序雖然讀寫的是自己的內(nèi)存,但這個內(nèi)存只是一個”邏輯地址“宇弛,實際讀寫的是內(nèi)核緩沖區(qū)鸡典。在Java中為MappedByteBuffer。注意枪芒,拷貝是把數(shù)據(jù)從一塊內(nèi)存中復(fù)制到另外一塊內(nèi)存里彻况,映射相當于只是持有了數(shù)據(jù)的一個引用(或者叫地址),數(shù)據(jù)本身只有1份舅踪。
sendfile:直接映射內(nèi)核緩沖區(qū)和socket緩沖區(qū)纽甘,數(shù)據(jù)無需在內(nèi)核層進行拷貝。在java中對應(yīng)的API為FileChannel.transferTo抽碌。
網(wǎng)絡(luò)IO模型
阻塞和非阻塞是從函數(shù)調(diào)用角度來說的悍赢,而同步和異步是從”讀寫是誰完成的“角度來說的。
阻塞:如果讀寫沒有就緒或者讀寫沒有完成货徙,則該函數(shù)一直等待左权。
非阻塞:函數(shù)立即返回,然后讓應(yīng)用程序輪詢痴颊。
同步:讀寫由應(yīng)用程序完成赏迟。
異步:讀寫由操作系統(tǒng)完成,完成之后蠢棱,回調(diào)或者事件通知應(yīng)用程序锌杀。
異步io一定是非阻塞io甩栈。
reactor模式與proactor模式
reactor模式:基于多路復(fù)用io產(chǎn)生的模式。主動模式糕再。
proactor:基于異步io產(chǎn)生的模式谤职。被動模式。
epoll的LT亿鲜、ET
水平觸發(fā):又稱為條件觸發(fā)允蜈,讀緩沖區(qū)只要不為空,就會一直觸發(fā)讀事件蒿柳;寫緩沖區(qū)只要不滿饶套,就會一直觸發(fā)寫事件。要避免”寫的死循環(huán)“垒探,寫緩沖區(qū)為滿的概率很小妓蛮,即”寫的條件“為一直滿足,所以當用戶注冊了寫事件卻沒有數(shù)據(jù)要寫時圾叼,它會一直觸發(fā)蛤克,因此在LT模式下寫完數(shù)據(jù)一定要取消寫事件。
邊緣觸發(fā):讀緩沖區(qū)從空轉(zhuǎn)為非空的時候觸發(fā)一次夷蚊;寫緩沖區(qū)的狀態(tài)构挤,從滿轉(zhuǎn)為非滿的時候觸發(fā)一次。要避免”short read“問題惕鼓,例如用戶收到了100個字節(jié)筋现,它觸發(fā)1次,但用戶只讀到了50個字節(jié)箱歧,剩下的50個字節(jié)不讀矾飞,它也不會再次觸發(fā)。因此在ET模式下呀邢,一定要把”讀緩沖區(qū)“的數(shù)據(jù)一次性讀完洒沦。
在實際開發(fā)中,大家一般都傾向于用LT价淌,這也是默認的模式申眼。Java NIO用的也是epoll的LT模式。因為ET容易漏事件输钩,一次觸發(fā)如果沒有處理好豺型,就沒有第二次寄回來。雖然LT觸發(fā)可能有少許的性能損耗买乃,但代碼寫起來更安全。
服務(wù)器的1+n+m模型
監(jiān)聽線程:負責accept事件的注冊和處理钓辆。和每一個新進來的客戶端建立socket連接剪验,然后把socket連接移交給IO線程肴焊,完成任務(wù),繼續(xù)監(jiān)聽新的客戶端功戚。
IO線程:負責每個socket連接上面read娶眷、write事件的注冊和實際的socket的讀寫。把讀到的request放入request隊列啸臀,交由worker線程處理届宠。
Worker線程:純粹的業(yè)務(wù)線程,沒有socket讀寫操作乘粒。對request隊列進行處理豌注,生成Response隊列,由IO線程再回復(fù)給客戶端灯萍。
進程轧铁、線程和協(xié)程
現(xiàn)代的編程語言像Go、Rust旦棉,原生就有協(xié)程的支持齿风,但偏傳統(tǒng)的Java、C++等語言沒有原生支持绑洛。因此產(chǎn)生了一些第三方的方案救斑,比如Java的Quasar Fiber、微信團隊為C++研發(fā)的libco等真屯,但普及程度還比較低系谐,開發(fā)者還是習(xí)慣多線程的開發(fā)模型。
內(nèi)存屏障
從用法來講讨跟,內(nèi)存屏障是在兩行代碼之間插入一個柵欄纪他。基于內(nèi)存屏障晾匠,有了Java中的volatile關(guān)鍵字茶袒,再加上但線程寫的原則,就有了java中的無鎖并發(fā)框架---Disruptor凉馆。其核心就是”一寫多讀薪寓,完全無鎖“。
CAS
如果是多線程寫澜共,則內(nèi)存屏障也不夠用了向叉,這時要用到CAS。CAS是在CPU層面提供的一個硬件原子指令嗦董,實現(xiàn)對同一個值的Compare和Set兩個操作的原子化母谎。基于CAS京革,上層可以實現(xiàn)樂觀鎖奇唤、無鎖隊列幸斥、無鎖棧、無鎖鏈表咬扇。