1. 程式人生 > >專家直播間的設計開發

專家直播間的設計開發

        首先說明,本文提到的直播間實際相當於一個特殊的聊天室,只支援文字,圖片,語音之類。

        公司某個版本的時候,產品提出要做個專家直播間,請一些有名氣的專家或者明星之類的來做些交流,提高社群的使用者活躍度。這個版本的需求提出到上線只有一個月時間,而且這個版本的需求量不小,關鍵的是我們leader還出去度假,整個版本後臺的設計開發跟進都由我去負責。

        接到這個需求的時候,初始以為簡單的很,想用現有im(當時使用的是釘釘的)的群來做這個直播間。而一分析發現捷徑被堵死了,因為im的大群最多支援5k人,而這個直播間的線上人數最多可能有幾十萬(我們app使用者量是幾千萬,每天活躍使用者幾百萬),產品叫囂要百萬級,用群之類的肯定不現實了,這就要求自己去重新實現一套。

       最初想到的實現方案是長連線的訊息推送,這樣會有兩種選擇:1.直接tcp層通訊(自定義或已有訊息協議,訊息處理,連線處理等都由自己維護)2.websockek。

       直接tcp層通訊效能肯定最好,但時間來不及,我個人傾向於websocket(spring4 mvc已經支援,入門簡單)。雖然websocket需要維護連線,但複雜性已經降低很多。

       可領導認為websocket協議過重(因為是基於http的),想要直接tcp層自己去處理,時間肯定不夠,最後討論結果是這個版本讓客戶端使用http來主動拉取,下個版本支援tcp的長連線。

       技術方案就這麼愉快的決定了:由客戶端迴圈的來拉取。實際上,直播間的這個方案後來一直沒有變化,支援使用者最多到了幾十萬,而之前提到的tcp長連線,再也沒有人提過。

       這裡要簡單說下直播間的業務,直播間不會一直存在,專家只會在線不到兩個小時(晚上8:00-9:30)。整個直播間的壽命也不過幾天,請求高峰期就是專家線上的那段時間,所以使用http迴圈垃取,對伺服器的壓力不是很大。

       技術方案確定了,就是做設計的時候了,設計分兩部分:1伺服器的業務實現,2提供給客戶端介面的設計。

       第二點簡單的很,無非是進入直播間,退出直播間(這兩個介面是為了統計之類),傳送訊息和拉取訊息(拉取全部訊息和拉取專家訊息)。

       但業務設計就複雜了。複雜點如下 :

       首先,使用者和專家的頭像及暱稱獲取問題,查詢過於頻繁。

       其次,我們web端是無狀態的,用token來驗證使用者請求的合法性,這裡的請求很大,不能走正常的驗證邏輯,此外還有禁言和封禁邏輯。

       最後,也是最麻煩的是資訊的讀取,請求量很大 ,別說直接查詢資料庫,查詢redis快取也會有很大壓力(包括網路的)。

       之前說過,任務多時間緊,而且要經常做些開發設計的工作(任務跟進,需求討論等),所以這個功能必須能快速的完成設計與開發,並且不能依賴太多的額外資源。整個系統的設計是在週五晚上坐公交回家,及週末兩天思考後敲定的。

     先說下頭像及暱稱問題,在使用者進入直播間的時間(之前提到的介面),會載入使用者的資訊快取。有一個單獨的使用者資訊管理類來儲存這些資訊,每一個直播間對應一個,這是本地的快取。此外,以直播間的id作為redis的map型別的key,使用者id作為map裡面的key,value是使用者資訊的json。每次查詢都會先查詢本地,查詢不到在查詢redis,最後走資料庫。

     接著,說下驗證的問題,每個直播間開啟的時候會生成一個隨機串作為key儲存在本地和redis(防止重啟後各個伺服器的key不一致),使用者在進入直播的時候會用這個唯一key對使用者uid加密,返回給客戶端,這樣客戶端在後邊的每次請求只驗證這個加密串,只用本地快取的key就可以,至於禁言和封禁的資訊也會儲存在上邊說到的使用者資訊管理類中,直播間的加密key和使用者封禁及禁言資訊,肯定需要各個伺服器保持一致,這個方法下邊說到。

     最後,說下訊息的問題,訊息裡除了資訊外,肯定要有發言人的暱稱和頭像,或者還要包含引用的訊息。在資料庫設計的時候,直接做了資料的冗餘,儲存一條資料包含了所有需要的資訊。這個只是解決了一條資訊需要查詢多次的問題,關於請求量大的問題解決方法也很簡單:本地快取,但各個伺服器之間怎麼保證資料的一致和完整,才是需要解決的核心,很多人肯定想到了使用訂閱模式,由於時間的限制,不好引入新的jms框架,我藉助了redis的pub/sub實現了資訊的同步,包括上邊說到的一些需要保持一致的資訊。

    正常來說使用者看到的和主動拉取的肯定是最新的資訊,所以本地只需要快取5k條的資料,已足夠滿足正常的介面需要。

    當用戶傳送訊息的時候,先插入資料庫,然後廣播這條資訊,各個伺服器收到後,儲存(如果是專家的要儲存一份到專家資料)並做排序。

    當用戶垃取資訊的時候,從本地讀取,只有滿足一定條件的才會查詢資料庫(直播中的情況下很少會滿足條件),這樣幾乎全部的請求都會攔截在伺服器,只在記憶體中查詢和操作。這樣做的好處是,如果伺服器扛不住壓力,可以橫向的去加伺服器,redis中儲存的資料會保證使用者及直播間資料的一致,訊息從資料庫垃取也只需要一次。

    這裡有一個需要注意的地方,就是本地資訊的快取,最初使用的是CopyOnWriteArrayList,但這個執行緒安全的list每次操作的時候是使用複製的模式,而且不支援排序。而我每次查詢也需要複製這個list,擷取排序後返回。使用CopyOnWriteArrayList沒有帶來任何好處,而且為了排序帶來了額外的開銷,所以最終選擇了普通的ArrayList,由自己去分段的加鎖控制。

     直播間上線到現在有半年多的時間了,最高線上使用者有幾十萬,線上也沒有出現過什麼問題,(迴圈的機制肯定會有資料拉取的延遲),只是第一次上線的時候網路併發突高導致停頓。今天有時間就記錄下自己的開發過程,後續會繼續記錄自己技術的成長。