所就職的公司是一家互聯網視頻公司,存在大量的實時計算需求,計算uv,pv等一些經典的實時指標統計。由於要統計當天的實時
UV,當天的uv由於要存儲當天的所有的key,面臨本地內存不夠用的問題,異常重啟后會丟失本地緩存,造成計算結果不准確的問題。;如果使用外部緩存比如redis,memcache等,在高並發時會出現效率問題。
在不斷的實踐中,不斷改進方案,積累了如下經驗:
1.使用bitMap可以節約內存。
使用redis的bitMap,並發時候會有問題。
a .只使用本地內存
由於reidis在數據量較大時,並發會達到瓶頸,干脆繞過redis,全部使用基於本地內存的bitMap方案。這樣即省內存,又避免了redis的並發達不到性能要求的問題。
b.使用備份來解決DAU異常恢復問題。
實時任務異常時,由於歷史緩存丟失,造成數據不准確問題。
定時或者定量將本地緩存的b的redis持久化的一個redis,hdfs,kafka中,這些數據只起到副本備份的作用。
當worker異常重啟時,先從備份中恢復歷史的歷史的redis中的DAU的數據。然后再開始實時計算。
c.如何備份?備份哪些數據?
解決思路:
使用redis,hdfs等來存儲備份數據,
bitMap的數據序列化,保存到文件,然后上傳到hdfs上,或者redis上面。
dauBolt,接收到消息時,在execute方法中先后台恢復本地緩存,恢復成功后,再進行后續的業務處理。
使用kafka來備份當天的uuidSet :需要備份當天的所有的uuid
副本的備份每5分鍾備份一次:使用kafka作為副本備份,可以 每1分鍾或者每積累2W條UUID,就將這些UUID封裝成一個msg,發送到kafka的一個topic主題隊列uuidSetTopic里面,誰先到達執行條件就先執行那個? msg中要包含備份時候的時間戳發送時候的當前時間。
DauBolt:用來恢復當天到目前為止的uuidSet;dau相關的指標統計等;DauBolt會接收到uuidSetTopic的數據,還有userActionTopic的數據。
處理邏輯:
1.DauBolt的計算邏輯:
如果接收到的是uuidSetTopic的數據:
取出數據中的時間戳,
判斷是否是當天的備份數據,如果不是,就不處理,丟棄掉。
判斷是否本地緩存已經恢復,如果恢復,就不處理,同時通知dauSet_Spout不再讀取uuidSetTopic的數據了。
如果沒恢復, 就解析uuidSet,並逐個恢復到bitMap中。
判斷恢復完成的邏輯:
取uuidSetTopic的數據中的時間戳dt。
dt 和當前時間的差小於1.5分鍾,(uuidSetTopic 最遲是一分鍾一條。1.5分鍾可以保證這個時間段內只有一個因為湊不夠2w條的強制發送的)並且msg的uuid個數小於2w,則說明恢復進入尾聲了,基本上追上了當前時間。
連續兩次差小於1.5分鍾,uuid個數小於2w,也說明追上了最新的值;
dt 和當前時間的差 小於1.5 分鍾(90s), 假設當前時間差是50s,msg的uuid個數等於2w,說明目前數據較多,不到一分鍾就生產了2w條數據;接着往下取數據,將時間差的閥值縮小當前的差值50s;繼續該邏輯,直到時間差大於設置的值,說明追上了最新的數據,本地緩存恢復成功。
如果當前時間和bolt的啟動時間的間隔差超過15分鍾(也就是允許有15分鍾來恢復dau的計算的本地緩存),也認為恢復完成(不能無限期的等待它恢復啊,但是要在日志中打印出來這種情況)。
2.如果接收到的是userAction的數據:
取出數據中的uuid
當前的本地緩存是否已經恢復: 如果恢復,取出uuid,判斷是否在本地的bitMap中,如果不在,更新DAU的值。
如果沒有恢復,不進行uuid是否在bitMap的判斷。
將uuid 發送到uuidSetSave_bolt.
恢復線程需要根據時間來判斷是否恢復成功了。
當storm 任務啟動前,先向dauBakTopic發送一條消息,UUID為空set,時間戳為當前時間。