來源:汪洋@infoQ
本文在《使用快取的9大誤區(上)》的基礎上繼續討論了使用快取的幾個誤區,包括:快取大量的資料集合,而讀取其中一部分;快取大量具有圖結構的物件導致記憶體浪費;快取應用程式的配置資訊;使用很多不同的鍵指向相同的快取項;沒有及時的更新或者刪除再快取中已經過期或者失效的資料。
快取大量的資料集合,而讀取其中一部分
在很多時候,我們往往會快取一個物件的集合,但是,我們在讀取的時候,只是每次讀取其中一部分。 我們舉個例子來說明這個問題(例子可能不是很恰當,但是足以說明問題)。
在購物站點中,常見的操作就是查詢一些產品的資訊,這個時候,如果使用者輸入了“25寸電視機”,然後查詢相關的產品。這個時候,在後臺,我們可以查詢資料庫,找到幾百條這樣的資料,然後,我們將這幾百條資料作為一個快取項快取起來,程式碼的程式碼如下:
1 |
myCache["25products"] =products; |
同時,我們對找出的產品進行分頁的顯示,每次展示10條。其實在每次分頁的時候,我們都是根據快取的鍵去獲取資料,然後選擇下一個10條資料,然後顯示。
如果是使用本地記憶體快取,那麼這可能不是什麼問題,如果是採用分散式快取,問題就來了。下圖可以清楚的說明這個過程,如圖所示:
相信大家看完這個圖,然後結合之前的講述應該很清楚了問題所在了:每次都按照快取鍵獲取全部資料,然後在應用伺服器那裡反序列化全部資料,但是隻是取其中10條。
這裡可以將資料集合再次拆分,分為例如25-0-10-products,25-11-20-products等的快取項,如下圖所示:
當然,查詢和快取的方式有很多,拆分的方式也有很多,這裡這是給出一些常見的問題!
快取大量具有圖結構的物件導致記憶體浪費
為了更好的說明這個問題,我們首先看到下面的一個類結構圖,如圖:
如果我們要把一些Customer資料快取起來,這裡就可以可能出現兩個問題:
1、由於使用.NET的預設序列化機制,或者沒有適當的加入相應Attribute(屬性),使得快取了一些原本不需要快取的資料。
2、將Customer快取的時候,同時,為了更快的獲取Customer的Order資訊,將Order資訊快取在了另外一個快取項中,導致同一份資料被快取兩次。
下面,我們就分別來看看這兩個問題。
首先看到第一個。如果我們使用分散式快取來快取一些Customer的資訊的時候,如果我們沒有自己重新Customer的序列化機制,而是採用的預設的,那麼序列化機制在序列化Customer的時候,會將Customer所引用的物件也序列化,然後在序列化被序列化物件中的其他引用物件,最後的結果就是:Customer被序列化,Customer的Order資訊被序列化,Order引用的OrderItem被序列化,最後OrderItem引用的Product也會序列化。
整個物件圖全部被序列化了,如果這種情況是我們想要的,那麼沒有問題;如果不是的,那麼,我們就浪費了很多的資源了,解決的方法有兩個:第一,自己實現序列化,自己完全控制哪些物件需要序列化,我們前面已經講過了;第二,如果使用預設的序列化機制,那麼在不要需要序列化的物件上面加上[NonSerialized]標記。
下面,我們看到第二個問題。這個問題主要是由於第一個問題引起的:原本在快取Customer的時候,已經將Customer的其他資訊,例如Order,Product已經快取了。但是很多的技術人員不清楚這一點,然後又把Customer的Order資訊去快取在其他的快取項,使用的使用就根據Customer的標識,例如ID去快取中獲取Order資訊,如下程式碼所示:
1 2 3 4 |
var customer = GetCustomerByName("aqilesharp"); var customer.Orders = GetOrderByCustomer(customer.ID); cache.Add("Customer",customer); cache.Add("Orders",orders); |
解決這個問題的方法也比較明顯,參看第一個問題的解決方案就可以了!
快取應用程式的配置資訊
因為快取是有一套資料失效檢測週期的(之前說過,要麼是固定時間失效,要麼是相對時間失效),所以,很多的技術人員喜歡把一些動態變化的資訊儲存在快取中,以充分利用快取機制的這種特性,其中,快取程式的配置資訊就是其中一個例子。
因為在應用的中的一些配置,可能會發生變化,最簡單的就是資料庫連線字串了,如下程式碼:
1 |
var connectionString = cache.Get("ConnectionString"); |
當這樣設定之後,每隔一段時間快取失效之後,就去重新讀取配置檔案,這時候,可能此時的配置就和之前不一樣了,並且其他的地方都可以讀取快取從而進行更新,特別是在多臺伺服器上面部署同一個站點的時候,有時候,我們沒有及時的去修改每個伺服器上面的站點的配置檔案裡面的資訊,這個時候如何使用分散式快取快取配置資訊,只要更新一個站點的配置檔案,其他站點就全部修改了,技術人員皆大歡喜。OK,這確實看起來是個不錯的方法(在必要的時候可以採用一下),但是,不是所有的配置資訊都要保持一樣的,而且還要考慮怎樣一個情況:如果快取伺服器出了問題,當機了,那麼我們所有使用這個配置資訊的站點可能都會出問題。
建議對於這些配置檔案的資訊,採用監控的機制,例如檔案監控,每次檔案發生變化,就重新載入配置資訊。
使用很多不同的鍵指向相同的快取項
我們有時候會遇到這樣的一個情況:我們把一個物件快取起來,用一個鍵作為快取鍵來獲取這個資料,之後,我們又通過一個索引作為快取鍵來獲取這個資料,如下程式碼所示:
1 2 3 4 |
var product = GetProduct(); cache["prod"] = product; //... cache[i] = product; |
我們之所以這樣寫,主要因為我們會以多種方式來從快取中讀取資料,例如在進行迴圈遍歷的時候,需要通過索引來獲取資料,例如index++等,而有些情況,我們可能需要通過其他的方式,例如,產品名來獲取產品的資訊。
如果遇到這樣的情況,那麼就建議將這些多個鍵組合起來,形成如下的形式:
1 2 3 4 5 |
var product = GetProduct(); cache["prod_1"] = product; //... var index = 1; var cacheProduct = cache["prod"+index]; |
另外一個常見的問題就是:相同的資料被快取在不同的快取項中,例如,如果使用者查詢尺寸為36寸的彩電,那麼可能有可能一個編號為100的電視產品就在結果中,此時,我們將結果快取。另外,使用者在查詢一個生產廠家為TCL的電視,如果編號為100的電視產品又出現在結果中,我們把結果又快取在另外一個快取項中。這個時候,很顯然,出現了記憶體的浪費。
對於這樣的情況,之前筆者採用的方法就是,在快取中建立了一個索引列表,如圖所示:
當然,這其中有很多的細節和問題需要解決,這裡就不一一述說,要看各自的應用和情況而定! 也非常歡迎大家提供更好的方法。
沒有及時的更新或者刪除再快取中已經過期或者失效的資料
這種情況應該是使用快取最常見的問題,例如,如果我們現在獲取了一個Customer的所有沒有處理的訂單的資訊,然後快取起來,類似的程式碼如下:
1 2 |
var unProcessedOrders = GetOrders(Orderstatus.Pending); cache.Add["pengingOrders"]unProcessedOrders; |
之後,使用者的一個訂單被處理了,但是快取還沒有更新,那麼這個時候,快取中的資料就已經有問題!當然,我這裡只是列舉的最簡單的場景,大家可以聯想自己應用中的其他產品,很有可能會出現快取中的資料和實際資料庫中的不一樣。
現在很多的時候,我們已經容忍了這種短時間的不一致的情況。其實對於這種情況,沒有非常完美的解決方案,如果要做,倒是可以實現,例如每次修改或者刪除一個資料,就去遍歷快取中的所有資料,然後進行操作,但是這樣往往得不償失。另外一個折中的方法就是,判斷資料的變化週期,然後儘可能的將快取的時間變短一點。
關於作者
汪洋,現任惠普架構師、資訊分析師《NET 應用架構設計:模式、原則與實踐》作者。上海益思研發管理諮詢有限公司首席軟體架構專家,軟體諮詢組副組長。