你是不是也經歷過這種情況?某個陽光明媚的上午,你正享受著枸杞泡水和敲代碼的美妙時刻,突然接到客服部門的電話。
電話那頭的聲音急切又焦慮:“客戶抱怨說,怎么點擊購買總是失敗?”
作為技術人員,你瞬間腦袋里浮現出無數可能的原因,其中最讓人頭疼的,莫過于緩存與數據庫的不一致問題。
這個問題不僅影響用戶體驗,還可能導致業務數據的混亂。
今兒就來聊聊緩存與數據庫不一致的問題,幫大家理清楚問題所在,并給出推薦方案。
在現代系統中,緩存可以極大地提升性能,減少數據庫的壓力。
然而,一旦緩存和數據庫的數據不一致,就會引發各種詭異的問題。
系統維護的重要性在此凸顯,極端手段比如清空緩存,可能會在短時間內解決問題,但從長遠來看,這些操作往往帶來更大的隱患。
我們來看看幾種常見的解決緩存與數據庫不一致的方案,每種方案都有各自的優缺點:
這種方案看似簡單,實際上很少被推薦。
原因在于如果在更新數據庫之前發生了錯誤,緩存中的數據將和數據庫中的數據不一致,最終導致更大的問題。
public void updateCacheThenDatabase(String key, String value) { cache.put(key, value); try { database.update(key, value); } catch (Exception e) { // 數據庫更新失敗,緩存數據可能錯誤 System.err.println("數據庫更新失敗: " + e.getMessage()); }}
這種方法解決了更新緩存失敗的問題,但可能引發另外一個問題:
在高并發場景下,數據庫已經更新,但緩存還沒有更新時,其他請求可能會讀到舊的緩存數據。
public void updateDatabaseThenCache(String key, String value) { database.update(key, value); cache.put(key, value);}
這種方案在高并發下容易產生問題:
在緩存刪除和數據庫更新之間的時間窗口內,其他請求可能會讀取到舊的數據,導致短時間內的數據不一致。
public void deleteCacheThenUpdateDatabase(String key, String value) { cache.remove(key); try { database.update(key, value); } catch (Exception e) { // 數據庫更新失敗,需要重新設置緩存 cache.put(key, getOldValueFromDatabase(key)); System.err.println("數據庫更新失敗: " + e.getMessage()); }}
這是較為推薦的一種方法,但在高并發場景下也有一定的局限性:
如果數據庫更新成功但緩存刪除失敗,可能導致短時間內的數據不一致。
public void updateDatabaseThenDeleteCache(String key, String value) { database.update(key, value); cache.remove(key);}
在討論一致性的時候,我們常常會提到強一致性和最終一致性。
強一致性保證每次讀取的數據都是最新的,但在分布式系統中實現成本較高。
最終一致性則允許數據在一定時間內不一致,適用于大多數實際業務場景。
根據業務需求權衡這兩者,是緩存策略設計中的重要一步。
后面我會給出一個弱一致性的推薦方案,供大家參考。
SpringCache是一個非常實用的緩存管理框架,能幫助我們簡化緩存操作。
以下是一個簡單的SpringCache配置示例:
@Configuration@EnableCachingpublic class CacheConfig { @Bean public CacheManager cacheManager() { return new ConcurrentMapCacheManager("entities"); }}
SpringCache 的優點:
SpringCache 的缺點:
常用的注解包括@Cacheable、@CachePut和@CacheEvict:
@Cacheable("entities")public Entity findById(Long id) { // 如果緩存中存在,則直接返回緩存中的數據 // 否則調用方法查詢數據庫,并將結果存入緩存 return repository.findById(id).orElse(null);}@CachePut(value = "entities", key = "#entity.id")public Entity updateEntity(Entity entity) { // 更新數據庫,同時更新緩存中的數據 return repository.save(entity);}@CacheEvict(value = "entities", allEntries = true)public void clearCache() { // 清空緩存}
緩存預熱的重要性不言而喻,上線后瞬時大流量可能導致緩存擊穿。
以下是幾種常見的緩存預熱方案:
@Componentpublic class CachePreloader { @Autowired private CacheManager cacheManager; @PostConstruct public void preload() { Cache cache = cacheManager.getCache("entities"); if (cache != null) { // 假設我們有一個服務可以獲取需要預熱的數據 List<Entity> entities = fetchEntitiesForPreloading(); for (Entity entity : entities) { cache.put(entity.getId(), entity); } } } private List<Entity> fetchEntitiesForPreloading() { // 獲取需要預熱的數據 return repository.findAll(); }}
@Componentpublic class ScheduledCachePreloader { @Autowired private CacheManager cacheManager; @Scheduled(fixedRate = 60000) // 每分鐘執行一次 public void preload() { Cache cache = cacheManager.getCache("entities"); if (cache != null) { // 獲取需要預熱的數據并加載到緩存 List<Entity> entities = fetchEntitiesForPreloading(); for (Entity entity : entities) { cache.put(entity.getId(), entity); } } } private List<Entity> fetchEntitiesForPreloading() { // 獲取需要預熱的數據 return repository.findAll(); }}
@Componentpublic class ManualCachePreloader { @Autowired private CacheManager cacheManager; public void preload() { Cache cache = cacheManager.getCache("entities"); if (cache != null) { // 獲取需要預熱的數據并加載到緩存 List<Entity> entities = fetchEntitiesForPreloading(); for (Entity entity : entities) { cache.put(entity.getId(), entity); } } } private List<Entity> fetchEntitiesForPreloading() { // 獲取需要預熱的數據 return repository.findAll(); }}
綜合考慮各種方案的優缺點,我給大家一種工作中真正常用的方案,也是我待過的互聯網公司中實踐過的方案。
基本策略:刪除Redis中緩存 -> 更新數據庫 -> 最新數據set到Redis
延遲雙刪:刪除Redis中緩存 -> 更新數據庫 -> 休眠500ms -> 再次刪除Redis中緩存 -> 最新數據set到Redis
延遲三刪:刪除Redis中緩存 -> 更新數據庫 -> 休眠500ms -> 再次刪除Redis中緩存 -> 休眠500ms -> 再次刪除Redis中緩存 -> 最新數據set到Redis
這是一種非常簡單且成本很低的操作,但能解決絕大多數的緩存與數據庫不一致問題。
原理很好理解,就是更新數據庫之后設置合理的休眠時間,然后再次刪除掉其他線程請求進來導致的舊緩存,最終達到緩存和數據庫都是最新數據的目的。
其中休眠時間要根據自身業務的平均耗時來決定,而延遲雙刪其實就夠了,延遲三刪只是為了開闊大家的思路,因為真有些公司刪除三次來保證一些極端情況的不一致,但我覺得沒必要,太極端就不是弱一致性了。
如果是比較復雜的項目,甚至能再進一步的優化,也就是借用定時任務和MQ來替代休眠線程,實現異步刪除緩存,達到弱一致性的結果。
緩存與數據庫一致性是一個復雜的問題,需要根據具體業務場景選擇合適的策略。
大家需要記住一點,高可用性是系統的最優先選擇,所以弱一致性就必然成為數據不一致性的最優解,這是一種良性閉環。
因為系統不能用,那是直接虧損,但數據出現低量的不一致,完全可以接受。
本文鏈接:http://www.tebozhan.com/showinfo-26-98863-0.html你知道緩存的這個問題到底把多少程序員坑慘了嗎?
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com