janus優(yōu)化系列之(一)

Janus Gateway是一個webrtc的server,一種插件式架構,基于這個架構,開發(fā)了一些列的插件,比如streaming、SIP、videoroom、audiobridge等,甚至支持lua、javascrip等腳本性語言去處理會話和音視頻內容等等,毫無疑問是個集大成者。
那我們原生的Janus適合生產環(huán)境部署不?能支持多少并發(fā)量?
我們目前的業(yè)務主要用到了videoroom、audiobridge和lua三個插件,在8核16G并且開啟loop=8(ice loop的feature,及RTP/RTCP/DataChannel的收發(fā)開啟了線程池的模式)的環(huán)境配置下,我們測試的情況如下:

  1. audiobridge支持的并發(fā)是300路64K碼率的音頻,這個跟他們的專業(yè)測評是一樣的,請參考https://core.ac.uk/download/pdf/74316352.pdf ,且延遲在300路時大道了500毫秒左右;
  2. 測試videoroom的時候,我們設置的腳本是每隔60毫秒加入一路視頻,到1000路之前,每次都會core dump,無法繼續(xù)測試;

以上是性能壓測的一個結果,實際上我們在繼續(xù)研究它的設計時,也有一些發(fā)現(xiàn),好的方面就不詳細描述,大致描述下不足,或者個人認為的不足:

  1. 函數(shù)代碼過長,甚至有多個函數(shù)代碼超過2000行,這讓二次開發(fā)和維護的困難較大,第一次開發(fā)個靜音通知的業(yè)務需求時,足足看了3天的代碼,最后還存在一個BUG,就是重復通知導致APP重復顯示聯(lián)系人,可以這么說在國內的很多公司都不太可能出現(xiàn)這樣的狀況;
  2. 插件間存在大量冗余的代碼,甚至有些業(yè)務的實現(xiàn)還不一致,比如token的業(yè)務,audiobridge和textroom、videoroom的實現(xiàn)不一致,且三個插件里重復代碼;
  3. libnice和janus都是基于glib開發(fā)的,glib的對象對內存是有緩存的,這就給內存方面的bug定位帶來了困難,比如內存泄露;
  4. libnice和janus的事件循環(huán)是基于glib的GMainContext和GMainLoop的,在它們之上實現(xiàn)事件源的接口就可以收發(fā)事件了:
struct GSourceFuncs {
  gboolean (*prepare)  (GSource    *source,
                        gint       *timeout_);
  gboolean (*check)    (GSource    *source);
  gboolean (*dispatch) (GSource    *source,
                        GSourceFunc callback,
                        gpointer    user_data);
  void     (*finalize) (GSource    *source); /* Can be NULL */
}

這樣的一個實現(xiàn)意味著你如果想要對事件源進行操作就必須挨個對每個事件調用prepare函數(shù),這顯然是低效的,而且該事件循環(huán)基于poll實現(xiàn),poll的性能明顯不如epoll。

  1. 并發(fā)模型采用線程的模式,線程間使用mutext同步原語的GAsyncQueue;janus core使用線程池,ice loop使用線程池,每個插件創(chuàng)建handle線程去處理接口,audiobridge的每個房間創(chuàng)建一個mixer線程去混音,并且每個participant都創(chuàng)建一個線程去opus編碼混音后的數(shù)據(jù),更不可接受的是mixer線程調用sleep去間隔性取音頻數(shù)據(jù)解碼并混音;所有這些線程關系導致幾乎每個對象每條消息處理都需要使用mutext或者原子操作去同步,這并不是一個好的并發(fā)模型。

  2. 在處理SDP的offer和answer時使用sleep這個野蠻的方式等待收集地址完成后的callback,并發(fā)量不大的情況下還可接受,如果并發(fā)量較大并發(fā)性接入服務器,會出現(xiàn)部分客戶端接入時因ICE過程而失敗,也較容易出現(xiàn)黑屏或者聲音斷斷續(xù)續(xù)的問題;

/* Are we still cleaning up from a previous media session? */
    if(janus_flags_is_set(&ice_handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_CLEANING)) {
      JANUS_LOG(LOG_VERB, "[%"SCNu64"] Still cleaning up from a previous media session, let's wait a bit...\n", ice_handle->handle_id);
      gint64 waited = 0;
      while(janus_flags_is_set(&ice_handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_CLEANING)) {
        JANUS_LOG(LOG_VERB, "[%"SCNu64"] Still cleaning up from a previous media session, let's wait a bit...\n", ice_handle->handle_id);
        g_usleep(100000);
        waited += 100000;
        if(waited >= 3*G_USEC_PER_SEC) {
          JANUS_LOG(LOG_VERB, "[%"SCNu64"]   -- Waited 3 seconds, that's enough!\n", ice_handle->handle_id);
          JANUS_LOG(LOG_ERR, "[%"SCNu64"] Still cleaning a previous session\n", ice_handle->handle_id);
          janus_sdp_destroy(parsed_sdp);
          return NULL;
        }
      }
    }

而且這段代碼很不好重構。

  1. videoroom在1.0版本前依然不支持一個peerconnection去拉取多個流,導致每有一個publish stream的用戶加入,現(xiàn)有用戶都要去再次創(chuàng)建peerconnection,然后subscribe這路stream,那么在多人房間的時候,這個設計給網絡、客戶端性能和服務端性能都帶來了極大的挑戰(zhàn),所以體驗也會很差。

以上種種問題導致Janus的author Lorenzo Miniero也會感嘆“I’m getting older, but unlike whisky, I'm not getting any better author of the Janus WebRTC Server,and the only viking with no muscles”,當然他很謙卑,也在自嘲,但是的確我們在測試性能的時候,以及后續(xù)的優(yōu)化都碰到了很多的困難,甚至有時候很莫名其妙。

那么針對以上問題我們又是如何優(yōu)化的呢?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容