jinfeng_wang

          G-G-S,D-D-U!

          BlogJava 首頁 新隨筆 聯(lián)系 聚合 管理
            400 Posts :: 0 Stories :: 296 Comments :: 0 Trackbacks

          轉(zhuǎn)載請注明出處哈:http://carlosfu.iteye.com/blog/2269678


           

            一. 緩存穿透 (請求數(shù)據(jù)緩存大量不命中):
              緩存穿透是指查詢一個(gè)一定不存在的數(shù)據(jù),由于緩存不命中,并且出于容錯(cuò)考慮, 如果從存儲(chǔ)層查不到數(shù)據(jù)則不寫入緩存,這將導(dǎo)致這個(gè)不存在的數(shù)據(jù)每次請求都要到存儲(chǔ)層去查詢,失去了緩存的意義。
              例如:下圖是一個(gè)比較典型的cache-storage架構(gòu),cache(例如memcache, redis等等) + storage(例如mysql, hbase等等)架構(gòu),查一個(gè)壓根就不存在的值, 如果不做兼容,永遠(yuǎn)會(huì)查詢storage。
          二. 危害:
               對底層數(shù)據(jù)源(mysql, hbase, http接口, rpc調(diào)用等等)壓力過大,有些底層數(shù)據(jù)源不具備高并發(fā)性。
               例如mysql一般來說單臺(tái)能夠扛1000-QPS就已經(jīng)很不錯(cuò)了(別說你的查詢都是select * from table where id=xx 以及你的機(jī)器多么牛逼,那就有點(diǎn)矯情了)
               例如他人提供的一個(gè)抗壓性很差的http接口,可能穿透會(huì)擊潰他的服務(wù)。
               
          三. 如何發(fā)現(xiàn):
             我們可以分別記錄cache命中數(shù), storage命中數(shù),以及總調(diào)用量,如果發(fā)現(xiàn)空命中(cache,storage都沒有命中)較多,可能就會(huì)在緩存穿透問題。
             注意:緩存本身的命中率(例如redis中的info提供了類似數(shù)字,只代表緩存本身)不代表storage和業(yè)務(wù)的命中率。
             
          四. 產(chǎn)生原因以及業(yè)務(wù)是否允許?
              產(chǎn)生原因有很多:可能是代碼本身或者數(shù)據(jù)存在的問題造成的,也很有可能是一些惡意攻擊、爬蟲等等(因?yàn)閔ttp讀接口都是開放的)
              業(yè)務(wù)是否允許:這個(gè)要看做的項(xiàng)目或者業(yè)務(wù)是否允許這種情況發(fā)生,比如做一些非實(shí)時(shí)的推薦系統(tǒng),假如新用戶來了,確實(shí)沒有他的推薦數(shù)據(jù)(推薦數(shù)據(jù)通常是根據(jù)歷史行為算出),這種業(yè)務(wù)是會(huì)發(fā)生穿透現(xiàn)象的,至于業(yè)務(wù)允不允許要具體問題具體分析了。
           
          五. 解決方法:
          解決思路大致有兩個(gè),如下表。下面將分別說明
          解決緩存穿透適用場景維護(hù)成本
          緩存空對象

          1. 數(shù)據(jù)命中不高

          2. 數(shù)據(jù)頻繁變化實(shí)時(shí)性高

          1.代碼維護(hù)簡單

          2.需要過多的緩存空間

          3. 數(shù)據(jù)不一致

          bloomfilter或者壓縮filter提前攔截

          1. 數(shù)據(jù)命中不高

          2. 數(shù)據(jù)相對固定實(shí)時(shí)性低

          1.代碼維護(hù)復(fù)雜

          2.緩存空間占用少

                 1. 緩存空對象
                   
                  (1). 定義:如上圖所示,當(dāng)?shù)?#9313;步MISS后,仍然將空對象保留到Cache中(可能是保留幾分鐘或者一段時(shí)間,具體問題具體分析),下次新的Request(同一個(gè)key)將會(huì)從Cache中獲取到數(shù)據(jù),保護(hù)了后端的Storage。
                  (2) 適用場景:數(shù)據(jù)命中不高,數(shù)據(jù)頻繁變化實(shí)時(shí)性高(一些亂轉(zhuǎn)業(yè)務(wù))
                  (3) 維護(hù)成本:代碼比較簡單,但是有兩個(gè)問題:
                       第一是空值做了緩存,意味著緩存系統(tǒng)中存了更多的key-value,也就是需要更多空間(有人說空值沒多少,但是架不住多啊),解決方法是我們可以設(shè)置一個(gè)較短的過期時(shí)間。
                       第二是數(shù)據(jù)會(huì)有一段時(shí)間窗口的不一致,假如,Cache設(shè)置了5分鐘過期,此時(shí)Storage確實(shí)有了這個(gè)數(shù)據(jù)的值,那此段時(shí)間就會(huì)出現(xiàn)數(shù)據(jù)不一致,解決方法是我們可以利用消息或者其他方式,清除掉Cache中的數(shù)據(jù)。
                  (4) 偽代碼:
          Java代碼  收藏代碼
          1. package com.carlosfu.service;  
          2.   
          3. import org.apache.commons.lang.StringUtils;  
          4.   
          5. import com.carlosfu.cache.Cache;  
          6. import com.carlosfu.storage.Storage;  
          7.   
          8. /** 
          9.  * 某服務(wù) 
          10.  *  
          11.  * @author carlosfu 
          12.  * @Date 2015-10-11 
          13.  * @Time 下午6:28:46 
          14.  */  
          15. public class XXXService {  
          16.   
          17.     /** 
          18.      * 緩存 
          19.      */  
          20.     private Cache cache = new Cache();  
          21.   
          22.     /** 
          23.      * 存儲(chǔ) 
          24.      */  
          25.     private Storage storage = new Storage();  
          26.   
          27.     /** 
          28.      * 模擬正常模式 
          29.      * @param key 
          30.      * @return 
          31.      */  
          32.     public String getNormal(String key) {  
          33.         // 從緩存中獲取數(shù)據(jù)  
          34.         String cacheValue = cache.get(key);  
          35.         // 緩存為空  
          36.         if (StringUtils.isBlank(cacheValue)) {  
          37.             // 從存儲(chǔ)中獲取  
          38.             String storageValue = storage.get(key);  
          39.             // 如果存儲(chǔ)數(shù)據(jù)不為空,將存儲(chǔ)的值設(shè)置到緩存  
          40.             if (StringUtils.isNotBlank(storageValue)) {  
          41.                 cache.set(key, storageValue);  
          42.             }  
          43.             return storageValue;  
          44.         } else {  
          45.             // 緩存非空  
          46.             return cacheValue;  
          47.         }  
          48.     }  
          49.   
          50.   
          51.     /** 
          52.      * 模擬防穿透模式 
          53.      * @param key 
          54.      * @return 
          55.      */  
          56.     public String getPassThrough(String key) {  
          57.         // 從緩存中獲取數(shù)據(jù)  
          58.         String cacheValue = cache.get(key);  
          59.         // 緩存為空  
          60.         if (StringUtils.isBlank(cacheValue)) {  
          61.             // 從存儲(chǔ)中獲取  
          62.             String storageValue = storage.get(key);  
          63.             cache.set(key, storageValue);  
          64.             // 如果存儲(chǔ)數(shù)據(jù)為空,需要設(shè)置一個(gè)過期時(shí)間(300秒)  
          65.             if (StringUtils.isBlank(storageValue)) {  
          66.                 cache.expire(key, 60 * 5);  
          67.             }  
          68.             return storageValue;  
          69.         } else {  
          70.             // 緩存非空  
          71.             return cacheValue;  
          72.         }  
          73.     }  
          74.   
          75. }  
           
          2. bloomfilter或者壓縮filter(bitmap等等)提前攔截
                  (1). 定義:如上圖所示,在訪問所有資源(cache, storage)之前,將存在的key用布隆過濾器提前保存起來,做第一層攔截, 例如: 我們的推薦服務(wù)有4億個(gè)用戶uid, 我們會(huì)根據(jù)用戶的歷史行為進(jìn)行推薦(非實(shí)時(shí)),所有的用戶推薦數(shù)據(jù)放到hbase中,但是每天有許多新用戶來到網(wǎng)站,這些用戶在當(dāng)天的訪問就會(huì)穿透到hbase。為此我們每天4點(diǎn)對所有uid做一份布隆過濾器。如果布隆過濾器認(rèn)為uid不存在,那么就不會(huì)訪問hbase,在一定程度保護(hù)了hbase(減少30%左右)。
                        注:有關(guān)布隆過濾器的相關(guān)知識(shí),請自行查閱,有關(guān)guava中如何使用布隆過濾器,之后會(huì)系列文章給大家介紹。
                  (2) 適用場景:數(shù)據(jù)命中不高,數(shù)據(jù)相對固定實(shí)時(shí)性低(通常是數(shù)據(jù)集較大)
                  (3) 維護(hù)成本:代碼維護(hù)復(fù)雜, 緩存空間占用少
                        第一是空值做了緩存,意味著緩存系統(tǒng)中存了更多的key-value,也就是需要更多空間(有人說空值沒多少,但是架不住多啊),解決方法是我們可以設(shè)置一個(gè)較短的過期時(shí)間。
                        第二是數(shù)據(jù)會(huì)有一段時(shí)間窗口的不一致,假如,Cache設(shè)置了5分鐘過期,此時(shí)Storage確實(shí)有了這個(gè)數(shù)據(jù)的值,那此段時(shí)間就會(huì)出現(xiàn)數(shù)據(jù)不一致,解決方法是我們可以利用消息或者其他方式,清除掉Cache中的數(shù)據(jù)。
          六、參考資料:
          http://blog.jobbole.com/83439/ (那些年我們一起追過的緩存寫法)
          附圖一張,單機(jī)負(fù)載,哈哈:
            
           
          posted on 2016-12-20 17:14 jinfeng_wang 閱讀(209) 評論(0)  編輯  收藏 所屬分類: 2016-REDIS
          主站蜘蛛池模板: 阜康市| 盐源县| 内黄县| 江城| 千阳县| 留坝县| 青田县| 拉萨市| 峨山| 西城区| 株洲市| 胶南市| 芜湖市| 封丘县| 五河县| 镇巴县| 万盛区| 崇义县| 关岭| 河津市| 惠水县| 丹阳市| 沙坪坝区| 逊克县| 台南市| 东安县| 宁海县| 隆德县| 林甸县| 开封市| 紫云| 贺兰县| 宁都县| 乌苏市| 柳河县| 雷波县| 剑阁县| 大悟县| 黑水县| 若尔盖县| 岱山县|