LMDB-基礎(chǔ)結(jié)構(gòu)與Mmap思想

2月9日學(xué)習(xí)手冊

ACID記錄

  • Atomicity(原子性):一個事務(wù)(transaction)中的所有操作集绰,或者全部完成菠劝,或者全部不完成赛糟,不會結(jié)束在中間某個環(huán)節(jié)埃叭。事務(wù)在執(zhí)行過程中發(fā)生錯誤摸恍,會被回滾(Rollback)到事務(wù)開始前的狀態(tài),就像這個事務(wù)從來沒有執(zhí)行過一樣游盲。即误墓,事務(wù)不可分割、不可約簡益缎。
  • Consistency(一致性):在事務(wù)開始之前和事務(wù)結(jié)束以后谜慌,數(shù)據(jù)庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預(yù)設(shè)約束莺奔、觸發(fā)器欣范、級聯(lián)回滾等。
  • Isolation(隔離性):數(shù)據(jù)庫允許多個并發(fā)事務(wù)同時對其數(shù)據(jù)進行讀寫和修改的能力令哟,隔離性可以防止多個事務(wù)并發(fā)執(zhí)行時由于交叉執(zhí)行而導(dǎo)致數(shù)據(jù)的不一致恼琼。事務(wù)隔離分為不同級別,包括未提交讀(Read uncommitted)屏富、提交讀(read committed)晴竞、可重復(fù)讀(repeatable read)和串行化(Serializable)。
  • Durability(持久性):事務(wù)處理結(jié)束后狠半,對數(shù)據(jù)的修改就是永久的噩死,即便系統(tǒng)故障也不會丟失。

整體架構(gòu)

image.png

LMDB(Lighting Memory DB)是在BerkeleyDB的基礎(chǔ)上進行改編具有高效神年、緊湊已维、健壯性的數(shù)據(jù)庫。它文件結(jié)構(gòu)簡單已日,一個文件夾垛耳,里面一個數(shù)據(jù)文件,一個鎖文件。數(shù)據(jù)隨意復(fù)制堂鲜,隨意傳輸栈雳。它的訪問簡單,不需要運行單獨的數(shù)據(jù)庫管理進程泡嘴,只要在訪問數(shù)據(jù)的代碼里引用LMDB庫甫恩,訪問時給文件路徑即可逆济。

lmdb的基本做法是使用mmap文件映射酌予,不管這個文件存儲實在內(nèi)存上還是在持久存儲上。lmdb的所有讀取操作都是通過mmap將要訪問的文件只讀的映射到虛擬內(nèi)存中奖慌,直接訪問相應(yīng)的地址.因為使用了read-only的mmap抛虫,同樣避免了程序錯誤將存儲結(jié)構(gòu)寫壞的風(fēng)險。并且IO的調(diào)度由操作系統(tǒng)的頁調(diào)度機制完成简僧。

而寫操作建椰,則是通過write系統(tǒng)調(diào)用進行的,這主要是為了利用操作系統(tǒng)的文件系統(tǒng)一致性岛马,避免在被訪問的地址上進行同步棉姐。

設(shè)計上使用了mmap和COW技術(shù),因此整體架構(gòu)比較簡單啦逆,沒有其他數(shù)據(jù)庫的緩存管理伞矩、日志管理、外存管理等組件夏志。

mmap文件映射是基礎(chǔ)乃坤,lmdb通過只讀文件映射(默認(rèn))避免了因為應(yīng)用程序bug導(dǎo)致數(shù)據(jù)庫被破壞的情形。其上的一些基礎(chǔ)結(jié)構(gòu)比如Locktables,MVCC,COW等都是實現(xiàn)事務(wù)控制的基礎(chǔ)沟蔑,通過這些理論基礎(chǔ)湿诊,lmdb實現(xiàn)了完整的ACI屬性,D通過mmap實現(xiàn)瘦材。最后厅须,系統(tǒng)對外提供了關(guān)于B+Tree的操作方式,利用cursor游標(biāo)進行食棕±屎停可以進行增刪改查。

B+Tree的所有變動方式與其他的實現(xiàn)類似宣蠕,不過lmdb的實現(xiàn)基礎(chǔ)是append only B+Tree

下面簡單的介紹一下該數(shù)據(jù)庫的基本情況:

1 數(shù)據(jù)庫在運行時需要創(chuàng)建一個環(huán)境mdb_env_open()例隆,我們需要傳入目錄路徑,并在目錄下產(chǎn)生一個鎖文件以及存儲文件抢蚀。

2 當(dāng)環(huán)境創(chuàng)建完成后镀层,我們便需要創(chuàng)建mdb_txn_begin()來創(chuàng)建事物,而該事物在同一個時間只能有一個線程來執(zhí)行。

3 我們可以調(diào)用mdb_dbi_open()來打開已有的數(shù)據(jù)庫唱逢。

4 使用mdb_get() 與mdb_put()操作鍵值對吴侦,而該KV通常會被表示為MDB_val結(jié)構(gòu),該結(jié)構(gòu)有兩個域坞古,包括mv_size以及mv_data

typedef struct MDB_val {
    size_t       mv_size;   /**< size of the data item */
    void        *mv_data;   /**< address of the data item */
} MDB_val;

由于LMDB使用0拷貝技術(shù)备韧,且直接將數(shù)據(jù)攻disk映射到Memory中。

5 為了提升操作的執(zhí)行效率痪枫,LMDB使用了游標(biāo)機制织堂,其能存儲、獲取或刪除多個值奶陈。

6 LMDB使用了POSIX文件鎖易阳,當(dāng)一個進程多次調(diào)用打開函數(shù)打開同一個文件的時候會產(chǎn)生問題。所以吃粒,LMDB對所有線程共享打開環(huán)境潦俺。

7 LMDB必須使用mdb_txn_commit()將事務(wù)提交,否則所有的操作均被丟棄徐勃。對于讀操作來說事示,所有的游標(biāo)不會被自動釋放而在讀寫事務(wù)中所有游標(biāo)被自動釋放且無法復(fù)用。

LMDB允許多個讀操作同時進行而只允許一次寫僻肖。一旦一個讀寫事務(wù)開始時肖爵,其他的操作將會被阻塞直到第一個進程完成。

8 mdb_get()與 mdb_put() 對于一個key對于多個value的情況檐涝,只會返回第一個value遏匆。

當(dāng)需要一個key多值時,那么需要對MDB_DUPSORT進行設(shè)置

9 如果用戶頻繁開始或者結(jié)束讀操作谁榜,那么LMDB會選擇reset而不是清除重建幅聘。

10 對于讀操作,結(jié)束后必須調(diào)用函數(shù)關(guān)閉游標(biāo)并清除窃植;

LMDB源碼中涉及的數(shù)據(jù)結(jié)構(gòu)

LMDB的C版本源碼只有四個非常關(guān)鍵的文件

image.png

首先我們需要學(xué)習(xí)一下LMDB的環(huán)境結(jié)構(gòu)體MDB_env帝蒿。

struct MDB_env {
    HANDLE      me_fd;      /**< The main data file */
    HANDLE      me_lfd;     /**< The lock file */
    HANDLE      me_mfd;     /**< For writing and syncing the meta pages */
    /** Failed to update the meta page. Probably an I/O error. */
#define MDB_FATAL_ERROR 0x80000000U
    /** Some fields are initialized. */
#define MDB_ENV_ACTIVE  0x20000000U
    /** me_txkey is set */
#define MDB_ENV_TXKEY   0x10000000U
    /** fdatasync is unreliable */
#define MDB_FSYNCONLY   0x08000000U

    uint32_t    me_flags;       /**< @ref mdb_env */
    unsigned int    me_psize;   /**< DB page size, inited from me_os_psize */
    unsigned int    me_os_psize;    /**< OS page size, from #GET_PAGESIZE */
    unsigned int    me_maxreaders;  /**< size of the reader table */
    /** Max #MDB_txninfo.%mti_numreaders of interest to #mdb_env_close() */
    volatile int    me_close_readers;
    MDB_dbi     me_numdbs;      /**< number of DBs opened */
    MDB_dbi     me_maxdbs;      /**< size of the DB table */
    MDB_PID_T   me_pid;     /**< process ID of this env */
    char        *me_path;       /**< path to the DB files */
    char        *me_map;        /**< the memory map of the data file */
    MDB_meta    *me_metas[NUM_METAS];   /**< pointers to the two meta pages */
    // 元數(shù)據(jù)列表,lmdb使用兩個頁面作為meta頁面巷怜,因此其大小為2. meta頁面的一個主要作用是
    // 用于保存B+Tree的root_page指針葛超。其內(nèi)部采用COW技術(shù)
    /*
      root page指針可能會被修改,因此使用兩個不同的頁面進行切換保存最新頁面延塑,
      類似于double-buffer設(shè)計绣张。由此可知,雖然lmdb支持一個文件中多個B+Tree关带,
      由于meta頁面的限制侥涵,其個數(shù)是有限的。
    */
    void        *me_pbuf;       /**< scratch area for DUPSORT put() */
    MDB_txninfo *me_txns;       /**< the memory map of the lock file or NULL */
    MDB_txn     *me_txn;        /**< current write transaction */
    /*
      me_txn,me_txns:目前環(huán)境中使用的事務(wù)列表芜飘,一個env對象歸屬于一個進程务豺,一個進程
      可能有多個線程使用同一個env,每個線程可以開啟一個事務(wù)嗦明,因此在一
      個進程級的env對象需要維護txn列表以了解目前多少個線程及事務(wù)在進行工作笼沥。
    */

    MDB_txn     *me_txn0;       /**< prealloc'd write transaction */
    size_t      me_mapsize;     /**< size of the data memory map */
    off_t       me_size;        /**< current file size */
    pgno_t      me_maxpg;       /**< me_mapsize / me_psize */
    MDB_dbx     *me_dbxs;       /**< array of static DB info */
    uint16_t    *me_dbflags;    /**< array of flags from MDB_db.md_flags */
    unsigned int    *me_dbiseqs;    /**< array of dbi sequence numbers */
    pthread_key_t   me_txkey;   /**< thread-key for readers */
    txnid_t     me_pgoldest;    /**< ID of oldest reader last time we looked */
    MDB_pgstate me_pgstate;     /**< state of old pages from freeDB */
#   define      me_pglast   me_pgstate.mf_pglast
#   define      me_pghead   me_pgstate.mf_pghead
    MDB_page    *me_dpages;     /**< list of malloc'd blocks for re-use */
    /** IDL of pages that became unused in a write txn */
    MDB_IDL     me_free_pgs;
    /** ID2L of pages written during a write txn. Length MDB_IDL_UM_SIZE. */
    // 可用頁面,可用頁面用于控制MVCC導(dǎo)致的文件大小膨脹娶牌,
    // 可用頁面是指已經(jīng)沒有事務(wù)使用但是已經(jīng)被修改奔浅,根據(jù)MVCC原理,其已經(jīng)是舊版本的頁面裙戏。


    /*
        對于需要查閱歷史數(shù)據(jù)的數(shù)據(jù)庫來說乘凸,比如說需要恢復(fù)到任意時刻的要求,
        所有的舊版本應(yīng)該被保存累榜,而對于只需要保持最新一致數(shù)據(jù)的數(shù)據(jù)庫系統(tǒng)比
        如lmdb來說,這些頁面是可以重用的灵嫌,頁面重用就可以有效避免物理文件的
        無限增大壹罚。free_pgs為當(dāng)前寫事務(wù)導(dǎo)致的可重用頁面列表。
    */

    MDB_ID2L    me_dirty_list;
    /** Max number of freelist items that can fit in a single overflow page */
    // 臟頁列表寿羞,是寫事務(wù)已經(jīng)修改過的但沒有提交到物理文件中的所有頁面列表猖凛。
    int         me_maxfree_1pg;
    /** Max size of a node on a page */
    unsigned int    me_nodemax;
#if !(MDB_MAXKEYSIZE)
    unsigned int    me_maxkey;  /**< max size of a key */
#endif
    int     me_live_reader;     /**< have liveness lock in reader table */
#ifdef _WIN32
    int     me_pidquery;        /**< Used in OpenProcess */
#endif
#ifdef MDB_USE_POSIX_MUTEX  /* Posix mutexes reside in shared mem */
#   define      me_rmutex   me_txns->mti_rmutex /**< Shared reader lock */
#   define      me_wmutex   me_txns->mti_wmutex /**< Shared writer lock */
#else
    mdb_mutex_t me_rmutex;
    mdb_mutex_t me_wmutex;
    /*
        鎖表互斥所,lmdb可以支持多線程绪穆、多進程辨泳。多進程之間的同步訪問通過系統(tǒng)級的互斥來達(dá)到。
        其mutex本身存在于系統(tǒng)的共享內(nèi)存當(dāng)中而非進程本身的內(nèi)存玖院,因此在進行讀寫頁面時菠红,首先訪
        問鎖表看看對應(yīng)的資源是否有別的進程、線程在進行难菌,有的話需要根據(jù)事務(wù)規(guī)則要求進行排隊等待试溯。
    */
#endif
    void        *me_userctx;     /**< User-settable context */
    // 用戶數(shù)據(jù),用戶上下文數(shù)據(jù)郊酒,主要用于進行key比較時進行輔助遇绞。
    MDB_assert_func *me_assert_func; /**< Callback for assertion failures */
};

下面是MDB_envinfo結(jié)構(gòu),其中存儲的是LMDB環(huán)境的信息:

/** @brief Information about the environment */
typedef struct MDB_envinfo {
    void    *me_mapaddr;            /**< Address of map, if fixed */
    size_t  me_mapsize;             /**< Size of the data memory map */
    size_t  me_last_pgno;           /**< ID of the last used page */
    size_t  me_last_txnid;          /**< ID of the last committed transaction */
    unsigned int me_maxreaders;     /**< max reader slots in the environment */
    unsigned int me_numreaders;     /**< max reader slots used in the environment */
} MDB_envinfo;

下面我們看一下數(shù)據(jù)庫的Meta頁情況:

其中主要關(guān)注MDB_db mm_dbs[CORE_DBS];以及uint32_t mm_version;燎窘。

typedef struct MDB_meta {
        /** Stamp identifying this as an LMDB file. It must be set
         *  to #MDB_MAGIC. */
    uint32_t    mm_magic;
        /** Version number of this file. Must be set to #MDB_DATA_VERSION. */
    uint32_t    mm_version;
    // mm_version: 當(dāng)前l(fā)ock文件的version摹闽,是實現(xiàn)MVCC的重要成員,必須設(shè)置為MDB_DATA_VERSION.
    void        *mm_address;        /**< address for fixed mapping */
    size_t      mm_mapsize;         /**< size of mmap region */
    MDB_db      mm_dbs[CORE_DBS];   /**< first is free space, 2nd is main db */
    // mm_dbs: 數(shù)據(jù)庫B+Tree根褐健,同時保存兩個付鹿,0為目前使用的可替代的root page指針,1為當(dāng)前使用的主數(shù)據(jù)庫。
    /** The size of pages used in this DB */
#define mm_psize    mm_dbs[FREE_DBI].md_pad
    /** Any persistent environment flags. @ref mdb_env */
#define mm_flags    mm_dbs[FREE_DBI].md_flags
    /** Last used page in the datafile.
     *  Actually the file may be shorter if the freeDB lists the final pages.
     */
    pgno_t      mm_last_pg;
    volatile txnid_t    mm_txnid;   /**< txnid that committed this page */
} MDB_meta;

下面是MDB_page函數(shù):

/** Common header for all page types. The page type depends on #mp_flags.
 *
 * #P_BRANCH and #P_LEAF pages have unsorted '#MDB_node's at the end, with
 * sorted #mp_ptrs[] entries referring to them. Exception: #P_LEAF2 pages
 * omit mp_ptrs and pack sorted #MDB_DUPFIXED values after the page header.
 *
 * #P_OVERFLOW records occupy one or more contiguous pages where only the
 * first has a page header. They hold the real data of #F_BIGDATA nodes.
 *
 * #P_SUBP sub-pages are small leaf "pages" with duplicate data.
 * A node with flag #F_DUPDATA but not #F_SUBDATA contains a sub-page.
 * (Duplicate data can also go in sub-databases, which use normal pages.)
 *
 * #P_META pages contain #MDB_meta, the start point of an LMDB snapshot.
 *
 * Each non-metapage up to #MDB_meta.%mm_last_pg is reachable exactly once
 * in the snapshot: Either used by a database or listed in a freeDB record.
 */
typedef struct MDB_page {
#define mp_pgno mp_p.p_pgno
#define mp_next mp_p.p_next
    union {
        pgno_t      p_pgno; /**< page number */
        struct MDB_page *p_next; /**< for in-memory list of freed pages */
    } mp_p;
    uint16_t    mp_pad;         /**< key size if this is a LEAF2 page */
/** @defgroup mdb_page  Page Flags
 *  @ingroup internal
 *  Flags for the page headers.
 *  @{
 */
#define P_BRANCH     0x01       /**< branch page */
#define P_LEAF       0x02       /**< leaf page */
#define P_OVERFLOW   0x04       /**< overflow page */
#define P_META       0x08       /**< meta page */
#define P_DIRTY      0x10       /**< dirty page, also set for #P_SUBP pages */
#define P_LEAF2      0x20       /**< for #MDB_DUPFIXED records */
#define P_SUBP       0x40       /**< for #MDB_DUPSORT sub-pages */
#define P_LOOSE      0x4000     /**< page was dirtied then freed, can be reused */
#define P_KEEP       0x8000     /**< leave this page alone during spill */
/** @} */
    uint16_t    mp_flags;       /**< @ref mdb_page */
#define mp_lower    mp_pb.pb.pb_lower
#define mp_upper    mp_pb.pb.pb_upper
#define mp_pages    mp_pb.pb_pages
    union {
        struct {
            indx_t      pb_lower;       /**< lower bound of free space */
            indx_t      pb_upper;       /**< upper bound of free space */
        } pb;
        uint32_t    pb_pages;   /**< number of overflow pages */
    } mp_pb;
    indx_t      mp_ptrs[1];     /**< dynamic size */
} MDB_page;

page描述了不同頁面的頭倘屹。不管是樹中的root银亲、還是branch、leaf頁面纽匙,都是用它描述务蝠。

對于overflow頁面來說,只有第一頁使用頭進行描述烛缔,其后的連續(xù)頁面不使用馏段,僅僅使用指針。

下面看MDB_node結(jié)構(gòu):

typedef struct MDB_node {
    /** part of data size or pgno
     *  @{ */
#if BYTE_ORDER == LITTLE_ENDIAN
    unsigned short  mn_lo, mn_hi;
#else
    unsigned short  mn_hi, mn_lo;
#endif
    /** @} */
/** @defgroup mdb_node Node Flags
 *  @ingroup internal
 *  Flags for node headers.
 *  @{
 */
#define F_BIGDATA    0x01           /**< data put on overflow page */
#define F_SUBDATA    0x02           /**< data is a sub-database */
#define F_DUPDATA    0x04           /**< data has duplicates */

/** valid flags for #mdb_node_add() */
#define NODE_ADD_FLAGS  (F_DUPDATA|F_SUBDATA|MDB_RESERVE|MDB_APPEND)

/** @} */
    unsigned short  mn_flags;       /**< @ref mdb_node */
    unsigned short  mn_ksize;       /**< key size */
    char        mn_data[1];         /**< key and data are appended here */
} MDB_node;

KV數(shù)據(jù)對被此數(shù)據(jù)結(jié)構(gòu)管理践瓷,mn_lo, mn_hi用來表示數(shù)據(jù)的大小院喜。

    /** Information about a single database in the environment. */
typedef struct MDB_db {
    uint32_t    md_pad;     /**< also ksize for LEAF2 pages */
    uint16_t    md_flags;   /**< @ref mdb_dbi_open */
    uint16_t    md_depth;   /**< depth of this tree */
    pgno_t      md_branch_pages;    /**< number of internal pages */
    pgno_t      md_leaf_pages;      /**< number of leaf pages */
    pgno_t      md_overflow_pages;  /**< number of overflow pages */
    size_t      md_entries;     /**< number of data items */
    pgno_t      md_root;        /**< the root page of this tree */
} MDB_db;

MDB_db表示數(shù)據(jù)庫中的一個b+樹。

最后是游標(biāo)結(jié)構(gòu):

struct MDB_cursor {
    /** Next cursor on this DB in this txn */
    MDB_cursor  *mc_next;
    /** Backup of the original cursor if this cursor is a shadow */
    MDB_cursor  *mc_backup;
    /** Context used for databases with #MDB_DUPSORT, otherwise NULL */
    struct MDB_xcursor  *mc_xcursor;
    /** The transaction that owns this cursor */
    MDB_txn     *mc_txn;
    /** The database handle this cursor operates on */
    MDB_dbi     mc_dbi;
    /** The database record for this cursor */
    MDB_db      *mc_db;
    /** The database auxiliary record for this cursor */
    MDB_dbx     *mc_dbx;
    /** The @ref mt_dbflag for this database */
    unsigned char   *mc_dbflag;
    unsigned short  mc_snum;    /**< number of pushed pages */
    unsigned short  mc_top;     /**< index of top page, normally mc_snum-1 */
/** @defgroup mdb_cursor    Cursor Flags
 *  @ingroup internal
 *  Cursor state flags.
 *  @{
 */
#define C_INITIALIZED   0x01    /**< cursor has been initialized and is valid */
#define C_EOF   0x02            /**< No more data */
#define C_SUB   0x04            /**< Cursor is a sub-cursor */
#define C_DEL   0x08            /**< last op was a cursor_del */
#define C_UNTRACK   0x40        /**< Un-track cursor when closing */
/** @} */
    unsigned int    mc_flags;   /**< @ref mdb_cursor */
    MDB_page    *mc_pg[CURSOR_STACK];   /**< stack of pushed pages */
    indx_t      mc_ki[CURSOR_STACK];    /**< stack of page indices */
};

該結(jié)構(gòu)對于DB所有操作均適用晕翠,其保存了頁指針與鍵的索引

mc_next: 同一個事務(wù)中關(guān)于同一個db的游標(biāo)組成一個列表喷舀。next指向下一個游標(biāo)

mc_top: 最上層頁面id

mc_xcursor: 用于key可重復(fù)b+tree。

mc_pg: cursor打開的頁面組成一個堆棧淋肾,最多32個硫麻,具體作用還有待探查。

mc_ki: 所有打開頁面的索引

最后是事務(wù)結(jié)構(gòu):

    /** A database transaction.
     *  Every operation requires a transaction handle.
     */
struct MDB_txn {
    MDB_txn     *mt_parent;     /**< parent of a nested txn */
    /** Nested txn under this txn, set together with flag #MDB_TXN_HAS_CHILD */
    MDB_txn     *mt_child;
    pgno_t      mt_next_pgno;   /**< next unallocated page */
    /** The ID of this transaction. IDs are integers incrementing from 1.
     *  Only committed write transactions increment the ID. If a transaction
     *  aborts, the ID may be re-used by the next writer.
     */
    txnid_t     mt_txnid;
    MDB_env     *mt_env;        /**< the DB environment */
    /** The list of pages that became unused during this transaction.
     */
    MDB_IDL     mt_free_pgs;
    /** The list of loose pages that became unused and may be reused
     *  in this transaction, linked through #NEXT_LOOSE_PAGE(page).
     */
    MDB_page    *mt_loose_pgs;
    /** Number of loose pages (#mt_loose_pgs) */
    int         mt_loose_count;
    /** The sorted list of dirty pages we temporarily wrote to disk
     *  because the dirty list was full. page numbers in here are
     *  shifted left by 1, deleted slots have the LSB set.
     */
    MDB_IDL     mt_spill_pgs;
    union {
        /** For write txns: Modified pages. Sorted when not MDB_WRITEMAP. */
        MDB_ID2L    dirty_list;
        /** For read txns: This thread/txn's reader table slot, or NULL. */
        MDB_reader  *reader;
    } mt_u;
    /** Array of records for each DB known in the environment. */
    MDB_dbx     *mt_dbxs;
    /** Array of MDB_db records for each known DB */
    MDB_db      *mt_dbs;
    /** Array of sequence numbers for each DB handle */
    unsigned int    *mt_dbiseqs;
/** @defgroup mt_dbflag Transaction DB Flags
 *  @ingroup internal
 * @{
 */
#define DB_DIRTY    0x01        /**< DB was written in this txn */
#define DB_STALE    0x02        /**< Named-DB record is older than txnID */
#define DB_NEW      0x04        /**< Named-DB handle opened in this txn */
#define DB_VALID    0x08        /**< DB handle is valid, see also #MDB_VALID */
#define DB_USRVALID 0x10        /**< As #DB_VALID, but not set for #FREE_DBI */
#define DB_DUPDATA  0x20        /**< DB is #MDB_DUPSORT data */
/** @} */
    /** In write txns, array of cursors for each DB */
    MDB_cursor  **mt_cursors;
    /** Array of flags for each DB */
    unsigned char   *mt_dbflags;
    /** Number of DB records in use, or 0 when the txn is finished.
     *  This number only ever increments until the txn finishes; we
     *  don't decrement it when individual DB handles are closed.
     */
    MDB_dbi     mt_numdbs;

/** @defgroup mdb_txn   Transaction Flags
 *  @ingroup internal
 *  @{
 */
    /** #mdb_txn_begin() flags */
#define MDB_TXN_BEGIN_FLAGS MDB_RDONLY
#define MDB_TXN_RDONLY      MDB_RDONLY  /**< read-only transaction */
    /* internal txn flags */
#define MDB_TXN_WRITEMAP    MDB_WRITEMAP    /**< copy of #MDB_env flag in writers */
#define MDB_TXN_FINISHED    0x01        /**< txn is finished or never began */
#define MDB_TXN_ERROR       0x02        /**< txn is unusable after an error */
#define MDB_TXN_DIRTY       0x04        /**< must write, even if dirty list is empty */
#define MDB_TXN_SPILLS      0x08        /**< txn or a parent has spilled pages */
#define MDB_TXN_HAS_CHILD   0x10        /**< txn has an #MDB_txn.%mt_child */
    /** most operations on the txn are currently illegal */
#define MDB_TXN_BLOCKED     (MDB_TXN_FINISHED|MDB_TXN_ERROR|MDB_TXN_HAS_CHILD)
/** @} */
    unsigned int    mt_flags;       /**< @ref mdb_txn */
    /** #dirty_list room: Array size - \#dirty pages visible to this txn.
     *  Includes ancestor txns' dirty pages not hidden by other txns'
     *  dirty/spilled pages. Thus commit(nested txn) has room to merge
     *  dirty_list into mt_parent after freeing hidden mt_parent pages.
     */
    unsigned int    mt_dirty_room;
};

LMDB中的Mmap

我們先從mdb_put()函數(shù)開始進行樊卓。

int mdb_put(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data, unsigned int flags);

該函數(shù)需要用戶傳入key與value拿愧,并傳入模式。而這個模式包括:

MDB_NODUPDATA:只能在key沒有被插入的情況下進行碌尔;
MDB_NOOVERWRITE:同上浇辜,但是最后會將指針指向已經(jīng)存在的那個數(shù)據(jù);
MDB_RESERVE:為給定的數(shù)據(jù)保留一個空間大小唾戚,并返回指針指向這個空間
MDB_APPEND:以追加的形式將數(shù)據(jù)放到DB柳洋,如果數(shù)據(jù)本身在插入時是有序的,那么這個會加速操作

在mdb.c文件中颈走,我們能看到具體的函數(shù)實現(xiàn)細(xì)節(jié):

int
mdb_put(MDB_txn *txn, MDB_dbi dbi,
    MDB_val *key, MDB_val *data, unsigned int flags)
{
    MDB_cursor mc;
    MDB_xcursor mx;
    int rc;

    if (!key || !data || !TXN_DBI_EXIST(txn, dbi, DB_USRVALID))
        return EINVAL;

    if (flags & ~(MDB_NOOVERWRITE|MDB_NODUPDATA|MDB_RESERVE|MDB_APPEND|MDB_APPENDDUP))
        return EINVAL;

    if (txn->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_BLOCKED))
        return (txn->mt_flags & MDB_TXN_RDONLY) ? EACCES : MDB_BAD_TXN;

    mdb_cursor_init(&mc, txn, dbi, &mx);
    mc.mc_next = txn->mt_cursors[dbi];
    txn->mt_cursors[dbi] = &mc;
    rc = mdb_cursor_put(&mc, key, data, flags);
    txn->mt_cursors[dbi] = mc.mc_next;
    return rc;
}

之后進行mdb_cursor_put操作膳灶。該方法在lmdb.h中定義。

該函數(shù)有500行立由,我們選擇重點進行分析轧钓。

1 開始均是錯誤檢測,包括傳入數(shù)據(jù)是否為空锐膜,flag是否是錯的等等毕箍。

暫時放這里

在LMDB中的Mmap介紹

普通文件IO中所有的文件內(nèi)容的讀取(無論一開始是命中頁緩存還是沒有命中頁緩存)最終都是直接來源于頁緩存道盏。當(dāng)將數(shù)據(jù)通過缺頁中斷從磁盤復(fù)制到頁緩存之后而柑,還要將頁緩沖的數(shù)據(jù)通過CPU復(fù)制到read調(diào)用提供的緩沖區(qū)中文捶。這樣,必須通過兩次數(shù)據(jù)拷貝過程媒咳,才能完成用戶進程對文件內(nèi)容的獲取任務(wù)粹排。

內(nèi)存映射就是把物理內(nèi)存映射到進程的地址空間之內(nèi),這些應(yīng)用程序就可以直接使用輸入輸出的地址空間.由此可以看出涩澡,使用內(nèi)存映射文件處理存儲于磁盤上的文件時顽耳,將不需要由應(yīng)用程序?qū)ξ募?zhí)行I/O操作,這意味著在對文件進行處理時將不必再為文件申請并分配緩存妙同,所有的文件緩存操作均由系統(tǒng)直接管理射富,由于取消了將文件數(shù)據(jù)加載到內(nèi)存、數(shù)據(jù)從內(nèi)存到文件的回寫以及釋放內(nèi)存塊等步驟粥帚,使得內(nèi)存映射文件在處理大數(shù)據(jù)量的文件時能起到相當(dāng)重要的作用胰耗。

下圖為普通文件的I/O訪問:
由此可知,步驟1表示將數(shù)據(jù)拷貝到物理頁cache中芒涡;步驟2表示將物理頁cache中的page1復(fù)制一份到page2(用戶只能訪問用戶空間)柴灯,之后通過指針對數(shù)據(jù)訪問。


image.png

而使用了MMap之后拖陆,可大致分為如下步驟:1 進程啟動映射過程弛槐,并在虛擬地址空間中為映射創(chuàng)建虛擬映射區(qū)域(過程1)

2 調(diào)用內(nèi)核空間的系統(tǒng)調(diào)用函數(shù)mmap(不同于用戶空間函數(shù)),實現(xiàn)文件物理地址和進程虛擬地址的一一映射關(guān)系(過程3)

3 進程發(fā)起對這片映射空間的訪問依啰,引發(fā)缺頁異常,實現(xiàn)文件內(nèi)容到物理內(nèi)存(主存)的拷貝

image.png

在寫的過程中:

1.進程(用戶態(tài))將需要寫入的數(shù)據(jù)直接copy到對應(yīng)的mmap地址(內(nèi)存copy)

2.若mmap地址未對應(yīng)物理內(nèi)存店枣,則產(chǎn)生缺頁異常速警,由內(nèi)核處理

3.若已對應(yīng),則直接copy到對應(yīng)的物理內(nèi)存

4.由操作系統(tǒng)調(diào)用鸯两,將臟頁回寫到磁盤(通常是異步的)

因為物理內(nèi)存是有限的闷旧,mmap在寫入數(shù)據(jù)超過物理內(nèi)存時,操作系統(tǒng)會進行頁置換钧唐,根據(jù)淘汰算法忙灼,將需要淘汰的頁置換成所需的新頁,所以mmap對應(yīng)的內(nèi)存是可以被淘汰的(若內(nèi)存頁是"臟"的钝侠,則操作系統(tǒng)會先將數(shù)據(jù)回寫磁盤再淘汰)。這樣,就算mmap的數(shù)據(jù)遠(yuǎn)大于物理內(nèi)存亲雪,操作系統(tǒng)也能很好地處理惨好,不會產(chǎn)生功能上的問題。

  • 優(yōu)點如下:

1忽舟、對文件的讀取操作跨過了頁緩存双妨,減少了數(shù)據(jù)的拷貝次數(shù)淮阐,用內(nèi)存讀寫取代I/O讀寫,提高了文件讀取效率刁品。

2泣特、實現(xiàn)了用戶空間和內(nèi)核空間的高效交互方式。兩空間的各自修改操作可以直接反映在映射的區(qū)域內(nèi)挑随,從而被對方空間及時捕捉状您。

3、提供進程間共享內(nèi)存及相互通信的方式镀裤。不管是父子進程還是無親緣關(guān)系的進程竞阐,都可以將自身用戶空間映射到同一個文件或匿名映射到同一片區(qū)域。從而通過各自對映射區(qū)域的改動暑劝,達(dá)到進程間通信和進程間共享的目的骆莹。同時,如果進程A和進程B都映射了區(qū)域C担猛,當(dāng)A第一次讀取C時通過缺頁從磁盤復(fù)制文件頁到內(nèi)存中幕垦;但當(dāng)B再讀C的相同頁面時,雖然也會產(chǎn)生缺頁異常傅联,但是不再需要從磁盤中復(fù)制文件過來先改,而可直接使用已經(jīng)保存在內(nèi)存中的文件數(shù)據(jù)。

4蒸走、可用于實現(xiàn)高效的大規(guī)模數(shù)據(jù)傳輸仇奶。內(nèi)存空間不足,是制約大數(shù)據(jù)操作的一個方面比驻,解決方案往往是借助硬盤空間協(xié)助操作该溯,補充內(nèi)存的不足。但是進一步會造成大量的文件I/O操作别惦,極大影響效率狈茉。這個問題可以通過mmap映射很好的解決。換句話說掸掸,但凡是需要用磁盤空間代替內(nèi)存的時候氯庆,mmap都可以發(fā)揮其功效。

  • 缺點如下:

1.文件如果很小扰付,是小于4096字節(jié)的堤撵,比如10字節(jié),由于內(nèi)存的最小粒度是頁悯周,而進程虛擬地址空間和內(nèi)存的映射也是以頁為單位粒督。雖然被映射的文件只有10字節(jié),但是對應(yīng)到進程虛擬地址區(qū)域的大小需要滿足整頁大小禽翼,因此mmap函數(shù)執(zhí)行后屠橄,實際映射到虛擬內(nèi)存區(qū)域的是4096個字節(jié)族跛,11~4096的字節(jié)部分用零填充。因此如果連續(xù)mmap小文件锐墙,會浪費內(nèi)存空間礁哄。

2 對變長文件不適合,文件無法完成拓展溪北,因為mmap到內(nèi)存的時候桐绒,你所能夠操作的范圍就確定了。

3.如果更新文件的操作很多之拨,會觸發(fā)大量的臟頁回寫及由此引發(fā)的隨機IO上茉继。所以在隨機寫很多的情況下,mmap方式在效率上不一定會比帶緩沖區(qū)的一般寫快蚀乔。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末烁竭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子吉挣,更是在濱河造成了極大的恐慌派撕,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件睬魂,死亡現(xiàn)場離奇詭異终吼,居然都是意外死亡,警方通過查閱死者的電腦和手機氯哮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門际跪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人喉钢,你說我怎么就攤上這事垫卤。” “怎么了出牧?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長歇盼。 經(jīng)常有香客問我舔痕,道長,這世上最難降的妖魔是什么豹缀? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任伯复,我火速辦了婚禮,結(jié)果婚禮上邢笙,老公的妹妹穿的比我還像新娘啸如。我一直安慰自己,他們只是感情好氮惯,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布叮雳。 她就那樣靜靜地躺著想暗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪帘不。 梳的紋絲不亂的頭發(fā)上说莫,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天,我揣著相機與錄音寞焙,去河邊找鬼储狭。 笑死,一個胖子當(dāng)著我的面吹牛捣郊,可吹牛的內(nèi)容都是我干的辽狈。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼呛牲,長吁一口氣:“原來是場噩夢啊……” “哼刮萌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起侈净,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤尊勿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后畜侦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體元扔,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年旋膳,在試婚紗的時候發(fā)現(xiàn)自己被綠了澎语。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡验懊,死狀恐怖擅羞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情义图,我是刑警寧澤减俏,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站碱工,受9級特大地震影響娃承,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜怕篷,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一历筝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧廊谓,春花似錦梳猪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呛哟。三九已至,卻和暖如春惕稻,著一層夾襖步出監(jiān)牢的瞬間竖共,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工俺祠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留公给,地道東北人。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓蜘渣,卻偏偏與公主長得像淌铐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蔫缸,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容