介紹
????????LevelDB 是 Google 開源的高性能鍵值對嵌入式數據庫,具有一系列設計上的優勢,特別適合寫多讀少、對存儲空間要求高效的場景。
核心優勢
1. 高寫入性能(順序寫磁盤)
-
基于 LSM-Tree(Log Structured Merge Tree);
-
所有寫操作首先寫入內存(memTable)+ WAL;
-
后臺異步 flush 到磁盤,避免頻繁隨機寫;
-
大量寫入性能遠高于 B+Tree 類數據庫(如 SQLite)。
2. 數據壓縮與空間利用率高
-
支持 Snappy 壓縮;
-
自動 Compaction(壓縮合并) 機制;
-
清理刪除或覆蓋的舊版本,回收磁盤空間;
-
多
.sst
層級結構使數據有序緊湊。
3. 支持有序遍歷與范圍查詢
-
支持通過
Iterator
順序遍歷鍵值; -
可高效進行范圍查詢(Range Scan):
4. 輕量、嵌入式、依賴少
-
無服務器(serverless),直接嵌入你的應用;
-
單一
.a
靜態庫或.so
動態庫,無需依賴外部組件; -
跨平臺支持良好(Linux、macOS、Windows)。
5. Crash-safe 崩潰恢復機制
-
所有寫入先寫入
.log
文件(Write-Ahead Log); -
崩潰后可自動恢復到一致狀態;
-
無需額外事務機制即可保證寫入安全性。
6. 支持快照(Snapshot)和原子批處理(WriteBatch)
-
Snapshot
: 提供一致性讀取視圖; -
WriteBatch
: 批量寫入操作可原子提交,提升性能并簡化邏輯。
使用場景
場景類型 | 是否適合 | 說明 |
---|---|---|
批量數據寫入 | ? 非常適合 | 寫優化結構(LSM-tree),順序寫性能極高 |
嵌入式/邊緣設備存儲 | ? 非常適合 | 輕量、無服務端,資源開銷小 |
日志系統、緩存系統 | ? 合適 | 大量寫入、偶爾查詢 |
用戶畫像、指標記錄 | ? 合適 | 小 key-value、高速寫入 |
離線分析數據落地 | ? 合適 | 批量寫入,偶爾按 key 掃描或查詢 |
查詢很少、但插入頻繁的系統 | ? 非常合適 | 查詢通過前置緩存減少壓力 |
不太適合的場景
場景類型 | 原因 |
---|---|
高并發讀場景(每秒幾千次以上) | 讀放大嚴重,需大量優化(如加 Bloom Filter、前置緩存) |
大量隨機讀 + 大量隨機寫 | 寫放大 + 查詢慢 |
多表 join、事務一致性需求強 | 不支持 SQL 和事務 |
需要按字段查詢、復雜查詢 | 不支持索引,只有鍵排序 |
數據結構
-
內存表(MemTable):最新寫入的數據,保存在內存中。
-
Immutable MemTable:舊內存表,尚未 flush。
-
SST 文件(磁盤):排序的 Key-Value 存儲在多層磁盤文件中。
-
Block Cache(塊緩存):LevelDB 會緩存 SST 文件中的數據塊(默認 8MB)。
文件結構
- 000123.log:正在寫的 WAL
- 000124.sst: L0 的第一個 SST 文件
- 000125.sst: L0 的第二個 SST 文件(寫入后產生)
- 000130.sst: L1 的文件(Compaction 后生成)
- MANIFEST-000001:元數據文件
- CURRENT:指向當前 MANIFEST
memTable
????????它是數據庫打開時創建的空內存結構,用于接收寫入。
生命周期如下:
-
DB.open()
時,創建一個空的memTable
。 -
寫入數據時(
put()
/delete()
),首先寫 WAL(Write-Ahead Log),然后寫入memTable
。 -
當
memTable
的大小超過閾值(writeBufferSize
)時,會:-
將當前
memTable
移入immutable memTable
; -
異步觸發 flush 操作,將其寫入
.sst
文件; -
創建新的
memTable
接收寫入。
-
觸發時機 | 會怎樣? |
---|---|
memTable flush | 提高 L0 score |
Compaction 完成 | 降低某層 score,增加下一層 |
寫入壓力上升 | 多文件生成,score 提高 |
刪除數據未及時 compact | score 居高不下,空間占用大 |
compactionScore
是 LevelDB 內部的一個關鍵指標,它決定是否需要觸發 Compaction(壓縮),以及優先壓縮哪個 Level。
-
每個 Level(L0 ~ L6)都有一個
compactionScore
; -
值越大,代表該 Level 越“擁堵”,越需要被壓縮;
-
當
compactionScore ≥ 1.0
,LevelDB 會調度一次 Compaction; -
通常由
VersionSet::Finalize()
計算。
JAVA客戶端配置
參數 | 默認值 | 說明 |
---|---|---|
createIfMissing | false | 如果數據庫不存在,是否自動創建(通常你會手動設為 true ) |
errorIfExists | false | 如果數據庫已存在,是否拋出異常 |
paranoidChecks | false | 是否進行一致性檢查 |
verifyChecksums | false | 讀取時是否校驗數據塊的校驗和 |
cacheSize | 8 * 1024 * 1024 (8MB) | 內存緩存大小 |
blockSize | 4 * 1024 (4KB) | 每個 block 的大小 |
writeBufferSize | 4 * 1024 * 1024 (4MB) | 寫緩沖區大小 |
maxOpenFiles | 1000 | 打開的文件數上限(僅 native JNI 實現中有效) |
compressionType | Snappy | 是否啟用 Snappy 壓縮 |
使用邏輯
寫數據流程
-
寫入 WAL(Write-Ahead Log)
-
?寫入先追加到
.log
文件(順序寫); -
保證宕機后數據可恢復;
-
默認采用 同步寫(Sync=true)才能確保持久化;
-
.log
文件位于Level 0
。
-
-
寫入 memTable(跳表)
-
同步寫入內存結構
memTable
; -
快速寫入(無鎖 skiplist),數據可被讀取;
-
內存中結構,不會持久化;
-
達到
writeBufferSize
限制(默認 4MB)后變為immutable memTable
,進入 flush。
-
-
觸發 MemTable Flush,生成 SST 文件
-
將
immutable memTable
轉為 SST 文件; -
寫入磁盤(SST 為排序存儲);
-
這些文件是查詢的主要來源(也是 compaction 的輸入);
-
會和已有 Level 0 文件產生重疊。
-
-
進行 Compaction(壓縮)
-
定期觸發(或寫壓力大時自動觸發);
-
將多個 SST 文件合并、去重、合并覆蓋;
-
數據從 L0 -> L1 -> L2 逐層下推;
-
保證后期查詢高效,數據唯一性提升。
-
舉例
執行 db.put("user123", "value1")
,可能流程如下:
-
日志:寫入
.log
文件中(WAL); -
內存:寫入到
memTable
; -
若 memTable 滿:
-
轉為 immutable;
-
后臺線程 flush 成一個新的
000123.sst
;
-
-
觸發 compaction,將多個 sst 合并入更低 level;
-
.log
文件在 flush 成功后被刪除。
讀取方式
-
先查 memTable(內存表)
-
memTable
是當前活躍寫入的跳表結構; -
有序、支持二分查找;
-
如果找到了 key,直接返回對應的 value。
-
-
再查 immutable memTable(只讀內存表)
-
memTable
flush 到磁盤前,會被轉為 immutable; -
查詢會優先在這查找;
-
如果 key 存在,會返回。
-
-
然后查各層 .sst 文件(從 Level 0 開始)
-
Level 0:
-
文件之間的 key 區間可能重疊;
-
必須逐個文件遍歷查找;
-
優先查最新的文件(文件編號大 → 數據新);
-
-
?Level 1~N(通常到 Level 6):
-
每層中的文件 key 區間互不重疊;
-
可以通過 key 二分定位到最多一個文件;
-
只需要在該文件中查找一次;
-
-
-
使用 Bloom Filter 加速排除(配置生效)
-
每個
.sst
文件都可帶一個 Bloom Filter; -
在讀文件前先看 Bloom Filter 是否可能包含該 key;
-
否 → 立即跳過;
-
是 → 真正讀取磁盤文件;
-
-
可大幅降低磁盤 IO 次數,特別是 key 不存在時。
-
-
讀取 Block → 解壓縮 → 查找 KV
-
.sst
文件是由多個 Block 組成的; -
使用索引塊定位 block;
-
如果有 Snappy 壓縮,先解壓再查找;
-
查到 key 返回 value,否則繼續查下一個層級。
-
舉例
執行 db.get("user123")
,可能流程如下:
-
不在 memTable;
-
immutable memTable 也沒有;
-
查 L0 中的 3 個文件,(Bloom Filter 排除 2 個),只查 1 個;
-
沒找到,再查 L1;
-
在 L1 的某個
.sst
文件中命中 -
去除文件中的block
-
解壓 block → 返回 value。
重啟恢復步驟
-
讀取
.log
文件; -
重建 memTable;
-
保證數據一致性;
-
再繼續寫入。