? 最近在用使用Parse來(lái)做數(shù)據(jù)收集的工作,后臺(tái)是mongodb。有個(gè)需求要求對(duì)數(shù)據(jù)庫(kù)中已經(jīng)收集的1000多萬(wàn)用戶所在城市數(shù)據(jù)來(lái)分析出在規(guī)定時(shí)間段內(nèi),用戶城市變化的次數(shù),從而確定該用戶是否為差旅用戶來(lái)做精準(zhǔn)推送。邏輯分析這塊沒(méi)什么好說(shuō)的,重點(diǎn)在于插入數(shù)據(jù)庫(kù)階段。
第一個(gè)版本
? 最開始的版本沒(méi)有想太多,按照常規(guī)思路來(lái)做的,就是數(shù)據(jù)分析完成后,根據(jù)用戶id發(fā)送請(qǐng)求向Parse查詢?cè)撚脩羰欠翊嬖?,如果不存在則插入,存在則比較用戶標(biāo)簽是否有改動(dòng),沒(méi)有改動(dòng)則跳過(guò)。代碼很快寫完,根據(jù)日志顯示總數(shù)據(jù)在1100萬(wàn)左右,最后統(tǒng)計(jì)出來(lái)的獨(dú)立用戶在600萬(wàn)??摧敵稣?qǐng)求挺慢的,于是下班后就讓腳本在后臺(tái)運(yùn)行了。結(jié)果第二天早上過(guò)來(lái)看時(shí)發(fā)現(xiàn)才插入了300萬(wàn)左右的數(shù)據(jù)。這個(gè)速度肯定是不行的,領(lǐng)導(dǎo)的意思是這個(gè)腳本以后一星期跑一次,我們?cè)禄钤?000萬(wàn)左右,后期數(shù)據(jù)量大起來(lái)后,豈不是要跑幾天!于是我開始尋思優(yōu)化的空間
第二個(gè)版本
? 考慮到第一階段的數(shù)據(jù)統(tǒng)計(jì)階段耗費(fèi)時(shí)間在數(shù)分鐘,時(shí)間主要耗費(fèi)在parse請(qǐng)求上。于是在mongodb里面對(duì)用戶id建了索引,同時(shí)在導(dǎo)入數(shù)據(jù)之前從統(tǒng)一從mongodb中導(dǎo)出數(shù)據(jù)緩存起來(lái)。在內(nèi)存中比較結(jié)果,有需要再插入,減少請(qǐng)求的次數(shù)。這次結(jié)果不錯(cuò),速度明顯快了很多,一天左右應(yīng)該能跑完。但是這個(gè)結(jié)果我還是不能接受,所以又開始琢磨有沒(méi)有優(yōu)化空間
第三個(gè)版本
? 查看日志發(fā)現(xiàn)數(shù)據(jù)處理還是很快的,時(shí)間主要耗費(fèi)在了請(qǐng)求以及等待請(qǐng)求返回的時(shí)間。于是自然想到了多線程,利用多線程來(lái)從隊(duì)列中取數(shù)據(jù)發(fā)送請(qǐng)求。一開始開了2000個(gè)線程,結(jié)果一起來(lái)就掛沒(méi)辦法最后起了900個(gè)線程來(lái)跑。因?yàn)橹皼](méi)怎么用python寫過(guò)代碼,用C++和golang比較多,尤其是golang里面方便的協(xié)程和channel導(dǎo)致我都快忘記傳統(tǒng)的多線程應(yīng)該怎么寫了。被鎖搞得各種奔潰,不過(guò)結(jié)果不錯(cuò),清空所有數(shù)據(jù)重新導(dǎo)入應(yīng)該可以在1個(gè)小時(shí)內(nèi)導(dǎo)入完成。
第四個(gè)版本
? 其實(shí)前面那個(gè)版本我已經(jīng)比較滿意了,但是查看日志的時(shí)候發(fā)現(xiàn),數(shù)據(jù)處理隊(duì)列經(jīng)常滿了,導(dǎo)致插入數(shù)據(jù)的線程經(jīng)常掛起來(lái)回加解鎖。于是又去翻了Parse的接口文檔,看看有沒(méi)有批量導(dǎo)入的接口結(jié)果還真讓我發(fā)現(xiàn)了。于是果斷每次提交批量導(dǎo)入的最大值30個(gè)數(shù)據(jù)導(dǎo)入。這個(gè)時(shí)候就發(fā)現(xiàn)900個(gè)線程已經(jīng)出現(xiàn)有線程等待的情況,于是將批量導(dǎo)數(shù)據(jù)的數(shù)量從之前的1萬(wàn)個(gè)增加到3萬(wàn)個(gè),這下差不多達(dá)到一個(gè)平衡了。時(shí)間也縮短到了30分鐘左右。
第五個(gè)版本
? ? 第五個(gè)版本完全是意外來(lái)的,就是我在導(dǎo)數(shù)據(jù)時(shí)候順便打開top看了下性能,結(jié)果發(fā)現(xiàn)32G的內(nèi)存被這個(gè)腳本吃掉了50%左右,而mongodb本身比較吃內(nèi)存也占了46%的空間,考慮到以后數(shù)據(jù)還有可能漲,這種將mongodb中的數(shù)據(jù)導(dǎo)入到內(nèi)存的方法似乎挺不妥的。思量過(guò)后,最后決定用數(shù)據(jù)庫(kù)循環(huán)迭代的方式迭代數(shù)據(jù),將取出來(lái)的數(shù)據(jù)和統(tǒng)計(jì)后的結(jié)果做比較,比較完成后再將該記錄從統(tǒng)計(jì)結(jié)果中刪除。最后統(tǒng)一處理統(tǒng)計(jì)結(jié)果中剩下的內(nèi)容,剩下的基本上只需要批量插入就行了,不用考慮更新的事情。 這個(gè)版本算是比較完美的版本了,導(dǎo)入時(shí)間在40分鐘左右,內(nèi)存占用在10%左右比之前大幅降低,時(shí)間也沒(méi)有增加很多。
總結(jié)
? 生產(chǎn)者消費(fèi)者模型雖然很常見但是在工程中還是一種很實(shí)用的方法的。面對(duì)這種大數(shù)據(jù)的處理工作其實(shí)一開始就應(yīng)該想到利用多線程來(lái)發(fā)送請(qǐng)求,但是畢竟對(duì)Python不熟,多線程更是從來(lái)都沒(méi)用過(guò)就偷懶了。緩存是個(gè)好東西但是真的太耗內(nèi)存,同一個(gè)關(guān)鍵字應(yīng)該考慮值緩存一份,另一份采用實(shí)時(shí)提取的方式來(lái)做。