有個(gè)同學(xué),說是系統(tǒng)中出現(xiàn)性能問題了,說是讓我?guī)椭\斷一下。本來是不想花這時(shí)間的,結(jié)果耐不住對方的死纏亂打,只要答應(yīng)幫看看。 故事發(fā)生的背景是,在文件上傳的時(shí)候,有時(shí)間會(huì)有人上傳了文件,但是最后沒有使用上傳的文件,這樣就會(huì)產(chǎn)生一些垃圾文件。 原來軟件作者就想寫一個(gè)后臺(tái)定時(shí)任務(wù)程序,來清除這些垃圾文件? 由于作者堅(jiān)定的不讓我發(fā)她的SQL語句(這個(gè)我也理解,這么丑陋的SQL),所以這里就不發(fā)源代碼了,發(fā)偽代碼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | void deleteMissLinkFile{ List fileList=getFileList(); List deleteFileList= new ArrayList(); for (file:fileList){ int count1=execute(select count(*) from ...); int count2=execute(select count(*) from ...); int count3=execute(select count(*) from ...); int count4=execute(select count(*) from ...); int count5=execute(select count(*) from ...); if (count1== 0 &&count2== 0 &&count3== 0 &&count4== 0 &&count5== 0 ){ deleteFileList.add(file); } } delete(deleteFileList); } |
當(dāng)然,這里我已經(jīng)給進(jìn)行了一定的加工,使得看起一漂亮了許多,實(shí)際上,嗯嗯,實(shí)在是丑。 這個(gè)時(shí)候的性能情況是怎么樣的呢?說是表里的數(shù)據(jù)只有500多條,但是執(zhí)行時(shí)間要100多秒,但是實(shí)際上實(shí)際的應(yīng)用場景都遠(yuǎn)不止這個(gè)數(shù)量級(jí),而且隨著數(shù)據(jù)的增加,性能會(huì)呈指數(shù)級(jí)下降。 我說你去加10萬條記錄測試一下,保證你一晚上算不出來。 好吧,廢話少說,接下來看看怎么優(yōu)化這段程序。 在開始之前,我們可以假設(shè)有N個(gè)文件,有M個(gè)文件引用表,而且假設(shè)所有的文件引用表中的記錄條數(shù)都一樣。 很顯然,原來的實(shí)現(xiàn)方法中執(zhí)行了:1次文件數(shù)查詢+N*M次統(tǒng)計(jì)操作 最笨的優(yōu)化方法 先用成本最低的方式來優(yōu)化一把:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | void deleteMissLinkFile{ List fileList=getFileList(); List deleteFileList= new ArrayList(); for (file:fileList){ int count1=execute(select count(*) from ...); int count2=execute(select count(*) from ...); int count3=execute(select count(*) from ...); int count4=execute(select count(*) from ...); int count5=execute(select count(*) from ...); if (count1== 0 &&count2== 0 &&count3== 0 &&count4== 0 &&count5== 0 ){ deleteFileList.add(file); } } delete(deleteFileList); } |
嗯嗯,通過上面的重構(gòu),性能馬上就可以提升一倍。難看是難看了一點(diǎn),但是1倍也是不小的提升哦。 原因,原來是要把所有的統(tǒng)計(jì)值都算出來,再進(jìn)行判斷,通過上面的重構(gòu),平均只要查一半就可以退出了,所以性能會(huì)有1倍的提升。 1次文件數(shù)查詢+N*M/2次統(tǒng)計(jì)操作 一般的優(yōu)化方法 偶當(dāng)時(shí)提醒她說,你可以把內(nèi)外換換,性能就會(huì)提升許多,結(jié)果死活聽不懂,<ignore_js_op>
。
實(shí)際上邏輯是這樣的,由于統(tǒng)計(jì)操作的執(zhí)行效率是非常低的,而帶主鍵的查詢速度是非常快的,也就是把邏輯從:遍歷所有的文件看看引用次數(shù)是多少,改變成從所有文件列表中刪除所有已經(jīng)引用的文件,其余就是要?jiǎng)h除的垃圾文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | void deleteMissLinkFile{ List fileList=getFileList(); List refList1=execute(select file from tb1…) for (ref:refList1){ fileList.remove(ref) } List refList2=execute(select file from tb2…) for (ref:refList2){ fileList.remove(ref) } …… delete(deleteFileList); } |
通過上面的優(yōu)化,需要執(zhí)行的SQL語句是: 1+m 條SQL語句,其它都是大量的內(nèi)存數(shù)據(jù)比對,相對來說,性能會(huì)高太多,通過一定的技巧進(jìn)行一些優(yōu)化,會(huì)有更大的提升。 這種方式,我毛估估比原始的方式,可以提高兩個(gè)數(shù)量級(jí)以上。 為什么提高了兩個(gè)左右數(shù)量級(jí)還是說比較笨的方法呢? 因?yàn)檫@種方法雖然比原始的方法有了顯著的提升,但是還是存在嚴(yán)重的設(shè)計(jì)問題的。 首先,當(dāng)數(shù)據(jù)量比較小的時(shí)候(這里的小是指與互聯(lián)網(wǎng)應(yīng)用中的數(shù)據(jù)相比),做完全遍歷是沒有問題的,但是當(dāng)數(shù)據(jù)量比較大的時(shí)候,用一條SQL來遍歷所有的數(shù)據(jù),就是有非常大的問題的。這個(gè)時(shí)候就要引入一系列的復(fù)雜問題來解決,比如:把單機(jī)計(jì)算變成集群計(jì)算,把整個(gè)計(jì)算變成分段時(shí)間,不管怎么樣,都是非常復(fù)雜的處理過程。 無為而治的方法 下面就要推出最快的、最省事的、效率最高的方法。 其實(shí)一般來說,只要是算法都是有優(yōu)化空間和余地的,因此一般來說本人很少把話說滿的。這次本人使用了“最”字,那就是用來表明未來已經(jīng)沒有優(yōu)化的空間了,那什么樣的算法才能沒有優(yōu)化的空間呢?答案就是:啥也不做。 當(dāng)然了,實(shí)際上也不可能啥也不做,問題就在哪里,你不做怎么可能好呢? 實(shí)際上就是把任務(wù)進(jìn)行一定的分解。通過把架構(gòu)進(jìn)行合理的分析與設(shè)計(jì),把所有的文件上傳、刪除都做成公共的方法(或服務(wù)),在需要與文件打交道的地方,凡是與文件打交道的時(shí)候,做如下處理:
- 文件上傳:在文件上傳數(shù)據(jù)中加一條數(shù)據(jù),比如:文件相關(guān)信息,唯一標(biāo)識(shí),引用次數(shù)為0
- 文件關(guān)聯(lián):當(dāng)數(shù)據(jù)與文件關(guān)聯(lián)的時(shí)候,修改引用次數(shù)為+1
- 文件取消關(guān)聯(lián):當(dāng)數(shù)據(jù)與文件取消關(guān)聯(lián)的時(shí)候(一般來說是刪除或編輯的時(shí)候置為空或者換成另外一個(gè)的時(shí)候),修改引用次數(shù)為-1
自次,當(dāng)要清理垃圾的時(shí)候,就非常簡單的了,只要: select ... from ... where ref_times=0 然后進(jìn)行相應(yīng)的清理工作就好。 這個(gè)時(shí)候就優(yōu)化了處理模式,并且把文件引用數(shù)據(jù)的維護(hù)分解到業(yè)務(wù)工作的過程當(dāng)中,可以極大幅度的提升清理垃圾的處理效率。當(dāng)然有的人說了:如果這么做,會(huì)使得我的業(yè)務(wù)處理過程變慢,那怎么辦?其實(shí)也沒有關(guān)系了,你可以把這個(gè)變成異步消息的方式,通知文件引用處理去做這件事情就行了,這樣就不會(huì)影響到你的業(yè)務(wù)處理效率了。 總結(jié) 通過上面的分析,我們對文件上傳過程中的垃圾清理過程進(jìn)行優(yōu)化,并分析了原來的問題之所在,及后面3種優(yōu)化方式及其優(yōu)缺點(diǎn)對比。 當(dāng)然,實(shí)際上許多朋友也會(huì)有更好的辦法來解決,歡迎大家參與討論,并批評指正。 如果,你喜歡我的博文,請關(guān)注我,以便收到我的最新動(dòng)態(tài)。 如果對我的開源框架感興趣,可以從這里獲取到最新的代碼,也可以訪問Tiny官網(wǎng)獲取更多的消息,或到Tiny社區(qū)進(jìn)行即時(shí)交流。