百萬在線的美拍直播彈幕系統架構實現

直播彈幕指直播間的用戶,禮物,評論,點贊等消息,是直播間交互的重要手段。美拍直播彈幕系統從 2015 年 11 月到如今,通過了三個階段的演進,目前能支撐百萬用戶同時在線。比較好地詮釋了根據項目的發展階段,進行平衡演進的過程。這三個階段分別是快速上線,高可用保障體系建設,長鏈接演進。前端

1、快速上線mysql

消息模型redis

美拍直播彈幕系統在設計初期的核心要求是:快速上線,並能支撐百萬用戶同時在線。基於這兩點,咱們策略是前中期 HTTP 輪詢方案,中後期替換爲長鏈接方案。所以在業務團隊進行 HTTP 方案研發的同時,基礎研發團隊也緊鑼密鼓地開發長鏈接系統。sql

直播間消息,相對於 IM 的場景,有其幾個特色緩存

  • 消息要求及時,過期的消息對於用戶來講不重要;數據結構

  • 鬆散的羣聊,用戶隨時進羣,隨時退羣;架構

  • 用戶進羣后,離線期間(接聽電話)的消息不須要重發;併發

對於用戶來講,在直播間有三個典型的操做:框架

  • 進入直播間,拉取正在觀看直播的用戶列表性能

  • 接收直播間持續接收彈幕消息

  • 本身發消息

咱們把禮物,評論,用戶的數據都當作消息來看待。通過考慮選擇了 Redis 的 sortedset 存儲消息,消息模型以下:

  • 用戶發消息,經過 Zadd,其中 score 消息的相對時間;

  • 接收直播間的消息,經過 ZrangeByScore 操做,兩秒一次輪詢;

  • 進入直播間,獲取用戶的列表,經過 Zrange 操做來完成;

所以總的流程是

  • 寫消息流程是: 前端機 -> Kafka -> 處理機 -> Redis

  • 讀消息流程是: 前端 -> Redis

不過這裏有一個隱藏的併發問題:用戶可能丟消息。

640.png

如上圖所示,某個用戶從第6號評論開始拉取,同時有兩個用戶在發表評論,分別是10,11號評論。若是11號評論先寫入,用戶恰好把6,7,8,9,11號拉走,用戶下次再拉取消息,就從12號開始拉取,結果是:用戶沒有看到10號消息。

爲了解決這個問題,咱們加上了兩個機制:

  • 在前端機,同一個直播間的同一種消息類型,寫入 Kafka 的同一個 partition

  • 在處理機,同一個直播間的同一種消息類型,經過 synchronized 保證寫入 Redis 的串行。

消息模型及併發問題解決後,開發就比較順暢,系統很快就上線,達到預先預約目標。

上線後暴露問題的解決

上線後,隨着量的逐漸增長,系統陸續暴露出三個比較嚴重的問題,咱們一一進行解決

問題一:消息串行寫入 Redis,若是某個直播間消息量很大,那麼消息會堆積在 Kafka 中,消息延遲較大。

解決辦法:

  • 消息寫入流程:前端機-> Kafka -> 處理機 -> Redis

  • 前端機:若是延遲小,則只寫入一個 Kafka 的partion;若是延遲大,則這個直播的這種消息類型寫入 Kafka 的多個partion。

  • 處理機:若是延遲小,加鎖串行寫入 Redis;若是延遲大,則取消鎖。所以有四種組合,四個檔位,分別是

    • 一個partion, 加鎖串行寫入 Redis, 最大併發度:1

    • 多個partition,加鎖串行寫入 Redis, 最大併發度:Kafka partion的個數

    • 一個partion, 不加鎖並行寫入 Redis, 最大併發度: 處理機的線程池個數

    • 多個partion, 不加鎖並行寫入 Redis,最大併發度: Kafka partition個數處理機線程池的個數

  • 延遲程度判斷:前端機寫入消息時,打上消息的統一時間戳,處理機拿到後,延遲時間 = 如今時間 - 時間戳;

  • 檔位選擇:自動選擇檔位,粒度:某個直播間的某個消息類型

問題二:用戶輪詢最新消息,須要進行 Redis 的 ZrangByScore 操做,redis slave 的性能瓶頸較大

解決辦法:

  • 本地緩存,前端機每隔1秒左右取拉取一次直播間的消息,用戶到前端機輪詢數據時,從本地緩存讀取數據;

  • 消息的返回條數根據直播間的大小自動調整,小直播間返回容許時間跨度大一些的消息,大直播間則對時間跨度以及消息條數作更嚴格的限制。

解釋:這裏本地緩存與日常使用的本地緩存問題,有一個最大區別:成本問題。

若是全部直播間的消息都進行緩存,假設同時有1000個直播間,每一個直播間5種消息類型,本地緩存每隔1秒拉取一次數據,40臺前端機,那麼對 Redis 的訪問QPS是 1000 * 5 * 40 = 20萬。成本過高,所以咱們只有大直播間才自動開啓本地緩存,小直播間不開啓。

問題三:彈幕數據也支持回放,直播結束後,這些數據存放於 Redis 中,在回放時,會與直播的數據競爭 Redis 的 cpu 資源。

解決辦法:

  • 直播結束後,數據備份到 mysql;

  • 增長一組回放的 Redis;

  • 前端機增長回放的 local cache;

解釋:回放時,讀取數據順序是: local cache -> Redis -> mysql。localcache 與回放 Redis 均可以只存某個直播某種消息類型的部分數據,有效控制容量;local cache與回放 Redis 使用SortedSet數據結構,這樣整個系統的數據結構都保持一致。

2、高可用保障

同城雙機房部署

分爲主機房和從機房,寫入都在主機房,讀取則由兩個機房分擔。從而有效保證單機房故障時,能快速恢復。

豐富的降級手段

6402.jpg

全鏈路的業務監控

6403.jpg

高可用保障建設完成後,迎來了 TFBOYS 在美拍的四場直播,這四場直播峯值同時在線人數達到近百萬,共 2860萬人次觀看,2980萬評論,26.23億次點贊,直播期間,系統穩定運行,成功抗住壓力。

使用長鏈接替換短鏈接輪詢方案

長鏈接總體架構圖以下

6404.jpg


詳細說明:

  • 客戶端在使用長鏈接前,會調用路由服務,獲取鏈接層IP,路由層特性:a. 能夠按照百分比灰度;b. 能夠對 uid,deviceId,版本進行黑白名單設置。黑名單:不容許使用長鏈接;白名單:即便長鏈接關閉或者不在灰度範圍內,也容許使用長鏈接。這兩個特性保證了咱們長短鏈接切換的順利進行;

  • 客戶端的特性:a. 同時支持長鏈接和短鏈接,可根據路由服務的配置來決定;b. 自動降級,若是長鏈接同時三次鏈接不上,自動降級爲短鏈接;c. 自動上報長鏈接性能數據;

  • 鏈接層只負責與客戶端保持長鏈接,沒有任何推送的業務邏輯。從而大大減小重啓的次數,從而保持用戶鏈接的穩定;

  • 推送層存儲用戶與直播間的訂閱關係,負責具體推送。整個鏈接層與推送層與直播間業務無關,不須要感知到業務的變化;

  • 長鏈接業務模塊用於用戶進入直播間的驗證工做;

  • 服務端之間的通信使用基礎研發團隊研發的tardis框架來進行服務的調用,該框架基於 gRPC,使用 etcd 作服務發現;

長鏈接消息模型

咱們採用了訂閱推送模型,下圖爲基本的介紹

6405.jpg



舉例說明:用戶1訂閱了A直播,A直播有新的消息

  • 推送層查詢訂閱關係後,知道有用戶1訂閱了A直播,同時知道用戶1在鏈接層1這個節點上,那麼就會告知鏈接層有新的消息

  • 鏈接層1收到告知消息後,會等待一小段時間(毫秒級),再拉取一次用戶1的消息,而後推送給用戶1.

若是是大直播間(訂閱用戶多),那麼推送層與鏈接層的告知/拉取模型,就會自動降級爲廣播模型。以下圖所示


6406.jpg


咱們經歷客戶端三個版本的迭代,實現了兩端(Android 與 iOS)長鏈接對短鏈接的替換,由於有灰度和黑白名單的支持,替換很是平穩,用戶無感知。


總結與展望

回顧了系統的發展過程,達到了原定的前中期使用輪詢,中後期使用長鏈接的預約目標,實踐了原定的平衡演進的原則。從發展來看,將來計劃要作的事情有

  • 針對某些地區會存在鏈接時間長的狀況。咱們如何讓長鏈接更靠近用戶。

  • 消息模型的進一步演進。

相關文章
相關標籤/搜索