談談多路轉接實現的伺服器

2022-11-26 23:03:54 字數 1394 閱讀 6503

通過多路轉接,使得伺服器能夠併發處理多個請求,這些請求與事件處理函式繫結並註冊到事件迴圈上

每次迴圈,從多路轉接函式獲得觸發事件的請求(即套接字),根據觸發事件型別順序呼叫相應的處理函式

以上就要求這些處理函式本身不能阻塞,或者說通過某些手段實現非阻塞

常規的實現方式有:

1. 通過執行緒池提交任務,執行cpu阻塞呼叫並提供**

2. 通過中介軟體實現任務非同步化

3. 通過再次註冊事件

所謂的高速伺服器一般指處理請求本身,不包括伺服器自身狀態維護

如nginx、redis,若要對伺服器狀態進行維護,一般會通過子程序或子執行緒等方式進行

首先從阻塞說起,網路io造成的阻塞時間相比cpu處理速度來說,過於漫長,大部分時間都在等待網路io

通過多路轉接,一旦發生io阻塞,可以立即切換到其他事件處理函式中,使得處理效率提升

其次是開銷,傳統通過程序、執行緒實現請求處理,讓系統自動排程,小型應用完全可以接受

一旦請求量提升或者說遇到高併發,程序/執行緒執行棧切換開銷就很明顯,另外程序/執行緒本身記憶體開銷也很大

早期解決方案是通過池化技術免去建立和銷燬部分的開銷,但依然無法解決高併發

畢竟一個系統能建立的程序或執行緒數量不會太多,池化後可使用的量也不會很多,併發量上來問題還是明顯

一個解決的思路是分散式,利用lb做多機,但又引入了額外的分散式問題

另一個解決思路是降低開銷,將核心上的切換引入到使用者態,降低切換開銷,通過協程實現單執行緒併發

單執行緒(協程)其實本質是一種由使用者控制的切換,遇到io就切換,讓系統去等待資料並通知

只要是遇到網路io,基本離不開多路轉接,因為沒有比它更高效的輪詢機制了

用上了多路轉接和協程,就能實現記憶體佔用低的同時保證效率的提升(畢竟單執行緒實現),當然不使用協程也能得到足夠的效率提升

畢竟實現協程還要連帶實現一系列的相關機制,往往執行緒池就足以

雖然也叫協程,但go語言本身的設計架構是m:n的

有一個runtime負責邏輯執行緒到物理執行緒的對映,在go中,提交一個g(goroutine),會被新增到p(processor)的一個佇列中,p從佇列中迴圈取出goroutine執行,而p又與m進行對映,p就是邏輯執行緒,m則是物理執行緒

等於說,go從語言層面實現了多核心併發,並且因為執行棧都是在使用者態管理(半使用者態,實際是runtime維護了一個記憶體池),遇到網路io阻塞,goroutine會被runtime從p佇列中取出掛起,通過專門負責網路io的g去多路轉接監聽事件,當事件到達則將這個goroutine按某個策略重新新增到某個p的佇列(runtime的排程策略)

從執行流的角度理解,每個goroutine相當於一個執行流,一個p對應一組執行流,物理執行緒m上執行p,執行過程中遇到網路io阻塞或其他排程權轉讓(從goroutine到goroutine),由runtime負責執行流的跳轉,對物理執行緒m是無感知的