【原創】構建高效能ASP.NET站點之三 細節決定成敗

weixin_34126215發表於2014-03-12
原文:【原創】構建高效能ASP.NET站點之三 細節決定成敗

構建高效能ASP.NET站點之三 細節決定成敗

 

  前言:曾經就因為一個小小的疏忽,從而導致了伺服器崩潰了,後來才發現:原來就是因為一個迴圈而導致的,所以,對“注意細節“這一說法是深有感觸。

 

 

  系列文章連結:

  構建高效能ASP.NET站點 開篇

  構建高效能ASP.NET站點之一 剖析頁面的處理過程(前端)

  構建高效能ASP.NET站點之二 優化HTTP請求(前端)

  構建高效能ASP.NET站點之三 細節決定成敗

  構建高效能ASP.NET站點 第五章—效能調優綜述(前篇)

  大型高效能ASP.NET系統架構設計  

  構建高效能ASP.NET站點 第五章—效能調優綜述(中篇)

  構建高效能ASP.NET站點 第五章—效能調優綜述(後篇)  

  構建高效能ASP.NET站點 第六章—效能瓶頸診斷與初步調優(上篇)—識別效能瓶頸

  構建高效能ASP.NET站點 第六章—效能瓶頸診斷與初步調優(下前篇)—簡單的優化措施

  構建高效能ASP.NET站點 第六章—效能瓶頸診斷與初步調優(下後篇)—減少不必要的請求

  構建高效能ASP.NET站點 第七章 如何解決記憶體的問題(前中篇)—託管資源優化—監測CLR效能

  構建高效能ASP.NET站點 第七章 如何解決記憶體的問題(前篇)—託管資源優化—垃圾回收機制深度剖析

  構建高效能ASP.NET站點 第七章 如何解決記憶體的問題(前中篇)—託管資源優化—監測CLR效能

 

  

 

 

本篇的議題如下:

問題的描述

細節的重要性

 

  問題的描述

 

  首先,描述一下故事的背景:(希望大家耐心的故事讀完)

  在網站中,網頁中的分頁控制元件每次顯示10條資料,每次點選下一頁,就再次去取下一個10條資料。至於分頁的方法怎樣做,方法有很多,相信這點大家都知道。

 

  過程是這樣的:在使用者請求資料的時候(考慮到了使用者的操作和網站的訪問量)我會第一次取出500條資料,然後把資料放在快取中,也就是說,我取出了50頁的資料,放在快取中,這樣如果,以後使用者請求第一頁到第49頁的時候,就直接從快取中拿資料。

  如下圖:

  

 
  第一個資料塊:

 

  採用鍵值對的形式:字典儲存

 

 

  如果使用者請求到了49頁以後,那麼就再次從資料庫中取出下一個資料塊(包含5011000資料),然後,現在記憶體中就有了1000條資料。

 

  至於快取多久,資料什麼失效,失效後怎麼做,這裡暫不談論。(網站在這種快取策略下執行的很好)。 

  程式碼如下:

 

List<Product> products=GetDataFromCacheOrDatabase(condition,pageIndex,count….);

 

 

  程式碼的意思很清楚,從快取中拿資料,如果快取中沒有對應的資料,那麼就先從資料庫中拿500條資料,然後放在快取中,最後返回10條資料。

 

  後來,因為某些功能的需要,需要返回當前頁的前6頁資料和後6頁的資料,例如:如果當前頁是第12頁,那麼就要返回12頁之前6Product(也就是第6,7,8,9,10,11頁的資料),和第12頁後的頁的Product(第13,14,15,16,17,18頁的資料)。 

  如下:

  

 

 

  當然,如果當前頁是第5頁,那麼就把之前所有5頁的資料都返回,另外再加上第5頁之後的6頁資料。

 

  這裡就可能涉及到跨塊獲取資料,如:

  如果當前頁是第48頁的時候,那麼返回前6頁資料是沒有什麼問題的,那麼後6頁的資料就不足了,因為49,40也得資料可以從快取的資料塊中取到,至於51,52,53,54頁的資料,就需要再次從資料庫中讀取,然後再次快取(如果事先沒有被快取)。

 

 

  最後在快取中的資料如下:

 

   

  然後呼叫方法:(偽碼)

 

 

List<Product> products=GetDataFromCacheOrDatabase(condition,42126….); 

 

 

  上面傳入的是從第42頁開始的資料,也就是第48頁的前6頁和後6頁的資料。

  這個方法的內部實現是這樣的:

    1.    首先從第一個資料塊中取出42頁到50頁的資料

    取出資料後儲存在一個List<Product> firstProductList;

    2.    從第二個資料塊中取出從51頁到54頁(如果第二個資料塊在快取不存在,就去資料庫中取501-1000條,然後再放在快取的第二個資料塊中)。

    儲存在第二個List<Product> secondProductList

    3. 然後把兩個list合併,返回結果。例如

           secondProductList.Foreach(u=>firstProductList.Add(u));

 

  基本的實現就是這樣,看起來還行,也比較的合理,但是就是因為這個操作,從而導致伺服器記憶體溢位。

  大家想想看是什麼原因。

 

  細節的重要性

 

  其實快取的資料不是很多,是不足以讓伺服器記憶體溢位的,但是伺服器還是出現了out of memory的異常。之前一直跑的很好,就是改了程式碼之後才出現問題的。

 

  其實這就是由於一個最基本的錯誤產生的:引用型別。

  下面就來分析下:

  首先是從第一個資料塊中取出資料,然後用

    List<Product> firstProductList 引用指向取出的資料

  然後從第二個資料塊中取出資料,用

    List<Product> secondProductList指向資料的引用

 

  如下圖

 

 

 
  在第三步中採用

 

    secondProductList.Foreach(u=>firstProductList.Add(u));

  把secondProductList中的資料加入到firstProductList中,就因為是引用型別,其實實際操作的結果是:不斷的在改變第一個資料塊中的資料,使得第一個資料塊中的資料逐漸的變多。

  現在當前頁是48頁,採用上面的操作,致使第一個資料塊中的資料增加了60條,

  如果使用者再次翻頁,到了49頁,那麼第一個資料塊中的資料又增多了60

  依此類推,最後導致了伺服器記憶體的不足,致使伺服器崩潰了。原本的“功臣”----快取卻成為了罪魁禍首。

 

  其實這個問題的解決,只要改變一點點的程式碼就行了:

    List<Product> firstProductList

    List<Product> secondProductList

  然後

  List<Product> resultProductList=new List<Product>();然後分別把firstProductListsecondProductList遍歷,加入到resultProductList就行了。

就這麼簡單。

 

  一個小的細節,導致了大的問題。 

  

  不要忽視每一個細節。不要做沒有比較的迴圈等操作,一定要審視程式碼,重構程式碼。 

  今天就寫到了這裡,囉嗦了這麼多,希望對大家有一點點的幫助。

  如有不正確,或者認為不妥的地方,希望大家斧正!而且留下高見!謝謝!

 

版權為小洋和部落格園所有,歡迎轉載,轉載請標明出處給作者。

   http://www.cnblogs.com/yanyangtian

 

相關文章