? 最近在用使用Parse來做數(shù)據(jù)收集的工作,后臺(tái)是mongodb。有個(gè)需求要求對(duì)數(shù)據(jù)庫中已經(jīng)收集的1000多萬用戶所在城市數(shù)據(jù)來分析出在規(guī)定時(shí)間段內(nèi)为朋,用戶城市變化的次數(shù)朴皆,從而確定該用戶是否為差旅用戶來做精準(zhǔn)推送。邏輯分析這塊沒什么好說的搔课,重點(diǎn)在于插入數(shù)據(jù)庫階段胰柑。
第一個(gè)版本
? 最開始的版本沒有想太多,按照常規(guī)思路來做的爬泥,就是數(shù)據(jù)分析完成后柬讨,根據(jù)用戶id發(fā)送請(qǐng)求向Parse查詢?cè)撚脩羰欠翊嬖冢绻淮嬖趧t插入袍啡,存在則比較用戶標(biāo)簽是否有改動(dòng)踩官,沒有改動(dòng)則跳過。代碼很快寫完境输,根據(jù)日志顯示總數(shù)據(jù)在1100萬左右蔗牡,最后統(tǒng)計(jì)出來的獨(dú)立用戶在600萬⌒崞剩看輸出請(qǐng)求挺慢的辩越,于是下班后就讓腳本在后臺(tái)運(yùn)行了。結(jié)果第二天早上過來看時(shí)發(fā)現(xiàn)才插入了300萬左右的數(shù)據(jù)信粮。這個(gè)速度肯定是不行的黔攒,領(lǐng)導(dǎo)的意思是這個(gè)腳本以后一星期跑一次,我們?cè)禄钤?000萬左右强缘,后期數(shù)據(jù)量大起來后督惰,豈不是要跑幾天!于是我開始尋思優(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ù)緩存起來商虐。在內(nèi)存中比較結(jié)果觉阅,有需要再插入崖疤,減少請(qǐng)求的次數(shù)。這次結(jié)果不錯(cuò)留拾,速度明顯快了很多戳晌,一天左右應(yīng)該能跑完。但是這個(gè)結(jié)果我還是不能接受痴柔,所以又開始琢磨有沒有優(yōu)化空間
第三個(gè)版本
? 查看日志發(fā)現(xiàn)數(shù)據(jù)處理還是很快的樱溉,時(shí)間主要耗費(fèi)在了請(qǐng)求以及等待請(qǐng)求返回的時(shí)間叁丧。于是自然想到了多線程煤蚌,利用多線程來從隊(duì)列中取數(shù)據(jù)發(fā)送請(qǐng)求锯茄。一開始開了2000個(gè)線程,結(jié)果一起來就掛沒辦法最后起了900個(gè)線程來跑谈火。因?yàn)橹皼]怎么用python寫過代碼侈询,用C++和golang比較多,尤其是golang里面方便的協(xié)程和channel導(dǎo)致我都快忘記傳統(tǒng)的多線程應(yīng)該怎么寫了糯耍。被鎖搞得各種奔潰扔字,不過結(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)常掛起來回加解鎖震檩。于是又去翻了Parse的接口文檔,看看有沒有批量導(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萬個(gè)增加到3萬個(gè)套才,這下差不多達(dá)到一個(gè)平衡了迂猴。時(shí)間也縮短到了30分鐘左右。
第五個(gè)版本
? ? 第五個(gè)版本完全是意外來的背伴,就是我在導(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)存的方法似乎挺不妥的儿普。思量過后崎逃,最后決定用數(shù)據(jù)庫循環(huán)迭代的方式迭代數(shù)據(jù),將取出來的數(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í)間也沒有增加很多凯旋。
總結(jié)
? 生產(chǎn)者消費(fèi)者模型雖然很常見但是在工程中還是一種很實(shí)用的方法的。面對(duì)這種大數(shù)據(jù)的處理工作其實(shí)一開始就應(yīng)該想到利用多線程來發(fā)送請(qǐng)求钉迷,但是畢竟對(duì)Python不熟至非,多線程更是從來都沒用過就偷懶了。緩存是個(gè)好東西但是真的太耗內(nèi)存糠聪,同一個(gè)關(guān)鍵字應(yīng)該考慮值緩存一份荒椭,另一份采用實(shí)時(shí)提取的方式來做。