標籤:blog http 使用 sp strong on 資料 問題 log
如果說要對一個網站或者應用程式經常最佳化,可以說緩衝的使用是最快也是效果最明顯的方式。一般而言,我們會把一些常用的,或者需要花費大量的資源或時間而產生的資料緩衝起來,使得後續的使用更加快速。
如果真要細說緩衝的好處,還真是不少,但是在實際的應用中,很多時候使用緩衝的時候,總是那麼的不盡人意。換句話說,假設本來採用緩衝,可以使得效能提升為100(這裡的數字只是一個計量符號而已,只是為了給大家一個“量”的體會),但是很多時候,提升的效果只有80,70,或者更少,甚至還會導致效能嚴重的下降,這個現象在使用分布式緩衝的時候尤為突出。
在本篇文章中,我們將為大家講述導致以上問題的9大癥結,並且給出相對應的解決方案。文章以.NET為例子進行代碼的示範,對於來及其他技術平台的朋友也是有參考價值的,只要替換相對應的代碼就行了!
為了使得後文的闡述更加的方便,也使得文章更為的完整,我們首先來看看緩衝的兩種形式:本地記憶體緩衝,分布式緩衝。
首先對於本地記憶體緩衝,就是把資料緩衝在原生記憶體中,如1所示:
從中可以很清楚的看出:
- 應用程式把資料緩衝在原生記憶體,需要的時候直接去本機記憶體進行擷取。
- 對於.NET的應用而言,在擷取緩衝中的資料的時候,是通過對象的引用去記憶體中尋找資料對象的,也就說,如果我們通過引用擷取了資料對象之後,我們直接修改這個對象,其實我們真正的是在修改處於記憶體中的那個緩衝對象。
對於分布式的緩衝,此時因為緩衝的資料是放在快取服務器中的,或者說,此時應用程式需要跨進程的去訪問分布式快取服務器,2:
不管快取服務器在哪裡,因為涉及到了跨進程,甚至是跨域訪問快取資料,那麼快取資料在發送到快取服務器之前就要先被序列化,當要用快取資料的時候,應用程式伺服器接收到了序列化的資料之後,會將之還原序列化。序列化與還原序列化的過程是非常消耗CPU的操作,很多問題就出現在這上面。
另外,如果我們把擷取到的資料,在應用程式中進行了修改,此時快取服務器中的原先的資料是沒有修改的,除非我們再次將資料儲存到快取服務器。請注意:這一點和之前的本地記憶體緩衝是不一樣的。
對於緩衝中的每一份資料,為了後文的講述方面,我們稱之為“快取項目“。
普及完了這兩個概念之後,我們就進入今天的主題:使用緩衝常見的9大誤區:
- 太過於依賴.NET預設的序列化機制
- 緩衝大對象
- 使用緩衝機制線上程間進行資料的共用
- 認為調用緩衝API之後,資料會被立刻緩衝起來
- 緩衝大量的資料集合,而讀取其中一部分
- 緩衝大量具有圖結構的對象導致記憶體浪費
- 緩衝應用程式的配置資訊
- 使用很多不同的鍵指向相同的快取項目
- 沒有及時的更新或者刪除再緩衝中已經到期或者失效的資料
下面,我們就每一點來具體的看看!
太過於依賴.NET預設的序列化機制
當我們在應用中使用跨進程的緩衝機制,例如分布式緩衝memcached或者微軟的AppFabric,此時資料被緩衝在應用程式之外的進程中。每次,當我們要把一些資料緩衝起來的時候,緩衝的API就會把資料首先序列化為位元組的形式,然後把這些位元組發送給快取服務器去儲存。同理,當我們在應用中要再次使用緩衝的資料的時候,快取服務器就會將緩衝的位元組發送給應用程式,而緩衝的用戶端類庫接受到這些位元組之後就要進行還原序列化的操作了,將之轉換為我們需要的資料對象。
另外還有三點需要注意的就是:
- 這個序列化與還原序列化的機制都是發生在應用程式伺服器上的,而快取服務器只是負責儲存而已。
- .NET中的預設使用的序列化機制不是最優的,因為它要使用反射機制,而反射機制是是非常耗CPU的,特別是當我們緩衝了比較複雜的資料對象的時候。
基於這個問題,我們要自己選擇一個比較好的序列化方法來儘可能的減少對CPU的使用。常用的方法就是讓對象自己來實現ISerializable介面。
首先我們來看看預設的序列化機制是怎麼樣的。3:
然後,我們自己來實現ISerializable介面,如4所示:
我們自己實現的方式與.NET預設的序列化機制的最大區別在於:沒有使用反射。自己實現的這種方式速度可以是預設機制的上百倍。
可能有人認為沒有什麼,不就是一個小小的序列化而已,有必要小題大做嗎?
在開發一個高效能應用(例如網站)而言,從架構,到代碼的編寫,以及後面的部署,每一個地方都需要最佳化。一個小問題,例如這個序列化的問題,初看起來不是問題,如果我們網站應用的訪問量是百萬,千萬,甚至更進階別的,而這些訪問需要去擷取一些公用的緩衝的資料,這個之前所謂的小問題就不小了!
下面,我們來看第二個誤區。
緩衝大對象
有時候,我們想要把一些大對象緩衝起來,因為產生一次大對象的代價很大,我們需要產生一次,儘可能的多次使用,從而提升響應。
提到大對象,這裡就很有必要對其進行一個比較深入的介紹了。在.NET中,所謂的大對象,就是指的其佔用的記憶體大於了85K的對象,下面通過一個比較將問題說清楚。
如果現在有一個Person類的集合,定義為List<Person>,每個Person對象佔用1K的記憶體,如果這個Person集合中包含了100個Person對象執行個體,那麼這個集合是否是大對象呢?
回答是:不是!
因為集合中只是包含的Person對象執行個體的引用而言,即,在.NET的託管堆上面,這個Person集合分配的記憶體大小也就是100個引用的大小而言。
然後,對於下面的這個對象,就是大對象了: byte[] data = new byte[87040](85 * 1024 = 87040)。
說到了這裡,那就就談談,為什麼說:產生一次大對象的代價很大。
因為在.NET中,大對象是分配在大對象託管堆上面的(我們簡稱為“大堆”,當然,還有一個對應的小堆),而這個大堆上面的對象的分配機制和小堆不一樣:大堆在分配的時候,總是去需找合適的記憶體空間,結果就是導致出現記憶體片段,導致記憶體不足!我們用一個圖來描述一下,5所示:
非常明了,在圖5中:
- 記憶體回收機制不會在回收對象之後壓縮大堆(小堆是壓縮的)。
- 指派至的時候,需要去遍曆大堆,去需找合適的空間,遍曆是要花成本的。
- 如果某些空間小於85K,那麼就不能分配了,只能白白浪費,也導致記憶體片段。
講完了這些之後,我們言歸正傳,來看看大對象的緩衝。
正如之前講過,將對象緩衝和讀取的時候是要進行序列化與還原序列化的,緩衝的對象越大(例如,有1M等),整個過程中就消耗更多的CPU。
對於這樣的大對象,要看它使用的是否很頻繁,是否是公用的資料對象,還是每個使用者都要產生的。因為我們一旦緩衝了(特別在分布式緩衝中),就需要同時消耗快取服務器的記憶體與應用程式伺服器的CPU。如果使用的不頻繁,建議每次產生!如果是公用的資料,那麼建議多多的測試:將生產大對象的成本與緩衝它的時候消耗的記憶體和CPU的成本進行比較,選擇成本小的!如果是每個使用者都要產生的,看看是否可以分解,如果實在不能分解,那麼緩衝,但是及時的釋放!
使用緩衝機制線上程間進行資料的共用
當資料放在緩衝中的時候,我們程式的多個線程都可以訪問這個公用的地區。多個線程在訪問快取資料的時候,會產生一些競爭,這也是多線程中常常發生的問題。
下面我們分別從本地記憶體緩衝與分布式緩衝兩個方面介紹競爭的帶來的問題。
看下面的一段代碼:
對於本地記憶體緩衝,對於上面的代碼,當這個三個線程運行起來之後,線上程1中,item的值很多時候可能為1,線程2可能是2,線程3可能是3。當然,這不一定!只是大多數情況下的可能值!
如果是對於分布式緩衝,就不好說了!因為資料的修改不是立刻發生在原生記憶體中的,而是經過了一個跨進程的過程。
有一些緩衝模組已經實現了加鎖的方式來解決這個問題,例如AppFabric。大家在修改快取資料的時候要特別注意這一點。
認為調用緩衝API之後,資料會被立刻緩衝起來
有時候,當我們調用了緩衝的API之後,我們就會認為:資料已經被換成了,之後就可以直接讀取緩衝中的資料。儘管情況很多時候如此,但是不是絕對的!很多的問題就是這樣產生的!
我們通過一個例子來講解。
例如,對於一個ASP.NET 應用而言,如果我們在一個按鈕的Click事件中調用了緩衝API,然後在頁面呈現的時候,就去讀取緩衝,代碼如下:
上面的代碼照道理來說是對的,但是會發生問題。按鈕點擊之後回傳頁面,然後呈現頁面的時候顯示資料,流程沒有問題。但是沒有考慮到這樣一個問題:如果伺服器的記憶體緊張,而導致進行伺服器記憶體的回收,那麼很有可能緩衝的資料就沒有了!
這裡有朋友就要說了:記憶體回收這麼快?
這主要看我們的一些設定和處理。
一般而言,緩衝機制都是會設定絕對到期時間與相對到期時間,二者的區別,大家應很清楚,我這裡不多說。對於上面的代碼而言,如果我們設定的是絕對到期時間,假設1分鐘,如果頁面處理的非常慢,時間超過了1分鐘,那麼等到呈現的時候,可能緩衝中的資料已經沒有了!
有時候,即使我們在第一行代碼中緩衝了資料,那麼也許在第三行代碼中,我們去緩衝讀取資料的時候,就已經沒有了。這或許是因為在伺服器記憶體壓力很大的,緩衝機制將最少訪問的資料直接清掉。或者伺服器CPU很忙,網路也不好,導致資料沒有被即使的序列化儲存到快取服務器中。
另外,對於ASP.NET而言,如果使用了本地記憶體緩衝,那麼,還涉及到IIS的配置問題(對緩衝記憶體的限制),我們有機會專門為大家分享這方面的知識。
所以,每次在使用快取資料的時候,要判斷是否存在,不然,會有很多的“找不到對象”的錯誤,產生一些我們認為的“奇怪而又合理的現象”。
關於作者
汪洋,現任惠普架構師、資訊分析師《NET應用架構設計:模式、原則與實踐》作者。上海益思研發管理諮詢有限公司首席軟體架構專家,軟體諮詢組副組長。
[轉]使用緩衝的9大誤區(上)