mongoDB的事務


官網傳送門:

https://docs.mongodb.com/manual/core/replica-set-write-concern/

https://docs.mongodb.com/manual/core/transactions/#read-concern-write-concern-read-preference

MongoDB ACID 多文檔事務支持

事務屬性 支持程度
Atomocity 原子性 單表單文檔 : 1.x 就支持
復制集多表多行:4.0 復制集
分片集群多表多行4.2
Consistency 一致性 writeConcern, readConcern (3.2)
Isolation 隔離性 readConcern (3.2)
Durability 持久性 Journal and Replication

 

Atomocity 原子性 

一個事務作為一個提交單位,要么一起成功要么一起失敗,分布式關注的重點就是多節點之間數據的原子性控制。

Consistency 一致性 

讀和寫的一致性,會不會讀到臟數據。

Isolation 隔離性   

每個事務相互之間是獨立的,每個事務內與主表之間也是互不影響、相互獨立的。
Durability 持久性   

數據落盤持久化,通過記錄Journal日志文件和備份副本。

先從mongoDB的一致性相關的writeConcern, readConcern說。

什么是 writeConcern ?

writeConcern 決定一個寫操作落到多少個節點上才算成功。writeConcern 的取值包括:
0:發起寫操作,不關心是否成功;
1~集群最大數據節點數:寫操作需要被復制到指定節點數才算成功;默認是1。
majority:寫操作需要被復制到大多數節點上才算成功。

• all:寫入所有節點才算成功。
發起寫操作的程序將阻塞到寫操作到達指定的節點數為止


默認情況 w:“1”

 

 

大多數節點確認(即一半以上節點)   w: “majority” 

全部節點確認 w: “all”

 

 

 

j:true

 writeConcern 可以決定寫操作到達多少個節點才算成功,journal 則定義如何才算成
功。取值包括:
true: 寫操作落到 journal 文件中才算成功;
false: 寫操作到達內存即算作成功。

數據庫數據寫入的順序  數據在內存中先寫日志文件(journal 中落地持久化日志文件),再寫數據文件

 

 

 mongoDB shell中使用

db.test.insert( {count: 1}, {writeConcern: {w: "majority"}})
db.test.insert( {count: 1}, {writeConcern: {w: 3 }})
db.test.insert( {count: 1}, {writeConcern: {w: 4 }})

 

readPreference與readConcern

readPreference 設置 分布式數據庫從哪里讀

readConcern 什么樣的數據可以讀

readPreference 

readPreference 決定使用哪一個節點來滿足正在發起的讀請求。可選值包括:

primary: 只選擇主節點;

primaryPreferred:優先選擇主節點,如果不可用則選擇從節點;

secondary:只選擇從節點;

secondaryPreferred:優先選擇從節點,如果從節點不可用則選擇主節點;

nearest:選擇最近的節點;

 

 場景舉例:大量高並發讀取的數據場景可以選擇從節點,寫入數據的時候,用主節點。

readPreference的 配置方式

通過 MongoDB 的連接串參數:
• mongodb://host1:27107,host2:27107,host3:27017/?replicaSet=rs&readPreference=secondary
通過 MongoDB 驅動程序 API:
• MongoCollection.withReadPreference(ReadPreference readPref)
Mongo Shell:
• db.collection.find({}).readPref( “secondary” ) 

readConcern

在 readPreference 選擇了指定的節點后,readConcern 決定這個節點上的數據哪些是可讀的,類似於關系數據庫的隔離級別。可選值包括:
available:讀取所有可用的數據;
local:讀取所有可用且屬於當前分片的數據;
majority:讀取在大多數節點上提交完成的數據;
linearizable:可線性化讀取文檔;
snapshot:讀取最近快照中的數據;

在復制集中 local 和 available 是沒有區別的。兩者的區別主要體現在分片集上。考慮以下場景:
• 一個 chunk x 正在從 shard1 向 shard2 遷移;
• 整個遷移過程中 chunk x 中的部分數據會在 shard1 和 shard2 中同時存在,但源分片 shard1仍然是chunk x 的負責方:
  所有對 chunk x 的讀寫操作仍然進入 shard1;
  config 中記錄的信息 chunk x 仍然屬於 shard1;
• 此時如果讀 shard2,則會體現出 local 和 available 的區別:
  local:只取應該由 shard2 負責的數據(不包括 x);
  available:shard2 上有什么就讀什么(包括 x);

注意事項:
• 雖然看上去總是應該選擇 local,但畢竟對結果集進行過濾會造成額外消耗。在一些無關緊要的場景(例如統計)下,也可以考慮 available;
• MongoDB <=3.6 不支持對從節點使用 {readConcern: "local"};
• 從主節點讀取數據時默認 readConcern 是 local,從從節點讀取數據時默認readConcern 是 available(向前兼容原因)。

readConcern : ”majority” vs “local”

majority 如果有如下場景:

應業務需要做讀寫分離,主節點A主要做寫入操作,從節點做讀取操作;向主節點寫入一條數據;立即從從節點讀取這條數據。

如果用默認配置local,有的復制集從節點B到主A可能是有延時的,而導致在主節點A上修改數據后,立刻在從節點讀取,發現有臟數據,主從數據不同步。所以需要majority配置,和其他節點互交確認大多數據節點上都是一樣的數據才返回。

#注意配置文件內server參數開啟 
enableMajorityReadConcern:ture

#這種方式讀取不到剛寫入的數據
db.orders.insert({ oid: 101, sku: ”kite", q: 1}) //主節點寫入
db.orders.find({oid:101}).readPref("secondary") //從節點讀取
#使用 writeConcern + readConcern majority來解決
db.orders.insert({ oid: 101, sku: "kiteboar", q: 1}, {writeConcern:{w: "majority”}}) //主節點寫入
db.orders.find({oid:101}).readPref(“secondary”).readConcern("majority")         //從節點讀取

readConcern: majority 與臟讀
MongoDB 中的回滾:

• 寫操作到達大多數節點之前都是不安全的,一旦主節點崩潰,而從節還沒復制到該次操作,剛才的寫操作就丟失了;
• 把一次寫操作視為一個事務,從事務的角度,可以認為事務被回滾了。所以從分布式系統的角度來看,事務的提交被提升到了分布式集群的多個節點級別的“提交”,而不再是單個節點上的“提交”。在可能發生回滾的前提下考慮臟讀問題:
• 如果在一次寫操作到達大多數節點前讀取了這個寫操作,然后因為系統故障該操作回滾了,則發生了臟讀問題;
使用 {readConcern: “majority”} 可以有效避免臟讀

readConcern: linearizable

只讀取大多數節點確認過的數據。和 majority 最大差別是保證絕對的操作線性順序:

在寫操作自然時間后面的發生的讀,一定可以讀到之前的寫(會在讀取的節點,向其他所有節點發起確認請求)
- 只對讀取單個文檔時有效;
- 可能導致非常慢的讀,因此總是建議配合使用 maxTimeMS;

下圖情況是在主節點網絡異常時,從節點發起選舉期間發生的臟讀

 

readConcern: snapshot(最高級別)

{readConcern: “snapshot”} 只在多文檔事務中生效。將一個事務的 readConcern
設置為 snapshot,將保證在事務中的讀:
• 不出現臟讀;
• 不出現不可重復讀;
• 不出現幻讀。
因為所有的讀都將使用同一個快照,直到事務提交為止該快照才被釋放。

官網mongoDB Java Client 事務使用示例:

final MongoClient client = MongoClients.create(uri);
/*
    Prereq: Create collections. CRUD operations in transactions must be on existing collections.
 */
client.getDatabase("mydb1").getCollection("foo")
        .withWriteConcern(WriteConcern.MAJORITY).insertOne(new Document("abc", 0));
client.getDatabase("mydb2").getCollection("bar")
        .withWriteConcern(WriteConcern.MAJORITY).insertOne(new Document("xyz", 0));
/* Step 1: Start a client session. */
final ClientSession clientSession = client.startSession();
/* Step 2: Optional. Define options to use for the transaction. */
TransactionOptions txnOptions = TransactionOptions.builder()
        .readPreference(ReadPreference.primary())
        .readConcern(ReadConcern.LOCAL)
        .writeConcern(WriteConcern.MAJORITY)
        .build();

/* Step 3: Define the sequence of operations to perform inside the transactions. */
TransactionBody txnBody = new TransactionBody<String>() {
    public String execute() {
        MongoCollection<Document> coll1 = client.getDatabase("mydb1").getCollection("foo");
        MongoCollection<Document> coll2 = client.getDatabase("mydb2").getCollection("bar");
        /*
           Important:: You must pass the session to the operations.
         */
        coll1.insertOne(clientSession, new Document("abc", 1));
        coll2.insertOne(clientSession, new Document("xyz", 999));
        return "Inserted into collections in different databases";
    }
};
try {
    /*
       Step 4: Use .withTransaction() to start a transaction,
       execute the callback, and commit (or abort on error).
    */

    clientSession.withTransaction(txnBody, txnOptions);
} catch (RuntimeException e) {
    // some error handling
} finally {
    clientSession.close();
}

java 示例2:

//MongoDB 多文檔事務的使用方式與關系數據庫非常相似:
try (ClientSession clientSession = client.startSession()) {
    clientSession.startTransaction();
    collection.insertOne(clientSession, docOne);
    collection.insertOne(clientSession, docTwo);
    clientSession.commitTransaction();
}

 

 db.fsyncLock() 與db.fsyncUnlock() 可以鎖定/解鎖節點的寫入,可以用來做事務代碼的測試。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM