前言
為了解決高並發的流量問題,通常我們都會添加緩存這一層,來扛住大量的讀請求。雖然緩存能夠幫數據庫分擔大量的讀請求,但是也伴隨着一個問題就是緩存中的數據怎么跟數據庫中的數據保持一致,又是一個新問題
數據實時性等級
這里我們需要保證緩存和數據庫的數據一致性,也可以根據數據所要求的實時性等級去評估,並不是所有的數據我們都需要保證強一致性,這里根據數據要求實時性不同大致分為2個等級:強一致性數據,弱一致性數據
強一致性數據:比如一些支付數據,金額數據,涉及到錢的數據對實時性要求就比較高,必須要保證數據的一致性
弱一致性數據:像一些用戶基礎信息,具有讀多寫少的特征,沒必要保證數據的強一致性,可以保證數據的最終一致性
下面來討論一下如何去保證緩存和數據庫雙寫時數據的一致性
解決方案
緩存的經典用法:請求進來,先走緩存,緩存存在,直接返回,緩存沒有,查數據庫,再將數據庫的值放到緩存中,供后續的讀請求使用
上面講述的是讀請求好理解,如果碰到的是寫請求,先刪除緩存,還是先更新數據庫,再更新緩存是一個問題
那么針對不同的場景,可以概括為以下幾個策略:
1. 先更新數據庫,后更新緩存
2. 先更新數據庫,后刪除緩存
3. 先更新緩存,后更新數據庫
4. 先刪除緩存,后更新數據庫
先更新數據庫,后更新緩存
這種方案比較嚴重的問題有2點:如果更新數據庫成功,而更新緩存失敗,則會導致緩存中一直存在舊數據。還有一個問題就是如果讀請求不多,便會導致每次寫請求都更新一下緩存,頻繁的更新緩存,這里可以采用刪除緩存的方式,而不是更新緩存的思想,讓后續有讀請求去做到更新緩存,有點類似lazy的思想
這種方案被采用的比較少,因為存在頻繁的更新緩存,性能低下
先更新數據庫,后刪除緩存
跟第一種都是先更新數據庫,再去操作緩存,比第一種方案的好處就是不是去更新緩存,而是直接刪除,避免頻繁的更新操作緩存
問題還是存在舊數據的讀取,緩存操作的失敗,出現數據不一致問題,先操作數據庫的話,再去操作緩存,都有可能存在緩存不一致的問題
解決問題1的思路可以有:
可以借助可靠消息一致性的特性來完成緩存的刪除
我們可以通過訂閱binlog日志,再借助mq的消息隊列達到成功刪除緩存的效果,如果中間刪除失敗,則消息還會一直重發,直到消息被成功消費
先更新緩存,后更新數據庫
后面兩個方案都是先操作緩存,再去操作數據庫。
先更新緩存,雖然能夠保證后續的請求讀到的不是舊數據,但是更新數據庫的時候,如果數據庫更新失敗,那么就會導致緩存中一直存在無效的數據,所造成的影響不比讀到舊數據小。還有一個問題就是采用的是更新緩存,而不是刪除緩存,就會導致在讀請求很少的情況下,會頻繁的操作緩存,性能低下
先刪除緩存,后更新數據庫
先刪除緩存雖然能解決第三種方案的頻繁更新緩存,緩存中保存的無效數據。但是加入了一個新問題就是更新數據庫時如果事務還未提交,這時有讀請求過來,還是會讀到舊數據。如果需要解決這種問題可以采用內部隊列去解決,將同一個數據的讀寫請求放到一個隊列中,看是否具有寫請求,讓讀請求進行阻塞
思路可以有:
更新數據的時候,根據數據的唯一標識,放到JVM的一個內部隊列中,后續有讀請求的時候,發現隊列中存在正在更新數據的線程,那么也將該讀請求放到內部隊列中,等待之前的寫請求更新完成,再來執行讀請求。這樣的話雖然能解決讀到舊數據問題,但是會引發以下的系列問題:
這樣會引起讀請求長時阻塞,如果隊列中寫請求過多的話
讀請求的並發量過高
需要將同一數據的請求路由到同一實例中
總結
上面四種方案總結下來,每種方案都有自己的缺點,但是對系統影響比較小的還是先刪除緩存,再更新數據庫方案。如果對於數據要求有很強的一致性,干脆還不如不要走緩存,讀寫都走數據庫,這樣也就不用保證緩存和數據庫雙寫的一致性了。當然對於那種技術方案是沒有最好的,只有最適合我們的