程序員過關(guān)斬將--從每秒6000寫請求談起
作者丨菜v菜來源丨架構(gòu)師修行之路
菜菜哥,緊急求助呀


怎么回事?產(chǎn)品經(jīng)理砍你了?


沒有,只是寫了個新項(xiàng)目,上線就被壓垮了


什么功能,這么強(qiáng)悍?


一個記錄用戶觀看視頻進(jìn)度信息的功能


那如果用戶基數(shù)大,確實(shí)是需要注意的,那我給你分析一下哈


背景
每一個片子的幕后,都保留了你的觀看記錄,詳細(xì)的記著你觀看了幾次,跳過了那些時長 ,據(jù)說根據(jù)這些數(shù)據(jù)可以分析出你喜歡哪個日本明星,以此來做定向推送......
雖然看起來很簡單的一個功能,其實(shí)涉及到的數(shù)據(jù)量非常大,極限情況下為你的用戶數(shù)*視頻數(shù)的乘積。那么在只有兩個網(wǎng)站服務(wù)器,一臺sqlserver的情況下,該如何面對這樣不算大數(shù)據(jù)量的寫請求呢?為什么說是寫請求呢?因?yàn)橛脩粲^看視頻的每一秒你都需要記錄下來,例如:視頻的第十秒用戶觀看了。要想把這個功能搞定,首先需要定義幾個事情:1. 記錄用戶觀看視頻情況的數(shù)據(jù)定義2. 和客戶端交互的數(shù)據(jù)協(xié)議3. 數(shù)據(jù)庫中記錄的數(shù)據(jù)格式4. 如何解決服務(wù)器寫的壓力(畢竟單臺服務(wù)器請求數(shù)還是比較大)
解決方案用戶觀看視頻進(jìn)度定義
對于一個視頻來說,假如有1個小時的時長,這3600秒對應(yīng)著3600個是否已經(jīng)觀看的狀態(tài),對于觀看狀態(tài)來說,只有觀看和未觀看兩種狀態(tài),所以一個bit足以,一個字節(jié)(byte)有8個bit,所以一個byte可以表示8秒的觀看狀態(tài),以此為基礎(chǔ),進(jìn)制越高,同樣數(shù)量的字符表示的狀態(tài)就越多。客戶端每次上傳新的數(shù)據(jù),需要和服務(wù)端已經(jīng)存在的數(shù)據(jù)做位運(yùn)算,例如:01000? 表示第二秒觀看了 ,客戶端新上傳:00011 表示第4,5秒都觀看了,對于用戶而言這個視頻第2,4,5 秒都看過,雖然只是一個簡單的運(yùn)算,但是量大的時候,對cpu的消耗不容小覷。第一字節(jié)????第二字節(jié)
??0?1?2?3?4?5?6?7??0?1?2?3?4?5?6?7?
bit:? 1 0?0?0 1 0?0?0??0 1 0?0?0?0?0?0
二進(jìn)制:??0x88 ???0x40
字符串:? 8840
和客戶端交互協(xié)議
用戶觀看視頻的進(jìn)度實(shí)時信息,只有客戶端知道,客戶端需要上傳用戶的觀看進(jìn)度數(shù)據(jù),和服務(wù)端交互的進(jìn)制可以選擇通用性比較強(qiáng)的16進(jìn)制,當(dāng)然你選擇100進(jìn)制也無所謂,只要雙方能同時支持,并且能正常解析即可數(shù)據(jù)庫數(shù)據(jù)格式
每種數(shù)據(jù)庫支持的數(shù)據(jù)類型有差異,所以這里不在過多敘述,當(dāng)然無論什么格式,占用空間越少越好,但也要根據(jù)業(yè)務(wù)的計(jì)算量來綜合考慮。
解決問題cpu性能問題
畢竟要把用戶每次最新的觀看數(shù)據(jù)和老數(shù)據(jù)做合并工作,在用戶量大的情況下不容小覷。在綜合了各種條件之后,最終采用10進(jìn)制來做合并工作,客戶端上傳上來16進(jìn)制數(shù)據(jù),然后轉(zhuǎn)化為十進(jìn)制,然后和觀看記錄(10進(jìn)制)做合并運(yùn)算,這部分cpu省略不了,具體轉(zhuǎn)化程序?yàn)椋?/span>//需要新加的數(shù)據(jù)
????????ConcurrentQueue?AddQueue?=?new?ConcurrentQueue();
//把16進(jìn)制的字符串按照兩位?分割成十進(jìn)制數(shù)組
????????protected?List<int>?ConvertToProgressArray(string?progressString)
????????{
????????????if?(string.IsNullOrWhiteSpace(progressString))
????????????{
????????????????return?null;
????????????}
????????????//驗(yàn)證是否為2的倍數(shù)長度
????????????if?(progressString.Length?%?2?!=?0)
????????????{
????????????????return?null;
????????????}
????????????var?proStrSpan?=?progressString.AsSpan();
????????????List<int>?ret?=?new?List<int>();
????????????int?i?=?0;
????????????while?(i?????????????{
????????????????ret.Add(int.Parse(proStrSpan.Slice(i,?2).ToString(),?System.Globalization.NumberStyles.HexNumber));?;
????????????????i?=?i?+?2;
????????????}
????????????return?ret;
????????}
客戶端請求數(shù)量問題
如果同時一萬用戶在同時觀看視頻,上傳數(shù)據(jù)時間間隔為2秒,意味著每秒有5000請求。由于這個業(yè)務(wù)只是一個用戶log型業(yè)務(wù),何為log型,就是說可以容忍一部分?jǐn)?shù)據(jù)丟失,針對這個數(shù)據(jù)形態(tài),客戶端可以先在本地做緩沖記錄,沒有必要一秒上傳一次記錄,例如現(xiàn)在約定的客戶端30秒上傳一次記錄,如果用戶關(guān)掉客戶端,下次啟動的時候會重新上傳未成功的記錄。數(shù)據(jù)庫壓力
如果每次請求都單獨(dú)更新數(shù)據(jù)庫,按照第二條的計(jì)算每秒高達(dá)5000次update請求。用戶觀看每次視頻都加載內(nèi)存中緩存,仔細(xì)分析這種業(yè)務(wù),由于是log型數(shù)據(jù),所以每次你請求沒有必要都去更新數(shù)據(jù)庫,而是先更新了緩存,然后定時去更新數(shù)據(jù)庫。由于數(shù)據(jù)量的問題,所有的更新操作都會發(fā)送到一個任務(wù)隊(duì)列,隊(duì)列的執(zhí)行者會根據(jù)配置批量更新數(shù)據(jù)庫,這樣比單條更新數(shù)據(jù)庫性能要高很多,其實(shí)這種方案在很多l(xiāng)og型的業(yè)務(wù)中都有使用,批量更新對數(shù)據(jù)庫的壓力要小很多,代碼類似以下
public?async?Task<int>?AddUserVideoData(UserVideoInfo?data,?DBProcessEnum?processType?=?DBProcessEnum.Update)
????????{
????????????if(processType==?DBProcessEnum.Add)
????????????{
????????????????AddQueue.Enqueue(data);
????????????}
????????????return?1;
????????}
?void?MulProcessData()
????????{
????????????//每次更新的條數(shù)
????????????int?maxNumber?=?50;
????????????List?data?=?new?List();
????????????while?(true)
????????????{
????????????????if?(data?==?null)
????????????????{
????????????????????data?=?new?List();
????????????????}
????????????????try
????????????????{???????????????????
????????????????????if?(!AddQueue.Any()?&&?!UpdateQueue.Any())
????????????????????{
????????????????????????System.Threading.Thread.Sleep(500);
????????????????????}???????????????????
????????????????????else
????????????????????{
????????????????????????//先處理?需要更新的
????????????????????????data.Clear();
????????????????????????while?(data.Count?<=?maxNumber?&&?AddQueue.Any())
????????????????????????{
????????????????????????????if?(!AddQueue.TryDequeue(out?UserVideoInfo?value))
????????????????????????????{????????????????????????????????
????????????????????????????????continue;
????????????????????????????}
????????????????????????????//判斷是否有重復(fù)對象
????????????????????????????if?(data.Any(s?=>?s.UserId?==?value.UserId?&&?s.VideoId?==?value.VideoId))
????????????????????????????{
????????????????????????????????var?exsitItem?=?data.First(s?=>?s.UserId?==?value.UserId?&&?s.VideoId?==?value.VideoId);
????????????????????????????????exsitItem?=?value;
????????????????????????????}
????????????????????????????else
????????????????????????????{
????????????????????????????????data.Add(value);
????????????????????????????}
????????????????????????}
????????????????????????if?(data?!=?null?&&?data.Any())
????????????????????????{
????????????????????????????var?ret?=?UserVideoProgressProxy.Add(data);
????????????????????????}
????????????????????}
????????????????}
????????????????catch?(Exception?err)
????????????????{
????????????????}
????????????}
????????}
寫在最后
其實(shí)這種高IO的操作用sqlserver這種關(guān)系型數(shù)據(jù)庫反而不好,Nosql在這種簡單高IO的情境下要很多,改天可以改為redis試一試,估計(jì)會比sqlserver要好很多。
近期精彩內(nèi)容推薦:??
?在 IntelliJ IDEA 中使用 Git,太方便了!

在看點(diǎn)這里
好文分享給更多人↓↓
評論
圖片
表情
