從零手寫Java版本的LSM Tree (四):SSTable 磁盤存儲

🔥 推薦一個高質量的Java LSM Tree開源項目!
https://github.com/brianxiadong/java-lsm-tree
java-lsm-tree 是一個從零實現的Log-Structured Merge Tree,專為高并發寫入場景設計。
核心亮點:
? 極致性能:寫入速度超過40萬ops/秒,完爆傳統B+樹
🏗? 完整架構:MemTable跳表 + SSTable + WAL + 布隆過濾器 + 多級壓縮
📚 深度教程:12章詳細教程,從基礎概念到生產優化,每行代碼都有注釋
🔒 并發安全:讀寫鎖機制,支持高并發場景
💾 數據可靠:WAL寫前日志確保崩潰恢復,零數據丟失
適合誰?

  • 想深入理解LSM Tree原理的開發者
  • 需要高寫入性能存儲引擎的項目
  • 準備數據庫/存儲系統面試的同學
  • 對分布式存儲感興趣的工程師
    ? 給個Star支持開源!

第4章:SSTable 磁盤存儲

什么是SSTable?

SSTable (Sorted String Table) 是LSM Tree中存儲在磁盤上的不可變有序文件。當MemTable達到大小閾值時,會將其內容刷盤生成SSTable文件。

SSTable 核心特性

1. 不可變性 (Immutability)

  • 一旦寫入完成,SSTable文件永不修改
  • 更新操作通過新的SSTable體現
  • 刪除操作通過墓碑標記實現

2. 有序性 (Sorted)

  • 所有鍵值對key的字典序排列
  • 支持高效的二分查找
  • 便于合并操作

3. 自包含性 (Self-contained)

  • 包含布隆過濾器用于快速過濾
  • 包含索引信息加速查找
  • 包含元數據信息

關于布隆過濾器,大家先簡單認為用來判斷數據存在不存在即可,下一小節會詳細進行講解。

文件格式設計

我們的SSTable采用簡化的文件格式:

┌─────────────────────────────────────────────────────────────┐
│                    SSTable 文件結構                         │
├─────────────────────────────────────────────────────────────┤
│  [條目數量: 4字節]                                           │
├─────────────────────────────────────────────────────────────┤
│  [數據條目區域]                                             │
│    條目1: key|value|timestamp|deleted                       │
│    條目2: key|value|timestamp|deleted                       │
│    ...                                                      │
│    條目N: key|value|timestamp|deleted                       │
├─────────────────────────────────────────────────────────────┤
│  [布隆過濾器區域]                                           │
│    過濾器數據                                               │
└─────────────────────────────────────────────────────────────┘

SSTable 實現解析

讓我們深入分析SSTable的實現:

package com.brianxiadong.lsmtree;import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;/*** Sorted String Table (SSTable) 實現* 磁盤上的有序不可變文件*/
public class SSTable {// 文件路徑:SSTable存儲在磁盤上的位置private final String filePath;// 布隆過濾器:用于快速判斷鍵是否可能存在private final BloomFilter bloomFilter;// 創建時間:用于壓縮時的文件排序private final long creationTime;// 構造函數:從有序數據創建SSTablepublic SSTable(String filePath, List<KeyValue> sortedData) throws IOException {this.filePath = filePath;                           // 設置文件路徑this.creationTime = System.currentTimeMillis();    // 記錄創建時間// 創建布隆過濾器,估算條目數和假陽性率this.bloomFilter = new BloomFilter(sortedData.size(), 0.01);// 將數據持久化到磁盤文件writeToFile(sortedData);}/*** 從文件路徑加載已存在的SSTable*/public SSTable(String filePath) throws IOException {this.filePath = filePath;                                           // 設置文件路徑this.creationTime = Files.getLastModifiedTime(Paths.get(filePath)) // 獲取文件修改時間作為創建時間.toMillis();this.bloomFilter = new BloomFilter(1000, 0.01);                    // 創建臨時布隆過濾器// 重新構建布隆過濾器rebuildBloomFilter();}
}

代碼解釋: 這個SSTable類是LSM Tree持久化存儲的核心。它包含三個關鍵組件:文件路徑用于磁盤存儲,布隆過濾器用于快速過濾不存在的鍵,有序數據列表用于內存中的快速訪問。構造函數會自動創建布隆過濾器并將數據寫入磁盤。

核心方法分析

1. 寫入文件 (writeToFile)
/*** 將排序數據寫入文件*/
private void writeToFile(List<KeyValue> sortedData) throws IOException {// 使用DataOutputStream進行二進制寫入,性能更好try (DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(filePath)))) {// 寫入條目數量dos.writeInt(sortedData.size());                    // 寫入整數類型的條目數// 寫入所有數據條目for (KeyValue kv : sortedData) {// 添加到布隆過濾器bloomFilter.add(kv.getKey());                   // 添加鍵到過濾器// 寫入數據:key, deleted, value(如果不是刪除), timestampdos.writeUTF(kv.getKey());                      // 寫入鍵(UTF-8編碼)dos.writeBoolean(kv.isDeleted());               // 寫入刪除標記if (!kv.isDeleted()) {                          // 如果不是刪除操作dos.writeUTF(kv.getValue());                // 寫入值}dos.writeLong(kv.getTimestamp());               // 寫入時間戳}}// try-with-resources自動關閉DataOutputStream
}

代碼解釋: 寫入過程采用順序寫入策略,這是磁盤I/O的最優模式。文件格式簡單明了:首先是條目數量,然后是所有數據條目,最后是布隆過濾器。使用管道符(|)分隔字段,便于解析。BufferedWriter提供緩沖以提高寫入性能。

寫入過程分析:

  1. 順序寫入: 充分利用磁盤順序寫入的高性能
  2. 文本格式: 便于調試和人工檢查
  3. 完整性: 包含所有必要的元數據
2. 重建布隆過濾器 (rebuildBloomFilter)
/*** 重新構建布隆過濾器*/
private void rebuildBloomFilter() throws IOException {// 使用DataInputStream讀取二進制文件try (DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(filePath)))) {int totalEntries = dis.readInt();                   // 讀取條目總數// 遍歷所有條目,將鍵添加到布隆過濾器for (int i = 0; i < totalEntries; i++) {String key = dis.readUTF();                     // 讀取鍵boolean deleted = dis.readBoolean();            // 讀取刪除標記if (!deleted) {                                 // 如果不是刪除操作dis.readUTF();                              // 跳過value,只讀取鍵用于布隆過濾器}dis.readLong();                                 // 跳過timestamp// 添加到布隆過濾器bloomFilter.add(key);                           // 將鍵添加到過濾器}}// try-with-resources自動關閉DataInputStream
}

代碼解釋: 加載過程是寫入的逆向操作。先讀取條目數量以便預估內存需求,然后逐行解析數據條目,最后恢復布隆過濾器。如果布隆過濾器數據損壞,會自動重建,增強了系統的容錯性。解析使用split方法按管道符分割字段。

3. 查詢操作 (get)
/*** 查詢鍵值 - 簡化實現,順序搜索*/
public String get(String key) {// 首先檢查布隆過濾器if (!bloomFilter.mightContain(key)) {return null;                                        // 布隆過濾器說不存在,直接返回null}// 布隆過濾器說可能存在,讀取文件進行查找try (DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(filePath)))) {int totalEntries = dis.readInt();                   // 讀取條目總數// 順序搜索所有條目for (int i = 0; i < totalEntries; i++) {String currentKey = dis.readUTF();              // 讀取當前鍵boolean deleted = dis.readBoolean();            // 讀取刪除標記String value = null;                            // 初始化值if (!deleted) {                                 // 如果不是刪除操作value = dis.readUTF();                      // 讀取值}long timestamp = dis.readLong();                // 讀取時間戳// 檢查是否找到目標鍵if (currentKey.equals(key)) {return deleted ? null : value;              // 如果是刪除標記返回null,否則返回值}// 由于數據有序,如果當前鍵大于目標鍵,則不存在if (currentKey.compareTo(key) > 0) {break;                                      // 提前退出循環}}} catch (IOException e) {e.printStackTrace();                                // 打印異常信息}return null;                                            // 未找到,返回null
}

代碼解釋: 查詢操作采用兩階段策略:首先用布隆過濾器快速過濾不存在的鍵,這能避免大部分無效的磁盤訪問。如果布隆過濾器表示鍵可能存在,再進行文件掃描。由于數據有序存儲,當遇到比目標鍵大的鍵時可以提前退出。這種實現雖然是順序查找,但在實際應用中由于布隆過濾器的過濾效果,大多數查詢都不需要訪問磁盤。

4. 獲取所有條目 (getAllEntries)
/*** 獲取所有鍵值對(用于合并)*/
public List<KeyValue> getAllEntries() throws IOException {List<KeyValue> entries = new ArrayList<>();            // 創建結果列表// 讀取文件中的所有數據try (DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(filePath)))) {int totalEntries = dis.readInt();                   // 讀取條目總數// 讀取所有條目for (int i = 0; i < totalEntries; i++) {String key = dis.readUTF();                     // 讀取鍵boolean deleted = dis.readBoolean();            // 讀取刪除標記String value = null;                            // 初始化值if (!deleted) {                                 // 如果不是刪除操作value = dis.readUTF();                      // 讀取值}long timestamp = dis.readLong();                // 讀取時間戳// 創建KeyValue對象并添加到列表entries.add(new KeyValue(key, value, timestamp, deleted));}}return entries;                                         // 返回所有條目
}/*** 刪除SSTable文件*/
public void delete() throws IOException {Files.deleteIfExists(Paths.get(filePath));              // 刪除文件,如果存在的話
}// Getter方法
public String getFilePath() {return filePath;                                        // 返回文件路徑
}public long getCreationTime() {return creationTime;                                    // 返回創建時間
}

代碼解釋: getAllEntries方法用于壓縮過程中讀取SSTable的所有數據。它按順序讀取文件中的所有條目,重建KeyValue對象。delete方法提供文件清理功能,在壓縮完成后刪除舊的SSTable文件。Getter方法提供對文件路徑和創建時間的訪問,用于壓縮策略中的文件排序。

小結

SSTable是LSM Tree的持久化存儲層,具有以下關鍵特性:

  1. 不可變性: 確保數據一致性和線程安全
  2. 有序性: 支持高效查找和范圍查詢
  3. 自包含: 包含布隆過濾器和索引信息
  4. 優化: 多種性能優化技術

思考題

  1. 為什么SSTable要設計為不可變的?
  2. 布隆過濾器如何提升SSTable的查詢性能?
  3. 如何在保持性能的同時減少SSTable的磁盤占用?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/85052.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/85052.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/85052.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Kotlin的5個主要作用域函數

applay, also,let, run, with 是kotlin標準庫提供的5個主要的作用域函數&#xff08;Scope Functions&#xff09;?&#xff0c;它們的設計目的是為了在特定作用域內更簡潔地操作對象。 如何使用這5個函數&#xff0c;要從它的設計目的來區分&#xff1a; apply : 配置/對象…

原型模式Prototype Pattern

模式定義 用原型實例指定創建對象的種類&#xff0c;并且通過復制這些原型創建新的對象&#xff0c;其允許一個對象再創建 另外一個可定制的對象&#xff0c;無須知道任何創建的細節 對象創建型模式 基本工作原理是通過將一個原型對象傳給那個要發動創建的對象&#xff0c;這…

基于深度學習的智能交通流量預測系統:技術與實踐

前言 隨著城市化進程的加速&#xff0c;交通擁堵問題日益嚴重&#xff0c;給人們的日常生活和經濟發展帶來了巨大的挑戰。智能交通系統&#xff08;ITS&#xff09;作為解決交通問題的重要手段&#xff0c;逐漸成為研究的熱點。其中&#xff0c;交通流量預測是智能交通系統中的…

Cilium動手實驗室: 精通之旅---23.Advanced Gateway API Use Cases

Cilium動手實驗室: 精通之旅---23.Advanced Gateway API Use Cases 1. Lab說明1.1 高級網關 API 使用案例 2. 負載均衡器2.1 部署應用程序2.2 部署 Gateway 和 HTTPRoute 3. HTTP 標頭請求修飾符3.1 部署 HTTPRoute3.2 可觀測性 4. HTTP 響應標頭重寫5. HTTP 流量鏡像5.1 demo應…

Agentic Workflow是什么?Agentic Workflow會成為下一個AI風口嗎?

無論是想要學習人工智能當做主業營收&#xff0c;還是像我一樣作為開發工程師但依然要運用這個顛覆開發的時代寵兒&#xff0c;都有必要了解、學習一下人工智能。 近期發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;入行門檻低&#x…

Some chunks are larger than 500 KiB after minification. Consider

在 vue3vite 項目開發中&#xff0c;build 打包時出現以下警告報錯&#xff1a; (!) Some chunks are larger than 500 KiB after minification. Consider: - Using dynamic import() to code-split the application - Use build.rollupOptions.output.manualChunks to improve…

NodeJS11和10以及之前的版本,關鍵差異?

Node.js 11 相比 10&#xff08;及更早版本&#xff09;&#xff0c;除了事件循環行為的重大改變&#xff0c;還有多個核心模塊和底層機制的升級。以下是它們的關鍵差異和新特性對比&#xff0c;幫助你快速掌握兩個版本的重要變化。 &#x1f527; 一、事件循環行為變化&#x…

調和級數 斂散性

調和級數的斂散性是一個非常經典的問題。我們來全面分析它。 &#x1f9e0; 調和級數定義 調和級數是指&#xff1a; ∑ n 1 ∞ 1 n 1 1 2 1 3 1 4 ? \sum_{n1}^{\infty} \frac{1}{n} 1 \frac{1}{2} \frac{1}{3} \frac{1}{4} \cdots n1∑∞?n1?121?31?41?? …

Python?元組集合字符串

????˙?˙? ? 元組&#x1f6e5;?創建訪問修改解包其他操作比較的依據 集合&#x1f6f8;創建添加和刪除其他操作 字符串&#x1fa82;創建索引和切片基本操作連接加號join() 重復查找in 關鍵字index()find()startswith()endswith() ??替換??分割??大小寫刪除 能…

??信息系統項目管理師-項目整合管理 知識點總結與例題分析??

??一、項目整合管理概述?? ??1. 定義與重要性?? 項目整合管理是項目管理知識領域中的核心過程,它協調所有其他知識領域的過程和活動,確保項目各要素有效整合。其核心目標是: ??統一項目目標??:確保各要素服務于共同目標??協調沖突??:解決項目執行中的各…

『uniapp』onThemeChange監聽主題樣式,動態主題不正確生效,樣式被覆蓋的坑

目錄 問題示例代碼解決思路1&#xff08;缺點影響顯示效果有延遲&#xff09;解決思路2——通過路由刷新頁面&#xff08;缺點只適用于部分網頁&#xff09;解決思路3——vuex&#xff08;沒學會~&#xff09;總結 歡迎關注 『uniapp』 專欄&#xff0c;持續更新中 歡迎關注 『…

LeetCode 高頻 SQL 50 題(基礎版)【題解】合集

點擊下方標題可跳轉至對應部分&#xff1a; LeetCode 高頻 SQL 50 題&#xff08;基礎版&#xff09;之 【查詢】部分 LeetCode 高頻 SQL 50 題&#xff08;基礎版&#xff09;之 【連接】部分 上 LeetCode 高頻 SQL 50 題&#xff08;基礎版&#xff09;之 【連接】部分 下…

Jenkins 全面深入學習目錄

Jenkins 全面深入學習目錄 第一部分&#xff1a;Jenkins 基礎入門 Jenkins 概述 持續集成/持續交付(CI/CD)概念Jenkins 的歷史與發展Jenkins 與其他 CI/CD 工具的比較 Jenkins 安裝與配置 系統要求與環境準備不同操作系統下的安裝方法初始配置與安全設置插件管理系統 Jenkins…

安裝laravel11和laravel12的一些報錯問題解決

前言 今天在安裝laravel的過程中遇到一些報錯問題&#xff0c;記錄一下。 laravel 12 Root composer.json requires laravel/tinker ^2.10.1, found laravel/tinker[2.x-dev] but it does not match your minimum-stability laravel/framework[v12.0.0, ..., v12.15.0] requ…

Oracle21cR3之客戶端安裝錯誤及處理方法

文章目錄 Oracle21cR3客戶端安裝1. 下載2. 安裝解壓到指定位置&#xff0c;如下&#xff1a;2. 安裝 3. 常見錯誤1. 無法將 JINSHENGYUAN\jinshengyuan 安裝用戶添加到 %2% 組。1. 問題原因分析2. 處理方法 Oracle21cR3客戶端安裝 1. 下載 官網下載 2. 安裝 解壓到指定位置…

web3 資訊網址

1. 新聞 幣圈導航| 區塊鏈導航| WEB3導航 | 聚合幣圈交易所、行情工具、空投資訊、DeFi入口及行業動態&#xff0c;一站式區塊鏈資源門戶網站 2.github位置 https://github.com/itgoyo/awesome-crypto

【C++】簡單商品價格計算程序練習

相信你是最棒噠!!! 文章目錄 一、題目代碼 二、題目解析 1.解析版 2.簡潔版 總結 一、題目代碼 構建一個類book,其中含有兩個私有數據成員qu和price,將price初始化為qu的10倍,建立一個有5個元素的數組對象,將qu初始化為6~10。要求通過對象指針訪問對象數組,按相反的順序…

現代數據工程實踐:基于Dagster的ETL架構設計與實現

在當今數據驅動的世界中&#xff0c;有效的數據處理流程至關重要。本文將帶您通過一個完整的教程&#xff0c;學習如何使用Dagster構建一個功能強大的ETL(提取、轉換、加載)管道。無論您是數據工程師、分析師還是對數據流水線感興趣的技術愛好者&#xff0c;本教程都將為您提供…

golang-linux環境配置

下載源碼包 &#xff1a;All releases - The Go Programming Language 解壓文件 sudo tar -zxvf go1.24.4.linux-amd64.tar.gz -C /usr/local/ 配置環境 vim ~/.bashrc 在配置文件最后加上下面三行&#xff1a; # 設置GO語言的路徑 export GOROOT/usr/local/go # 當前go…

【模擬 貪心】B4207 [常州市賽 2021] 戰士|普及+

B4207 [常州市賽 2021] 戰士 題目背景 搬運自 http://czoj.com.cn/p/443。數據為民間數據。 題目描述 小 X \text X X 在玩一款操控戰士和怪物戰斗的游戲。戰士初始生命值為 iH \text{iH} iH 、初始攻擊力為 iA \text{iA} iA 。怪物只有一個&#xff0c;初始生命值為 H…