麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 學院 > 開發設計 > 正文

使用緩存的9大誤區

2019-11-14 16:00:33
字體:
來源:轉載
供稿:網友

  如果說要對一個站點或者應用程序經常優化,可以說緩存的使用是最快也是效果最明顯的方式。一般而言,我們會把一些常用的,或者需要花費大量的資源或時間而產生的數據緩存起來,使得后續的使用更加快速。

  如果真要細說緩存的好處,還真是不少,但是在實際的應用中,很多時候使用緩存的時候,總是那么的不盡人意。換句話說,假設本來采用緩存,可以使得性能提升為100(這里的數字只是一個計量符號而已,只是為了給大家一個“量”的體會),但是很多時候,提升的效果只有80,70,或者更少,甚至還會導致性能嚴重的下降,這個現象在使用分布式緩存的時候尤為突出。

  在本篇文章中,我們將為大家講述導致以上問題的9大癥結,并且給出相對應的解決方案。文章以.NET為例子進行代碼的演示,對于來及其他技術平臺的朋友也是有參考價值的,只要替換相對應的代碼就行了!

  為了使得后文的闡述更加的方便,也使得文章更為的完整,我們首先來看看緩存的兩種形式:本地內存緩存,分布式緩存。

  首先對于本地內存緩存,就是把數據緩存在本機的內存中,如下圖1所示:

  從上圖中可以很清楚的看出:

  • 應用程序把數據緩存在本機的內存,需要的時候直接去本機內存進行獲取。
  • 對于.NET的應用而言,在獲取緩存中的數據的時候,是通過對象的引用去內存中查找數據對象的,也就說,如果我們通過引用獲取了數據對象之后,我們直接修改這個對象,其實我們真正的是在修改處于內存中的那個緩存對象。

  對于分布式的緩存,此時因為緩存的數據是放在緩存服務器中的,或者說,此時應用程序需要跨進程的去訪問分布式緩存服務器,如圖2:

  不管緩存服務器在哪里,因為涉及到了跨進程,甚至是跨域訪問緩存數據,那么緩存數據在發送到緩存服務器之前就要先被序列化,當要用緩存數據的時候,應用程序服務器接收到了序列化的數據之后,會將之反序列化。序列化與反序列化的過程是非常消耗CPU的操作,很多問題就出現在這上面。

  另外,如果我們把獲取到的數據,在應用程序中進行了修改,此時緩存服務器中的原先的數據是沒有修改的,除非我們再次將數據保存到緩存服務器。請注意:這一點和之前的本地內存緩存是不一樣的。

  對于緩存中的每一份數據,為了后文的講述方面,我們稱之為“緩存項“。

  普及完了這兩個概念之后,我們就進入今天的主題:使用緩存常見的9大誤區:

  1. 太過于依賴.NET默認的序列化機制
  2. 緩存大對象
  3. 使用緩存機制在線程間進行數據的共享
  4. 認為調用緩存API之后,數據會被立刻緩存起來
  5. 緩存大量的數據集合,而讀取其中一部分
  6. 緩存大量具有圖結構的對象導致內存浪費
  7. 緩存應用程序的配置信息
  8. 使用很多不同的鍵指向相同的緩存項
  9. 沒有及時的更新或者刪除再緩存中已經過期或者失效的數據

  下面,我們就每一點來具體的看看!

  太過于依賴.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的配置問題(對緩存內存的限制),我們有機會專門為大家分享這方面的知識。

  所以,每次在使用緩存數據的時候,要判斷是否存在,不然,會有很多的“找不到對象”的錯誤,產生一些我們認為的“奇怪而又合理的現象”。

  緩存大量的數據集合,而讀取其中一部分

  在很多時候,我們往往會緩存一個對象的集合,但是,我們在讀取的時候,只是每次讀取其中一部分。 我們舉個例子來說明這個問題(例子可能不是很恰當,但是足以說明問題)。

  在購物站點中,常見的操作就是查詢一些產品的信息,這個時候,如果用戶輸入了“25寸電視機”,然后查找相關的產品。這個時候,在后臺,我們可以查詢數據庫,找到幾百條這樣的數據,然后,我們將這幾百條數據作為一個緩存項緩存起來,代碼的代碼如下:

  同時,我們對找出的產品進行分頁的顯示,每次展示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信息,如下代碼所示:

  解決這個問題的方法也比較明顯,參看第一個問題的解決方案就可以了!

  緩存應用程序的配置信息

  因為緩存是有一套數據失效檢測周期的(之前說過,要么是固定時間失效,要么是相對時間失效),所以,很多的技術人員喜歡把一些動態變化的信息保存在緩存中,以充分利用緩存機制的這種特性,其中,緩存程序的配置信息就是其中一個例子。

  因為在應用的中的一些配置,可能會發生變化,最簡單的就是數據庫連接字符串了,如下代碼:

  當這樣設置之后,每隔一段時間緩存失效之后,就去重新讀取配置文件,這時候,可能此時的配置就和之前不一樣了,并且其他的地方都可以讀取緩存從而進行更新,特別是在多臺服務器上面部署同一個站點的時候,有時候,我們沒有及時的去修改每個服務器上面的站點的配置文件里面的信息,這個時候如何使用分布式緩存緩存配置信息,只要更新一個站點的配置文件,其他站點就全部修改了,技術人員皆大歡喜。OK,這確實看起來是個不錯的方法(在必要的時候可以采用一下),但是,不是所有的配置信息都要保持一樣的,而且還要考慮怎樣一個情況:如果緩存服務器出了問題,宕機了,那么我們所有使用這個配置信息的站點可能都會出問題。

  建議對于這些配置文件的信息,采用監控的機制,例如文件監控,每次文件發生變化,就重新加載配置信息。

  使用很多不同的鍵指向相同的緩存項

  我們有時候會遇到這樣的一個情況:我們把一個對象緩存起來,用一個鍵作為緩存鍵來獲取這個數據,之后,我們又通過一個索引作為緩存鍵來獲取這個數據,如下代碼所示:

  我們之所以這樣寫,主要因為我們會以多種方式來從緩存中讀取數據,例如在進行循環遍歷的時候,需要通過索引來獲取數據,例如index++等,而有些情況,我們可能需要通過其他的方式,例如,產品名來獲取產品的信息。

  如果遇到這樣的情況,那么就建議將這些多個鍵組合起來,形成如下的形式:

  另外一個常見的問題就是:相同的數據被緩存在不同的緩存項中,例如,如果用戶查詢尺寸為36寸的彩電,那么可能有可能一個編號為100的電視產品就在結果中,此時,我們將結果緩存。另外,用戶在查找一個生產廠家為TCL的電視,如果編號為100的電視產品又出現在結果中,我們把結果又緩存在另外一個緩存項中。這個時候,很顯然,出現了內存的浪費。

  對于這樣的情況,之前筆者采用的方法就是,在緩存中創建了一個索引列表,如圖所示:

  當然,這其中有很多的細節和問題需要解決,這里就不一一述說,要看各自的應用和情況而定! 也非常歡迎大家提供更好的方法。

  沒有及時的更新或者刪除再緩存中已經過期或者失效的數據

  這種情況應該是使用緩存最常見的問題,例如,如果我們現在獲取了一個Customer的所有沒有處理的訂單的信息,然后緩存起來,類似的代碼如下:

  之后,用戶的一個訂單被處理了,但是緩存還沒有更新,那么這個時候,緩存中的數據就已經有問題!當然,我這里只是列舉的最簡單的場景,大家可以聯想自己應用中的其他產品,很有可能會出現緩存中的數據和實際數據庫中的不一樣。

  現在很多的時候,我們已經容忍了這種短時間的不一致的情況。其實對于這種情況,沒有非常完美的解決方案,如果要做,倒是可以實現,例如每次修改或者刪除一個數據,就去遍歷緩存中的所有數據,然后進行操作,但是這樣往往得不償失。另外一個折中的方法就是,判斷數據的變化周期,然后盡可能的將緩存的時間變短一點。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 精品国产中文字幕 | 中文字幕22页 | 国产一区二区精品在线观看 | 九九热欧美 | 国产免费高清在线视频 | 99欧美视频 | 男女无套免费视频 | 91小视频在线观看免费版高清 | 手机免费看一级片 | 久久国产精品久久久久久电车 | 麻豆小视频在线观看 | 日韩 综合| 午夜精品久久久久久中宇 | 视频在线色 | 欧美日韩亚洲不卡 | 国产精品久久久麻豆 | 污在线观看网站 | 亚洲视频在线免费看 | 日本精品久久久一区二区三区 | 欧美一级淫片免费播放口 | 国产视频在线免费观看 | 亚洲91在线| 少妇的肉体k8经典 | 免费播放欧美毛片 | 激情视频导航 | 深夜小视频在线观看 | 把娇妻调教成暴露狂 | 欧美一级黄色片免费观看 | 久久久一区二区精品 | 伊久在线 | 亚洲精品午夜在线 | 欧洲精品久久久久69精品 | 免费在线观看亚洲 | 最新中文字幕日本 | 亚洲成人黄色片 | 91精品久久久久久久 | 1314av| 爱逼av | 一级毛片电影院 | 久久久久久久久久一本门道91 | 久久精品中文字幕一区二区 |