LMDB基本架構
lmdb的基本架構如下: 
lmdb的基本做法是使用mmap文件映射,不管這個文件存儲實在內存上還是在持久存儲上。lmdb的所有讀取操作都是通過mmap將要訪問的文件只讀的映射到虛擬內存中,直接訪問相應的地址.因為使用了read-only的mmap,同樣避免了程序錯誤將存儲結構寫壞的風險。並且IO的調度由操作系統的頁調度機制完成。而寫操作,則是通過write系統調用進行的,這主要是為了利用操作系統的文件系統一致性,避免在被訪問的地址上進行同步。
lmdb把整個虛擬存儲組織成B+Tree存儲,索引和值讀存儲在B+Tree的頁面上.對外提供了關於B+Tree的操作方式,利用cursor游標進行。可以進行增刪改查。
使用Memory Map
Memory Map原理
內存映射就是把物理內存映射到進程的地址空間之內,這些應用程序就可以直接使用輸入輸出的地址空間.由此可以看出,使用內存映射文件處理存儲於磁盤上的文件時,將不需要由應用程序對文件執行I/O操作,這意味着在對文件進行處理時將不必再為文件申請並分配緩存,所有的文件緩存操作均由系統直接管理,由於取消了將文件數據加載到內存、數據從內存到文件的回寫以及釋放內存塊等步驟,使得內存映射文件在處理大數據量的文件時能起到相當重要的作用。
Linux下mmap的實現過程與普通文件io操作
mmap映射原理與過程1: 
一般文件io操作方式: 
通過內存映射的方法訪問硬盤上的文件,效率要比read和write系統調用高, read()是系統調用,其中進行了數據拷貝,它首先將文件內容從硬盤拷貝到內核空間的一個緩沖區,然后再將這些數據拷貝到用戶空間,在這個過程中,實際上完成了 兩次數據拷貝 ;而mmap()也是系統調用,如前所述,mmap()中沒有進行數據拷貝,真正的數據拷貝是在缺頁中斷處理時進行的,由於mmap()將文件直接映射到用戶空間,所以中斷處理函數根據這個映射關系,直接將文件從硬盤拷貝到用戶空間,只進行了 一次數據拷貝 。因此,內存映射的效率要比 read/write效率高。
lmdb使用mmap過程
lmdb創建完env對象,打開時,會做data file和lock file的mmap映射:
env->me_lfd = open(lpath, O_RDWR|O_CREAT|MDB_CLOEXEC, mode);
void *m = mmap(NULL, rsize, PROT_READ|PROT_WRITE, MAP_SHARED,
env->me_lfd, 0);
env->me_txns = m;
env->me_fd = open(dpath, oflags, mode);
env->me_map = mmap(addr, env->me_mapsize, prot, MAP_SHARED,
env->me_fd, 0);
其他時刻都直接使用內存指針,通過系統級別的缺頁異常獲取對應的數據。頁面內數據的獲取和使用 MDB_CURSOR_GET 進行。頁面的獲取和key查詢通過 mdb_page_get/mdb_page_search 完成.
頁面頭部大小及內容是固定的,具體的含義代表根據flags決定,在頭部之后緊接的是node,真正的key-value值對所在位置的索引,因此訪問這些node時通過指針計算即可得到對應的位置。
lmdb 之后是如何將頁面給映射進進程地址空間呢.lmdb通過 mdb_page_get 函數以 pgno 為主要參數獲得頁面並返回頁面指針。若僅僅是只讀事務且環境對象是以只讀方式打開的,page的獲取很簡單,根據 page= (MDB_page *)(env->me_map + env->me_psize * pgno); 獲得。
在lmdb中B+Tree的是基於append-only B+Tree改造的。對於數據增加、修改、刪除導致頁面增加時,pageno也增加,當舊頁面(數據舊版本)被重用時,pageno 保持不變,因此pageno保持了在數據文件中的順序性,從而在獲取頁面時,只需要進行簡單計算即可以。同時在創建env對象時,數據庫已經被整個映射進整個進程空間,因此系統在映射時,會給數據庫文件保留全部地址空間,從而在根據上述算法獲取真實數據庫,系統觸發缺頁錯誤,進而從數據文件中獲取整個頁面內容。此為最簡單有效方式,否則不將全部數據映射進地址空間,對於未映射部分還需要在訪問頁面時判斷是否已經被映射,未被映射時進行映射。
在需要時在通過文件方式寫入。lmdb保證任意時刻只有一個寫操作在進行,從而避免了並發時數據被破壞。
COW(Copy-on-write)
寫入時復制(Copy-on-write,COW)是一種計算機程序設計領域的優化策略。其核心思想是,如果有多個調用者(callers)同時要求相同資源(如內存或磁盤上的數據存儲),他們會共同獲取相同的指針指向相同的資源,直到某個調用者試圖修改資源的內容時,系統才會真正復制一份專用副本(private copy)給該調用者,而其他調用者所見到的最初的資源仍然保持不變。這過程對其他的調用者都是透明的(transparently)。此作法主要的優點是如果調用者沒有修改該資源,就不會有副本(private copy)被創建,因此多個調用者只是讀取操作時可以共享同一份資源。4
VCC/COW在LMDB中的實現
LMDB對MVCC加了一個限制,即只允許一個寫線程存在,從根源上避免了寫寫沖突,當然代價就是寫入的並發性能下降。因為只有一個寫線程,所以不會不需要wal 日志、讀寫依賴隊列、鎖隊列等一系列控制並發、事務回滾、數據恢復的基礎工具。
MVCC的基礎就是COW,對於不同的用戶來說,若其在整個操作過程中不進行任何的數據改變,其就使用同一份數據即可,若需要進行改變,比如增加、刪除、修改等,就需要在私有數據版本上進行,修改完成提交之后才給其他事務可見。
LMDB中,數據操作的基本單元是頁,因此COW也是以頁為單位,對應函數是 mdb_page_touch, mdb_page_copy ,copy真正實現頁面復制,touch調用copy完成復制,然后修改pgno后插入到B+Tree當中,這樣對於此次事務,后續的操作訪問的數據頁就是最新的數據頁面,而非事務啟動時對應的數據頁面,且此頁面與其他頁面的關聯關系僅在本事務頁面列表中可見,對其他事務不可見。
實際上通過以上兩個函數實現了MVCC的核心,對於讀寫的控制,通過 mdb_txn_begin 控制,在其中,事務啟動時會檢查讀寫鎖的情況,若事務需要更新數據,則會被阻止,若只是讀數據,則不管是否有寫事務存在,讀鎖都可以獲得。
MVCC的一個副作用就是對於存在大量寫的應用,其數據版本很多,因此舊數據會占用大量空間,LMDB中通過freedb解決,即將不再使用的舊的數據頁面空間插入到一棵B+Tree當中,這樣舊空間在所有事務不再訪問之后就可以被LMDB使用,從而避免了需要定期執行清理操作。當然其副作用是數據只能保持最新不能恢復到任意時刻.
摘自:http://wiki.dreamrunner.org/public_html/C-C++/Library-Notes/LMDB.html
