伺服器快取

lm_y發表於2017-09-07
 我們在開發中,經常會用到各種快取,比如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功能等

相關文章