自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何保證Mongodb和數(shù)據(jù)庫雙寫數(shù)據(jù)一致性?

數(shù)據(jù)庫 其他數(shù)據(jù)庫
很多小伙伴看到雙寫數(shù)據(jù)一致性問題,首先會想到的是Redis和數(shù)據(jù)庫的數(shù)據(jù)雙寫一致性問題。有些小伙伴認為,Redis和數(shù)據(jù)庫?的數(shù)據(jù)雙寫一致性問題,跟Mongodb和數(shù)據(jù)庫的數(shù)據(jù)雙寫一致性問題,是同一個問題。

?前言

最近在我的技術(shù)群里,有位小伙伴問了大家一個問題:如何保證Mongodb和數(shù)據(jù)庫雙寫的數(shù)據(jù)一致性?

群友們針對這個技術(shù)點討論的內(nèi)容,引起了我的興趣。

其實我在實際工作中的有些業(yè)務(wù)場景,也在使用Mongodb,也遇到過雙寫的數(shù)據(jù)一致性問題。

今天跟大家一起分享一下,這類問題的解決辦法,希望對你會有所幫助。

1.常見誤區(qū)

很多小伙伴看到雙寫數(shù)據(jù)一致性問題,首先會想到的是Redis和數(shù)據(jù)庫的數(shù)據(jù)雙寫一致性問題。

有些小伙伴認為,Redis和數(shù)據(jù)庫?的數(shù)據(jù)雙寫一致性問題,跟Mongodb和數(shù)據(jù)庫的數(shù)據(jù)雙寫一致性問題,是同一個問題。

但如果你仔細想想它們的使用場景,就會發(fā)現(xiàn)有一些差異。

1.1 我們是如何用緩存的?

Redis緩存能提升我們系統(tǒng)的性能。

一般情況下,如果有用戶請求過來,先查緩存,如果緩存中存在數(shù)據(jù),則直接返回。如果緩存中不存在,則再查數(shù)據(jù)庫,如果數(shù)據(jù)庫中存在,則將數(shù)據(jù)放入緩存,然后返回。如果數(shù)據(jù)庫中也不存在,則直接返回失敗。

流程圖如下:

圖片

有了緩存之后,能夠減輕數(shù)據(jù)庫的壓力,提升系統(tǒng)性能。

通常情況下,保證緩存和數(shù)據(jù)雙寫數(shù)據(jù)一致性,最常用的技術(shù)方案是:延遲雙刪。

感興趣的小伙伴,可以看看我的另一篇文章《如何保證數(shù)據(jù)庫和緩存雙寫一致性?》,里面有非常詳細的介紹。

1.2 我們是如何用MongoDB的?

MongoDB?是一個高可用、分布式的文檔數(shù)據(jù)庫?,用于大容量數(shù)據(jù)存儲。文檔存儲一般用類似json的格式存儲,存儲的內(nèi)容是文檔型的。

通常情況下,我們用來存儲大數(shù)據(jù)或者json格式的數(shù)據(jù)。

用戶寫數(shù)據(jù)的請求,核心數(shù)據(jù)?會被寫入數(shù)據(jù)庫,json格式的非核心數(shù)據(jù),可能會寫入MongoDB。

流程圖如下:

圖片

此外,在數(shù)據(jù)庫的表中,保存了MongoDB相關(guān)文檔的id。

用戶讀數(shù)據(jù)的請求,會先讀數(shù)據(jù)庫中的數(shù)據(jù),然后通過文檔的id,讀取MongoDB中的數(shù)據(jù)。

流程圖如下:

圖片

這樣可以保證核心屬性不會丟失,同時存儲用戶傳入的較大的數(shù)據(jù),兩全其美。

Redis和MongoDB在我們實際工作中的用途不一樣,導致了它們雙寫數(shù)據(jù)一致性問題的解決方案是不一樣的。

接下來我們一起看看,如何保證MongoDB和數(shù)據(jù)庫的雙寫的數(shù)據(jù)一致性?

2.如何保證雙寫一致性?

目前雙寫MongoDB和數(shù)據(jù)庫的數(shù)據(jù),用的最多的就是下面這兩種方案。

2.1 先寫數(shù)據(jù)庫,再寫MongoDB

該方案最簡單,先在數(shù)據(jù)庫中寫入核心數(shù)據(jù),再在MongoDB中寫入非核心數(shù)據(jù)。

流程圖如下:

圖片

如果有些業(yè)務(wù)場景,對數(shù)據(jù)的完整性要求不高,即非核心數(shù)據(jù)可有可無,使用該方案也是可以的。

但如果有些業(yè)務(wù)場景,對數(shù)據(jù)完整性要求比較高,用這套方案可能會有問題。

當數(shù)據(jù)庫剛保存了核心數(shù)據(jù),此時網(wǎng)絡(luò)出現(xiàn)異常,程序保存MongoDB的非核心數(shù)據(jù)時失敗了。

但MongoDB并沒有拋出異常,數(shù)據(jù)庫中已經(jīng)保存的數(shù)據(jù)沒法回滾,這樣會出現(xiàn)數(shù)據(jù)庫中保存了數(shù)據(jù),而MongoDB中沒保存數(shù)據(jù)的情況,從而導致MongoDB中的非核心數(shù)據(jù)丟失的問題。

所以這套方案,在實際工作中使用不多。

2.2 先寫MongoDB,再寫數(shù)據(jù)庫

在該方案中,先在MongoDB中寫入非核心數(shù)據(jù),再在數(shù)據(jù)庫中寫入核心數(shù)據(jù)。

流程圖如下:

圖片

關(guān)鍵問題來了:如果MongoDB中非核心數(shù)據(jù)寫入成功了,但數(shù)據(jù)庫中的核心數(shù)據(jù)寫入失敗了怎么辦?

這時候MongoDB中非核心數(shù)據(jù)不會回滾,可能存在MongoDB中保存了數(shù)據(jù),而數(shù)據(jù)庫中沒保存數(shù)據(jù)的問題,同樣會出現(xiàn)數(shù)據(jù)不一致的問題。

答:我們忘了一個前提,查詢MongoDB文檔中的數(shù)據(jù),必須通過數(shù)據(jù)庫的表中保存的mongo id?。但如果這個mongo id在數(shù)據(jù)庫中都沒有保存成功,那么,在MongoDB文檔中的數(shù)據(jù)是永遠都查詢不到的。

也就是說,這種情況下MongoDB文檔中保存的是垃圾數(shù)據(jù),但對實際業(yè)務(wù)并沒有影響。

這套方案可以解決雙寫數(shù)據(jù)一致性問題,但它同時也帶來了兩個新問題:

用戶修改操作如何保存數(shù)據(jù)?

如何清理垃圾數(shù)據(jù)?

3 用戶修改操作如何保存數(shù)據(jù)?

我之前聊的先寫MongoDB,再寫數(shù)據(jù)庫,這套方案中的流程圖,其實主要說的是新增數(shù)據(jù)的場景。

但如果在用戶修改數(shù)據(jù)的操作中,用戶先修改MongoDB文檔中的數(shù)據(jù),再修改數(shù)據(jù)庫表中的數(shù)據(jù)。

流程圖如下:

圖片

如果出現(xiàn)MongoDB文檔中的數(shù)據(jù)修改成功了,但數(shù)據(jù)庫表中的數(shù)據(jù)修改失敗了,不也出現(xiàn)問題了?

那么,用戶修改操作時如何保存數(shù)據(jù)呢?

這就需要把流程調(diào)整一下,在修改MongoDB文檔時,還是新增一條數(shù)據(jù),不直接修改,生成一個新的mongo id。然后在修改數(shù)據(jù)庫表中的數(shù)據(jù)時,同時更新mongo id字段為這個新值。

流程圖如下:

圖片

這樣如果新增MongoDB文檔中的數(shù)據(jù)成功了,但修改數(shù)據(jù)庫表中的數(shù)據(jù)失敗了,也沒有關(guān)系,因為數(shù)據(jù)庫中老的數(shù)據(jù),保存的是老的mongo id。通過該id,依然能從MongoDB文檔中查詢出數(shù)據(jù)。

使用該方案能夠解決修改數(shù)據(jù)時,數(shù)據(jù)一致性問題,但同樣會存在垃圾數(shù)據(jù)。

其實這個垃圾數(shù)據(jù)是可以即使刪除的,具體流程圖如下:

圖片

在之前的流程中,修改完數(shù)據(jù)庫,更新了mongo id為新值,接下來,就把MongoDB文檔中的那條老數(shù)據(jù)直接刪了。

該方案可以解決用戶修改操作中,99%的的垃圾數(shù)據(jù),但還有那1%的情況,即如果最后刪除失敗該怎么辦?

答:這就需要加重試機制了。

我們可以使用job?或者mq?進行重試,優(yōu)先推薦使用mq增加重試功能。特別是想RocketMQ?,自帶了失敗重試機制,有專門的重試隊列?,我們可以設(shè)置重試次數(shù)。

流程圖優(yōu)化如下:

圖片

將之前刪除MongoDB文檔中的數(shù)據(jù)操作,改成發(fā)送mq消息,有個專門的mq消費者,負責刪除數(shù)據(jù)工作,可以做成共用的功能。它包含了失敗重試機制,如果刪除5次還是失敗,則會把該消息保存到?死信隊列中。

然后專門有個程序監(jiān)控死信隊列中的數(shù)據(jù),如果發(fā)現(xiàn)有數(shù)據(jù),則發(fā)報警郵件。

這樣基本可以解決修改刪除垃圾數(shù)據(jù)失敗的問題。

4 如何清理新增的垃圾數(shù)據(jù)?

還有一種垃圾數(shù)據(jù)還沒處理,即在用戶新增數(shù)據(jù)時,如果寫入MongoDB文檔成功了,但寫入數(shù)據(jù)庫表失敗了。由于MongoDB不會回滾數(shù)據(jù),這時候MongoDB文檔就保存了垃圾數(shù)據(jù),那么這種數(shù)據(jù)該如何清理呢?

4.1 定時刪除

我們可以使用job定時掃描,比如:每天掃描一次MongoDB文檔,將mongo id取出來,到數(shù)據(jù)庫查詢數(shù)據(jù),如果能查出數(shù)據(jù),則保留MongoDB文檔中的數(shù)據(jù)。

如果在數(shù)據(jù)庫中該mongo id不存在,則刪除MongoDB文檔中的數(shù)據(jù)。

如果MongoDB文檔中的數(shù)據(jù)量不多,是可以這樣處理的。但如果數(shù)據(jù)量太大,這樣處理會有性能問題。

這就需要做優(yōu)化,常見的做法是:縮小掃描數(shù)據(jù)的范圍。

比如:掃描MongoDB文檔數(shù)據(jù)時,根據(jù)創(chuàng)建時間,只查最近24小時的數(shù)據(jù),查出來之后,用mongo id去數(shù)據(jù)庫查詢數(shù)據(jù)。

如果直接查最近24小時的數(shù)據(jù),會有問題,會把剛寫入MongoDB文檔,但還沒來得及寫入數(shù)據(jù)庫的數(shù)據(jù)也查出來,這種數(shù)據(jù)可能會被誤刪。

可以把時間再整體提前一小時,例如:

in_time < 當前時間-1 and in_time >= 當前時間-25

獲取25小時前到1小時前的數(shù)據(jù)。

這樣可以解決大部分系統(tǒng)中,因為數(shù)據(jù)量過多,在一個定時任務(wù)的執(zhí)行周期內(nèi),job處理不完的問題。

但如果根據(jù)時間縮小范圍之后,數(shù)據(jù)量還是太大,job還是處理不完該怎么辦?

答:我們可以在job用多線程刪除數(shù)據(jù)。

當然我們還可以將job的執(zhí)行時間縮短,根據(jù)實際情況而定,比如每隔12小時,查詢創(chuàng)建時間是13小時前到1小時前的數(shù)據(jù)。

或者每隔6小時,查詢創(chuàng)建時間是7小時前到1小時前的數(shù)據(jù)。

或者每隔1小時,查詢創(chuàng)建時間是2小時前到1小時前的數(shù)據(jù)等等。

4.2 隨機刪除

其實刪除垃圾數(shù)據(jù)還有另外一種思路。

不知道你了解過Redis?刪除數(shù)據(jù)的策略?嗎?它在處理大批量數(shù)據(jù)時,為了防止使用過多的CPU資源,用了一種隨機刪除的策略。

我們在這里可以借鑒一下。

有另外一個job,每隔500ms隨機獲取10條數(shù)據(jù)進行批量處理,當然獲取的數(shù)據(jù)也是根據(jù)時間縮小范圍的。

責任編輯:武曉燕 來源: 蘇三說技術(shù)
相關(guān)推薦

2022-03-31 08:21:14

數(shù)據(jù)庫緩存雙寫數(shù)據(jù)一致性

2024-01-22 08:52:00

AQS雙異步數(shù)據(jù)一致性

2020-09-03 09:45:38

緩存數(shù)據(jù)庫分布式

2024-10-28 12:41:25

2022-03-29 10:39:10

緩存數(shù)據(jù)庫數(shù)據(jù)

2024-12-26 15:01:29

2025-03-27 08:20:54

2023-09-07 08:11:24

Redis管道機制

2021-06-11 09:21:58

緩存數(shù)據(jù)庫Redis

2024-08-20 16:13:52

2023-05-26 07:34:50

RedisMySQL緩存

2021-12-14 07:15:57

MySQLRedis數(shù)據(jù)

2025-04-27 08:52:21

Redis數(shù)據(jù)庫緩存

2024-07-04 12:36:50

2023-09-15 14:24:54

ByteHouseClickHouse開源

2021-12-01 08:26:27

數(shù)據(jù)庫緩存技術(shù)

2022-02-23 09:17:09

數(shù)據(jù)庫分離變更

2022-04-01 16:55:22

數(shù)據(jù)庫緩存日志

2022-08-23 07:46:45

數(shù)據(jù)一致性數(shù)據(jù)庫

2023-12-19 09:43:43

MongoDB并發(fā)
點贊
收藏

51CTO技術(shù)棧公眾號