構建高效能ASP.NET站點之三 細節決定成敗
前言:曾經就因為一個小小的疏忽,從而導致了伺服器崩潰了,後來才發現:原來就是因為一個迴圈而導致的,所以,對“注意細節“這一說法是深有感觸。
系列文章連結:
構建高效能ASP.NET站點之一 剖析頁面的處理過程(前端)
構建高效能ASP.NET站點 第六章—效能瓶頸診斷與初步調優(上篇)—識別效能瓶頸
構建高效能ASP.NET站點 第六章—效能瓶頸診斷與初步調優(下前篇)—簡單的優化措施
構建高效能ASP.NET站點 第六章—效能瓶頸診斷與初步調優(下後篇)—減少不必要的請求
構建高效能ASP.NET站點 第七章 如何解決記憶體的問題(前中篇)—託管資源優化—監測CLR效能
構建高效能ASP.NET站點 第七章 如何解決記憶體的問題(前篇)—託管資源優化—垃圾回收機制深度剖析
構建高效能ASP.NET站點 第七章 如何解決記憶體的問題(前中篇)—託管資源優化—監測CLR效能
本篇的議題如下:
問題的描述
細節的重要性
問題的描述
首先,描述一下故事的背景:(希望大家耐心的故事讀完)
在網站中,網頁中的分頁控制元件每次顯示10條資料,每次點選下一頁,就再次去取下一個10條資料。至於分頁的方法怎樣做,方法有很多,相信這點大家都知道。
過程是這樣的:在使用者請求資料的時候(考慮到了使用者的操作和網站的訪問量)我會第一次取出500條資料,然後把資料放在快取中,也就是說,我取出了50頁的資料,放在快取中,這樣如果,以後使用者請求第一頁到第49頁的時候,就直接從快取中拿資料。
如下圖:
採用鍵值對的形式:字典儲存
如果使用者請求到了49頁以後,那麼就再次從資料庫中取出下一個資料塊(包含501到1000資料),然後,現在記憶體中就有了1000條資料。
至於快取多久,資料什麼失效,失效後怎麼做,這裡暫不談論。(網站在這種快取策略下執行的很好)。
程式碼如下:
程式碼的意思很清楚,從快取中拿資料,如果快取中沒有對應的資料,那麼就先從資料庫中拿500條資料,然後放在快取中,最後返回10條資料。
後來,因為某些功能的需要,需要返回當前頁的前6頁資料和後6頁的資料,例如:如果當前頁是第12頁,那麼就要返回12頁之前6頁Product(也就是第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頁的資料,就需要再次從資料庫中讀取,然後再次快取(如果事先沒有被快取)。
最後在快取中的資料如下:
然後呼叫方法:(偽碼)
上面傳入的是從第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>();然後分別把firstProductList,secondProductList遍歷,加入到resultProductList就行了。
就這麼簡單。
一個小的細節,導致了大的問題。
不要忽視每一個細節。不要做沒有比較的迴圈等操作,一定要審視程式碼,重構程式碼。
今天就寫到了這裡,囉嗦了這麼多,希望對大家有一點點的幫助。
如有不正確,或者認為不妥的地方,希望大家斧正!而且留下高見!謝謝!
版權為小洋和部落格園所有,歡迎轉載,轉載請標明出處給作者。