如何設(shè)計(jì)一個(gè)在線觀看人數(shù)功能
前言
由于bilibili一直都有稿件在線觀看人數(shù)的功能,想了解他是如何做到的,因此有了這篇筆記。
1、架構(gòu)選型
常用的技術(shù)架構(gòu)
通過Url對access_log等日志記錄進(jìn)行篩選,通過日期分割等操作實(shí)現(xiàn)。
實(shí)現(xiàn)容易,只需要文本讀取
實(shí)時(shí)性不高
IO要求高
缺點(diǎn)
優(yōu)點(diǎn)
WebSocket+Redis
實(shí)時(shí)性高
要求服務(wù)器性能
實(shí)現(xiàn)稍微復(fù)雜
缺點(diǎn)
優(yōu)點(diǎn)
Bilibili采用的方案(猜測)
通過對
network進(jìn)行查看,可以看到Bilibili采用的方案是第二種,使用WebSocket的方式Bilibili 采用 連接
wss://broadcast.chat.bilibili.com:7826/sub服務(wù)。根據(jù)別人的開源來看,應(yīng)該是傳輸了這么一段東西,以下稱為[data1]
0x00, 0x00, 0x00, 0x5B, 0x00, 0x12, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x7B, 0x22, 0x72, 0x6F, 0x6F, 0x6D,
0x5F, 0x69, 0x64, 0x22, 0x3A, 0x22, 0x76, 0x69, 0x64, 0x65, 0x6F, 0x3A,
0x2F, 0x2F, 0x35, 0x30, 0x33, 0x33, 0x34, 0x35, 0x38, 0x30, 0x2F, 0x38,
0x38, 0x31, 0x32, 0x30, 0x37, 0x39, 0x32, 0x22, 0x2C, 0x22, 0x70, 0x6C,
0x61, 0x74, 0x66, 0x6F, 0x72, 0x6D, 0x22, 0x3A, 0x22, 0x77, 0x65, 0x62,
0x22, 0x2C, 0x22, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x73, 0x22, 0x3A,
0x5B, 0x31, 0x30, 0x30, 0x30, 0x5D, 0x7D
復(fù)制代碼看起來是16進(jìn)制的byte數(shù)組,解析一下
? ?/**
? ? * 可以分析出
? ? * ? 0x00, 0x00, 0x00, 0x5B, 0x00, 0x12, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07,0x00, 0x00, 0x00, 0x01, 0x00, 0x00
? ? * ? 這一部分應(yīng)該是定義的消息頭,無法解析出實(shí)際的意義
? ? * 剩下的部分,解析后是
? ? * {"room_id":"video://50334580/88120792","platform":"web","accepts":[1000]}
? ? */
? ?@Test
? ?public void test3() {
? ? ? ?byte[] bytes = {
? ? ? ? ? ? ? 0x7B, 0x22, 0x72, 0x6F, 0x6F, 0x6D,
? ? ? ? ? ? ? ?0x5F, 0x69, 0x64, 0x22, 0x3A, 0x22, 0x76, 0x69, 0x64, 0x65, 0x6F, 0x3A,
? ? ? ? ? ? ? ?0x2F, 0x2F, 0x35, 0x30, 0x33, 0x33, 0x34, 0x35, 0x38, 0x30, 0x2F, 0x38,
? ? ? ? ? ? ? ?0x38, 0x31, 0x32, 0x30, 0x37, 0x39, 0x32, 0x22, 0x2C, 0x22, 0x70, 0x6C,
? ? ? ? ? ? ? ?0x61, 0x74, 0x66, 0x6F, 0x72, 0x6D, 0x22, 0x3A, 0x22, 0x77, 0x65, 0x62,
? ? ? ? ? ? ? ?0x22, 0x2C, 0x22, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x73, 0x22, 0x3A,
? ? ? ? ? ? ? ?0x5B, 0x31, 0x30, 0x30, 0x30, 0x5D, 0x7D
? ? ? };
? ? ? ?StringBuilder result = new StringBuilder();
? ? ? ?for (int index = 0, len = bytes.length; index <= len - 1; index += 1) {
? ? ? ? ? ?int char1 = ((bytes[index] >> 4) & 0xF);
? ? ? ? ? ?char chara1 = Character.forDigit(char1, 16);
? ? ? ? ? ?int char2 = ((bytes[index]) & 0xF);
? ? ? ? ? ?char chara2 = Character.forDigit(char2, 16);
? ? ? ? ? ?result.append(chara1);
? ? ? ? ? ?result.append(chara2);
? ? ? }
? ? ? ?System.out.println(new String(new BigInteger(result.toString(), 16).toByteArray()));
? }
復(fù)制代碼重點(diǎn)應(yīng)該在room_id,這里
video分為兩部分,50334580應(yīng)該是視頻對應(yīng)的av號(hào)(現(xiàn)在使用了bv號(hào),但是實(shí)際上還是有對應(yīng)的av號(hào)的),88120792這里應(yīng)該是視頻的彈幕信息cid根據(jù)代碼來看,應(yīng)該是只有一個(gè)連接地址
aid/cid作為了一個(gè)key,點(diǎn)擊視頻連接后,進(jìn)行websocket連接,然后發(fā)送[data1],然后服務(wù)器返回b'\x00\x00\x00+\x00\x12\x00\x01\x00\x00\x00\x08\x00\x00\x00\x01\x00\x00{"code":0,"message":"ok"}'之后會(huì)發(fā)送一個(gè)
base64編碼過的用戶信息之類的?這里我沒看懂代碼,然后就返回了b'\x00\x00\x00n\x00\x12\x00\x01\x00\x00\x00\x03\x00\x00\x00\t\x00\x00{"code":0,"message":"0","data":{"room":{"online":3,"room_id":"video://85919470/146861497"}}}'
2、我的方案-Java
采用Webscoket連接,用戶訂閱
/video/{vid}根據(jù)
vid創(chuàng)建對應(yīng)的Map存儲(chǔ),vid和對應(yīng)的websocket-session發(fā)送給后臺(tái)消息,帶有用戶的
userId根據(jù)
vid發(fā)送到Redis,利用視頻的vid作為key,使用Redis的set數(shù)據(jù)結(jié)構(gòu),存儲(chǔ)userId,通過scard key查看在線人數(shù)通過heatbeat刷新,或者定時(shí)刷新。
用戶關(guān)閉標(biāo)簽,斷開websocket服務(wù),帶上
vid告知,移除Redis中的對應(yīng)的vid作為key的userId
3、不足
由于可能存在用戶同時(shí)打開多個(gè)同一個(gè)視頻的情況,這時(shí)如果關(guān)閉,其中一個(gè),則其他也會(huì)斷開連接,無法時(shí)時(shí)推送最新的在線觀看人數(shù)。
參考的代碼 github.com/penpen456/b…
import websocket
import base64
import requests
import json
import datetime
import time
def get_cid(av):
? ?response = requests.get("https://api.bilibili.com/x/web-interface/view?aid=" + str(av))
? ?response.encoding = 'utf-8'
? ?res = response.text
? ?# print(res)
? ?data = json.loads(res)
? ?c = data['data']['cid']
? ?# print(c)
? ?return c
def make_send(av):
? ?cid = str(get_cid(av))
? ?res = b'\x00\x00\x00\\x00\x12\x00\x01\x00\x00\x00\x07\x00\x00\x00\x01\x00\x00{"room_id":"video://' + str(av).encode('utf-8') + '/'.encode('utf-8') + cid.encode('utf-8') + '","platform":"web","accepts":[1000]}'.encode('utf-8')
? ?return res
def get_online(text):
? ?cache = text.find(b'"online":')
? ?# print(cache)
? ?cache2 = text[cache+9:].find(b',')
? ?get = int(text[cache+9:cache2+9+cache])
? ?print(get)
? ?return get
def connect(plz):
? ?url = "wss://broadcast.chat.bilibili.com:7823/sub"
? ?normal = base64.b64decode('AAAAIQASAAEAAAACAAAACQAAW29iamVjdCBPYmplY3Rd')
? ?ws = websocket.create_connection(url,timeout=10)
? ?ws.send(bytes(plz))
? ?get = ws.recv()
? ?print(get)
? ?print(normal)
? ?ws.send(bytes(normal))
? ?get = ws.recv()
? ?print(get)
? ?if get.find(b'online') != -1:
? ? ? ?# online = get_online(get)
? ? ? ?online=get_online(get)
? ? ? ?return online
? ?else :
? ? ? ?print("None")
def get_online_from_av(av):
? ?send = make_send(av)
? ?online = connect(send)
? ?return online
def write_file(onlines,times):
? ?
? ?with open(file_name,'a') as file_obj:
? ? ?file_obj.write(str(times) + ',' + str(onlines) + '\r')
get_online_from_av(85919470)
# file_name = str(input("File_Name(a.txt):"))
# avid = int(input('AVid(85919470):'))
# while True:
? ?
# ? ? online=get_online_from_av(avid)
# ? ? now_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# ? ? print(now_time)
# ? ? # write_file(online,now_time)
? ?
# ? ? time.sleep(60)
作者:ClassLoader
鏈接:https://juejin.cn/post/6991085092110073863
來源:稀土掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。
