注解式兩級緩存服務框架設計與構建
前言
緩存在現代計算機系統中無處不在,各式各樣硬件和軟件的組合構成和管理著緩存,一個編寫良好的計算機程序傾向于展示出良好的局部性。
在高性能服務架構設計中,緩存是一個不可或缺的環節。以Java體系為例,我們從傳統的硬編碼方式使用緩存到基于注解的spring-cache框架,確實大大提升了我們的效率,代碼也更加的簡潔易維護。
但隨著越來越多的項目使用spring-cache,場景越來越復雜,我們逐漸發現緩存配置代碼重復、緩存策略不能在注解上直接配置、不支持多級緩存、不支持自動刷新緩存等問題逐漸突顯。
基于這些在業務中遇到的問題點,我們構建了一套注解式兩級緩存服務框架。在實際設計和構建過程中積累了一些經驗,借此機會分享給大家,希望對業務中使用緩存尤其使用spring-cache場景的可以提供一些幫助。
1. spring-cache簡介
Spring 3.1之后,引入了注解緩存技術,其本質上不是一個具體的緩存實現方案,而是一個對緩存使用的抽象,不僅能夠使用SpEL(Spring Expression Language)來定義緩存的key和各種condition,還提供開箱即用的緩存臨時存儲方案,也支持和主流的專業緩存集成。
事物都有兩面性,優點如此優秀,那么缺點或不足是否也是如此的突出呢?
- spring-cache問題點(我們認為)
- 不支持緩存策略在注解上設置,每個方法的緩存策略需要單獨硬編碼方式配置
- 不支持多級緩存
- 不支持自動刷新緩存
- 不支持緩存統計看板
- 不支持熔斷降級
- 不支持數據壓縮
- 代碼重復不易維護
基于以上我們認為的問題點,我們造了一個輪子,來試圖解決這些問題,這個輪子就是“注解式兩級緩存框架”。
2. 注解式兩級緩存框架簡介
- 是一個注解式兩級緩存框架:通過注解實現聲明式的方法緩存,使用方式和spring-cache類似,提供了比spring-cache更加強大的注解。
- 是一個全新的注解式兩級緩存框架:一級緩存使用本地緩存(目前只支持Caffeine,后續可擴展),二級緩存使用集中式緩存(目前只支持Redis)。目前支持三種緩存策略:
只使用一級緩存
只使用二級緩存
同時用兩級緩存
2.1全部特性
- 支持TTL在注解上直接配置
- 支持本地緩存容量在注解上直接配置
- 支持condition在注解上直接配置,指定符合條件的情況下才緩存
- 支持緩存Key的SpEL表達式、自定義生成策略(已提供默認生成策略)
- 支持只使用一級緩存或者只使用二級緩存或者使用兩級緩存
- 支持value序列化策略配置,默認GenericJackson2JsonRedisSerializer
- 支持異步加載緩存的方式
- 支持自動刷新緩存
- redis客戶端選擇
- 支持熔斷與降級 --- 延遲支持
- 支持緩存數據壓縮 --- 延遲支持
- 支持緩存一致性 --- 延遲支持
- 支持緩存監控統計看板 --- 延遲支持
- 支持自定義緩存中間件 --- 延遲支持
- 支持緩存接口用于手工緩存操作 --- 延遲支持
前菜我們品完了,接下來我們開始正餐,一步步介紹下設計思路,聊下如何站在spring-cache巨人肩膀上,試圖解決上述問題點的。
3. 注解式兩級緩存框架架構設計
3.1注解@EnableCache
1、注解@EnableCache導入CacheConfigurationSelector。
CacheConfigurationSelector向容器內注入了AutoProxyRegistrar和ProxyCacheAutoConfiguration這兩個Bean
2、AutoProxyRegistrar會確保容器中存在一個自動代理創建器(APC),緩存的代理對象最終是委托給自動代理創建器來完成。
AutoProxyRegistrar在容器啟動階段對每個bean創建進行處理,如果bean中有方法標記了cache注解,為其創建代理對象, 包裹定義的CacheOperationSourceAdvisor bean
3、ProxyCacheAutoConfiguration向容器定義如下基礎設施bean。
CacheOperationSourceAdvisor 用于管理CacheOperationSource和CacheInterceptor, CacheOperationSource 用于獲取方法調用時最終應用的Cache注解的元數據, CacheInterceptor 包裹在目標bean外面用于操作Cache的AOP Advice
3.2攔截器
由于AutoProxyRegistrar在容器啟動階段會對標有cache注解的bean創建代理對象,這時我們可以獲取到具體方法和注解元數據, 我們針對兩部分數據進行綁定提前緩存起來,這樣目標方法調用時直接從緩存中獲取元數據即可,避免了反射效率低下影響性能。
1、根據目標方法和目標類獲取注解元數據,元數據包括緩存名稱、緩存key、過期時間、自動刷新時間、本地緩存容量、緩存類型、緩存條件等。
2、根據緩存條件是否走注解緩存,緩存條件支持SpEL表達式,如果為false則直接執行目標方法,為ture走緩存邏輯。
3、生成key:支持SpEL表達式,可以自定義生成規則,默認規則:命名空間、所屬類名稱、方法名稱、方法參數以冒號相連。
4、獲取cache:根據cacheName和cacheType獲取cache,對應cache有本地cache、遠程cache、兩級cache,根據key獲取緩存結果, 緩存結果為空則執行目標方法并對結果緩存,反之直接返回緩存結果。
3.3獲取cache組件
cache實現類有三種LocalCache、RemoteCache和TwoLevelCache,每個緩存實現類集成了具體的緩存中間件,LocalCache可以集成Caffeine、Guava、ehCache等, RemoteCache可以集成Redis、Memcache等,TwoLevelCache是LocalCache和RemoteCache組合實現。
1、CacheManagerContainer管理著所有的CacheManager,每個cacheType對應一個CacheManager的實現。
2、CacheManager提供了cache實現bean的創建,管理著多個cache,每個cache有對應的cacheName,每個應用里可以通過cacheName來對cache進行隔離,如果cacheName對應的cache不存在則會注冊一個新的cache。
3、Cache接口提供了緩存的具體操作,例如放入,讀取,清理等。
3.4兩級緩存
兩級緩存的產生是因為遠程緩存有網絡開銷,大量的緩存讀取會導致遠程緩存網絡成為整個系統的瓶頸,本地緩存是和應用程序在一個進程內,請求緩存速度快,沒有過多的網絡開銷, 加入本地緩存目標是降低對遠程緩存的讀取次數,減輕網絡開銷,從而再次提升程序的響應速度與服務性能。
1、從本地緩存讀出數據,如果存在則直接返回,進行后續具體業務邏輯。
2、本地緩存如果不存在則讀取遠程緩存,遠程緩存如果存在則更新本地緩存,不存在則從數據源讀取,然后依次更新遠程緩存、本地緩存,然后進行后續具體業務邏輯。
3.5自動刷新緩存
防止某個緩存失效時,訪問量突然大增,所有請求訪問數據庫,可能導致數據庫掛掉;適用場景:key數量比較少,訪問量大,加載開銷較大的情況。
1、緩存讀取時如果元數據自動刷新時間有值,會根據緩存key、目標方法、刷新時間創建一個給定初始延遲的間隔性的任務,任務自動執行間隔為自動刷新時間, 任務執行時會根據緩存key、目標方法重新加載緩存,保持緩存一直生效。
2、根據自動刷新時間會生成一個停止刷新時間,如果緩存key訪問間隔時間超過了停止刷新時間或者緩存key過期,會刪除該定時任務,釋放資源,避免無效的刷新緩存。
3、兩級緩存刷新緩存順序為:先刷新遠程緩存,然后根據Redis的pub/sub模式去監測和操作本地cache的刪除動作,隨后第一次請求會檢查本地緩存--->再檢查Redis緩存--->回源。
4、遠程緩存自動刷新使用分布式鎖,對同一key,全局只有一臺機器自動刷新。
3.6注解@Cacheable
1、 @Cacheable可以作用在方法上,也可以標記在一個類上,當標記在一個方法上時表示該方法是支持緩存的,當標記在一個類上時則表示該類所有的方法都是支持緩存的
名稱 | 默認值 | 說明 |
value | 空字符串 | 緩存名稱,cacheName的別名 |
cacheName | 空字符串 | 緩存名稱 |
key | 空字符串 | 緩存key,支持SpEL表達式,提供默認生成策略 |
ttl | 10分鐘 | 過期時間,d/h/m/s四種時間單位選擇,分別代表天/時/分/秒, ttl="10m"表示10分鐘過期時間 |
refreshTime | 空字符串 | 自動刷新時間,d/h/m/s四種時間單位選擇,分別代表天/時/分/秒 |
maximumSize | 5000 | 本地緩存容量 |
cacheType | REMOTE | 緩存類型,LOCAL/REMOTE/BOTH三種選擇,分別代表本地緩存/集中式緩存/兩級緩存 |
condition | 空字符串 | 指定符合條件的情況下才緩存,為空則認為全部無條件緩存,支持SpEL表達式 |
2、key默認生成規則:命名空間、所屬類名稱、方法名稱、方法參數以冒號相連。
3、如果設置ttl為空:表示緩存永不過期。
3.7緩存配置
這里舉個例子,具體的參數值,根據自己業務情況自行調整。
4. 總結和展望
本文主要是記錄了商業資源組在使用spring-cache過程中遇到的問題點及注解式兩級緩存服務框架設計思路,通過一步步拆解,將問題點逐個擊破。該框架在實際項目中也經過了千萬級別的驗證,為我們的線上服務提供了良好的性能。
構建一套完整的服務框架需要不斷的迭代功能開發,后續要逐步支持的功能如下:
增加熔斷與降級
增加緩存數據壓縮
增加緩存一致性
增加緩存監控統計看板
增加自定義緩存中間件
增加緩存接口用于手工緩存操作
參考文獻
??https://github.com/ben-manes/caffeine/wiki/Benchmarks??
作者介紹:王云朋
- 經銷商技術部-商業資源團隊
- 2017年加入汽車之家經銷商事業部,目前主要負責智能展廳核心功能開發工作