亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区

Chinaunix

標(biāo)題: MongoDB 4.0 事務(wù)實(shí)現(xiàn)解析 [打印本頁(yè)]

作者: lyhabc    時(shí)間: 2018-07-16 10:42
標(biāo)題: MongoDB 4.0 事務(wù)實(shí)現(xiàn)解析

MongoDB 4.0 引入的事務(wù)功能,支持多文檔ACID特性,例如使用 mongo shell 進(jìn)行事務(wù)操作

> s = db.getMongo().startSession()
session { "id" : UUID("3bf55e90-5e88-44aa-a59e-a30f777f1d89") }
> s.startTransaction()
> db.coll01.insert({x: 1, y: 1})
WriteResult({ "nInserted" : 1 })
> db.coll02.insert({x: 1, y: 1})
WriteResult({ "nInserted" : 1 })
> s.commitTransaction()  (或者 s.abortTransaction()回滾事務(wù))
支持 MongoDB 4.0 的其他語(yǔ)言 Driver 也封裝了事務(wù)相關(guān)接口,用戶(hù)需要?jiǎng)?chuàng)建一個(gè) Session,然后在 Session 上開(kāi)啟事務(wù),提交事務(wù)。例如

python 版本
with client.start_session() as s:
    s.start_transaction()
    collection_one.insert_one(doc_one, session=s)
    collection_two.insert_one(doc_two, session=s)
    s.commit_transaction()
java 版本
try (ClientSession clientSession = client.startSession()) {
   clientSession.startTransaction();
   collection.insertOne(clientSession, docOne);
   collection.insertOne(clientSession, docTwo);
   clientSession.commitTransaction();
}
Session
Session 是 MongoDB 3.6 版本引入的概念,引入這個(gè)特性主要就是為實(shí)現(xiàn)多文檔事務(wù)做準(zhǔn)備。Session 本質(zhì)上就是一個(gè)「上下文」。

在以前的版本,MongoDB 只管理單個(gè)操作的上下文,mongod 服務(wù)進(jìn)程接收到一個(gè)請(qǐng)求,為該請(qǐng)求創(chuàng)建一個(gè)上下文 (源碼里對(duì)應(yīng) OperationContext),然后在服務(wù)整個(gè)請(qǐng)求的過(guò)程中一直使用這個(gè)上下文,內(nèi)容包括,請(qǐng)求耗時(shí)統(tǒng)計(jì)、請(qǐng)求占用的鎖資源、請(qǐng)求使用的存儲(chǔ)快照等信息。有了 Session 之后,就可以讓多個(gè)請(qǐng)求共享一個(gè)上下文,讓多個(gè)請(qǐng)求產(chǎn)生關(guān)聯(lián),從而有能力支持多文檔事務(wù)。

每個(gè) Session 包含一個(gè)唯一的標(biāo)識(shí) lsid,在 4.0 版本里,用戶(hù)的每個(gè)請(qǐng)求可以指定額外的擴(kuò)展字段,主要包括:

lsid: 請(qǐng)求所在 Session 的 ID, 也稱(chēng) logic session id
txnNmuber: 請(qǐng)求對(duì)應(yīng)的事務(wù)號(hào),事務(wù)號(hào)在一個(gè) Session 內(nèi)必須單調(diào)遞增
stmtIds: 對(duì)應(yīng)請(qǐng)求里每個(gè)操作(以insert為例,一個(gè)insert命令可以插入多個(gè)文檔)操作ID
實(shí)際上,用戶(hù)在使用事務(wù)時(shí),是不需要理解這些細(xì)節(jié),MongoDB Driver 會(huì)自動(dòng)處理,Driver 在創(chuàng)建 Session 時(shí)分配 lsid,接下來(lái)這個(gè) Session 里的所以操作,Driver 會(huì)自動(dòng)為這些操作加上 lsid,如果是事務(wù)操作,會(huì)自動(dòng)帶上 txnNumber。

值得一提的是,Session lsid 可以通過(guò)調(diào)用 startSession 命令讓 server 端分配,也可以客戶(hù)端自己分配,這樣可以節(jié)省一次網(wǎng)絡(luò)開(kāi)銷(xiāo);而事務(wù)的標(biāo)識(shí),MongoDB 并沒(méi)有提供一個(gè)單獨(dú)的 startTransaction的命令,txnNumber 都是直接由 Driver 來(lái)分配的,Driver 只需保證一個(gè) Session 內(nèi),txnNumber 是遞增的,server 端收到新的事務(wù)請(qǐng)求時(shí),會(huì)主動(dòng)的開(kāi)始一個(gè)新事務(wù)。

MongoDB 在 startSession 時(shí),可以指定一系列的選項(xiàng),用于控制 Session 的訪(fǎng)問(wèn)行為,主要包括:

causalConsistency: 是否提供 causal consistency的語(yǔ)義,如果設(shè)置為true,不論從哪個(gè)節(jié)點(diǎn)讀取,MongoDB 會(huì)保證 “read your own write” 的語(yǔ)義。參考 causal consistency
readConcern:參考 MongoDB readConcern 原理解析
writeConcern:參考 MongoDB writeConcern 原理解析
readPreference: 設(shè)置讀取時(shí)選取節(jié)點(diǎn)的規(guī)則,參考 read preference
retryWrites:如果設(shè)置為true,在復(fù)制集場(chǎng)景下,MongoDB 會(huì)自動(dòng)重試發(fā)生重新選舉的場(chǎng)景; 參考retryable write
ACID
Atomic
針對(duì)多文檔的事務(wù)操作,MongoDB 提供 “All or nothing” 的原子語(yǔ)義保證。

Consistency
太難解釋了,還有拋棄 Consistency 特性的數(shù)據(jù)庫(kù)?

Isolation
MongoDB 提供 snapshot 隔離級(jí)別,在事務(wù)開(kāi)始創(chuàng)建一個(gè) WiredTiger snapshot,然后在整個(gè)事務(wù)過(guò)程中使用這個(gè)快照提供事務(wù)讀。

Durability
事務(wù)使用 WriteConcern {j: ture} 時(shí),MongoDB 一定會(huì)保證事務(wù)日志提交才返回,即使發(fā)生 crash,MongoDB 也能根據(jù)事務(wù)日志來(lái)恢復(fù);而如果沒(méi)有指定 {j: true} 級(jí)別,即使事務(wù)提交成功了,在 crash recovery 之后,事務(wù)的也可能被回滾掉。

事務(wù)與復(fù)制
復(fù)制集配置下,MongoDB 整個(gè)事務(wù)在提交時(shí),會(huì)記錄一條 oplog(oplog 是一個(gè)普通的文檔,所以目前版本里事務(wù)的修改加起來(lái)不能超過(guò)文檔大小 16MB的限制),包含事務(wù)里所有的操作,備節(jié)點(diǎn)拉取oplog,并在本地重放事務(wù)操作。

事務(wù) oplog 示例,包含事務(wù)操作的 lsid,txnNumber,以及事務(wù)內(nèi)所有的操作日志(applyOps字段)

> “ts” : Timestamp(1530696933, 1), “t” : NumberLong(1), “h” : NumberLong(“4217817601701821530″), “v” : 2, “op” : “c”, “ns” : “admin.$cmd”, “wall” : ISODate(“2018-07-04T09:35:33.549Z”), “l(fā)sid” : { “id” : UUID(“e675c046-d70b-44c2-ad8d-3f34f2019a7e”), “uid” : BinData(0,”47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=”) }, “txnNumber” : NumberLong(0), “stmtId” : 0, “prevOpTime” : { “ts” : Timestamp(0, 0), “t” : NumberLong(-1) }, “o” : { “applyOps” : [ { "op" : "i", "ns" : "test.coll2", "ui" : UUID("a49ccd80-6cfc-4896-9740-c5bff41e7cce"), "o" : { "_id" : ObjectId("5b3c94d4624d615ede6097ae"), "x" : 20000 } }, { "op" : "i", "ns" : "test.coll3", "ui" : UUID("31d7ae62-fe78-44f5-ba06-595ae3b871fc"), "o" : { "_id" : ObjectId("5b3c94d9624d615ede6097af"), "x" : 20000 } } ] } }

整個(gè)重放過(guò)程如下:

獲取當(dāng)前 Batch (后臺(tái)不斷拉取 oplog 放入 Batch)
設(shè)置 OplogTruncateAfterPoint 時(shí)間戳為 Batch里第一條 oplog 時(shí)間戳 (存儲(chǔ)在 local.replset.oplogTruncateAfterPoint 集合)
寫(xiě)入 Batch 里所有的 oplog 到 local.oplog.rs 集合,根據(jù) oplog 條數(shù),如果數(shù)量較多,會(huì)并發(fā)寫(xiě)入加速
清理 OplogTruncateAfterPoint, 標(biāo)識(shí) oplog 完全成功寫(xiě)入;如果在本步驟完成前 crash,重啟恢復(fù)時(shí),發(fā)現(xiàn) oplogTruncateAfterPoint 被設(shè)置,會(huì)將 oplog 截短到該時(shí)間戳,以恢復(fù)到一致的狀態(tài)點(diǎn)。
將 oplog 劃分到到多個(gè)線(xiàn)程并發(fā)重放,為了提升并發(fā)效率,事務(wù)產(chǎn)生的 oplog 包含的所有修改操作,跟一條普通單條操作的 oplog 一樣,會(huì)據(jù)文檔ID劃分到多個(gè)線(xiàn)程。
更新 ApplyThrough 時(shí)間戳為 Batch 里最后一條 oplog 時(shí)間戳,標(biāo)識(shí)下一次重啟后,從該位置重新同步,如果本步驟之前失敗,重啟恢復(fù)時(shí),會(huì)從 ApplyThrough 上一次的值(上一個(gè) Batch 最后一條 oplog)拉取 oplog。
更新 oplog 可見(jiàn)時(shí)間戳,如果有其他節(jié)點(diǎn)從該備節(jié)點(diǎn)同步,此時(shí)就能讀到這部分新寫(xiě)入的 oplog
更新本地 Snapshot(時(shí)間戳),新的寫(xiě)入將對(duì)用戶(hù)可見(jiàn)。
事務(wù)與存儲(chǔ)引擎
事務(wù)時(shí)序統(tǒng)一
WiredTiger 很早就支持事務(wù),在 3.x 版本里,MongoDB 就通過(guò) WiredTiger 事務(wù),來(lái)保證一條修改操作,對(duì)數(shù)據(jù)、索引、oplog 三者修改的原子性。但實(shí)際上 MongoDB 經(jīng)過(guò)多個(gè)版本的迭代,才提供了事務(wù)接口,核心難點(diǎn)就是時(shí)序問(wèn)題。

MongoDB 通過(guò) oplog 時(shí)間戳來(lái)標(biāo)識(shí)全局順序,而 WiredTiger 通過(guò)內(nèi)部的事務(wù)ID來(lái)標(biāo)識(shí)全局順序,在實(shí)現(xiàn)上,2者沒(méi)有任何關(guān)聯(lián)。這就導(dǎo)致在并發(fā)情況下, MongoDB 看到的事務(wù)提交順序與 WiredTiger 看到的事務(wù)提交順序不一致。

為解決這個(gè)問(wèn)題,WiredTier 3.0 引入事務(wù)時(shí)間戳(transaction timestamp)機(jī)制,應(yīng)用程序可以通過(guò) WT_SESSION::timestamp_transaction 接口顯式的給 WiredTiger 事務(wù)分配 commit timestmap,然后就可以實(shí)現(xiàn)指定時(shí)間戳讀(read "as of" a timestamp)。有了 read "as of" a timestamp 特性后,在重放 oplog 時(shí),備節(jié)點(diǎn)上的讀就不會(huì)再跟重放 oplog 有沖突了,不會(huì)因重放 oplog 而阻塞讀請(qǐng)求,這是4.0版本一個(gè)巨大的提升。

/*
* __wt_txn_visible --
*  Can the current transaction see the given ID / timestamp?
*/
static inline bool
__wt_txn_visible(
    WT_SESSION_IMPL *session, uint64_t id, const wt_timestamp_t *timestamp)
{
    if (!__txn_visible_id(session, id))
        return (false);

    /* Transactions read their writes, regardless of timestamps. */
    if (F_ISSET(&session->txn, WT_TXN_HAS_ID) && id == session->txn.id)
        return (true);

#ifdef HAVE_TIMESTAMPS
    {
    WT_TXN *txn = &session->txn;

    /* Timestamp check. */
    if (!F_ISSET(txn, WT_TXN_HAS_TS_READ) || timestamp == NULL)
        return (true);

    return (__wt_timestamp_cmp(timestamp, &txn->read_timestamp) <= 0);
    }
#else
    WT_UNUSED(timestamp);
    return (true);
#endif
}
從上面的代碼可以看到,再引入事務(wù)時(shí)間戳之后,在可見(jiàn)性判斷時(shí),還會(huì)額外檢查時(shí)間戳,上層讀取時(shí)指定了時(shí)間戳讀,則只能看到該時(shí)間戳以前的數(shù)據(jù)。而 MongoDB 在提交事務(wù)時(shí),會(huì)將 oplog 時(shí)間戳跟事務(wù)關(guān)聯(lián),從而達(dá)到 MongoDB Server 層時(shí)序與 WiredTiger 層時(shí)序一致的目的。

事務(wù)對(duì) cache 的影響
WiredTiger(WT) 事務(wù)會(huì)打開(kāi)一個(gè)快照,而快照的存在的 WiredTiger cache evict 是有影響的。一個(gè) WT page 上,有N個(gè)版本的修改,如果這些修改沒(méi)有全局可見(jiàn)(參考 __wt_txn_visible_all),這個(gè) page 是不能 evict 的(參考 __wt_page_can_evict)。

在 3.x 版本里,一個(gè)寫(xiě)請(qǐng)求對(duì)數(shù)據(jù)、索引、oplog的修改會(huì)放到一個(gè) WT 事務(wù)里,事務(wù)的提交由 MongoDB 自己控制,MongoDB 會(huì)盡可能快的提交事務(wù),完成寫(xiě)清求;但 4.0 引入事務(wù)之后,事務(wù)的提交由應(yīng)用程序控制,可能出現(xiàn)一個(gè)事務(wù)修改很多,并且很長(zhǎng)時(shí)間不提交,這會(huì)給 WT cache evict 造成很大的影響,如果大量?jī)?nèi)存無(wú)法 evict,最終就會(huì)進(jìn)入 cache stuck 狀態(tài)。

為了盡量減小 WT cache 壓力,MongoDB 4.0 事務(wù)功能有一些限制,但事務(wù)資源占用超過(guò)一定閾值時(shí),會(huì)自動(dòng) abort 來(lái)釋放資源。規(guī)則包括

事務(wù)的生命周期不能超過(guò) transactionLifetimeLimitSeconds (默認(rèn)60s),該配置可在線(xiàn)修改
事務(wù)修改的文檔數(shù)不能超過(guò) 1000 ,不可修改
事務(wù)修改產(chǎn)生的 oplog 不能超過(guò) 16mb,這個(gè)主要是 MongoDB 文檔大小的限制, oplog 也是一個(gè)普通的文檔,也必須遵守這個(gè)約束。
Read as of a timestamp 與 oldest timestamp
Read as of a timestamp 依賴(lài) WiredTiger 在內(nèi)存里維護(hù)多版本,每個(gè)版本跟一個(gè)時(shí)間戳關(guān)聯(lián),只要 MongoDB 層可能需要讀的版本,引擎層就必須維護(hù)這個(gè)版本的資源,如果保留的版本太多,也會(huì)對(duì) WT cache 產(chǎn)生很大的壓力。

WiredTiger 提供設(shè)置 oldest timestamp 的功能,允許由 MongoDB 來(lái)設(shè)置該時(shí)間戳,含義是Read as of a timestamp 不會(huì)提供更小的時(shí)間戳來(lái)進(jìn)行一致性讀,也就是說(shuō),WiredTiger 無(wú)需維護(hù) oldest timestamp 之前的所有歷史版本。MongoDB 層需要頻繁(及時(shí))更新 oldest timestamp,避免讓 WT cache 壓力太大。


引擎層 Rollback 與 stable timestamp
在 3.x 版本里,MongoDB 復(fù)制集的回滾動(dòng)作是在 Server 層面完成,但節(jié)點(diǎn)需要回滾時(shí),會(huì)根據(jù)要回滾的 oplog 不斷應(yīng)用相反的操作,或從回滾源上讀取最新的版本,整個(gè)回滾操作效率很低。

4.0 版本實(shí)現(xiàn)了存儲(chǔ)引擎層的回滾機(jī)制,當(dāng)復(fù)制集節(jié)點(diǎn)需要回滾時(shí),直接調(diào)用 WiredTiger 接口,將數(shù)據(jù)回滾到某個(gè)穩(wěn)定版本(實(shí)際上就是一個(gè) Checkpoint),這個(gè)穩(wěn)定版本則依賴(lài)于 stable timestamp。WiredTiger 會(huì)確保 stable timestamp 之后的數(shù)據(jù)不會(huì)寫(xiě)到 Checkpoint里,MongoDB 根據(jù)復(fù)制集的同步狀態(tài),當(dāng)數(shù)據(jù)已經(jīng)同步到大多數(shù)節(jié)點(diǎn)時(shí)(Majority commited),會(huì)更新 stable timestamp,因?yàn)檫@些數(shù)據(jù)已經(jīng)提交到大多數(shù)節(jié)點(diǎn)了,一定不會(huì)發(fā)生 ROLLBACK,這個(gè)時(shí)間戳之前的數(shù)據(jù)就都可以寫(xiě)到 Checkpoint 里了。

MongoDB 需要確保頻繁(及時(shí))的更新 stable timestamp,否則影響 WT Checkpoint 行為,導(dǎo)致很多內(nèi)存無(wú)法釋放。例如主備延時(shí)很大,導(dǎo)致數(shù)據(jù)一直沒(méi)有被同步到大多數(shù)節(jié)點(diǎn),這時(shí)主上 stable timestamp 就無(wú)法更新,內(nèi)存不斷積累就可能把 cache 撐滿(mǎn)。




















歡迎光臨 Chinaunix (http://72891.cn/) Powered by Discuz! X3.2