最經典的緩存+數據庫讀寫的模式:cache aside pattern
Cache Aside Pattern
讀的時候,先讀緩存,緩存沒有的話,那么就讀數據庫,然后取出數據后放入緩存,同時返回響應
更新的時候,先刪除緩存,然后再更新數據庫 (很多地方都說應該先更新數據庫,再刪緩存)
為什么是刪除緩存,而不是更新緩存呢?
原因很簡單,很多時候,復雜點的緩存的場景,因為緩存有的時候,不簡單是數據庫中直接取出來的值
比如可能更新了某個表的一個字段,然后其對應的緩存,是需要查詢另外兩個表的數據,並進行運算,才能計算出緩存最新的值的
更新緩存的代價是很高的
如果你頻繁修改一個緩存涉及的多個表,那么這個緩存會被頻繁的更新
但是問題在於,這個緩存到底會不會被頻繁訪問到?
舉個例子,一個緩存涉及的表的字段,在1分鍾內就修改了20次,或者是100次,那么緩存更新20次,100次; 但是這個緩存在1分鍾內就被讀取了1次,有大量的冷數據
實際上,如果你只是刪除緩存的話,那么1分鍾內,這個緩存不過就重新計算一次而已,開銷大幅度降低
每次數據過來,就只是刪除緩存,然后修改數據庫,如果這個緩存,在1分鍾內只是被訪問了1次,那么只有那1次,緩存是要被重新計算的,用緩存才去算緩存
其實刪除緩存,而不是更新緩存,就是一個lazy計算的思想,不要每次都重新做復雜的計算,不管它會不會用到,而是讓它到需要被使用的時候再重新計算
最初級的緩存不一致問題以及解決方案
問題:先修改數據庫,再刪除緩存,如果刪除緩存失敗了,那么會導致數據庫中是新數據,緩存中是舊數據,數據出現不一致
解決思路:先刪除緩存,再修改數據庫,如果刪除緩存成功,修改數據庫失敗,那么數據庫中是舊數據,緩存中是空的,那么數據不會不一致
因為讀的時候緩存沒有,則讀數據庫中舊數據,然后更新到緩存中
比較復雜的數據不一致問題分析
數據發生了變更,先刪除了緩存,然后要去修改數據庫,此時還沒修改
一個請求過來,去讀緩存,發現緩存空了,去查詢數據庫,查到了修改前的舊數據,放到了緩存中
數據變更的程序完成了數據庫的修改,此時緩存是舊數據,數據庫是新數據
解決方案:更新與讀取操作進行異步串行化
更新數據的時候,根據數據的唯一標識,將操作路由之后,發送到一個jvm內部的隊列中
讀取數據的時候,如果發現數據不在緩存中,那么將重新讀取數據+更新緩存的操作,根據唯一標識路由之后,也發送同一個jvm內部的隊列中
一個隊列對應一個工作線程
每個工作線程串行拿到對應的操作,然后一條一條的執行
這樣的話,一個數據變更的操作,先執行,刪除緩存,然后再去更新數據庫,但是還沒完成更新
此時如果一個讀請求過來,讀到了空的緩存,那么可以先將緩存更新的請求發送到隊列中,此時會在隊列中積壓,然后同步等待緩存更新完成
這里有一個優化點,一個隊列中,其實多個更新緩存請求串在一起是沒意義的,因此可以做過濾,如果發現隊列中已經有一個更新緩存的請求了,那么就不用再放個更新請求操作進去了,直接等待前面的更新操作請求完成即可
待那個隊列對應的工作線程完成了上一個操作的數據庫的修改之后,才會去執行下一個操作,也就是緩存更新的操作,此時會從數據庫中讀取最新的值,然后寫入緩存中
如果請求還在等待時間范圍內,不斷輪詢發現可以取到值了,那么就直接返回; 如果請求等待的時間超過一定時長,那么這一次直接從數據庫中讀取當前的舊值
轉自:中華石杉Java工程師面試突擊