支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

直播彈幕是直播系統的核心功能之一。如何迅速作出一個有很好擴展性的彈幕系統?如何應對業務迅速發展?相信很多工程師/架構師都有自己的想法。

本文作者美拍架構師王靜波經歷了直播彈幕系統從無到有,從小到大的過程並對構建彈幕系統的經驗進行了總結。

直播彈幕是直播系統的核心功能之一。如何迅速作出一個有很好擴展性的彈幕系統?如何應對業務迅速發展?相信很多工程師/架構師都有自己的想法。

本文作者美拍架構師王靜波經歷了直播彈幕系統從無到有,從小到大的過程並對構建彈幕系統的經驗進行了總結。

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

直播彈幕指直播間的用戶,禮物,評論,點贊等消息,是直播間交互的重要手段。

美拍直播彈幕系統從 2015 年 11 月到現在,經過了三個階段的演進,目前能支撐百萬用戶同時在線。

本文比較好地詮釋了根據項目的發展階段,直播彈幕系統進行平衡演進的過程。這三個階段分別是快速上線,高可用保障體系建設,長連接演進。

快速上線

消息模型

美拍直播彈幕系統在設計初期的核心要求是:快速上線,並能支撐百萬用戶同時在線。

基於這兩點,我們的策略是前中期使用 HTTP 輪詢方案,中後期替換為長連接方案。

因此在業務團隊進行 HTTP 方案研發的同時,基礎研發團隊也緊鑼密鼓地開發長連接系統。

直播間消息,相對於 IM 的場景,有如下幾個特點:

  • 消息要求及時,過時的消息對於用戶來說不重要。

  • 鬆散的群聊,用戶隨時進群,隨時退群。

  • 用戶進群后,離線期間(接聽電話)的消息不需要重發。

對於用戶來說,在直播間有三個典型的操作:

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

  • 接收直播間持續發佈的彈幕消息。

  • 自己發消息。

我們把禮物,評論,用戶的數據都當做消息來看待。

經過考慮選擇了 Redis 的 sortedset 存儲消息,消息模型如下:

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

  • 接收直播間的消息,通過 ZrangeByScore 操作,兩秒一次輪詢。

  • 進入直播間,獲取用戶的列表,通過 Zrange 操作來完成。

因此總的流程是:

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

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

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

直播彈幕是直播系統的核心功能之一。如何迅速作出一個有很好擴展性的彈幕系統?如何應對業務迅速發展?相信很多工程師/架構師都有自己的想法。

本文作者美拍架構師王靜波經歷了直播彈幕系統從無到有,從小到大的過程並對構建彈幕系統的經驗進行了總結。

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

直播彈幕指直播間的用戶,禮物,評論,點贊等消息,是直播間交互的重要手段。

美拍直播彈幕系統從 2015 年 11 月到現在,經過了三個階段的演進,目前能支撐百萬用戶同時在線。

本文比較好地詮釋了根據項目的發展階段,直播彈幕系統進行平衡演進的過程。這三個階段分別是快速上線,高可用保障體系建設,長連接演進。

快速上線

消息模型

美拍直播彈幕系統在設計初期的核心要求是:快速上線,並能支撐百萬用戶同時在線。

基於這兩點,我們的策略是前中期使用 HTTP 輪詢方案,中後期替換為長連接方案。

因此在業務團隊進行 HTTP 方案研發的同時,基礎研發團隊也緊鑼密鼓地開發長連接系統。

直播間消息,相對於 IM 的場景,有如下幾個特點:

  • 消息要求及時,過時的消息對於用戶來說不重要。

  • 鬆散的群聊,用戶隨時進群,隨時退群。

  • 用戶進群后,離線期間(接聽電話)的消息不需要重發。

對於用戶來說,在直播間有三個典型的操作:

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

  • 接收直播間持續發佈的彈幕消息。

  • 自己發消息。

我們把禮物,評論,用戶的數據都當做消息來看待。

經過考慮選擇了 Redis 的 sortedset 存儲消息,消息模型如下:

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

  • 接收直播間的消息,通過 ZrangeByScore 操作,兩秒一次輪詢。

  • 進入直播間,獲取用戶的列表,通過 Zrange 操作來完成。

因此總的流程是:

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

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

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

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

如上圖所示,某個用戶從第 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 數據結構,這樣整個系統的數據結構都保持一致。

高可用保障

同城雙機房部署

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

豐富的降級手段

直播彈幕是直播系統的核心功能之一。如何迅速作出一個有很好擴展性的彈幕系統?如何應對業務迅速發展?相信很多工程師/架構師都有自己的想法。

本文作者美拍架構師王靜波經歷了直播彈幕系統從無到有,從小到大的過程並對構建彈幕系統的經驗進行了總結。

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

直播彈幕指直播間的用戶,禮物,評論,點贊等消息,是直播間交互的重要手段。

美拍直播彈幕系統從 2015 年 11 月到現在,經過了三個階段的演進,目前能支撐百萬用戶同時在線。

本文比較好地詮釋了根據項目的發展階段,直播彈幕系統進行平衡演進的過程。這三個階段分別是快速上線,高可用保障體系建設,長連接演進。

快速上線

消息模型

美拍直播彈幕系統在設計初期的核心要求是:快速上線,並能支撐百萬用戶同時在線。

基於這兩點,我們的策略是前中期使用 HTTP 輪詢方案,中後期替換為長連接方案。

因此在業務團隊進行 HTTP 方案研發的同時,基礎研發團隊也緊鑼密鼓地開發長連接系統。

直播間消息,相對於 IM 的場景,有如下幾個特點:

  • 消息要求及時,過時的消息對於用戶來說不重要。

  • 鬆散的群聊,用戶隨時進群,隨時退群。

  • 用戶進群后,離線期間(接聽電話)的消息不需要重發。

對於用戶來說,在直播間有三個典型的操作:

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

  • 接收直播間持續發佈的彈幕消息。

  • 自己發消息。

我們把禮物,評論,用戶的數據都當做消息來看待。

經過考慮選擇了 Redis 的 sortedset 存儲消息,消息模型如下:

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

  • 接收直播間的消息,通過 ZrangeByScore 操作,兩秒一次輪詢。

  • 進入直播間,獲取用戶的列表,通過 Zrange 操作來完成。

因此總的流程是:

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

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

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

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

如上圖所示,某個用戶從第 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 數據結構,這樣整個系統的數據結構都保持一致。

高可用保障

同城雙機房部署

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

豐富的降級手段

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

全鏈路的業務監控

直播彈幕是直播系統的核心功能之一。如何迅速作出一個有很好擴展性的彈幕系統?如何應對業務迅速發展?相信很多工程師/架構師都有自己的想法。

本文作者美拍架構師王靜波經歷了直播彈幕系統從無到有,從小到大的過程並對構建彈幕系統的經驗進行了總結。

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

直播彈幕指直播間的用戶,禮物,評論,點贊等消息,是直播間交互的重要手段。

美拍直播彈幕系統從 2015 年 11 月到現在,經過了三個階段的演進,目前能支撐百萬用戶同時在線。

本文比較好地詮釋了根據項目的發展階段,直播彈幕系統進行平衡演進的過程。這三個階段分別是快速上線,高可用保障體系建設,長連接演進。

快速上線

消息模型

美拍直播彈幕系統在設計初期的核心要求是:快速上線,並能支撐百萬用戶同時在線。

基於這兩點,我們的策略是前中期使用 HTTP 輪詢方案,中後期替換為長連接方案。

因此在業務團隊進行 HTTP 方案研發的同時,基礎研發團隊也緊鑼密鼓地開發長連接系統。

直播間消息,相對於 IM 的場景,有如下幾個特點:

  • 消息要求及時,過時的消息對於用戶來說不重要。

  • 鬆散的群聊,用戶隨時進群,隨時退群。

  • 用戶進群后,離線期間(接聽電話)的消息不需要重發。

對於用戶來說,在直播間有三個典型的操作:

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

  • 接收直播間持續發佈的彈幕消息。

  • 自己發消息。

我們把禮物,評論,用戶的數據都當做消息來看待。

經過考慮選擇了 Redis 的 sortedset 存儲消息,消息模型如下:

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

  • 接收直播間的消息,通過 ZrangeByScore 操作,兩秒一次輪詢。

  • 進入直播間,獲取用戶的列表,通過 Zrange 操作來完成。

因此總的流程是:

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

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

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

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

如上圖所示,某個用戶從第 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 數據結構,這樣整個系統的數據結構都保持一致。

高可用保障

同城雙機房部署

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

豐富的降級手段

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

全鏈路的業務監控

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

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

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

長連接整體架構圖

直播彈幕是直播系統的核心功能之一。如何迅速作出一個有很好擴展性的彈幕系統?如何應對業務迅速發展?相信很多工程師/架構師都有自己的想法。

本文作者美拍架構師王靜波經歷了直播彈幕系統從無到有,從小到大的過程並對構建彈幕系統的經驗進行了總結。

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

直播彈幕指直播間的用戶,禮物,評論,點贊等消息,是直播間交互的重要手段。

美拍直播彈幕系統從 2015 年 11 月到現在,經過了三個階段的演進,目前能支撐百萬用戶同時在線。

本文比較好地詮釋了根據項目的發展階段,直播彈幕系統進行平衡演進的過程。這三個階段分別是快速上線,高可用保障體系建設,長連接演進。

快速上線

消息模型

美拍直播彈幕系統在設計初期的核心要求是:快速上線,並能支撐百萬用戶同時在線。

基於這兩點,我們的策略是前中期使用 HTTP 輪詢方案,中後期替換為長連接方案。

因此在業務團隊進行 HTTP 方案研發的同時,基礎研發團隊也緊鑼密鼓地開發長連接系統。

直播間消息,相對於 IM 的場景,有如下幾個特點:

  • 消息要求及時,過時的消息對於用戶來說不重要。

  • 鬆散的群聊,用戶隨時進群,隨時退群。

  • 用戶進群后,離線期間(接聽電話)的消息不需要重發。

對於用戶來說,在直播間有三個典型的操作:

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

  • 接收直播間持續發佈的彈幕消息。

  • 自己發消息。

我們把禮物,評論,用戶的數據都當做消息來看待。

經過考慮選擇了 Redis 的 sortedset 存儲消息,消息模型如下:

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

  • 接收直播間的消息,通過 ZrangeByScore 操作,兩秒一次輪詢。

  • 進入直播間,獲取用戶的列表,通過 Zrange 操作來完成。

因此總的流程是:

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

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

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

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

如上圖所示,某個用戶從第 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 數據結構,這樣整個系統的數據結構都保持一致。

高可用保障

同城雙機房部署

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

豐富的降級手段

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

全鏈路的業務監控

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

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

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

長連接整體架構圖

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

詳細說明:

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

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

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

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

  • 長連接業務模塊用於用戶進入直播間的驗證工作。

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

長連接消息模型

我們採用了訂閱推送模型,下圖為基本的介紹:

直播彈幕是直播系統的核心功能之一。如何迅速作出一個有很好擴展性的彈幕系統?如何應對業務迅速發展?相信很多工程師/架構師都有自己的想法。

本文作者美拍架構師王靜波經歷了直播彈幕系統從無到有,從小到大的過程並對構建彈幕系統的經驗進行了總結。

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

直播彈幕指直播間的用戶,禮物,評論,點贊等消息,是直播間交互的重要手段。

美拍直播彈幕系統從 2015 年 11 月到現在,經過了三個階段的演進,目前能支撐百萬用戶同時在線。

本文比較好地詮釋了根據項目的發展階段,直播彈幕系統進行平衡演進的過程。這三個階段分別是快速上線,高可用保障體系建設,長連接演進。

快速上線

消息模型

美拍直播彈幕系統在設計初期的核心要求是:快速上線,並能支撐百萬用戶同時在線。

基於這兩點,我們的策略是前中期使用 HTTP 輪詢方案,中後期替換為長連接方案。

因此在業務團隊進行 HTTP 方案研發的同時,基礎研發團隊也緊鑼密鼓地開發長連接系統。

直播間消息,相對於 IM 的場景,有如下幾個特點:

  • 消息要求及時,過時的消息對於用戶來說不重要。

  • 鬆散的群聊,用戶隨時進群,隨時退群。

  • 用戶進群后,離線期間(接聽電話)的消息不需要重發。

對於用戶來說,在直播間有三個典型的操作:

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

  • 接收直播間持續發佈的彈幕消息。

  • 自己發消息。

我們把禮物,評論,用戶的數據都當做消息來看待。

經過考慮選擇了 Redis 的 sortedset 存儲消息,消息模型如下:

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

  • 接收直播間的消息,通過 ZrangeByScore 操作,兩秒一次輪詢。

  • 進入直播間,獲取用戶的列表,通過 Zrange 操作來完成。

因此總的流程是:

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

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

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

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

如上圖所示,某個用戶從第 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 數據結構,這樣整個系統的數據結構都保持一致。

高可用保障

同城雙機房部署

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

豐富的降級手段

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

全鏈路的業務監控

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

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

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

長連接整體架構圖

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

詳細說明:

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

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

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

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

  • 長連接業務模塊用於用戶進入直播間的驗證工作。

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

長連接消息模型

我們採用了訂閱推送模型,下圖為基本的介紹:

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

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

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

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

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

直播彈幕是直播系統的核心功能之一。如何迅速作出一個有很好擴展性的彈幕系統?如何應對業務迅速發展?相信很多工程師/架構師都有自己的想法。

本文作者美拍架構師王靜波經歷了直播彈幕系統從無到有,從小到大的過程並對構建彈幕系統的經驗進行了總結。

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

直播彈幕指直播間的用戶,禮物,評論,點贊等消息,是直播間交互的重要手段。

美拍直播彈幕系統從 2015 年 11 月到現在,經過了三個階段的演進,目前能支撐百萬用戶同時在線。

本文比較好地詮釋了根據項目的發展階段,直播彈幕系統進行平衡演進的過程。這三個階段分別是快速上線,高可用保障體系建設,長連接演進。

快速上線

消息模型

美拍直播彈幕系統在設計初期的核心要求是:快速上線,並能支撐百萬用戶同時在線。

基於這兩點,我們的策略是前中期使用 HTTP 輪詢方案,中後期替換為長連接方案。

因此在業務團隊進行 HTTP 方案研發的同時,基礎研發團隊也緊鑼密鼓地開發長連接系統。

直播間消息,相對於 IM 的場景,有如下幾個特點:

  • 消息要求及時,過時的消息對於用戶來說不重要。

  • 鬆散的群聊,用戶隨時進群,隨時退群。

  • 用戶進群后,離線期間(接聽電話)的消息不需要重發。

對於用戶來說,在直播間有三個典型的操作:

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

  • 接收直播間持續發佈的彈幕消息。

  • 自己發消息。

我們把禮物,評論,用戶的數據都當做消息來看待。

經過考慮選擇了 Redis 的 sortedset 存儲消息,消息模型如下:

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

  • 接收直播間的消息,通過 ZrangeByScore 操作,兩秒一次輪詢。

  • 進入直播間,獲取用戶的列表,通過 Zrange 操作來完成。

因此總的流程是:

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

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

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

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

如上圖所示,某個用戶從第 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 數據結構,這樣整個系統的數據結構都保持一致。

高可用保障

同城雙機房部署

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

豐富的降級手段

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

全鏈路的業務監控

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

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

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

長連接整體架構圖

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

詳細說明:

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

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

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

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

  • 長連接業務模塊用於用戶進入直播間的驗證工作。

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

長連接消息模型

我們採用了訂閱推送模型,下圖為基本的介紹:

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

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

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

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

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

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

我們經歷了客戶端三個版本的迭代,實現了兩端(Android 與 iOS)長連接對短連接的替換,因為有灰度和黑白名單的支持,替換非常平穩,用戶無感知。

總結與展望

回顧了系統的發展過程,達到了原定的前中期使用輪詢,中後期使用長連接的預定目標,實踐了原定的平衡演進的原則。

從發展來看,未來計劃要做的事情有:

  • 針對機房在北京,南方某些地區會存在連接時間長的情況。我們如何讓長連接更靠近用戶。

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

直播彈幕是直播系統的核心功能之一。如何迅速作出一個有很好擴展性的彈幕系統?如何應對業務迅速發展?相信很多工程師/架構師都有自己的想法。

本文作者美拍架構師王靜波經歷了直播彈幕系統從無到有,從小到大的過程並對構建彈幕系統的經驗進行了總結。

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

直播彈幕指直播間的用戶,禮物,評論,點贊等消息,是直播間交互的重要手段。

美拍直播彈幕系統從 2015 年 11 月到現在,經過了三個階段的演進,目前能支撐百萬用戶同時在線。

本文比較好地詮釋了根據項目的發展階段,直播彈幕系統進行平衡演進的過程。這三個階段分別是快速上線,高可用保障體系建設,長連接演進。

快速上線

消息模型

美拍直播彈幕系統在設計初期的核心要求是:快速上線,並能支撐百萬用戶同時在線。

基於這兩點,我們的策略是前中期使用 HTTP 輪詢方案,中後期替換為長連接方案。

因此在業務團隊進行 HTTP 方案研發的同時,基礎研發團隊也緊鑼密鼓地開發長連接系統。

直播間消息,相對於 IM 的場景,有如下幾個特點:

  • 消息要求及時,過時的消息對於用戶來說不重要。

  • 鬆散的群聊,用戶隨時進群,隨時退群。

  • 用戶進群后,離線期間(接聽電話)的消息不需要重發。

對於用戶來說,在直播間有三個典型的操作:

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

  • 接收直播間持續發佈的彈幕消息。

  • 自己發消息。

我們把禮物,評論,用戶的數據都當做消息來看待。

經過考慮選擇了 Redis 的 sortedset 存儲消息,消息模型如下:

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

  • 接收直播間的消息,通過 ZrangeByScore 操作,兩秒一次輪詢。

  • 進入直播間,獲取用戶的列表,通過 Zrange 操作來完成。

因此總的流程是:

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

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

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

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

如上圖所示,某個用戶從第 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 數據結構,這樣整個系統的數據結構都保持一致。

高可用保障

同城雙機房部署

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

豐富的降級手段

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

全鏈路的業務監控

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

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

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

長連接整體架構圖

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

詳細說明:

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

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

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

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

  • 長連接業務模塊用於用戶進入直播間的驗證工作。

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

長連接消息模型

我們採用了訂閱推送模型,下圖為基本的介紹:

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

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

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

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

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

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

我們經歷了客戶端三個版本的迭代,實現了兩端(Android 與 iOS)長連接對短連接的替換,因為有灰度和黑白名單的支持,替換非常平穩,用戶無感知。

總結與展望

回顧了系統的發展過程,達到了原定的前中期使用輪詢,中後期使用長連接的預定目標,實踐了原定的平衡演進的原則。

從發展來看,未來計劃要做的事情有:

  • 針對機房在北京,南方某些地區會存在連接時間長的情況。我們如何讓長連接更靠近用戶。

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

支撐百萬用戶同時在線的高併發直播彈幕系統是如何煉成的?

王靜波,畢業於西安交通大學,曾任職於網易和新浪微博,微博工作期間負責開放平臺業務和技術體系建設。2015 年 9 月加入美圖,就職於架構平臺部,目前負責部分核心業務和基礎設施的研發工作,包括彈幕服務、Feed 服務、任務調度和質量監控體系等。十餘年的後端研發經歷,擁有豐富的後端研發經驗,對於構建高可用、高併發的系統有較多實踐經驗。歡迎通過 [email protected] 跟他交流。

相關推薦

推薦中...