聊聊WebRTC網關伺服器3:如何優化Server的線程方案?
12 人贊了文章
《聊聊WebRTC網關伺服器》系列文章系由WebRTCon2018中網易雲信音視頻技術專家的分享內容《從零開始構建音視頻網關伺服器》整理而成,該系列文章將和大家分享網易NRTC在WebRTC網關項目的自研過程中遇到的一些問題,以及我們最終的解決方法。
在分享完埠方案與PeerConnection的方案後,本篇文章我們將講解如何優化WebRTC網關伺服器的線程方案。這個也是網關伺服器架構設計的核心部分。
我們做WebRTC網關伺服器的時候,不僅要考慮功能可用,還要考慮並發性能。有三種方案可以選擇:
- 第一種方案是一種多線程的方案,就是為每一個client或者是每一個PeerConnection建立一個獨立的線程去做收發。這個方案的劣勢很明顯,它跨線程的流程就會很多,效率也不高。但是,這確實是某些開源伺服器的方案;
- 第二種方案是單線程的方案,所有用戶的I/O以及業務操作都在服務端的一個線程里做完,在用戶量不大或者服務端硬體資源可以隔離的情況下,它是一個比較好的方案。但是這種方案也有劣勢,當我們使用物理機作為伺服器時,單線程是沒有辦法利用多核優勢的;如果同時啟動多個進程,則需要多個外網埠和域名,維護的成本也會比較高;
- 第三種方案是多線程優化方案,也是網關一期實現的方案,I/O單獨在一個線程來做,同一個通話的用戶在同一個業務邏輯線程做業務操作,同時創建一組業務邏輯線程為所有用戶服務。
我們看一下這個方案具體是怎麼做的?
多線程優化方案
多線程優化方案,主線程監聽在一個埠,並使用前面提到方案在伺服器將用戶的數據包進行識別和區分。
首先我們伺服器端主線程會創建一個UDP的socket,綁定到7000埠,並註冊監聽可讀事件,WebRTC客戶端會給服務端7000埠發數據包。當然在之前SDP和ICE Candidate的協商流程已經做完了,我們從stun的第一個數據包(stun binding request)中就可以拿到這個WebRTC的web address(IP+埠)以及區分會議的RoomID和區分與會者的UserID。
拿到RoomID以後,我們可以把客戶端的web address和RoomID綁定,建立一個映射關係。為什麼要做這個綁定關係?
這是為了後續我們可以把同一個會議的用戶全部投遞到一個業務邏輯處理線程來處理。如果用戶的數據是在不同邏輯處理線程上處理,我們在做數據處理的時候就要做跨線程的處理,跨線程處理就一定要加鎖,這是我們不太願意看到的。
如果同一會議的用戶數據都在一個邏輯線程裡面處理,其實它是在一個單線程裡面的順序處理,沒有並發的問題,也不需要加鎖,所以它的性能會比較高。
有了剛剛我們建立的映射關係,後續處理同一個會議的用戶上行的stun包、dtls、SRTP、SRTCP,我們都根據RoomID做同樣的Hash演算法,Hash到我們後面的業務主線程裡面,這樣每一次主線程把I/O做完後,一次性將主線程收到的各個數據包按照Hash演算法投遞給對應的後端的邏輯處理多線程,這樣同一個會議的SFU邏輯都在同一個線程裡面處理完了。
這個是我們一開始使用的方案,但是大家也能看到,這個方案還有一個缺陷,就是I/O都是在主線程做的,後續的數據都需要通過跨線程的投遞方式讓後續的邏輯處理現場來做,那麼有沒有辦法不要做跨線程的投遞,讓用戶的數據包可以直接在對應的邏輯處理多線程裡面處理。
其實傳統的TCP伺服器是比較容易做到的,它可以在主線程上Accept後為每個客戶端創建一些新的fd,並分配一個子線程處理這個新fd的讀寫。那UDP能不能這麼做?UDP在常規的編程方法是無法做到的,針對這個問題,我們設計了一個進階方案。
多線程優化方案(進階)
我們利用Linux reuseaddr和connect以後的udp fd特性,只有第一個數據包經主線程收包後投遞到邏輯處理線程,其它數據包直接在邏輯線程接收。
我們具體是怎麼做的?
首先主線程還是創建一個fd,綁定7000埠。當然這裡面有一個關鍵點是需要設置套接字的選項——reuseaddr,這個套接字選項在TCP領域用的很多,在UDP領域中大家可能還比較少接觸。同樣在主線監聽可讀事件。主線程開始收包了,同樣的流程,收到到第一個stun包,獲取WebRTC的web address(IP+埠)以及區分會議的RoomID和區分與會者的UserID。使用Hash(RoomID)到一個邏輯處理的子線程,到此為止流程與之前的多線程方案沒有太大區別。
在邏輯處理子線程裡面的方案就有一定的技巧了。它首先會在這個子線程裡面再創建一個fd,這個fd監聽的埠和主線程是一樣的,也是7000埠。因為它設置了reuseaddr,所以說它的綁定可以成功,也就是說這個7000埠可以在多個線程裡面同時監聽。
接下來的流程就比較重要了,我們有了這個fd之後,我們需要做一次connect, connect在UDP領域大家使用得不多,但是它connect之後有什麼效果呢?
大家可以閱讀《UNIX網路編程 卷1第3版》的8.11,簡單概括:UDP fd connect之後,會綁定本端與對端的四元組。當內核選擇fd來收udp包時採用的是最佳匹配的原則。
因此connect過後的fd是四元組匹配最高的,這時內核會直接選擇我們在子線程裡面創建的fd。這個方案有點繞,但是還是比較有意思的,涉及到一些內核的事情,推薦大家感興趣可以深入研究一下。在邏輯處理子線程中,我們還是會把這個新創建的fd註冊到我們的事件循環裡面。此後這個用戶的上下行數據的IO操作將直接在這個邏輯處理線程。
這個方案就是我們優化後的Server的線程方案,它可以最大程度的降低跨線程的調用與加鎖,整體的性能和代碼的可讀性也都會變高。
WebRTC網關伺服器的線程方案就介紹到這裡,大家都知道音視頻應用中的QoS策略是非常重要的部分,《聊聊WebRTC網關伺服器》第四篇文章將具體為大家介紹WebRTC網關服務端的QoS內容。
推薦閱讀:
※怎樣可以延長投影機的壽命?
※京廣華夏簡介
※毛豆故事 | 我們為誰做產品,做什麼樣的產品?
※聊聊WebRTC網關伺服器2:如何選擇PeerConnection方案?
※一文看懂音視頻直播雲服務 | 雷鋒網硬創公開課