構(gòu)建高性能ASP.NET站點(diǎn)之三 細(xì)節(jié)決定成敗
前言:曾經(jīng)就因為一個小小的疏忽,從而導(dǎo)致了服務(wù)器崩潰了,后來才發(fā)現(xiàn):原來就是因為一個循環(huán)而導(dǎo)致的,所以,對“注意細(xì)節(jié)“這一說法是深有感觸。
系列文章鏈接:
構(gòu)建高性能ASP.NET站點(diǎn)之一 剖析頁面的處理過程(前端)
構(gòu)建高性能ASP.NET站點(diǎn)之二 優(yōu)化HTTP請求(前端)
構(gòu)建高性能ASP.NET站點(diǎn)之三 細(xì)節(jié)決定成敗
構(gòu)建高性能ASP.NET站點(diǎn) 第五章—性能調(diào)優(yōu)綜述(前篇)
大型高性能ASP.NET系統(tǒng)架構(gòu)設(shè)計
構(gòu)建高性能ASP.NET站點(diǎn) 第五章—性能調(diào)優(yōu)綜述(中篇)
構(gòu)建高性能ASP.NET站點(diǎn) 第五章—性能調(diào)優(yōu)綜述(后篇)
構(gòu)建高性能ASP.NET站點(diǎn) 第六章—性能瓶頸診斷與初步調(diào)優(yōu)(上篇)—識別性能瓶頸
構(gòu)建高性能ASP.NET站點(diǎn) 第六章—性能瓶頸診斷與初步調(diào)優(yōu)(下前篇)—簡單的優(yōu)化措施
構(gòu)建高性能ASP.NET站點(diǎn) 第六章—性能瓶頸診斷與初步調(diào)優(yōu)(下后篇)—減少不必要的請求
構(gòu)建高性能ASP.NET站點(diǎn) 第七章 如何解決內(nèi)存的問題(前中篇)—托管資源優(yōu)化—監(jiān)測CLR性能
構(gòu)建高性能ASP.NET站點(diǎn) 第七章 如何解決內(nèi)存的問題(前篇)—托管資源優(yōu)化—垃圾回收機(jī)制深度剖析
構(gòu)建高性能ASP.NET站點(diǎn) 第七章 如何解決內(nèi)存的問題(前中篇)—托管資源優(yōu)化—監(jiān)測CLR性能
本篇的議題如下:
問題的描述
細(xì)節(jié)的重要性
問題的描述
首先,描述一下故事的背景:(希望大家耐心的故事讀完)
在網(wǎng)站中,網(wǎng)頁中的分頁控件每次顯示10條數(shù)據(jù),每次點(diǎn)擊下一頁,就再次去取下一個10條數(shù)據(jù)。至于分頁的方法怎樣做,方法有很多,相信這點(diǎn)大家都知道。
過程是這樣的:在用戶請求數(shù)據(jù)的時候(考慮到了用戶的操作和網(wǎng)站的訪問量)我會第一次取出500條數(shù)據(jù),然后把數(shù)據(jù)放在緩存中,也就是說,我取出了50頁的數(shù)據(jù),放在緩存中,這樣如果,以后用戶請求第一頁到第49頁的時候,就直接從緩存中拿數(shù)據(jù)。
如下圖:
采用鍵值對的形式:字典保存
如果用戶請求到了49頁以后,那么就再次從數(shù)據(jù)庫中取出下一個數(shù)據(jù)塊(包含501到1000數(shù)據(jù)),然后,現(xiàn)在內(nèi)存中就有了1000條數(shù)據(jù)。
至于緩存多久,數(shù)據(jù)什么失效,失效后怎么做,這里暫不談?wù)?。(網(wǎng)站在這種緩存策略下運(yùn)行的很好)。
代碼如下:
代碼的意思很清楚,從緩存中拿數(shù)據(jù),如果緩存中沒有對應(yīng)的數(shù)據(jù),那么就先從數(shù)據(jù)庫中拿500條數(shù)據(jù),然后放在緩存中,最后返回10條數(shù)據(jù)。
后來,因為某些功能的需要,需要返回當(dāng)前頁的前6頁數(shù)據(jù)和后6頁的數(shù)據(jù),例如:如果當(dāng)前頁是第12頁,那么就要返回12頁之前6頁Product(也就是第6,7,8,9,10,11頁的數(shù)據(jù)),和第12頁后的頁的Product(第13,14,15,16,17,18頁的數(shù)據(jù))。
如下:
當(dāng)然,如果當(dāng)前頁是第5頁,那么就把之前所有5頁的數(shù)據(jù)都返回,另外再加上第5頁之后的6頁數(shù)據(jù)。
這里就可能涉及到跨塊獲取數(shù)據(jù),如:
如果當(dāng)前頁是第48頁的時候,那么返回前6頁數(shù)據(jù)是沒有什么問題的,那么后6頁的數(shù)據(jù)就不足了,因為49,40也得數(shù)據(jù)可以從緩存的數(shù)據(jù)塊中取到,至于51,52,53,54頁的數(shù)據(jù),就需要再次從數(shù)據(jù)庫中讀取,然后再次緩存(如果事先沒有被緩存)。
最后在緩存中的數(shù)據(jù)如下:
上面?zhèn)魅氲氖菑牡?/span>42頁開始的數(shù)據(jù),也就是第48頁的前6頁和后6頁的數(shù)據(jù)。
這個方法的內(nèi)部實現(xiàn)是這樣的:
1. 首先從第一個數(shù)據(jù)塊中取出42頁到50頁的數(shù)據(jù)
取出數(shù)據(jù)后保存在一個List<Product> firstProductList;
2. 從第二個數(shù)據(jù)塊中取出從51頁到54頁(如果第二個數(shù)據(jù)塊在緩存不存在,就去數(shù)據(jù)庫中取501-1000條,然后再放在緩存的第二個數(shù)據(jù)塊中)。
保存在第二個List<Product> secondProductList
3. 然后把兩個list合并,返回結(jié)果。例如
secondProductList.Foreach(u=>firstProductList.Add(u));
基本的實現(xiàn)就是這樣,看起來還行,也比較的合理,但是就是因為這個操作,從而導(dǎo)致服務(wù)器內(nèi)存溢出。
大家想想看是什么原因。
細(xì)節(jié)的重要性
其實緩存的數(shù)據(jù)不是很多,是不足以讓服務(wù)器內(nèi)存溢出的,但是服務(wù)器還是出現(xiàn)了out of memory的異常。之前一直跑的很好,就是改了代碼之后才出現(xiàn)問題的。
其實這就是由于一個最基本的錯誤產(chǎn)生的:引用類型。
下面就來分析下:
首先是從第一個數(shù)據(jù)塊中取出數(shù)據(jù),然后用
List<Product> firstProductList 引用指向取出的數(shù)據(jù)
然后從第二個數(shù)據(jù)塊中取出數(shù)據(jù),用
List<Product> secondProductList指向數(shù)據(jù)的引用
如下圖
secondProductList.Foreach(u=>firstProductList.Add(u));
把secondProductList中的數(shù)據(jù)加入到firstProductList中,就因為是引用類型,其實實際操作的結(jié)果是:不斷的在改變第一個數(shù)據(jù)塊中的數(shù)據(jù),使得第一個數(shù)據(jù)塊中的數(shù)據(jù)逐漸的變多。
現(xiàn)在當(dāng)前頁是48頁,采用上面的操作,致使第一個數(shù)據(jù)塊中的數(shù)據(jù)增加了60條,
如果用戶再次翻頁,到了49頁,那么第一個數(shù)據(jù)塊中的數(shù)據(jù)又增多了60條
依此類推,最后導(dǎo)致了服務(wù)器內(nèi)存的不足,致使服務(wù)器崩潰了。原本的“功臣”----緩存卻成為了罪魁禍?zhǔn)住?/span>
其實這個問題的解決,只要改變一點(diǎn)點(diǎn)的代碼就行了:
List<Product> firstProductList;
List<Product> secondProductList;
然后
List<Product> resultProductList=new List<Product>();然后分別把firstProductList,secondProductList遍歷,加入到resultProductList就行了。
就這么簡單。
一個小的細(xì)節(jié),導(dǎo)致了大的問題。
不要忽視每一個細(xì)節(jié)。不要做沒有比較的循環(huán)等操作,一定要審視代碼,重構(gòu)代碼。
今天就寫到了這里,啰嗦了這么多,希望對大家有一點(diǎn)點(diǎn)的幫助。
如有不正確,或者認(rèn)為不妥的地方,希望大家斧正!而且留下高見!謝謝!
聯(lián)系客服