天天看點

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

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

本篇的議題如下:

問題的描述

細節的重要性

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

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

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

如下圖:

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

第一個資料塊:

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

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

至于緩存多久,資料什麼失效,失效後怎麼做,這裡暫不談論。(網站在這種緩存政策下運作的很好)。 

代碼如下:

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

代碼的意思很清楚,從緩存中拿資料,如果緩存中沒有對應的資料,那麼就先從資料庫中拿500條資料,然後放在緩存中,最後傳回10條資料。

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

如下:

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

 

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

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

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

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

最後在緩存中的資料如下:

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

然後調用方法:(僞碼)

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

上面傳入的是從第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指向資料的引用

如下圖

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

在第三步中采用

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

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

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

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

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

List<Product> firstProductList;  

List<Product> secondProductList; 

然後

List<Product> resultProductList=new List<Product>();然後分别把firstProductList,secondProductList周遊,加入到resultProductList就行了。

就這麼簡單。

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

本文轉自cnn23711151CTO部落格,原文連結:http://blog.51cto.com/cnn237111/501441,如需轉載請自行聯系原作者