<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Redis的三大問題

      一般我們對緩存讀操作的時候有這么一個固定的套路:

      • 如果我們的數據在緩存里邊有,那么就直接取緩存的。
      • 如果緩存里沒有我們想要的數據,我們會先去查詢數據庫,然后將數據庫查出來的數據寫到緩存中。
      • 最后將數據返回給請求

      代碼例子:

       1 @Override
       2 public  R selectOrderById(Integer id) {
       3     //查詢緩存
       4     Object redisObj = valueOperations.get(String.valueOf(id));
       5 
       6     //命中緩存
       7     if(redisObj != null) {
       8         //正常返回數據
       9         return new R().setCode(200).setData(redisObj).setMsg("OK");
      10     }
      11     Order order = orderMapper.selectOrderById(id);
      12     if (order != null) {
      13          valueOperations.set(String.valueOf(id), order);  //加入緩存
      14          return new R().setCode(200).setData(order).setMsg("OK");
      15      }
      16      return new R().setCode(500).setData(new NullValueResultDO()).setMsg("查詢無果");
      17 }   

      但這樣寫的代碼是不行的,這代碼里就有我們緩存的三大問題的兩大問題.穿透,擊穿.

      一,緩存雪崩

      1.1什么是緩存雪崩?

      第一種情況:Redis掛掉了,請求全部走數據庫.

      第二種情況:緩存數據設置的過期時間是相同的,然后剛好這些數據刪除了,全部失效了,這個時候全部請求會到數據庫

      緩存雪崩如果發生了,很有可能會把我們的數據庫搞垮,導致整個服務器癱瘓.

      1.2如何解決緩存雪崩?

      對于第二種情況,非常好解決:

        在存緩存的時候給過期時間加上一個隨機值,這樣大幅度的減少緩存同時過期.

      第一種情況:

        事發前:實現Redis的高可用(主從架構+Sentinel 或者Redis Cluster),盡量避免Redis掛掉這種情況發生。
        事發中:萬一Redis真的掛了,我們可以設置本地緩存(ehcache)+限流(hystrix),盡量避免我們的數據庫被干掉(起碼能保證我們的服務還是能正常工作的)
        事發后:redis持久化,重啟后自動從磁盤上加載數據,快速恢復緩存數據。

      二,緩存穿透

      2.1什么是緩存穿透?

      比如你搶了你同事的女神,你同事很氣,想搞你,在你的項目里,每次請求的ID為負數.這個時候緩存肯定是沒有的,緩存就沒用了,請求就會全部找數據庫,但數據庫也沒用這個值.所以每次返回空出去.

      緩存穿透是指查詢一個一定不存在的數據。由于緩存不命中,并且出于容錯考慮,如果從數據庫查不到數據則不寫入緩存,這將導致這個不存在的數據每次請求都要到數據庫去查詢,失去了緩存的意義。

      這就是緩存穿透:

      請求的數據在緩存大量不命中,導致請求走數據庫。

      緩存穿透如果發生了,也可能把我們的數據庫搞垮,導致整個服務癱瘓!

      2.2如何解決緩存穿透?

      解決緩存穿透也有兩種方案:

      • 由于請求的參數是不合法的(每次都請求不存在的參數),于是我們可以使用布隆過濾器(BloomFilter)或者壓縮filter提前攔截,不合法就不讓這個請求到數據庫層!
      • 當我們從數據庫找不到的時候,我們也將這個空對象設置到緩存里邊去。下次再請求的時候,就可以從緩存里邊獲取了。這種情況我們一般會將空對象設置一個較短的過期時間。

      緩存空對象代碼例子:

      1     public R selectOrderById(Integer id) {
      2         return cacheTemplate.redisFindCache(String.valueOf(id), 10, TimeUnit.MINUTES, new CacheLoadble<Order>() {
      3             @Override
      4             public Order load() {
      5                 return orderMapper.selectOrderById(id);
      6             }
      7         },false);
      8     }
       1 public R redisFindCache(String key, long expire, TimeUnit unit, CacheLoadble<T> cacheLoadble, boolean b) {
       2         //查詢緩存
       3         Object redisObj = valueOperations.get(String.valueOf(key));
       4         //命中緩存
       5         if (redisObj != null) {
       6             if(redisObj instanceof NullValueResultDO){
       7                 return new R().setCode(500).setData(new NullValueResultDO()).setMsg("查詢無果");
       8             }
       9             //正常返回數據
      10             return new R().setCode(200).setData(redisObj).setMsg("OK");
      11         }
      12         try {
      13             T load = cacheLoadble.load();//查詢數據庫
      14             if (load != null) {
      15                 valueOperations.set(key, load, expire, unit);  //加入緩存
      16                 return new R().setCode(200).setData(load).setMsg("OK");
      17             }else{
      18                 valueOperations.set(key,new NullValueResultDO(),expire,unit);
      19             }
      20 
      21         } finally {
      22 
      23         }
      24         return new R().setCode(500).setData(new NullValueResultDO()).setMsg("查詢無果");
      25     }

      這里封裝了一個模板redisFindCache,不然每一個方法都要寫這個流程.注意在命中緩存時,要判斷數據是否是空對象.

      空對象:

      1 @Getter
      2 @Setter
      3 @ToString
      4 public class NullValueResultDO{
      5 
      6 }

      緩存空對象的缺點:有大量的空數據占用redis的內存.治標不治本.

      布隆過濾器:

        有谷歌的guava,但是是單機版的,不支持分布式.

        也可以用redis的位數組bit手寫一個分布式布隆過濾器,代碼就不寫了.過程就是先把id(比如你是用id為key的)存進布隆過濾器(會經過特定的算法),當我們請求接口的時候先讓它查詢布隆過濾器,判斷數據是否存在.

      上面的代碼還有個緩存擊穿(緩存當中沒有,數據庫中有)問題,就是并發的時候.比如99個人同時請求,還是會打印99條sql語句,還是會找數據庫.

      這里的代碼是用的分布式鎖(互斥鎖)

       1 public R redisFindCache(String key, long expire, TimeUnit unit, CacheLoadble<T> cacheLoadble,boolean b){
       2         //判斷是否走過濾器
       3         if(b){
       4             //先走過濾器
       5             boolean bloomExist = bloomFilter.isExist(String.valueOf(key));
       6             if(!bloomExist){
       7                 return new R().setCode(600).setData(null).setMsg("查詢無果");
       8             }
       9         }
      10         //查詢緩存
      11         Object redisObj = valueOperations.get(String.valueOf(key));
      12         //命中緩存
      13         if(redisObj != null) {
      14             //正常返回數據
      15             return new R().setCode(200).setData(redisObj).setMsg("OK");
      16         }
      17 //        RLock lock0 = redisson.getLock("{taibai0}:" + key);
      18 //        RLock lock1 = redisson.getLock("{taibai1}:" + key);
      19 //        RLock lock2 = redisson.getLock("{taibai2}:" + key);
      20 //        RedissonMultiLock lock = new RedissonMultiLock(lock0,lock1, lock2);
      21         try {
      22         redisLock.lock(key);//上鎖
      23 //        lock.lock();
      24         //查詢緩存
      25         redisObj = valueOperations.get(String.valueOf(key));
      26         //命中緩存
      27         if(redisObj != null) {
      28             //正常返回數據
      29             return new R().setCode(200).setData(redisObj).setMsg("OK");
      30         }
      31         T load = cacheLoadble.load();//查詢數據庫
      32         if (load != null) {
      33             valueOperations.set(key, load,expire, unit);  //加入緩存
      34             return new R().setCode(200).setData(load).setMsg("OK");
      35         }
      36             return new R().setCode(500).setData(new NullValueResultDO()).setMsg("查詢無果");
      37         }finally {
      38             redisLock.unlock(key);//解鎖
      39 //            lock.unlock();
      40         }
      41     }

      三,緩存與數據庫雙寫一致

      3.1什么是緩存與數據庫雙寫一致問題?

      如果僅僅查詢的話,緩存的數據和數據庫的數據是沒問題的。但是,當我們要更新時候呢?各種情況很可能就造成數據庫和緩存的數據不一致了。

      • 這里不一致指的是:數據庫的數據跟緩存的數據不一致

      從理論上說,只要我們設置了鍵的過期時間,我們就能保證緩存和數據庫的數據最終是一致的。因為只要緩存數據過期了,就會被刪除。隨后讀的時候,因為緩存里沒有,就可以查數據庫的數據,然后將數據庫查出來的數據寫入到緩存中。

      除了設置過期時間,我們還需要做更多的措施來盡量避免數據庫與緩存處于不一致的情況發生。

      3.2對于更新操作

      一般來說,執行更新操作時,我們會有兩種選擇:

      • 先操作數據庫,再操作緩存
      • 先操作緩存,再操作數據庫

      首先,要明確的是,無論我們選擇哪個,我們都希望這兩個操作要么同時成功,要么同時失敗。所以,這會演變成一個分布式事務的問題。

      所以,如果原子性被破壞了,可能會有以下的情況:

      • 操作數據庫成功了,操作緩存失敗了。
      • 操作緩存成功了,操作數據庫失敗了。

      如果第一步已經失敗了,我們直接返回Exception出去就好了,第二步根本不會執行。

      下面我們具體來分析一下吧。

      3.2.1操作緩存

      操作緩存也有兩種方案:

      • 更新緩存
      • 刪除緩存

      一般我們都是采取刪除緩存緩存策略的,原因如下:

      1. 高并發環境下,無論是先操作數據庫還是后操作數據庫而言,如果加上更新緩存,那就更加容易導致數據庫與緩存數據不一致問題。(刪除緩存直接和簡單很多)
      2. 如果每次更新了數據庫,都要更新緩存【這里指的是頻繁更新的場景,這會耗費一定的性能】,倒不如直接刪除掉。等再次讀取時,緩存里沒有,那我到數據庫找,在數據庫找到再寫到緩存里邊(體現懶加載)

      基于這兩點,對于緩存在更新時而言,都是建議執行刪除操作!

      3.2.2先更新數據庫,再刪除緩存

      正常情況是這樣的:

      • 先操作數據庫,成功
      • 在刪除緩存,也成功

      如果原子性被破壞了:

      • 第一步成功(操作數據庫),第二步失敗(刪除緩存),會導致數據庫里是新數據,而緩存里是舊數據。
      • 如果第一步(操作數據庫)就失敗了,我們可以直接返回錯誤(Exception),不會出現數據不一致。

      如果在高并發的場景下,出現數據庫與緩存數據不一致的概率特別低,也不是沒有:

      • 緩存剛好失效
      • 線程A查詢數據庫,得一個舊值
      • 線程B將新值寫入數據庫
      • 線程B刪除緩存
      • 線程A將查到的舊值寫入緩存

      要達成上述情況,還是說一句概率特別低:

      因為這個條件需要發生在讀緩存時緩存失效,而且并發著有一個寫操作。而實際上數據庫的寫操作會比讀操作慢得多,而且還要鎖表,而讀操作必需在寫操作前進入數據庫操作,而又要晚于寫操作更新緩存,所有的這些條件都具備的概率基本并不大。

      對于這種策略,其實是一種設計模式:Cache Aside Pattern

       

      刪除緩存失敗的解決思路:

      • 將需要刪除的key發送到消息隊列中
      • 自己消費消息,獲得需要刪除的key
      • 不斷重試刪除操作,直到成功

       3.2.3先刪除緩存,在更新數據庫

      正常情況是這樣的:

      • 先刪除緩存,成功;
      • 再更新數據庫,也成功;

      如果原子性被破壞了:

      • 第一步成功(刪除緩存),第二步失敗(更新數據庫),數據庫和緩存的數據還是一致的。
      • 如果第一步(刪除緩存)就失敗了,我們可以直接返回錯誤(Exception),數據庫和緩存的數據還是一致的。

      看起來是很美好,但是我們在并發場景下分析一下,就知道還是有問題的了:

      • 線程A刪除了緩存
      • 線程B查詢,發現緩存已不存在
      • 線程B去數據庫查詢得到舊值
      • 線程B將舊值寫入緩存
      • 線程A將新值寫入數據庫

      所以也會導致數據庫和緩存不一致的問題。

      并發下解決數據庫與緩存不一致的思路:

      • 將刪除緩存、修改數據庫、讀取緩存等的操作積壓到隊列里邊,實現串行化。

       

      3.2.4對比著兩種策略

      我們可以發現,兩種策略各自有優缺點:

      • 先刪除緩存,再更新數據庫

          在高并發下表現不如意,在原子性被破壞時表現優異

      • 先更新數據庫,再刪除緩存(Cache Aside Pattern設計模式)

          在高并發下表現優異,在原子性被破壞時表現不如意

       3.2.5其他保障數據一致的方案與資料

      可以用databus或者阿里的canal監聽binlog進行更新。

      參考資料:

      • 緩存更新的套路

          https://coolshell.cn/articles/17416.html

      • 如何保證緩存與數據庫雙寫時的數據一致性?

          https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/redis-consistence.md

      • 分布式之數據庫和緩存雙寫一致性方案解析

          https://zhuanlan.zhihu.com/p/48334686

      • Cache Aside Pattern

          https://blog.csdn.net/z50l2o08e2u4aftor9a/article/details/81008933

      posted @ 2020-04-24 15:27  歐小文大叔  閱讀(1455)  評論(7)    收藏  舉報
      主站蜘蛛池模板: 日韩精品有码中文字幕| 凌源市| 不卡无码人妻一区三区音频| 成人深夜节目在线观看| 国产青榴视频在线观看| 亚洲av综合色区无码专区| 综艺| 亚洲色最新高清AV网站| 成人午夜在线观看日韩| 99久久精品国产一区二区暴力| 色综合网天天综合色中文| 人妻丝袜无码专区视频网站| 日韩人妻熟女中文字幕a美景之屋| 免费无遮挡无码视频网站| 国产激情福利短视频在线| 激情影院内射美女| 亚洲欧美日本久久网站| 久久国产综合色免费观看| 免费看婬乱a欧美大片| 国产亚洲精品aaaa片app| 国产免费午夜福利在线观看| 亚洲精品国产自在现线最新| 国产老熟女视频一区二区| 亚洲天堂伊人久久a成人| 免费国产好深啊好涨好硬视频| 俄罗斯美女真人性做爰| 黄色A级国产免费大片视频| 岛国中文字幕一区二区 | 天天综合色天天综合色h| 无码熟妇人妻AV影音先锋| 久久亚洲精品11p| 国产视频不卡一区二区三区| 女人下边被添全过视频的网址| 日韩一区二区三区av在线| 亚洲人成色99999在线观看| 寻甸| 蜜臀98精品国产免费观看| 欧美 变态 另类 人妖 | 亚洲无av在线中文字幕| 福利一区二区1000| 亚洲精品一二三四区|