如何保證 mongodb 和數據庫雙寫數據一致性?
大家好,我是蘇三,又跟大家見面了。
前言
最近在我的技術羣裏,有位小夥伴問了大家一個問題:如何保證 Mongodb 和數據庫雙寫的數據一致性?
羣友們針對這個技術點討論的內容,引起了我的興趣。
其實我在實際工作中的有些業務場景,也在使用Mongodb
,也遇到過雙寫的數據一致性問題。
今天跟大家一起分享一下,這類問題的解決辦法,希望對你會有所幫助。
1 常見誤區
很多小夥伴看到雙寫數據一致性問題,首先會想到的是Redis
和數據庫
的數據雙寫一致性問題。
有些小夥伴認爲,Redis
和數據庫
的數據雙寫一致性問題,跟Mongodb
和數據庫
的數據雙寫一致性問題,是同一個問題。
但如果你仔細想想它們的使用場景,就會發現有一些差異。
1.1 我們是如何用緩存的?
Redis 緩存能提升我們系統的性能。
一般情況下,如果有用戶請求過來,先查緩存,如果緩存中存在數據,則直接返回。如果緩存中不存在,則再查數據庫,如果數據庫中存在,則將數據放入緩存,然後返回。如果數據庫中也不存在,則直接返回失敗。
流程圖如下:
通常情況下,保證緩存和數據雙寫數據一致性,最常用的技術方案是:延遲雙刪
。
感興趣的小夥伴,可以看看我的另一篇文章《如何保證數據庫和緩存雙寫一致性?》,裏面有非常詳細的介紹。
1.2 我們是如何用 MongoDB 的?
MongoDB
是一個高可用、分佈式的文檔數據庫
,用於大容量數據存儲。文檔存儲一般用類似json
的格式存儲,存儲的內容是文檔型的。
通常情況下,我們用來存儲大數據或者 json 格式的數據。
用戶寫數據的請求,核心數據
會被寫入數據庫,json 格式的非核心數據
,可能會寫入 MongoDB。
流程圖如下:
用戶讀數據的請求,會先讀數據庫中的數據,然後通過文檔的 id,讀取 MongoDB 中的數據。
流程圖如下:
Redis 和 MongoDB 在我們實際工作中的用途不一樣,導致了它們雙寫數據一致性問題的解決方案是不一樣的。
接下來我們一起看看,如何保證 MongoDB 和數據庫的雙寫的數據一致性?
2 如何保證雙寫一致性?
目前雙寫 MongoDB 和數據庫的數據,用的最多的就是下面這兩種方案。
2.1 先寫數據庫,再寫 MongoDB
該方案最簡單,先在數據庫中寫入核心數據,再在 MongoDB 中寫入非核心數據。
流程圖如下:
但如果有些業務場景,對數據完整性要求比較高,用這套方案可能會有問題。
當數據庫剛保存了核心數據,此時網絡出現異常,程序保存 MongoDB 的非核心數據時失敗了。
但 MongoDB 並沒有拋出異常,數據庫中已經保存的數據沒法回滾,這樣會出現數據庫中保存了數據,而 MongoDB 中沒保存數據的情況,從而導致 MongoDB 中的非核心數據丟失的問題。
所以這套方案,在實際工作中使用不多。
2.2 先寫 MongoDB,再寫數據庫
在該方案中,先在 MongoDB 中寫入非核心數據,再在數據庫中寫入核心數據。
流程圖如下:
這時候 MongoDB 中非核心數據不會回滾,可能存在 MongoDB 中保存了數據,而數據庫中沒保存數據的問題,同樣會出現數據不一致的問題。
答:我們忘了一個前提,查詢 MongoDB 文檔中的數據,必須通過數據庫的表中保存的mongo id
。但如果這個mongo id
在數據庫中都沒有保存成功,那麼,在 MongoDB 文檔中的數據是永遠都查詢不到的。
也就是說,這種情況下 MongoDB 文檔中保存的是垃圾數據,但對實際業務並沒有影響。
這套方案可以解決雙寫數據一致性問題,但它同時也帶來了兩個新問題:
-
用戶修改操作如何保存數據?
-
如何清理垃圾數據?
3 用戶修改操作如何保存數據?
我之前聊的先寫 MongoDB,再寫數據庫,這套方案中的流程圖,其實主要說的是新增數據的場景。
但如果在用戶修改數據的操作中,用戶先修改 MongoDB 文檔中的數據,再修改數據庫表中的數據。
流程圖如下:
那麼,用戶修改操作時如何保存數據呢?
這就需要把流程調整一下,在修改 MongoDB 文檔時,還是新增一條數據,不直接修改,生成一個新的 mongo id。然後在修改數據庫表中的數據時,同時更新 mongo id 字段爲這個新值。
流程圖如下:
使用該方案能夠解決修改數據時,數據一致性問題,但同樣會存在垃圾數據。
其實這個垃圾數據是可以即使刪除的,具體流程圖如下:
該方案可以解決用戶修改操作中,99% 的的垃圾數據,但還有那 1% 的情況,即如果最後刪除失敗該怎麼辦?
答:這就需要加重試機制
了。
我們可以使用job
或者mq
進行重試,優先推薦使用 mq 增加重試功能。特別是想RocketMQ
,自帶了失敗重試機制,有專門的重試隊列
,我們可以設置重試次數
。
流程圖優化如下:死信隊列
中。
然後專門有個程序監控死信隊列中的數據,如果發現有數據,則發報警郵件
。
這樣基本可以解決修改刪除垃圾數據失敗的問題。
4 如何清理新增的垃圾數據?
還有一種垃圾數據還沒處理,即在用戶新增數據時,如果寫入 MongoDB 文檔成功了,但寫入數據庫表失敗了。由於 MongoDB 不會回滾數據,這時候 MongoDB 文檔就保存了垃圾數據,那麼這種數據該如何清理呢?
4.1 定時刪除
我們可以使用 job 定時掃描,比如:每天
掃描一次 MongoDB 文檔,將 mongo id 取出來,到數據庫查詢數據,如果能查出數據,則保留 MongoDB 文檔中的數據。
如果在數據庫中該 mongo id 不存在,則刪除 MongoDB 文檔中的數據。
如果 MongoDB 文檔中的數據量不多,是可以這樣處理的。但如果數據量太大,這樣處理會有性能問題。
這就需要做優化,常見的做法是:縮小掃描數據的範圍
。
比如:掃描 MongoDB 文檔數據時,根據創建時間,只查最近 24 小時的數據,查出來之後,用 mongo id 去數據庫查詢數據。
如果直接查最近 24 小時的數據,會有問題,會把剛寫入 MongoDB 文檔,但還沒來得及寫入數據庫的數據也查出來,這種數據可能會被誤刪。
可以把時間再整體提前一小時,例如:
in_time < 當前時間-1 and in_time >= 當前時間-25
獲取 25 小時前到 1 小時前的數據。
這樣可以解決大部分系統中,因爲數據量過多,在一個定時任務的執行週期內,job 處理不完的問題。
但如果根據時間縮小範圍之後,數據量還是太大,job 還是處理不完該怎麼辦?
答:我們可以在 job 用多線程
刪除數據。
當然我們還可以將 job 的執行時間縮短,根據實際情況而定,比如每隔 12 小時,查詢創建時間是 13 小時前到 1 小時前的數據。
或者每隔 6 小時,查詢創建時間是 7 小時前到 1 小時前的數據。
或者每隔 1 小時,查詢創建時間是 2 小時前到 1 小時前的數據等等。
4.2 隨機刪除
其實刪除垃圾數據還有另外一種思路。
不知道你瞭解過Redis
刪除數據的策略
嗎?它在處理大批量數據時,爲了防止使用過多的 CPU 資源,用了一種隨機刪除
的策略。
我們在這裏可以借鑑一下。
有另外一個 job,每隔 500ms隨機
獲取 10 條數據進行批量處理,當然獲取的數據也是根據時間縮小範圍的。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/ObOURbVhxtpW5B-f3a9qBQ