成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

緩存擊穿!竟然不知道怎么寫代碼???

存儲 存儲軟件
在Redis中有三大問題:緩存雪崩、緩存擊穿、緩存穿透,今天我們來聊聊緩存擊穿。關于緩存擊穿相關理論文章,相信大家已經看過不少,但是具體代碼中是怎么實現的,怎么解決的等問題,可能就一臉懵逼了。

[[407549]]

在Redis中有三大問題:緩存雪崩、緩存擊穿、緩存穿透,今天我們來聊聊緩存擊穿。

關于緩存擊穿相關理論文章,相信大家已經看過不少,但是具體代碼中是怎么實現的,怎么解決的等問題,可能就一臉懵逼了。

今天,老田就帶大家來看看,緩存擊穿解決和代碼實現。

場景

請看下面這段代碼:

  1. /**  
  2.  * @author 田維常 
  3.  * @公眾號 java后端技術全棧 
  4.  * @date 2021/6/27 15:59 
  5.  */ 
  6. @Service 
  7. public class UserInfoServiceImpl implements UserInfoService { 
  8.  
  9.     @Resource 
  10.     private UserMapper userMapper; 
  11.     @Resource 
  12.     private RedisTemplate<Long, String> redisTemplate; 
  13.  
  14.     @Override 
  15.     public UserInfo findById(Long id) { 
  16.         //查詢緩存 
  17.         String userInfoStr = redisTemplate.opsForValue().get(id); 
  18.         //如果緩存中不存在,查詢數據庫 
  19.         //1 
  20.         if (isEmpty(userInfoStr)) { 
  21.             UserInfo userInfo = userMapper.findById(id); 
  22.             //數據庫中不存在 
  23.             if(userInfo == null){ 
  24.                   return null
  25.             } 
  26.             userInfoStr = JSON.toJSONString(userInfo); 
  27.             //2 
  28.             //放入緩存 
  29.             redisTemplate.opsForValue().set(id, userInfoStr); 
  30.         } 
  31.         return JSON.parseObject(userInfoStr, UserInfo.class); 
  32.     } 
  33.  
  34.     private boolean isEmpty(String string) { 
  35.         return !StringUtils.hasText(string); 
  36.     } 

整個流程:

如果,在//1到//2之間耗時1.5秒,那就代表著在這1.5秒時間內所有的查詢都會走查詢數據庫。這也就是我們所說的緩存中的“緩存擊穿”。

其實,你們項目如果并發量不是很高,也不用怕,并且我見過很多項目也就差不多是這么寫的,也沒那么多事,畢竟只是第一次的時候可能會發生緩存擊穿。

但,我們也不要抱著一個僥幸的心態去寫代碼,既然是多線程導致的,估計很多人會想到鎖,下面我們使用鎖來解決。

改進版

既然使用到鎖,那么我們第一時間應該關心的是鎖的粒度。

如果我們放在方法findById上,那就是所有查詢都會有鎖的競爭,這里我相信大家都知道我們為什么不放在方法上。

  1. /**  
  2.  * @author 田維常 
  3.  * @公眾號 java后端技術全棧 
  4.  * @date 2021/6/27 15:59 
  5.  */ 
  6. @Service 
  7. public class UserInfoServiceImpl implements UserInfoService { 
  8.  
  9.     @Resource 
  10.     private UserMapper userMapper; 
  11.     @Resource 
  12.     private RedisTemplate<Long, String> redisTemplate; 
  13.  
  14.     @Override 
  15.     public UserInfo findById(Long id) { 
  16.         //查詢緩存 
  17.         String userInfoStr = redisTemplate.opsForValue().get(id); 
  18.         if (isEmpty(userInfoStr)) { 
  19.             //只有不存的情況存在鎖 
  20.             synchronized (UserInfoServiceImpl.class){ 
  21.                 UserInfo userInfo = userMapper.findById(id); 
  22.                 //數據庫中不存在 
  23.                 if(userInfo == null){ 
  24.                      return null
  25.                 } 
  26.                 userInfoStr = JSON.toJSONString(userInfo); 
  27.                 //放入緩存 
  28.                 redisTemplate.opsForValue().set(id, userInfoStr); 
  29.             } 
  30.         } 
  31.         return JSON.parseObject(userInfoStr, UserInfo.class); 
  32.     } 
  33.  
  34.     private boolean isEmpty(String string) { 
  35.         return !StringUtils.hasText(string); 
  36.     } 

看似解決問題了,其實,問題還是沒得到解決,還是會緩存擊穿,因為排隊獲取到鎖后,還是會執行同步塊代碼,也就是還會查詢數據庫,完全沒有解決緩存擊穿。

雙重檢查鎖

由此,我們引入雙重檢查鎖,我們在上的版本中進行稍微改變,在同步模塊中再次校驗緩存中是否存在。

  1. /**  
  2.  * @author 田維常 
  3.  * @公眾號 java后端技術全棧 
  4.  * @date 2021/6/27 15:59 
  5.  */ 
  6. @Service 
  7. public class UserInfoServiceImpl implements UserInfoService { 
  8.  
  9.     @Resource 
  10.     private UserMapper userMapper; 
  11.     @Resource 
  12.     private RedisTemplate<Long, String> redisTemplate; 
  13.  
  14.     @Override 
  15.     public UserInfo findById(Long id) { 
  16.         //查緩存 
  17.         String userInfoStr = redisTemplate.opsForValue().get(id); 
  18.         //第一次校驗緩存是否存在 
  19.         if (isEmpty(userInfoStr)) { 
  20.             //上鎖 
  21.             synchronized (UserInfoServiceImpl.class){  
  22.                 //再次查詢緩存,目的是判斷是否前面的線程已經set過了 
  23.                 userInfoStr = redisTemplate.opsForValue().get(id); 
  24.                 //第二次校驗緩存是否存在 
  25.                 if (isEmpty(userInfoStr)) { 
  26.                     UserInfo userInfo = userMapper.findById(id); 
  27.                     //數據庫中不存在 
  28.                     if(userInfo == null){ 
  29.                         return null
  30.                     } 
  31.                     userInfoStr = JSON.toJSONString(userInfo); 
  32.                     //放入緩存 
  33.                     redisTemplate.opsForValue().set(id, userInfoStr); 
  34.                 } 
  35.             } 
  36.         } 
  37.         return JSON.parseObject(userInfoStr, UserInfo.class); 
  38.     } 
  39.  
  40.     private boolean isEmpty(String string) { 
  41.         return !StringUtils.hasText(string); 
  42.     } 

這樣,看起來我們就解決了緩存擊穿問題,大家覺得解決了嗎?

惡意攻擊

回顧上面的案例,在正常的情況下是沒問題,但是一旦有人惡意攻擊呢?

比如說:入參id=10000000,在數據庫里并沒有這個id,怎么辦呢?

第一步、緩存中不存在

第二步、查詢數據庫

第三步、由于數據庫中不存在,直接返回了,并沒有操作緩存

第四步、再次執行第一步.....死循環了吧

方案1:設置空對象

就是當緩存中和數據庫中都不存在的情況下,以id為key,空對象為value。

  1. set(id,空對象); 

回到上面的四步,就變成了。

比如說:入參id=10000000,在數據庫里并沒有這個id,怎么辦呢?

第一步、緩存中不存在

第二步、查詢數據庫

第三步、由于數據庫中不存在,以id為key,空對象為value放入緩存中

第四步、執行第一步,此時,緩存就存在了,只是這時候只是一個空對象。

代碼實現部分:

  1. /**  
  2.  * @author 田維常 
  3.  * @公眾號 java后端技術全棧 
  4.  * @date 2021/6/27 15:59 
  5.  */ 
  6. @Service 
  7. public class UserInfoServiceImpl implements UserInfoService { 
  8.  
  9.     @Resource 
  10.     private UserMapper userMapper; 
  11.     @Resource 
  12.     private RedisTemplate<Long, String> redisTemplate;  
  13.  
  14.     @Override 
  15.     public UserInfo findById(Long id) { 
  16.         String userInfoStr = redisTemplate.opsForValue().get(id); 
  17.         //判斷緩存是否存在,是否為空對象 
  18.         if (isEmpty(userInfoStr)) { 
  19.             synchronized (UserInfoServiceImpl.class){ 
  20.                 userInfoStr = redisTemplate.opsForValue().get(id); 
  21.                 if (isEmpty(userInfoStr)) { 
  22.                     UserInfo userInfo = userMapper.findById(id); 
  23.                     if(userInfo == null){ 
  24.                         //構建一個空對象 
  25.                         userInfo= new UserInfo(); 
  26.                     } 
  27.                     userInfoStr = JSON.toJSONString(userInfo); 
  28.                     redisTemplate.opsForValue().set(id, userInfoStr); 
  29.                 } 
  30.             } 
  31.         } 
  32.         UserInfo userInfo = JSON.parseObject(userInfoStr, UserInfo.class); 
  33.         //空對象處理 
  34.         if(userInfo.getId() == null){ 
  35.             return null
  36.         } 
  37.         return JSON.parseObject(userInfoStr, UserInfo.class); 
  38.     } 
  39.  
  40.     private boolean isEmpty(String string) { 
  41.         return !StringUtils.hasText(string); 
  42.     } 

方案2 布隆過濾器

布隆過濾器(Bloom Filter):是一種空間效率極高的概率型算法和數據結構,用于判斷一個元素是否在集合中(類似Hashset)。它的核心一個很長的二進制向量和一系列hash函數,數組長度以及hash函數的個數都是動態確定的。

Hash函數:SHA1,SHA256,MD5..

布隆過濾器的用處就是,能夠迅速判斷一個元素是否在一個集合中。因此他有如下三個使用場景:

  • 網頁爬蟲對URL的去重,避免爬取相同的URL地址
  • 反垃圾郵件,從數十億個垃圾郵件列表中判斷某郵箱是否垃圾郵箱(垃圾短信)
  • 緩存擊穿,將已存在的緩存放到布隆過濾器中,當黑客訪問不存在的緩存時迅速返回避免緩存及DB掛掉。

其內部維護一個全為0的bit數組,需要說明的是,布隆過濾器有一個誤判率的概念,誤判率越低,則數組越長,所占空間越大。誤判率越高則數組越小,所占的空間越小。布隆過濾器的相關理論和算法這里就不聊了,感興趣的可以自行研究。

優勢和劣勢

優勢

  • 全量存儲但是不存儲元素本身,在某些對保密要求非常嚴格的場合有優勢;
  • 空間高效率
  • 插入/查詢時間都是常數O(k),遠遠超過一般的算法

劣勢

  • 存在誤算率(False Positive),默認0.03,隨著存入的元素數量增加,誤算率隨之增加;
  • 一般情況下不能從布隆過濾器中刪除元素;
  • 數組長度以及hash函數個數確定過程復雜;

代碼實現:

  1. /**  
  2.  * @author 田維常 
  3.  * @公眾號 java后端技術全棧 
  4.  * @date 2021/6/27 15:59 
  5.  */ 
  6. @Service 
  7. public class UserInfoServiceImpl implements UserInfoService { 
  8.  
  9.     @Resource 
  10.     private UserMapper userMapper; 
  11.     @Resource 
  12.     private RedisTemplate<Long, String> redisTemplate; 
  13.     private static Long size = 1000000000L; 
  14.  
  15.     private static BloomFilter<Long> bloomFilter = BloomFilter.create(Funnels.longFunnel(), size); 
  16.  
  17.     @Override 
  18.     public UserInfo findById(Long id) { 
  19.         String userInfoStr = redisTemplate.opsForValue().get(id); 
  20.         if (isEmpty(userInfoStr)) { 
  21.             //校驗是否在布隆過濾器中 
  22.             if(bloomFilter.mightContain(id)){ 
  23.                 return null
  24.             } 
  25.             synchronized (UserInfoServiceImpl.class){ 
  26.                 userInfoStr = redisTemplate.opsForValue().get(id); 
  27.                 if (isEmpty(userInfoStr) ) { 
  28.                     if(bloomFilter.mightContain(id)){ 
  29.                         return null
  30.                     } 
  31.                     UserInfo userInfo = userMapper.findById(id); 
  32.                     if(userInfo == null){ 
  33.                         //放入布隆過濾器中 
  34.                         bloomFilter.put(id); 
  35.                         return null
  36.                     } 
  37.                     userInfoStr = JSON.toJSONString(userInfo); 
  38.                     redisTemplate.opsForValue().set(id, userInfoStr); 
  39.                 } 
  40.             } 
  41.         } 
  42.         return JSON.parseObject(userInfoStr, UserInfo.class); 
  43.     }  
  44.     private boolean isEmpty(String string) { 
  45.         return !StringUtils.hasText(string); 
  46.     } 

方案3 互斥鎖

使用Redis實現分布式的時候,有用到setnx,這里大家可以想象,我們是否可以使用這個分布式鎖來解決緩存擊穿的問題?

這個方案留給大家去實現,只要掌握了Redis的分布式鎖,那這個實現起來就非常簡單了。

總結

搞定緩存擊穿、使用雙重檢查鎖的方式來解決,看到雙重檢查鎖,大家肯定第一印象就會想到單例模式,這里也算是給大家復習一把雙重檢查鎖的使用。

由于惡意攻擊導致的緩存擊穿,解決方案我們也實現了兩種,至少在工作和面試中,肯定是能應對了。

另外,使用鎖的時候注意鎖的力度,這里建議換成分布式鎖(Redis或者Zookeeper實現),因為我們既然引入緩存,大部分情況下都會是部署多個節點的,同時,引入分布式鎖了,我們就可以使用方法入參id用起來,這樣是不是更爽!

希望大家能領悟到的是文中的一些思路,并不是死記硬背技術。

責任編輯:武曉燕 來源: Java后端技術全棧
相關推薦

2020-12-21 09:00:04

MySQL緩存SQL

2019-10-28 08:44:29

Code Review代碼團隊

2020-12-21 09:44:53

MySQL查詢緩存數據庫

2020-07-21 18:37:14

代碼條件變量

2017-01-19 15:11:37

AndroidRetrofitRxCache

2021-02-03 08:24:32

JavaScript技巧經驗

2022-07-04 07:09:55

架構

2021-06-03 08:05:46

VSCode 代碼高亮原理前端

2021-07-12 10:37:42

Spring面試事務

2022-03-03 07:00:43

Mybatiswhere標簽

2020-06-12 09:20:33

前端Blob字符串

2020-07-28 08:26:34

WebSocket瀏覽器

2019-07-12 15:28:41

緩存數據庫瀏覽器

2018-09-02 15:43:56

Python代碼編程語言

2020-08-26 13:30:18

代碼設計模式前端

2022-04-24 16:00:15

LinuxLinux命令ls命令

2011-09-15 17:10:41

2021-02-01 23:23:39

FiddlerCharlesWeb

2022-10-13 11:48:37

Web共享機制操作系統

2009-12-10 09:37:43

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日韩网站免费观看 | 亚洲高清中文字幕 | 香蕉视频在线播放 | 日韩欧美在线视频 | 综合久久综合久久 | 久久99这里只有精品 | 国产精品久久久久久久久动漫 | 国产精品久久亚洲 | 真人女人一级毛片免费播放 | 免费特黄视频 | av黄色在线| av大片 | 久久视频精品 | 日本视频在线播放 | 国产成人精品一区二区三 | 免费污视频 | 精品亚洲永久免费精品 | 好姑娘影视在线观看高清 | 国产ts人妖系列高潮 | 2019天天干夜夜操 | 天天看天天干 | 免费看色 | 97视频成人 | 国产区精品视频 | 久久午夜精品福利一区二区 | 国产区在线视频 | 日日骚av | 国产精品久久毛片av大全日韩 | 精品一区二区三区视频在线观看 | 亚洲综合在线播放 | 国产精品视频网 | 久久成人免费视频 | 久久国产免费看 | 亚洲精品黄 | 亚洲精品一区中文字幕 | 国产精品视频久久久 | 国产精品美女久久久久aⅴ国产馆 | 成人妇女免费播放久久久 | 午夜影院黄 | 欧美成人精品一区二区男人看 | 日本二区在线观看 |