雙重檢測同步鎖---防止Redis快取穿透
阿新 • • 發佈:2019-02-07
快取穿透:
注:
上面三個圖會有什麼問題呢?
我們在專案中使用快取通常都是先檢查快取中是否存在,如果存在直接返回快取內容,如果不存在就直接查詢資料庫然後再快取查詢結果返回。這個時候如果我們查詢的某一個數據在快取中一直不存在,就會造成每一次請求都查詢DB,這樣快取就失去了意義,在流量大時,可能DB就掛掉了。
那這種問題有什麼好辦法解決呢?
要是有人利用不存在的key頻繁攻擊我們的應用,這就是漏洞。
有一個比較巧妙的作法是,可以將這個不存在的key預先設定一個值。
比如,”key” , “&&”。
在返回這個&&值的時候,我們的應用就可以認為這是不存在的key,那我們的應用就可以決定是否繼續等待繼續訪問,還是放棄掉這次操作。如果繼續等待訪問,過一個時間輪詢點後,再次請求這個key,如果取到的值不再是&&,則可以認為這時候key有值了,從而避免了透傳到資料庫,從而把大量的類似請求擋在了快取之中。
快取併發:
有時候如果網站併發訪問高,一個快取如果失效,可能出現多個程序同時查詢DB,同時設定快取的情況,如果併發確實很大,這也可能造成DB壓力過大,還有快取頻繁更新的問題。
我現在的想法是對快取查詢加鎖,如果KEY不存在,就加鎖,然後查DB入快取,然後解鎖;其他程序如果發現有鎖就等待,然後等解鎖後返回資料或者進入DB查詢。
這種情況和剛才說的預先設定值問題有些類似,只不過利用鎖的方式,會造成部分請求等待。
以上來源:http://ifeve.com/concurrency-cache-cross/
根據快取併發這種思想,寫了以下的雙重檢測同步鎖:
@Service public class StudentServiceImpl implements StudentService { @Autowired private StudentMapper studentMapper; //springboot自動配置的,直接注入到類中即可使用 @Autowired private RedisTemplate<Object, Object> redisTemplate; /**指定key的序列化方式,字串的方式*/ private RedisSerializer keyRedisSerializer = new StringRedisSerializer(); /**指定value的序列化方式,字串的方式*/ private RedisSerializer valueRedisSerializer = new Jackson2JsonRedisSerializer<Student>(Student.class); /** * 查詢所有學生資訊,帶有快取 * * @return */ public List<Student> getAllStudent() { //KEY 是按照字串方式序列化,可讀性較好 redisTemplate.setKeySerializer(keyRedisSerializer); //在高併發條件下,會出現快取穿透 List<Student> studentList = (List<Student>)redisTemplate.opsForValue().get("allStudent"); if (null == studentList) { //5個人, 4個等,1個進入 synchronized (this) { //雙重檢測鎖,假使同時有5個請求進入了上一個if(null == studentList),加了鎖之後one by one 的訪問,這裡再次對快取進行檢測,盡一切可能防止快取穿透的產生,但是效能會有所損失 studentList = (List<Student>)redisTemplate.opsForValue().get("allStudent"); if (null == studentList) { studentList = studentMapper.getAllStudent(); redisTemplate.opsForValue().set("allStudent", studentList); System.out.println("請求的資料庫。。。。。。"); } else { //System.out.println("請求的快取。。。。。。"); } } } else { System.out.println("請求的快取。。。。。。"); } return studentList; } }