前幾天聽了部門內朋春大牛講分布式緩存的一個技術分享,還是非常有收獲。
PPT如下:
這個分享的副標題是“簡單的事情從來不簡單”,這句話講得非常在理。緩存看似簡單,但要做“好”一個緩存系統也是很有講究的。
寫點自己的心得收獲吧:
1. 分布式緩存面臨比較大的三個問題:
(1) 數據一致性。
在分布式系統這點顯得尤為重要,主要原因有三點:
緩存系統與底層數據的一致性。這點在底層系統是“可讀可寫”時,寫得尤為重要
有繼承關系的緩存之間的一致性。為了盡量提高緩存命中率,緩存也是分層:全局緩存,二級緩存。他們是存在繼承關系的。全局緩存可以有二級緩存來組成。
多個緩存副本之間的一致性。為了保證系統的高可用性,緩存系統背后往往會接兩套存儲系統(如memcache,redis等),以上的ppt也主要是講這方面的內容。
(2)緩存雪崩
當緩存系統重啟或者所有緩存在同一時刻失效(比如某些系統為了提高速度,會在系統啟動是統一將大部分數據刷到緩存中,此時如果設置緩存時間都是24小時,那24小時過后,那就悲劇)時,應用系統由於扛不住壓力而直接掛掉。
(3)緩存穿透
查詢一個必然不存在的數據,查詢一個必然不存在的key,每次都會訪問DB,如果有人惡意破壞,那么很可能直接對DB造成影響。
第一點偏重數據的真實性和實時性,而第二點和第三點更多從性能上考慮。同時緩存並不一定是必需的,特別是當寫操作特別頻繁時。
2. 緩存數據的淘汰
原先緩存數據的淘汰往往是用設置緩存時間,比如我設置某個數據的緩存時間是24小時,之后的24內這個緩存是不會失效的。優點當然是簡單,缺點也很明顯就是不都靈活,沒做到好的精細化管理。
我們能利用的資源就是:1. 給緩存加tag,2. 版本號(必須單調遞增,時間戳是最好的選擇)3. 提供手動清理緩存的接口。
相關步驟可以參見以上的PPT內容。
緩存相關接口:
var me = Cache.create(...); me.set(key, value, ttl, tags); me.get(key); me.tagrm(tag, offset, flush);
緩存的數據結構如下:
var data = { ‘i’:now, /** 數據寫入時間戳 */ ‘e’:now + ttl,/** 預期過期時間 */ ‘k’:key, /** 原始key */ ‘v’:value, /** 原始值 */ ‘t’:tags /** tag列表 */ };
我剛開始的也沒弄明白為什么在data中還要存“原始的key”,后來經朋春提醒,才弄明白:原始的key可能過長或者存在特殊字符時,是不能直接作為某些系統的key,因此往往會對原始key做一次hash來作為緩存的新key。
3. 緩存淘汰的策略
緩存淘汰的策略有兩種:
(1) 定時去清理過期的緩存。
(2)當有用戶請求過來時,再判斷這個請求所用到的緩存是否過期,過期的話就去底層系統得到新數據並更新緩存。
兩者各有優劣,第一種的缺點是維護大量緩存的key是比較麻煩的,第二種的缺點就是每次用戶請求過來都要判斷緩存失效,邏輯相對比較復雜,具體用哪種方案,大家可以根據自己的應用場景來權衡。