參考文章:html
http://chenbowen.baijia.baidu.com/article/472127前端
http://blog.csdn.net/cabbage2008/article/details/50582899html5
大流量、高併發場景下,大型直播的技術挑戰通常體如今以下幾個方面:
視頻流的處理、分發
播放質量保障
視頻可用性監控
超大直播間實時彈幕及聊天互動
高性能消息通道
內容控制,如算法鑑黃、文本過濾
系統可用性、穩定性保障java
本文將針對其中的一些技術細節,抽絲剝繭,但願經過些許文字的分析和介紹,能讓你們有所啓發。mysql
視頻直播
ios
對於直播平臺來講,爲了保障各類網絡環境下可以流暢的觀看視頻,須要將高清的輸入流轉換出多路不一樣清晰度的視頻流,以支持不一樣網絡條件下視頻清晰度的切換,而因爲不一樣的端所支持的協議及封裝格式並不徹底相同,好比無線端的HTML5頁面能夠很好的支持HLS協議,可是對於RTMP這類協議基本無能爲力,而PC端爲了下降延時,須要採用RTMP這一類流媒體協議。
所以,爲了支持多終端(PC、Andriod、iOS、HTML5)觀看,須要對輸入流進行編碼及封裝格式的轉換。轉碼完成以後,還須要對視頻流進行分發,畢竟源站的負載能力有限,節點數有限,離大部分用戶的物理距離遠,對視頻這一類十分佔用帶寬資源的場景來講,爲了提升播放質量減小卡頓,須要儘可能減小到用戶的傳輸鏈路。
所以,一般的作法就是將視頻流進行切片存儲到分佈式文件系統上,分發到CDN,或者是直接經過CDN進行流的二級轉發,由於CDN離用戶最近,這樣才能保證直播內容對於用戶的低延時,以及用戶的最短路徑訪問。客戶端對延時的要求,以及採用何種協議,決定了視頻是否須要分片,分片的目的在於,經過HTTP協議,用戶不須要下載整個視頻,只須要下載幾個分片,就能夠播放,實際上直播與錄播的技術是相通的,區別在於直播的流沒法預測終結時間,而錄播的視頻流終止時間是已知的。
對延時要求沒那麼高的場景來講,客戶端能夠採用HLS協議,畢竟IOS、Andriod、HTML5等無線端應用可以很好的兼容和支持,且經常使用的靜態資源CDN能夠不作相應改造,就能支持,可是HLS協議的一大天生缺陷就是延時較高,視頻內容在切片、分發、客戶端下載的過程當中耗費了很長時間。對於時效性要求很是高的場景,就須要採用RTMP、RTSP一類的實時流媒體協議,來下降延時,而且,爲了下降源站的壓力,須要CDN邊緣節點來作流的轉發,那麼,CDN就必須得支持相應的流媒體協議,也就是一般所說的流媒體CDN。
因爲直播流由主播上傳,如何控制違法違規內容特別是黃色內容,成了十分棘手的問題。使人欣慰的是,隨着技術的發展,算法對於黃色圖像的識別準確率已經很高,基本達到能夠在生產環境應用的程度,所以咱們也嘗試在視頻流分發以前,對視頻幀進行提取,而且將圖像交給算法進行識別,當超過預設的閾值時,可進行預警或者關停直播間。通過一段時間的實踐,取得了必定的效果,下降了人力成本,但不可避免的是圖像識別算法時間複雜度高,吞吐率較通常算法低。
直播整個鏈路是比較長的,包含有接流、轉碼、切片、分發、客戶端下載、播放等衆多環節,鏈路上任何一個節點有問題,均可能致使視頻不能播放。所以,關鍵節點的監控就十分重要,除此以外,還須要對整個鏈路的可用性進行監控,好比,針對HLS協議來講,可經過監控相應的m3u8索引列表有沒有更新,來判斷視頻直播流是否中斷。
固然,如需判斷視頻流中的幀有沒有花屏、有沒有黑屏,就更復雜了,何況,監控節點所訪問的CDN節點與用戶所訪問的CDN節點可能並不在同一地域。當前中國的網絡環境,特別是跨網段的網絡訪問,對於流媒體應用來講,存在較大的不可控因素,客戶端網絡接入環境對視頻的播放有決定性影響。
所以,收集終端用戶的播放數據質量數據進行反饋,及時進行視頻清晰度切換, 顯得特別重要。這些數據包括客戶端的地域分佈、播放卡頓信息、視頻分片加載時間等等,根據這些信息反饋,能夠較爲全面的評估CDN節點部署是否合理,是否須要新增CDN節點,視頻的轉碼參數對於不一樣機型的兼容性等等,及時進行調整以改善用戶體驗。
web
消息/彈幕
ajax
WEB IM應用及彈幕近年來有愈來愈火的趨勢,是營銷與氣氛活躍的一種很是重要的手段。對於同時在線人數龐大的實時聊天互動、實時直播彈幕這一類場景來講,在保障消息實時性的前提條件下,將會面臨很是高的併發壓力。
舉個例子來講,假設一個活躍的直播間有10w人同時在線,正在直播一場熱門的遊戲賽事,假設每秒鐘每一個人說一句話,將會產生10w條消息,也就是10w/s的消息上行QPS,而每條消息又須要廣播給房間裏面的每個人,也就是說消息下行將成10w倍的放大,達到驚人的10w*10w=100億/s的消息下行QPS,而這僅僅是一場直播的QPS,相似的直播可能有多場正在同時進行,對於消息通道來講,無疑將是一個巨大的挑戰。所以,在系統設計的時候,首先要考慮的問題,就是如何下降消息通道的壓力。
redis
用戶將信息投遞到消息系統以後,系統首先對消息進行一系列的過濾,包括反垃圾、敏感關鍵詞、黑名單等等,對於信息的過濾後面會詳細介紹,此處暫且不表。爲了不繫統被瞬間出現的峯值壓垮,可先將消息投遞到消息隊列,削峯填谷,在流量的高峯期積壓消息,給系統留必定裕度,下降因限流丟消息對業務產生的影響。
然後端始終以固定的頻率處理消息,經過異步機制保障峯值時刻系統的穩定,這是一個典型的生產者—消費者模型。對於消息的消費端,則可採用多線程模型以固定的頻率從消息隊列中消費消息,遍歷對應房間所對應的在線人員列表,將消息經過不一樣的消息通道投遞出去。多線程增長了系統的吞吐能力,特別是對須要將消息一次性投遞給幾萬上十萬用戶這樣的場景,能夠異步使用大集羣並行處理,提升系統的吞吐能力。異步使後端的消息投遞可不受前端消息上行峯值流量的干擾,提升系統穩定性。
除了採用消息隊列異步處理以外,當房間人數太多,或者消息下行壓力太大的狀況下,還須要進一步下降消息下行通道的壓力,這就須要採用分桶策略。所謂的分桶策略,實際上就是限制消息的傳播範圍,假設10w人在同一個房間聊天,每人說一句可能瞬間就會排滿整個屏幕,消息在這種狀況下基本沒有可讀性。
爲提升信息的可讀性,同時也下降下行壓力,可將每10000人(具體每一個桶的容量能夠根據實際需求來調整,這裏只舉例)放一個桶,用戶發送的消息,只在一個桶或者部分桶可見,用戶按照桶的維度接收消息,這樣一方面前端用戶接收到的消息量會少不少(跟用戶所處的桶的活躍度相關),而且一條消息也不用發送給全部用戶,只發送給一個或者部分桶,以下降消息下行壓力。
分桶的策略有不少,最簡單粗暴的方式就是根據用戶隨機。首先根據房間的活躍程度,預估房間該分多少個桶,而後將用戶經過hash函數映射到各個桶,隨機策略的好處是實現很是簡單,能夠將用戶較爲均衡的分配到每一個桶,可是會有不少弊端。首先,準確的預估房間的活躍用戶數自己就比較困難,基本靠蒙,這將致使單個桶的用戶數量過大或者偏小,太大就會增長消息鏈路的壓力,而偏小則下降用戶積極性,後期調整分桶數也會很麻煩,須要將所有用戶進行重hash。
另外從用戶體驗的角度來考慮,在直播過程當中,在線用戶數正常狀況下會經歷一個逐漸上升達到峯值而後逐漸降低的過程,因爲分桶的緣故,在上升的過程當中,每一個桶的人是比較少的,這必然會影響到彈幕的活躍度,也可能所以致使用戶流失,而在降低的過程當中,逐漸會有用戶退出直播,又會致使各個桶不均勻的狀況出現:
算法
另外一種方案是按需分桶,固定每一個桶的大小,當現有的桶都滿了以後,再開闢新的分桶,以控制每一個桶的人數,使其不至於太多也不至於太少,這樣就解決了以前可能出現的每一個桶人數過少的問題,可是,有個問題將比以前的隨機分桶更爲明顯,老的桶中不斷有用戶離開,人將逐漸減小,新開闢的桶將愈來愈多,如不進行清理的話,最後的結果仍然是分桶不均衡,而且會產生不少空桶,所以,就須要在算法和數據結構上進行調整:
經過一個排序list,每次將新增用戶添加到人數最少分桶,這樣可讓新用戶加入最空閒的桶,以保持均衡,當桶滿的時候,就再也不添加新的用戶,可是,當老用戶離開的速度大大高於新用戶進來的速度時,桶仍是不均勻的,這時,就須要按期對分桶進行整理,以合併人數少的桶或者回收空桶,而合併的過程當中,新用戶又會不斷的加入進來,而且,還須要保證消息發送時能讀到正確的用戶列表,在分佈式高併發場景下,爲了保證效率,有時候加鎖並非那麼容易,這就有可能出現髒寫與髒讀,桶的整理算法將會很是複雜,有點相似於JVM中的內存回收算法。與大數據量高併發場景下的分庫分表策略相似,實際上分桶策略也是一種取捨權衡與妥協,雖解決了原有下行通道壓力過大的問題,也引入了新問題。首先,分桶改變了本來普通用戶對於消息的可見性,一條消息只對於部分桶的用戶可見,而非全部桶的用戶,這樣不一樣的桶內的用戶看到的消息多是不一樣的,另外一個問題是,以上的分桶策略有可能致使「熱門桶」和「冷門桶」效應出現,便可能將不少「吐槽達人」分配到同一個桶,致使該桶的氛圍十分活躍,而其餘不那麼活躍的用戶分配到一塊兒,以至於出現「冰火兩重天」的局面,從而影響產品體驗。
固然,對於部分特殊的消息,如系統公告內容,或者是部分特殊角色(房間管理員、貴賓、授課的老師等等)所發送的消息,這一類消息須要廣播給全部用戶,這種狀況下就須要系統對消息類型作區分,特殊的消息類型另做處理。
對於消息投遞任務來講,須要知道消息將以什麼方式被投遞給誰,這樣就須要動態地維護一個房間的人員列表,用戶上線/下線及時通知系統,以便將用戶添加到房間人員列表或者從房間人員列表中移除。用戶上線十分簡單,只須要在進入房間的時候通知系統便可,但對於下線用戶的處理則有點折騰,爲何這麼說呢?用戶退出直播間的方式可能有多種,關閉瀏覽器tab、關閉窗口、關閉電源、按下home鍵將進程切換到後臺等等,有的操做可能能夠獲取到事件回調,但也有不少種狀況是沒法獲取事件通知的,這樣就會致使人員列表只增不減,房間的人愈來愈多,消息投遞量也隨之增長,白白的浪費了資源。爲解決這一問題,就需採用心跳。
心跳指的是客戶端每隔一段時間向服務端彙報在線狀態,以維持服務端的在線人員列表,當同時在線人數達到一個很大的量級(如百萬級)的時候,每秒心跳的QPS也會變得很是之高,如何保障心跳的高效率、高吞吐就成了岑待解決的問題。首先是通訊協議的選擇,是HTTP協議,仍是WebSocket,仍是TCP協議或者其餘。
HTTP協議的好處在於兼容性及跨終端,全部瀏覽器、Andriod、IOS的WebView,都能很好支持和兼容,在目前移動重於PC的大環境下,顯得尤其重要,可是HTTP協議劣勢也是顯而易見的,做爲應用層協議,單次通訊所要攜帶的附加信息太多,效率低。WebSocket在移動端的場景下比較合適,可是運用在PC端,需解決衆多老版本瀏覽器的兼容性問題,socket.io的出現則大大簡化了這一本來很是複雜的問題。TCP協議在傳統的客戶端應用上使用較多,可是對於WEB應用來講,存在自然障礙。使用WebSocket和TCP協議的好處顯而易見的,通訊效率會比HTTP協議高不少,而且這兩種協議支持雙工通訊。
另外一個問題是後端存儲的選擇,該使用怎樣的存儲結構來存儲在線人員列表這樣的數據結構,以支撐這麼高的併發讀寫。通過優化而且使用SSD的關係型數據庫相較以往性能已經有了很大的提高,可是對於頻繁變化的大量在線人員列表來講,持久化存儲其實是沒太大意義的。所以,讀寫性能更高的內存存儲,如memcache,Redis,多是一種更現實的選擇。memcache只能支持簡單的KV對象存儲,數據讀寫須要進行頻繁的序列化和反序列化,但吞吐更高,而redis的好處在於豐富的數據類型,如Lists、Hashs、Sets,省去了序列化和反序列化操做,而且支持更高效的分頁遍歷及count操做:
消息通道
HTTP協議請求/響應式特性決定了它擅長處理瀏覽型業務,而對於須要與服務端進行頻繁交互的即時通信場景來講,則會顯得十分蹩腳。在直播進行的過程當中,用戶能夠對主播進行吐槽、評論,用戶與用戶之間也能夠進行頻繁的交流,須要有像彈幕、WEB IM這樣的工具來支持,這種場景下,消息的實時性尤其重要。
要實現這類場景,最簡單最粗暴的方式莫過於不斷地輪詢應用服務器,採用拉的方式讀取彈幕以及用戶的聊天內容,消息的實時程度取決於拉的頻率,拉的過快,可能服務器沒法承受,拉的頻率太低,則消息的實時性跟不上。輪詢的方式太過於粗暴,須要頻繁的請求服務器,成本較高,而且用戶更新信息的頻率實時變化,很難選擇比較合理的輪詢時間,由於不一樣時間段用戶發送消息的頻率是有很大差別的,對於拉取的信息,客戶端須要進行篩選和去重。所以,對於WEB端的即時交互應用,須要採用其餘解決方案,如comit服務端推送,或者經過websocket來實現相似的場景。
comet又被稱做爲反向ajax(Reverse AJAX ),分爲兩種實現方式,一種是長輪詢(long-polling)方式,一種是流(streaming)的形式。在長輪詢的方式下,客戶端與服務端保持HTTP鏈接,服務端會阻塞,直到服務端有信息傳遞或者是HTTP請求超時,客戶端若是收到響應,則會從新創建鏈接,另外一種方式是流的形式,服務器推送數據給客戶端,可是鏈接並不關閉,而是始終保持,直到鏈接超時,超時後客戶端從新創建鏈接,並關閉以前的鏈接。經過這兩種方式,即可借用HTTP協議,來實現服務端與客戶端的即時通信。(注:comet, https://software.intel.com/zh-cn/articles/comet-java-realtime-system-essay)
而WebSocket是IETF所提出的一種新的協議,旨在實現瀏覽器與服務器之間的全雙工(full-duplex)通訊,而傳統的HTTP協議僅可以實現單向的通訊,即客戶端發起的到服務端的請求,雖然comet在必定程度上能夠模擬雙向通訊,可是通訊的效率較低,且依賴特定的服務器實現。(注:WebSocket, https://tools.ietf.org/html/rfc6455)
爲什麼說comet的通訊效率會低於WebSocket呢,由於無論是comet的長輪詢機制仍是流機制,都須要在請求超時後發送請求到服務端,而且,長輪詢在服務端響應消息以後,須要從新創建鏈接,這樣又帶來了額外的開銷。
咱們知道HTTP協議的Request Header中附帶了不少信息,可是這中間包含的不少信息有些場景其實並非必須的,這樣就浪費了大量的帶寬及網絡傳輸時間。而WebSocket協議使得瀏覽器與服務器只須要一次握手動做,便造成了穩定的通訊通道,二者之間就能夠經過frame進行數據傳遞,而消息傳遞所發送的Header是也是很小的,這樣就會帶來效率的提高。
經過wireshark抓包能夠作一個簡單的測試對比, 假設服務端每秒推送50條消息給用戶,每條消息的長度爲10byte,對應不一樣的用戶規模,採用websocket和comet兩種不一樣的通訊機制,所須要傳遞的字節數如圖8所示,可見,隨着用戶規模及消息量的提高,採用websocket協議進行通訊,將對通訊效率帶來數量級的提高,詳細的測試過程可參見筆者博客。引入WebSocket協議,雖然原則上解決了瀏覽器與服務端實時通訊的效率問題,相較comet這種基於HTTP協議的實現方式,能得到更好的性能,但同時也引入了一些新的問題。擺在首位的即是瀏覽器的兼容性問題,開發WEB應用程序的一個最頭痛的問題就是多版本瀏覽器的兼容,不一樣瀏覽器產商對於協議的實現有各自的理解,而且,市面上還充斥着大量低版本不支持HTML5協議的瀏覽器,且這部分用戶在中國還佔有較大基數,所以在產品研發的過程當中不得不予以考慮。得益於socket.io的開源,經過websocket、Adobe Flash Socket、long-polling、streaming、輪詢等多種機制的自適應切換,幫咱們解決了絕大部分瀏覽器(包括各類內核的webview)的兼容性問題,統一了客戶端以及服務端的編程方式。對於不一樣平臺的消息推送,咱們內部也衍生了一些成熟的技術平臺,封裝了包括消息推送,消息訂閱,序列化與反序列化,鏈接管理,集羣擴展等工做,限於篇幅,這裏就很少說了。