關於服務器緩存的思考


  我們在開發中,經常會用到各種緩存,比如Session、Application、HttpRuntime.Cache、Redis、Memcached、MongoDB、Riak等。而一般項目中使用緩存時,都是比較初級的,大多都是常見的Key-Value方式,通過依賴、時間、同步更新或直接刪除方法來管理緩存的過期。當然網上對於緩存的介紹絕大部分都是這方面的,而對於多級緩存、緩存與緩存相互關聯、表記錄與多緩存關聯、后端緩存與前端頁面緩存關聯、緩存名稱動態生成的緩存與其他緩存聯動處理、頻繁更新的緩存與其他緩存聯動問題......等等不同情況下該如果去管理這些緩存的知識點,我在園子里找了半天也沒有看到......而最近自己開發的項目中就碰到了緩存管理上的問題,所以發這篇貼子同大家討論一下有什么更好的解決方案。

 

  隨着項目參與人員數量的增加,大家經驗的不同和對緩存的認知不一樣,而項目為了達到在生產環境上能處理更大的並發和良好的性能,緩存的使用也越來越廣泛了。項目在老板、運營部門和項目經理的推動下,新功能、新需求不斷的推陳出新,代碼與復雜度也幾何級的爆增起來,緩存的使用幾乎充斥在所有代碼的調用當中,由於沒有專門寫一個處理插件對它進行統一管理,造成緩存管理開始有些混亂。

  比如對運行中的系統在某些情況下執行了清空全部緩存時,偶爾會發生一些小異常(有些數據讀取不正常);對同一結果重復使用緩存(A同事創建了一個緩存來存儲H任務執行結果的,而B同事不知道A同事創建過,也創建了一個),浪費內存空間;多個緩存依賴同一個緩存值的變動,某些人由於一些需要修改了所依賴緩存的名稱以及所影響的一些緩存時,個別非自己編寫的緩存沒有處理到或忘記處理了;某些緩存存儲內容依賴臨時表來創建的,緩存名稱有規律但不固定,而另外一些緩存的內容是根據這些緩存來計算的,當這些緩存更新時如何能自動同步所依賴它的緩存......

  當然上面這些情況或還有其他的情況單獨編寫代碼來處理肯定可以實現,只需要花多一些時間而已,問題是如果系統很龐大后,充斥差各種交叉關聯的緩存時,它們已像蜘蛛網一樣,動一發而牽動全身,寫這些處理代碼一個沒考慮好就會影響到其他內容。況且有很多時候更新臨時表記錄時,有些緩存名稱是不固定的(根據某些規則關聯到其他表記錄或日期等方式生成的緩存名稱),代碼並不可能智能識別要同步更新那些緩存。所以編寫一個強大的自動化緩存處理插件也勢在必行了。

  相信博客園團隊和其他一些大型的網站都有着自己一套完善的緩存管理辦法,由於自己知識廣度還不足夠,暫時沒有在網上發現有一套完善、高效的解決方案可以借鑒,只好自己來想辦法解決,所以先拋出一塊磚頭,看能否引來一堆美玉了,嘿嘿...

 

  下面我們先來了解一下緩存中的一些分類與名詞說明

  按名稱的命名可分為:

  固定名稱:通常以表名、字段名、功能名稱、前幾項的組合......等按各人的喜好來進行命名,調用時也直接方便

  有固定前綴(單個或多個可變后綴或可變后綴+固定后綴組合等):表名+記錄Id、臨時表名(表名按一定規則進行變動,比如后綴為年月、關聯表的Id等)、功能名稱+編碼......調用時需要動態傳入指定的參數,在不知道參數的情況下無法對該緩存進行操作

 

  按緩存依賴內容可分為:

  依賴指定表:指定表記錄增加、修改、刪除、更新時,需要同步更新該緩存內容

  依賴指定表中的某些或某條記錄:同上

  依賴多個表數據:同上

  依賴指定字段值:默字段值改變時,同步修改(主要用於更新頻率比較高的字段,比如頁面點擊計數等,如果需要用到緩存的,需要獨立出來存儲,以免更新時執行同步清除功能)

  依賴其他緩存:指定緩存值改動時,需要同步修改所依賴它的其他緩存值(比如依賴某些計算結果或狀態值;存儲某些臨時記錄等)

  ......

 

  按影響緩存值的操作可分為:

  數據表記錄的添加、修改、刪除;其他緩存值的更新變化;某些計算結果的變化等

  對於所依賴的內容變化后,相關緩存就需要同步更新,這樣又可分為實時同步和延時同步等方式

  緩存更新策略通過有:實時更新(主要針對記錄級別緩存,直接同步更新指定記錄;當然也可以整表更新,但這樣對程序執行性能有較大的影響)、超時檢測(比如緩存依賴其他緩存時,設置一個最后更新時間與獲取時間,通過比較兩個時間來確定緩存是否過期)、絕對時間過期(為緩存設定過期時間)、動態時間過期(緩存被訪問后過期時間順延)等。

 

  按緩存數據集合大小分:

  單值、單條記錄、小型數據集合、中型數據集合、大型數據集合、超大型數據集合

  對於緩存管理,數據集越小則存取與轉換速度越快,所以當數據集合過大時,就必須進行分割,將集合盡量分成小塊,提升緩存使用性能

 

  按緩存更新頻率分:

  固定值(指的是某些配置信息,存儲進緩存后它的值就不再變化)、偶爾更新、經常更新、頻繁更新

 

  按緩存級別分:

  無分級緩存、二級緩存

 

  其他:

  數據緩存、頁面緩存......

 

  一般來說,大部分人使用緩存都是直接key-value,這樣種操作簡單方便,無需太多的算法去處理。而這樣操作對於記錄集合比較大的數據(當然不能直接緩存大型或超大型數據)來說,頻繁的進行數據存取轉換也會消耗不少資源,所以有時需要在這個基礎上再加個二級緩存,將NOSQL緩存中讀取出來的數據載入IIS緩存中,程序直接編寫代碼調用,只有相關值更改時再重新加載一次,這樣就減少了對大數據轉換的性能損耗,當然程序的復雜度就大大提升了很多。

  對於使用二級緩存或依賴其他緩存的緩存來說,經常更新或頻繁更新影響是最大的,程序寫的不好直接會造成性能幾何級的下降(因為每一次更新都需要同步更新相關的所有緩存)。

 

  目前系統使用緩存狀況

  目前我的框架使用的是二級緩存,首先是使用Redis緩存存儲各種表記錄和值,然后對於某些數據量不小,修改不多但使用相對比較頻繁的數據,為了減少從Redis緩存中不停的讀取出來后進行反序列化操作,會從Redis緩存中讀取出來后將它存儲到IIS緩存中,當這些數據有更新時會實時同步更新IIS緩存中的數據,這些代碼都封裝在邏輯層中,統一使用模板生成,方便快捷。

  將業務數據量大的模塊進行了分割,每天按不同屬性生成N個臨時表,第二天凌晨會執行定時任務將對這些臨時表進行分析處理,去除無效數據后統一更新到歷史表中(歷史表按月生成)。業務數據分割后,每個表的記錄量都很少,它們都會存儲到相應的緩存中給前后端、服務、Socket等接口進行共同調用處理,目前是不同服務器所有功能共用一個緩存服務。各種系統服務會將常用的數據或記錄存儲到指定的緩存中,減少跨臨時表查詢操作或全表數據查詢操作。有些功能只使用當天要用到的一些最新數據,舊數據不再使用不需要參與查詢,也會使用單獨的緩存來進行存儲,緩存存儲按固定前綴+有規則的后綴進行管理。

  前端頁面則直接緩存在Redis中。

  ......

 

  緩存處理存在問題

  除了前面所講的緩存問題外,我們后端更新某些數據時(比如商品資料),就必須清除前端所有頁面緩存,全面重新生成(因為很多頁面都會展示商品相關信息),由於沒有一個綜合管理緩存的框架,在更新時就會將一些不必刪除的緩存也同步清除了。而在某些時候緩存被很多其他緩存所依賴時,清除該緩存也會清除一些多余的緩存,而不是精確定位。對於動態生成的可變后綴的緩存,在某些時候無法傳遞后綴參數時,將很難同步更新這些緩存內容。

 

  緩存處理解決思路

  對於出現上面的一些問題,在綜合考慮后,想寫個獨立的緩存處理插件來處理這些問題。主要通過配置來將緩存直接綁定數據表、字段、記錄Id、關聯緩存、頁面緩存等關聯內容,在這些緩存更新時同步清除對應的緩存模塊,以便其他緩存重啟緩存載入程序來加載相應數據到緩存中。在清除時有針對性,而不會跨界清空多余的緩存。不知大家有什么好的建議?

 

  下面是我的一些解決思路:

  1、首先緩存插件必須是一個獨立的程序

  2、調用必須通過統一的接口來進行處理

  3、緩存關聯必須通過配置來實現綁定

  4、緩存命名必須符合一定的規范

 

  具體實現辦法:

  獲取緩存:Get Cache => Check null => Load => Save(保存時會執行存儲數據的檢查,這里開發時要小心,避免出現死循環) => Return Cache (即取緩存時必須檢查指定緩存是否為空,為空時調用Load接口載入數據到緩存——Load函數功能由操作方實現,使用配置+IoC來調用,IoC配置文件和接口文件可以用T4模板直接生成——,然后將數據存儲到緩存中,最后返回所要的緩存;當然如果緩存不為空時直接返回緩存)

  存儲數據:Save Cache => Save => Check Relevance => Delete Relevance Cache (即存儲數據時,首先將數據保存到緩存中,然后讀取配置信息檢查該緩存與那些緩存關聯,如果存在關聯關系的緩存,則同步清除這些緩存,以便下次獲取這些緩存時能重新加載)

  刪除緩存:Delete Cache => Delete => Check Relevance => Delete Relevance Cache(刪除時執行遞歸調用,按正常來說,這種關聯應該不會太深)

  設置緩存參數:Set (修改緩存插件的一些全局配置)

 

  給外部直接調用的只有Get/Save/Delete,需要外部程序實現的接口暫定為Load這一個,里面實現數據加載的代碼

  在配置時,緩存依賴必須單向,避免出現死循環(可寫程序檢查配置)

  要處理好動態后綴緩存的處理,能通過參數控制智能判斷緩存的關聯。比如名稱為tablename_id的緩存,在執行Load時會將id截取出來傳遞給操作函數,那么載入時就只加載該id的記錄;

  對於更新頻繁的數據,比如頁面點擊計數等,如果需要用到緩存的,需要獨立出來存取和更新,以免更新時執行同步清除功能

  可以通過Set來開啟或關閉Load、Delete Relevance Cache功能等

  

 

  由於工作時間繁忙,本隨筆斷斷續續寫了好長時間,有些想法和思路沒有及時記下來都忘了,暫時想到這么多,思路也不是很成熟,不知大家有什么好的建議?這種處理模式是否存在什么問題?歡迎大家出來拍磚

 

 

 版權聲明:

  本文由AllEmpty原創並發布於博客園,歡迎轉載,未經本人同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,否則保留追究法律責任的權利。如有問題,可以通過1654937@qq.com 聯系我,非常感謝。

 

  發表本編內容,為了和大家共同學習共同進步,有興趣的朋友可以加加Q群:327360708 ,大家一起探討。

 

  更多內容,敬請觀注博客:http://www.cnblogs.com/EmptyFS/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM