快取與資料庫一致性問題深度剖析
前言
本篇文章是我之前系列文章中的一篇,主要討論了我們在平時的開發過程中,各大系統中都要用到的快取資料的問題,進一步延伸到資料庫和快取的雙寫一致性問題,並且給出了所有方案的實現程式碼方便大家參考。
本篇文章主要內容
- 資料快取
- 為何要使用快取
- 哪類資料適合快取
- 快取的利與弊
- 如何保證快取和資料庫一致性
- 不更新快取,而是刪除快取
- 先操作快取,還是先操作資料庫
- 非要保證資料庫和快取資料強一致該怎麼辦
- 快取和資料庫一致性實戰
- 實戰:先刪除快取,再更新資料庫
- 實戰:先更新資料庫,再刪快取
- 實戰:快取延時雙刪
- 實戰:刪除快取重試機制
- 實戰:讀取binlog非同步刪除快取
碼字不易,只求關注,歡迎關注我的原創技術公眾號:後端技術漫談(二維碼見文章底部)
專案原始碼在這裡
https://github.com/qqxx6661/miaosha
資料快取
在我們實際的業務場景中,一定有很多需要做資料快取的場景,比如售賣商品的頁面,包括了許多併發訪問量很大的資料,它們可以稱作是是“熱點”資料,這些資料有一個特點,就是更新頻率低,讀取頻率高,這些資料應該儘量被快取,從而減少請求打到資料庫上的機會,減輕資料庫的壓力。
為何要使用快取
快取是為了追求“快”而存在的。我們用程式碼舉一個例子。
我在自己的Demo程式碼倉庫中增加了兩個查詢庫存的介面getStockByDB和getStockByCache,分別表示從資料庫和快取查詢某商品的庫存量。
隨後我們用JMeter進行併發請求測試。(JMeter的使用請參考我之前寫的文章:
需要宣告的是,我的測試並不嚴謹,只是作對比測試,不要作為實際服務效能的參考。
這是兩個介面的程式碼:
/** * 查詢庫存:通過資料庫查詢庫存 * @param sid * @return */ @RequestMapping("/getStockByDB/{sid}") @ResponseBody public String getStockByDB(@PathVariable int sid) { int count; try { count = stockService.getStockCountByDB(sid); } catch (Exception e) { LOGGER.error("查詢庫存失敗:[{}]", e.getMessage()); return "查詢庫存失敗"; } LOGGER.info("商品Id: [{}] 剩餘庫存為: [{}]", sid, count); return String.format("商品Id: %d 剩餘庫存為:%d", sid, count); } /** * 查詢庫存:通過快取查詢庫存 * 快取命中:返回庫存 * 快取未命中:查詢資料庫寫入快取並返回 * @param sid * @return */ @RequestMapping("/getStockByCache/{sid}") @ResponseBody public String getStockByCache(@PathVariable int sid) { Integer count; try { count = stockService.getStockCountByCache(sid); if (count == null) { count = stockService.getStockCountByDB(sid); LOGGER.info("快取未命中,查詢資料庫,並寫入快取"); stockService.setStockCountToCache(sid, count); } } catch (Exception e) { LOGGER.error("查詢庫存失敗:[{}]", e.getMessage()); return "查詢庫存失敗"; } LOGGER.info("商品Id: [{}] 剩餘庫存為: [{}]", sid, count); return String.format("商品Id: %d 剩餘庫存為:%d", sid, count); }
首先設定為10000個併發請求的情況下,執行JMeter,結果首先出現了大量的報錯,10000個請求中98%的請求都直接失敗了。讓人很慌張~
開啟日誌,報錯如下:
SpringBoot內建的Tomcat最大併發數搞的鬼,其預設值為200,對於10000的併發,單機服務實在是力不從心。當然,你可以修改這裡的併發數設定,但是你的小機器仍然可能會扛不住。
將其修改為如下配置後,我的小機器才在通過快取拿庫存的情況下,保證了10000個併發的100%返回請求:
server.tomcat.max-threads=10000
server.tomcat.max-connections=10000
可以看到,不使用快取的情況下,吞吐量為668個請求每秒:
使用快取的情況下,吞吐量為2177個請求每秒:
在這種“十分不嚴謹”的對比下,有快取對於一臺單機,效能提升了3倍多,如果在多臺機器,更多併發的情況下,由於資料庫有了更大的壓力,快取的效能優勢應該會更加明顯。
測完了這個小實驗,我看了眼我掛著MySql的小水管騰訊雲伺服器,生怕他被這麼高流量搞掛。這種突發的流量,指不定會被檢測為異常攻擊流量呢~
我用的是騰訊雲伺服器1C4G2M,活動買的,很便宜。這裡打個免費的廣告,請騰訊雲看到後聯絡我給我打錢