title: 學習筆記:cache 和spring cache 技術---本地緩存-分佈式緩存,緩存穿透,雪崩,和熱點key的問題 author: Eric liu tags: [] categories:html
將數據緩存在JVM中,使用Map或者Guava的Table來保存數據。redis
考慮因素: 使用內存緩存時,需考慮緩存數據消耗多大內存。spring
優點:sql
場景:數據庫
將數據緩存在緩存中間件中,例如,redis、memcached。後端
推薦使用redis:緩存
(1)memcached:鍵值對 redis 還能夠支持更多形式tomcat
(2)一樣是內存數據庫,redis 能夠持久化,雖然redis是基於內存的存儲系統,可是他自己是支持內存數據的持久化,並且主要提供兩種主要的持久化策略,RDB快照和AOF日誌,memcache不能bash
(3)性能 :redis 單線程io複用,,只有IO操做來講,性能好,也有一些計算,如排序聚合,可是計算的時候影響吞吐量網絡
memcached 多線程,非阻塞io複用有對全局變量加鎖 性能有損耗,
(4)內存管理機制不一樣。
memcached 是提早將 分配的內存切分紅規定大小的塊,而後使用的使用 用多少分配多少,有一個空閒列表進行統計。 不會用內存碎片,可是存在內存浪費
redis,會把剩餘內存大小存在內存塊中,Redis使用現場申請內存的方式來存儲數據,會在必定程度上存在內存碎片。
在redis中,並非全部的數據都一一直存儲在內存中的,這是和memcached相比最大的一個區別
Redis只會緩存全部的key端的信息,若是redis發現內存的使用量超過某一個值,將觸發swap的操做,redis根據相應的表達式計算出那些key對應value須要swap到磁盤,而後再將這些這些key對應的value持久化到磁盤中,同時再內存清除。同時因爲redis將內存中的數據swap到磁盤的時候,提供服務的主線程和進行swap操做的子進程會共享這部份內存,因此若是更新須要swap的數據,redis將阻塞這個操做,直到子線程完成swap操做後才能夠進行修改
https://www.cnblogs.com/hanfei-1005/p/5692455.html
複製代碼
(5)數據一致性 memcached 有cas 保證,redis 提供了事務
參考文檔:http://blog.csdn.net/u013256816/article/details/51146314
在jvm以及redis中均緩存數據,服務優先從jvm獲取,miss後從redis中獲取
目前ugc 使用 本地緩存 guava 和 redis 分佈式緩存。
使用spring cache的註解使用,經過名字區分指定使用哪一個緩存。 g- 開頭爲 使用本地緩存,在guava的建立方法裏判斷 若是非g-開頭 return null, 而後去redis 緩存中建立緩存
針對失效時間沒有作特殊處理 如失效後加鎖 或失效前預處理等,由於量級不大 且沒有 某時刻的大流量。
ugc 業務中 查詢固定的 和不太常改變的 使用本地緩存,文章等放在分佈式緩存中。
複製代碼
在緩存初始化時,緩存中是沒有任何緩存數據的,需先將數據緩存後,緩存服務纔算徹底啓動。預熱方式:
當用戶發生數據變動時,優先更新數據庫數據。在更新數據庫數據成功後,再更新緩存中數據。儘可能避免緩存數據與數據庫數據不一致的狀況。
當數據庫數據發生變動後,將變動後的key值放入到異步刷新緩存隊列中。後臺線程根據隊列中數據,刷新緩存數據。
先刷緩存會形成
缺點:
在高併發下,異步隊列會對下游系統產生壓力。例如,10K的客戶端同時請求服務端,單個客戶端的請求QPS是200,且每次請求的key不一樣及緩存中不存在數據,則每次都將key寫入到數據庫中,則數據庫扛不住。所以,先將數據寫入本地中,先本地冪等,而後在異步的寫入到數據庫中及緩存中。所以,每次入異步隊列的時候,都查詢redis中是否已經將這個key放入異步刷新隊列中。若是已經放入待刷新隊列中,則再也不再次入隊列。
問題:
緣由:代碼問題, 爬蟲,攻擊,大量空命中
場景:查詢某個文章,給了一個錯誤的文章id。一直查詢不到。
緩存空對象
能夠緩存到本地內存中,空對想用一個靜態變量。這樣不會形成 形成佔用內存。
問題:熱點key問題,這裏指 緩存層直接失效的問題。
方法:集羣,隔離組件 把重要資源隔離。讓每種資源都單獨運行在本身的線程池中。
而Hystrix 是解決依賴隔離的利器
問題: 熱點key 緩存過時或者失效 形成段時間大量訪問數據庫
緣由:
通常使用,緩存 + 過時時間的策略,加速接口的訪問速度,減小了後端負載,同時保證功能的更新
可是有兩個問題:
(1) 這個key是一個熱點key(例如一個重要的新聞,一個熱門的八卦新聞等等),因此這種key訪問量可能很是大。
(2) 緩存的構建是須要必定時間的。(多是一個複雜計算,例如複雜的sql、屢次IO、多個依賴(各類接口)等等)
從而在緩存失效的瞬間,有大量線程來構建緩存
熱點key 問題解決 一:如何解決失效時 大量併發
1.加鎖
(1)單機,synchronized ,spring cache 有sync 關鍵字
(2)分佈式,分佈式加鎖(redis,添加一個key_mutex , "1" , 若是添加上了 至關於獲取鎖,若是這個存在說明其餘人在用鎖,獲取失敗
String get(String key) {
String value = redis.get(key);
if (value == null) {
if (redis.setnx(key_mutex, "1")) {
// 3 min timeout to avoid mutex holder crash
redis.expire(key_mutex, 3 * 60)
value = db.get(key);
redis.set(key, value);
redis.delete(key_mutex);
} else {
//其餘線程休息50毫秒後重試
Thread.sleep(50);
get(key);
}
}
}
複製代碼
缺點:擠滿線程池
2.不過時
redis 設置物理不過時,
異步-邏輯過時:存值中設置timeout,若是發現timeout 過時,後臺異步線程構建緩存
缺點:於性能很是友好,惟一不足的就是構建緩存時候,其他線程(非構建緩存的線程)可能訪問的是老數據,
String get(final String key) {
V v = redis.get(key);
String value = v.getValue();
long timeout = v.getTimeout();
if (v.timeout <= System.currentTimeMillis()) {
// 異步更新後臺異常執行
threadPool.execute(new Runnable() {
public void run() {
String keyMutex = "mutex:" + key;
if (redis.setnx(keyMutex, "1")) {
// 3 min timeout to avoid mutex holder crash
redis.expire(keyMutex, 3 * 60);
String dbValue = db.get(key);
redis.set(key, dbValue);
redis.delete(keyMutex);
}
}
});
}
return value;
}
複製代碼
(1)
在value內部設置1個超時值(proTimeout), proTimeout比實際的redis timeout小。當從cache讀取到proTimeout發現它已通過期時候. 而後 加分佈式鎖,設置一個短暫的過時時間。保證有一個線程在刷緩存,其餘的正常使用。
若是這個線程刷緩存出了問題沒成功,短暫的過時時間 事後 鎖解開,下一個線程會機型刷緩存。
原創 僞代碼
String get(String key) {
String v = redis.get(key);
if (v == null) {
if (redis.setnx(key_mutex, "1")) {
// 3 min timeout to avoid mutex holder crash
redis.expire(key_mutex, 1 * 60)
value = db.get(key);
redis.set(key, value);
redis.delete(key_mutex);
} else {
//其餘線程休息50毫秒後重試
Thread.sleep(50);
get(key);
}
}
else{
if (v.get(timeout) <= now()) {
if ( redis.setnx(key_mutex, "1") ) {
// 1 min timeout to avoid mutex holder crash
redis.expire(key_mutex, 1 * 60)
value = db.get(key);
redis.set(key, value);
redis.delete(key_mutex);
}
}
//若是沒到提早的時間 或者有線程在刷,則繼續取
return v.get(value);
}
}
複製代碼
緩存數據的設計 也能夠是多值,
好比key是aaa,設置失效時間爲30s,則另外一個key爲expire_aaa,失效時間爲25s。
好比一個key是aaa,失效時間是30s。查詢DB在1s內。
若是是冷數據,30秒都沒有人訪問,那麼數據會過時。
若是是熱門數據,一直有大流量訪問,那麼數據就是一直熱的,並且數據一直不會過時。
(2)其餘失效前 刷緩存的方式
a.按期從DB裏查詢數據,再刷到redis 裏 有點扯,不適用 常變化的 緩存
b.緩存失效 加鎖查