posts - 27,  comments - 0,  trackbacks - 0

          有個(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í)交流。

          posted on 2015-08-13 15:30 柏然 閱讀(70) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          <2015年8月>
          2627282930311
          2345678
          9101112131415
          16171819202122
          23242526272829
          303112345

          常用鏈接

          留言簿

          隨筆檔案

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 凤凰县| 黄冈市| 安乡县| 贵州省| 台州市| 东兰县| 抚宁县| 十堰市| 青川县| 资中县| 台山市| 栾川县| 南阳市| 台安县| 长阳| 贵州省| 株洲市| 平阳县| 靖州| 竹山县| 岫岩| 都昌县| 赤城县| 兴山县| 墨竹工卡县| 大厂| 四会市| 横山县| 旌德县| 辰溪县| 岑巩县| 永嘉县| 高唐县| 江陵县| 重庆市| 扶风县| 西吉县| 霍城县| 宝鸡市| 仁化县| 寻乌县|