MongoDB 單文檔原子操作的實現

transaction的支持在 MongoDB 裏面是逐漸進行的, 從3.2版本開始支持單文檔的原子操作, 這是後續的多文檔原子操作, 以及4.0 的副本集的事務, 以及後續的計劃推出的4.2的分片的事務操作的基礎。本文着重描述一下單文檔的原子操作。

原子性: 就是一個操作要麼成功, 要麼失敗。具體來說, 就是一個插入、更新或者刪除操作, 應該只有commit或者rollback兩種結果中的一種。對於每一個操作, 都有一個上下文, 來記錄相關的信息,OperationContext以及子類OperationContextImpl就是用來記錄能夠進行的操作, 以及相關的狀態信息。

class OperationContext {
protected:
    RecoveryUnitState _ruState = kNotInUnitOfWork;

private:
    friend class WriteUnitOfWork;
    Client* const _client;
    const unsigned int _opId;

    // Not owned.
    Locker* const _locker;

    // Follows the values of ErrorCodes::Error. The default value is 0 (OK), which means the
    // operation is not killed. If killed, it will contain a specific code. This value changes only
    // once from OK to some kill code.
    AtomicWord<ErrorCodes::Error> _killCode{ErrorCodes::OK};

    WriteConcernOptions _writeConcern;
};
class OperationContextImpl : public OperationContext {

private:
    std::unique_ptr<RecoveryUnit> _recovery;
    bool _writesAreReplicated;
};

從OperationContext看出, 它的主要實現類是RecoveryUnit, 該類是在storage下面定義的, 進行不同存儲引擎下的change操作的commit與rollbackup行爲。要保持某個操作的原子性, 最只要的實現就在RecoveryUnit的函數。對於每一個change操作, 要從RecoveryUnit:Chnage來繼承和定義其commit和rollback行爲。定義好一個Change類之後, 可以通過registerChange函數, 將該Change添加到這個RecoveryUnit裏面的一個保存此次操作的所有的change的集合內。比如, 在WiredTiger下面, 我們定義了class WiredTigerRecoveryUnit。

class RecoveryUnit {
    class Change {
    public:
        virtual ~Change() {}
        virtual void rollback() = 0;
        virtual void commit() = 0;
    };
    virtual void registerChange(Change* change) = 0;
    virtual void beginUnitOfWork(OperationContext* opCtx) = 0;
    virtual void commitUnitOfWork() = 0;
    virtual void abortUnitOfWork() = 0;
 }
 class WiredTigerRecoveryUnit final : public RecoveryUnit {
 private:
    WiredTigerSessionCache* _sessionCache;  // not owned
    WiredTigerSession* _session;            // owned, but from pool
    bool _areWriteUnitOfWorksBanned = false;
    bool _inUnitOfWork;
    bool _active;
    
    // 記錄RecoveryUnit對應的snapshotId
    uint64_t _mySnapshotId;
    bool _everStartedWrite;
    Timer _timer;
    RecordId _oplogReadTill;
    bool _readFromMajorityCommittedSnapshot = false;
    SnapshotName _majorityCommittedSnapshot = SnapshotName::min();

    // 包含operationContext裏面的所有的Change
    typedef OwnedPointerVector<Change> Changes;
    Changes _changes;
};

如上所示, RecoveryUnit主要進行beginUnitOfWork, commitUnitOfWork以及abortUnitOfWork。其用法爲:

beginUnitOfWork
//insert or update
if normal {
   commitUnitOfWork
}else {
    abortUnitOfWork
 }

是不是很類似常見的關係型數據庫的事務操作?

原子性讀操作

在分佈式下同下, 併發的讀寫是很常見的情形, MongoDB是如何避免髒讀的?
在MongoDB裏面, 通過snapshot, 來記錄下一個個的數據庫瞬間的狀態。通過SnapshotThread類, 週期性的創建一系列的snapshot, 這些snapshot, 通過WiredTigerSnapshotManager來管理。WiredTigerSnapshotManager::_committedSnapshot來記錄已經提交的snapshot 的ID, 在這個ID之前的snapshot是可讀的, 並且是可以被清理的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章