繼續答水友提問。
問題抽象:(1)用戶會員系統;(2)用戶會有分數流水,每月要作一次分數統計,對不一樣分數等級的會員作不一樣業務處理;數據庫
數據假設:多線程
(1)假設用戶在100w級別;優化
(2)假設用戶日均1條流水,也就是說日增流水數據量在100W級別,月新增流水在3kW級別,3個月流水數據量在億級別; ui
常看法決方案:用一個定時任務,每月的第一天計算一次。spa
//(1)查詢出全部用戶 uids[] = select uid from t_user; //(2)遍歷每一個用戶 foreach $uid in uids[]{ //(3)查詢用戶3個月內分數流水 scores[]= select score from t_flow where uid=$uid and time=[3個月內]; //(4)遍歷分數流水 foreach $score in scores[]{ //(5)計算總分數 sum+= $score; } //(6)根據分數作業務處理 switch(sum) 升級降級,發優惠券,發獎勵; }
一個月執行一次的定時任務,會存在什麼問題?線程
計算量很大,處理的數據量很大,耗時好久,按照水友的說法,須要1-2天。3d
畫外音:外層循環100W級別用戶;內層循環9kW級別流水;業務處理須要10幾回數據庫交互。 日誌
可不能夠多線程並行處理?code
能夠,每一個用戶的流水處理不耦合。 blog
改成多線程並行處理,例如按照用戶拆分,會存在什麼問題?
每一個線程都要訪問數據庫作業務處理,數據庫有可能扛不住。
這類問題的優化方向是:(1)同一份數據,減小重複計算次數;(2)分攤CPU計算時間,儘可能分散處理,而不是集中處理;(3)減小單次計算數據量;
如何減小同一份數據,重複計算次數?
如上圖,假設每個方格是1個月的分數流水數據(約3kW)。
3月底計算時,要查詢並計算1月,2月,3月三個月的9kW數據;
4月底計算時,要查詢並計算2月,3月,4月三個月的9kW數據;
… 會發現,2月和3月的數據(粉色部分),被重複查詢和計算了屢次。
畫外音:該業務,每月的數據會被計算3次。
新增月積分流水彙總表,每次只計算當月增量:
flow_month_sum(month, uid, flow_sum)
(1)每到月底,只計算當月分數,數據量減小到1/3,耗時也減小到1/3;
(2)同時,把前2個月流水加和,就能獲得最近3個月總分數(這個動做幾乎不花時間);
畫外音:該表的數量級和用戶表數據量一致,100w級別。
這樣一來,每條分數流水只會被計算一次。
如何分攤CPU計算時間,減小單次計算數據量呢?
業務需求是一個月從新計算一次分數,但一個月集中計算,數據量太大,耗時過久,能夠將計算分攤到天天。
如上圖,月積分流水彙總表,升級爲,日積分流水彙總表。
把每個月1次集中計算,分攤爲30次分散計算,每次計算數據量減小到1/30,就只須要花幾十分鐘處理了。
甚至,每個小時計算一次,每次計算數據量又能減小到1/24,每次就只須要花幾分鐘處理了。
雖然時間縮短了,但畢竟是定時任務,能不能實時計算分數流水呢?
天天只新增100w分數流水,徹底能夠實時累加計算「日積分流水彙總」。
使用DTS(或者canal)增長一個分數流水錶的監聽,當用戶的分數變化時,實時進行日分數流水累加,將1小時一次的定時任務計算,均勻分攤到「每時每刻」,天天新增100w流水,數據庫寫壓力每秒鐘10屢次,徹底扛得住。
畫外音:若是不能使用DTS/canal,能夠使用MQ。
總結,對於這類一次性集中處理大量數據的定時任務,優化思路是:
(1)同一份數據,減小重複計算次數;
(2)分攤CPU計算時間,儘可能分散處理(甚至能夠實時),而不是集中處理;
(3)減小單次計算數據量; 但願你們有所啓示,思路比結論重要。
課後做業:假設,某系統登陸日誌(日誌比數據庫更難,數據庫能夠建索引檢索)以下:
2019-08-15 23:11:15 uid=123 action=login
2019-08-15 23:11:18 uid=234 action=logout
…
求,2019-8-15這一天,系統同時在線用戶數曲線,精確到秒。
說明:
(1)action只能爲login/logout;
(2)在線用戶的定義爲,已經login,尚未logout,正在使用系統的用戶;
(3)8-15以前登陸,8-15尚未登出的用戶,也算當天在線用戶(潛臺詞是,只掃描當天的日誌是不夠的);