精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

從0開始:500行代碼實(shí)現(xiàn) LSM 數(shù)據(jù)庫

運(yùn)維 數(shù)據(jù)庫運(yùn)維
本文基于《數(shù)據(jù)密集型應(yīng)用系統(tǒng)設(shè)計(jì)》中對(duì) LSM-Tree 數(shù)據(jù)庫的設(shè)計(jì)思路,結(jié)合代碼實(shí)現(xiàn)完整地闡述了一個(gè)迷你數(shù)據(jù)庫,核心代碼 500 行左右,通過理論結(jié)合實(shí)踐來更好地理解數(shù)據(jù)庫的原理。

[[407855]]

前言

LSM-Tree 是很多 NoSQL 數(shù)據(jù)庫引擎的底層實(shí)現(xiàn),例如 LevelDB,Hbase 等。本文基于《數(shù)據(jù)密集型應(yīng)用系統(tǒng)設(shè)計(jì)》中對(duì) LSM-Tree 數(shù)據(jù)庫的設(shè)計(jì)思路,結(jié)合代碼實(shí)現(xiàn)完整地闡述了一個(gè)迷你數(shù)據(jù)庫,核心代碼 500 行左右,通過理論結(jié)合實(shí)踐來更好地理解數(shù)據(jù)庫的原理。

一  SSTable(排序字符串表)

之前基于哈希索引實(shí)現(xiàn)了一個(gè)數(shù)據(jù)庫,它的局限性是哈希表需要整個(gè)放入到內(nèi)存,并且區(qū)間查詢效率不高。

在哈希索引數(shù)據(jù)庫的日志中,key 的存儲(chǔ)順序就是它的寫入順序,并且對(duì)于同一個(gè) key 后出現(xiàn)的 key 優(yōu)先于之前的 key,因此日志中的 key 順序并不重要。這樣的好處是寫入很簡單,但沒有控制 key 重復(fù)帶來的問題是浪費(fèi)了存儲(chǔ)空間,初始化加載的耗時(shí)會(huì)增加。

現(xiàn)在簡單地改變一下日志的寫入要求:要求寫入的 key 有序,并且同一個(gè) key 在一個(gè)日志中只能出現(xiàn)一次。這種日志就叫做 SSTable,相比哈希索引的日志有以下優(yōu)點(diǎn):

1)合并多個(gè)日志文件更加簡單高效。

因?yàn)槿罩臼怯行虻模钥梢杂梦募w并排序算法,即并發(fā)讀取多個(gè)輸入文件,比較每個(gè)文件的第一個(gè) key,按照順序拷貝到輸出文件。如果有重復(fù)的 key,那就只保留最新的日志中的 key 的值,老的丟棄。

2)查詢 key 時(shí),不需要在內(nèi)存中保存所有 key 的索引。

如下圖所示,假設(shè)需要查找 handiwork,且內(nèi)存中沒有記錄該 key 的位置,但因?yàn)?SSTable 是有序的,所以我們可以知道 handiwork 如果存在一定是在 handbag 和 handsome 的中間,然后從 handbag 開始掃描日志一直到 handsome 結(jié)束。這樣的好處是有三個(gè):

  • 內(nèi)存中只需要記錄稀疏索引,減少了內(nèi)存索引的大小。

  • 查詢操作不需要讀取整個(gè)日志,減少了文件 IO。

  • 可以支持區(qū)間查詢。

二  構(gòu)建和維護(hù) SSTable

我們知道寫入時(shí) key 會(huì)按照任意順序出現(xiàn),那么如何保證 SSTable 中的 key 是有序的呢?一個(gè)簡單方便的方式就是先保存到內(nèi)存的紅黑樹中,紅黑樹是有序的,然后再寫入到日志文件里面。

存儲(chǔ)引擎的基本工作流程如下:

  • 當(dāng)寫入時(shí),先將其添加到內(nèi)存的紅黑樹中,這個(gè)內(nèi)存中的樹稱為內(nèi)存表。

  • 當(dāng)內(nèi)存表大于某個(gè)閾值時(shí),將其作為 SSTable 文件寫入到磁盤,因?yàn)闃涫怯行虻模詫懘疟P的時(shí)候直接按順序?qū)懭刖托小?/p>

  • 為了避免內(nèi)存表未寫入文件時(shí)數(shù)據(jù)庫崩潰,可以在保存到內(nèi)存表的同時(shí)將數(shù)據(jù)也寫入到另一個(gè)日志中(WAL),這樣即使數(shù)據(jù)庫崩潰也能從 WAL 中恢復(fù)。這個(gè)日志寫入就類似哈希索引的日志,不需要保證順序,因?yàn)槭怯脕砘謴?fù)數(shù)據(jù)的。

  • 處理讀請(qǐng)求時(shí),首先嘗試在內(nèi)存表中查找 key,然后從新到舊依次查詢 SSTable 日志,直到找到數(shù)據(jù)或者為空。

  • 后臺(tái)進(jìn)程周期性地執(zhí)行日志合并與壓縮過程,丟棄掉已經(jīng)被覆蓋或刪除的值。

以上的算法就是 LSM-Tree(基于日志結(jié)構(gòu)的合并樹 Log-Structured Merge-Tree) 的實(shí)現(xiàn),基于合并和壓縮排序文件原理的存儲(chǔ)引擎通常就被稱為 LSM 存儲(chǔ)引擎,這也是 Hbase、LevelDB 等數(shù)據(jù)庫的底層原理。

三  實(shí)現(xiàn)一個(gè)基于 LSM 的數(shù)據(jù)庫

前面我們已經(jīng)知道了 LSM-Tree 的實(shí)現(xiàn)算法,在具體實(shí)現(xiàn)的時(shí)候還有很多設(shè)計(jì)的問題需要考慮,下面我挑一些關(guān)鍵設(shè)計(jì)進(jìn)行分析。

1  內(nèi)存表存儲(chǔ)結(jié)構(gòu)

內(nèi)存表的 value 存儲(chǔ)什么?直接存儲(chǔ)原始數(shù)據(jù)嗎?還是存儲(chǔ)寫命令(包括 set 和 rm )?這是我們面臨的第一個(gè)設(shè)計(jì)問題。這里我們先不做判斷,先看下一個(gè)問題。

內(nèi)存表達(dá)到一定大小之后就要寫入到日志文件中持久化。這個(gè)過程如果直接禁寫處理起來就很簡單。但如果要保證內(nèi)存表在寫入文件的同時(shí),還能正常處理讀寫請(qǐng)求呢?

一個(gè)解決思路是:在持久化內(nèi)存表 A 的同時(shí),可以將當(dāng)前的內(nèi)存表指針切換到新的內(nèi)存表實(shí)例 B,此時(shí)我們要保證切換之后 A 是只讀,只有 B 是可寫的,否則我們無法保證內(nèi)存表 A 持久化的過程是原子操作。

  • get 請(qǐng)求:先查詢 B,再查詢 A,最后查 SSTable。

  • set 請(qǐng)求:直接寫入 A。

  • rm 請(qǐng)求:假設(shè) rm 的 key1 只在 A 里面出現(xiàn)了,B 里面沒有。這里如果內(nèi)存表存儲(chǔ)的是原始數(shù)據(jù),那么 rm 請(qǐng)求是沒法處理的,因?yàn)?A 是只讀的,會(huì)導(dǎo)致 rm 失敗。如果我們?cè)趦?nèi)存表里面存儲(chǔ)的是命令的話,這個(gè)問題就是可解的,在 B 里面寫入 rm 命令,這樣查詢 key1 的時(shí)候在 B 里面就能查到 key1 已經(jīng)被刪除了。

因此,假設(shè)我們持久化內(nèi)存表時(shí)做禁寫,那么 value 是可以直接存儲(chǔ)原始數(shù)據(jù)的,但是如果我們希望持久化內(nèi)存表時(shí)不禁寫,那么 value 值就必須要存儲(chǔ)命令。我們肯定是要追求高性能不禁寫的,所以 value 值需要保存的是命令, Hbase 也是這樣設(shè)計(jì)的,背后的原因也是這個(gè)。

另外,當(dāng)內(nèi)存表已經(jīng)超過閾值要持久化的時(shí)候,發(fā)現(xiàn)前一次持久化還沒有做完,那么就需要等待前一次持久化完成才能進(jìn)行本次持久化。換句話說,內(nèi)存表持久化只能串行進(jìn)行。

2  SSTable 的文件格式

為了實(shí)現(xiàn)高效的文件讀取,我們需要好好設(shè)計(jì)一下文件格式。

以下是我設(shè)計(jì)的 SSTable 日志格式:

  • 數(shù)據(jù)區(qū): 數(shù)據(jù)區(qū) 主要是存儲(chǔ)寫入的命令,同時(shí)為了方便分段讀取,是按照一定的數(shù)量大小分段的。

  • 稀疏索引區(qū): 稀疏索引保存的是數(shù)據(jù)段每一段在文件中的位置索引,讀取 SSTable 時(shí)候只會(huì)加載稀疏索引到內(nèi)存,查詢的時(shí)候根據(jù)稀疏索引加載對(duì)應(yīng)數(shù)據(jù)段進(jìn)行查詢。

  • 文件索引區(qū): 存 儲(chǔ)數(shù)據(jù)區(qū)域的位置。

以上的日志格式是迷你的實(shí)現(xiàn),相比 Hbase 的日志格式是比較簡單的,這樣方便理解原理。同時(shí)我也使用了 JSON 格式寫入文件,目的是方便閱讀。而生產(chǎn)實(shí)現(xiàn)是效率優(yōu)先的,為了節(jié)省存儲(chǔ)會(huì)做壓縮。

四  代碼實(shí)現(xiàn)分析

我寫的代碼實(shí)現(xiàn)在:TinyKvStore,下面分析一下關(guān)鍵的代碼。代碼比較多,也比較細(xì)碎,如果只關(guān)心原理的話可以跳過這部分,如果想了解代碼實(shí)現(xiàn)可以繼續(xù)往下讀。

1  SSTable

內(nèi)存表持久化

內(nèi)存表持久化到 SSTable 就是把內(nèi)存表的數(shù)據(jù)按照前面我們提到的日志格式寫入到文件。對(duì)于 SSTable 來說,寫入的數(shù)據(jù)就是數(shù)據(jù)命令,包括 set 和 rm,只要我們能知道 key 的最新命令是什么,就能知道 key 在數(shù)據(jù)庫中的狀態(tài)。

  1. /** 
  2.  * 從內(nèi)存表轉(zhuǎn)化為ssTable 
  3.  * @param index 
  4.  */ 
  5.   private void initFromIndex(TreeMap<String, Command> index) { 
  6.     try { 
  7.         JSONObject partData = new JSONObject(true); 
  8.         tableMetaInfo.setDataStart(tableFile.getFilePointer()); 
  9.         for (Command command : index.values()) { 
  10.             //處理set命令 
  11.             if (command instanceof SetCommand) { 
  12.                 SetCommand set = (SetCommand) command; 
  13.                 partData.put(set.getKey(), set); 
  14.             } 
  15.             //處理RM命令 
  16.             if (command instanceof RmCommand) { 
  17.                 RmCommand rm = (RmCommand) command; 
  18.                 partData.put(rm.getKey(), rm); 
  19.              } 
  20.  
  21.  
  22.             //達(dá)到分段數(shù)量,開始寫入數(shù)據(jù)段 
  23.             if (partData.size() >= tableMetaInfo.getPartSize()) { 
  24.                 writeDataPart(partData); 
  25.             } 
  26.         } 
  27.         //遍歷完之后如果有剩余的數(shù)據(jù)(尾部數(shù)據(jù)不一定達(dá)到分段條件)寫入文件 
  28.         if (partData.size() > 0) { 
  29.              writeDataPart(partData); 
  30.         } 
  31.         long dataPartLen = tableFile.getFilePointer() - tableMetaInfo.getDataStart(); 
  32.         tableMetaInfo.setDataLen(dataPartLen); 
  33.         //保存稀疏索引 
  34.         byte[] indexBytes = JSONObject.toJSONString(sparseIndex).getBytes(StandardCharsets.UTF_8); 
  35.         tableMetaInfo.setIndexStart(tableFile.getFilePointer()); 
  36.         tableFile.write(indexBytes); 
  37.         tableMetaInfo.setIndexLen(indexBytes.length); 
  38.         LoggerUtil.debug(LOGGER, "[SsTable][initFromIndex][sparseIndex]: {}", sparseIndex); 
  39.  
  40.  
  41.       //保存文件索引 
  42.       tableMetaInfo.writeToFile(tableFile); 
  43.       LoggerUtil.info(LOGGER, "[SsTable][initFromIndex]: {},{}", filePath, tableMetaInfo); 
  44.  
  45.  
  46.     } catch (Throwable t) { 
  47.          throw new RuntimeException(t); 
  48.     } 

寫入的格式是基于讀取倒推的,主要是為了方便讀取。例如 tableMetaInfo 寫入是從前往后寫的,那么讀取的時(shí)候就要從后往前讀。這也是為什么 version 要放到最后寫入,因?yàn)樽x取的時(shí)候是第一個(gè)讀取到的,方便對(duì)日志格式做升級(jí)。這些 trick 如果沒有動(dòng)手嘗試,光看是很難理解為什么這么干的。

  1. /** 
  2.  * 把數(shù)據(jù)寫入到文件中 
  3. * @param file 
  4. */ 
  5. public void writeToFile(RandomAccessFile file) { 
  6.     try { 
  7.         file.writeLong(partSize); 
  8.         file.writeLong(dataStart); 
  9.         file.writeLong(dataLen); 
  10.         file.writeLong(indexStart); 
  11.         file.writeLong(indexLen); 
  12.         file.writeLong(version); 
  13.     } catch (Throwable t) { 
  14.         throw new RuntimeException(t); 
  15.     } 
  16.  
  17.  
  18. /** 
  19. * 從文件中讀取元信息,按照寫入的順序倒著讀取出來 
  20. * @param file 
  21. * @return 
  22. */ 
  23. public static TableMetaInfo readFromFile(RandomAccessFile file) { 
  24.     try { 
  25.         TableMetaInfo tableMetaInfo = new TableMetaInfo(); 
  26.         long fileLen = file.length(); 
  27.  
  28.  
  29.         file.seek(fileLen - 8); 
  30.         tableMetaInfo.setVersion(file.readLong()); 
  31.  
  32.  
  33.         file.seek(fileLen - 8 * 2); 
  34.         tableMetaInfo.setIndexLen(file.readLong()); 
  35.  
  36.  
  37.         file.seek(fileLen - 8 * 3); 
  38.         tableMetaInfo.setIndexStart(file.readLong()); 
  39.  
  40.  
  41.         file.seek(fileLen - 8 * 4); 
  42.         tableMetaInfo.setDataLen(file.readLong()); 
  43.  
  44.  
  45.         file.seek(fileLen - 8 * 5); 
  46.         tableMetaInfo.setDataStart(file.readLong()); 
  47.  
  48.  
  49.         file.seek(fileLen - 8 * 6); 
  50.         tableMetaInfo.setPartSize(file.readLong()); 
  51.  
  52.  
  53.         return tableMetaInfo; 
  54.     } catch (Throwable t) { 
  55.         throw new RuntimeException(t); 
  56.     } 

從文件中加載 SSTable

從文件中加載 SSTable 時(shí)只需要加載稀疏索引,這樣能節(jié)省內(nèi)存。數(shù)據(jù)區(qū)等查詢的時(shí)候按需讀取就行。

  1. /** 
  2.      * 從文件中恢復(fù)ssTable到內(nèi)存 
  3.      */ 
  4.     private void restoreFromFile() { 
  5.         try { 
  6.             //先讀取索引 
  7.             TableMetaInfo tableMetaInfo = TableMetaInfo.readFromFile(tableFile); 
  8.             LoggerUtil.debug(LOGGER, "[SsTable][restoreFromFile][tableMetaInfo]: {}", tableMetaInfo); 
  9.             //讀取稀疏索引 
  10.             byte[] indexBytes = new byte[(int) tableMetaInfo.getIndexLen()]; 
  11.             tableFile.seek(tableMetaInfo.getIndexStart()); 
  12.             tableFile.read(indexBytes); 
  13.             String indexStr = new String(indexBytes, StandardCharsets.UTF_8); 
  14.             LoggerUtil.debug(LOGGER, "[SsTable][restoreFromFile][indexStr]: {}", indexStr); 
  15.             sparseIndex = JSONObject.parseObject(indexStr, 
  16.                     new TypeReference<TreeMap<String, Position>>() { 
  17.                     }); 
  18.             this.tableMetaInfo = tableMetaInfo; 
  19.             LoggerUtil.debug(LOGGER, "[SsTable][restoreFromFile][sparseIndex]: {}", sparseIndex); 
  20.         } catch (Throwable t) { 
  21.             throw new RuntimeException(t); 
  22.         } 
  23.     } 

SSTable 查詢

從 SSTable 查詢數(shù)據(jù)首先是要從稀疏索引中找到 key 所在的區(qū)間,找到區(qū)間之后根據(jù)索引記錄的位置讀取區(qū)間的數(shù)據(jù),然后進(jìn)行查詢,如果有數(shù)據(jù)就返回,沒有就返回 null。

  1. /** 
  2.  * 從ssTable中查詢數(shù)據(jù) 
  3.  * @param key 
  4.  * @return 
  5.  */ 
  6. public Command query(String key) { 
  7.     try { 
  8.         LinkedList<Position> sparseKeyPositionList = new LinkedList<>(); 
  9.  
  10.  
  11.         Position lastSmallPosition = null
  12.         Position firstBigPosition = null
  13.  
  14.  
  15.         //從稀疏索引中找到最后一個(gè)小于key的位置,以及第一個(gè)大于key的位置 
  16.         for (String k : sparseIndex.keySet()) { 
  17.             if (k.compareTo(key) <= 0) { 
  18.                 lastSmallPosition = sparseIndex.get(k); 
  19.             } else { 
  20.                 firstBigPosition = sparseIndex.get(k); 
  21.                 break
  22.             } 
  23.         } 
  24.         if (lastSmallPosition != null) { 
  25.             sparseKeyPositionList.add(lastSmallPosition); 
  26.         } 
  27.         if (firstBigPosition != null) { 
  28.             sparseKeyPositionList.add(firstBigPosition); 
  29.         } 
  30.         if (sparseKeyPositionList.size() == 0) { 
  31.             return null
  32.         } 
  33.         LoggerUtil.debug(LOGGER, "[SsTable][restoreFromFile][sparseKeyPositionList]: {}", sparseKeyPositionList); 
  34.         Position firstKeyPosition = sparseKeyPositionList.getFirst(); 
  35.         Position lastKeyPosition = sparseKeyPositionList.getLast(); 
  36.         long start = 0
  37.         long len = 0
  38.         start = firstKeyPosition.getStart(); 
  39.         if (firstKeyPosition.equals(lastKeyPosition)) { 
  40.             len = firstKeyPosition.getLen(); 
  41.         } else { 
  42.             len = lastKeyPosition.getStart() + lastKeyPosition.getLen() - start; 
  43.         } 
  44.         //key如果存在必定位于區(qū)間內(nèi),所以只需要讀取區(qū)間內(nèi)的數(shù)據(jù),減少io 
  45.         byte[] dataPart = new byte[(int) len]; 
  46.         tableFile.seek(start); 
  47.         tableFile.read(dataPart); 
  48.         int pStart = 0
  49.         //讀取分區(qū)數(shù)據(jù) 
  50.         for (Position position : sparseKeyPositionList) { 
  51.             JSONObject dataPartJson = JSONObject.parseObject(new String(dataPart, pStart, (int) position.getLen())); 
  52.             LoggerUtil.debug(LOGGER, "[SsTable][restoreFromFile][dataPartJson]: {}", dataPartJson); 
  53.             if (dataPartJson.containsKey(key)) { 
  54.                 JSONObject value = dataPartJson.getJSONObject(key); 
  55.                 return ConvertUtil.jsonToCommand(value); 
  56.             } 
  57.             pStart += (int) position.getLen(); 
  58.         } 
  59.         return null
  60.     } catch (Throwable t) { 
  61.         throw new RuntimeException(t); 
  62.     } 

2  LsmKvStore

初始化加載

  • dataDir:數(shù)據(jù)目錄存儲(chǔ)了日志數(shù)據(jù),所以啟動(dòng)的時(shí)候需要從目錄中讀取之前的持久化數(shù)據(jù)。

  • storeThreshold:持久化閾值,當(dāng)內(nèi)存表超過一定大小之后要進(jìn)行持久化。

  • partSize:SSTable 的數(shù)據(jù)分區(qū)閾值。

  • indexLock:內(nèi)存表的讀寫鎖。

  • ssTables:SSTable 的有序列表,按照從新到舊排序。

  • wal:順序?qū)懭肴罩荆糜诒4鎯?nèi)存表的數(shù)據(jù),用作數(shù)據(jù)恢復(fù)。

啟動(dòng)的過程很簡單,就是加載數(shù)據(jù)配置,初始化內(nèi)容,如果需要做數(shù)據(jù)恢復(fù)就將數(shù)據(jù)恢復(fù)到內(nèi)存表。

  1. /** 
  2.  * 初始化 
  3.  * @param dataDir 數(shù)據(jù)目錄 
  4.  * @param storeThreshold 持久化閾值 
  5.  * @param partSize 數(shù)據(jù)分區(qū)大小 
  6. */ 
  7. public LsmKvStore(String dataDir, int storeThreshold, int partSize) { 
  8.     try { 
  9.         this.dataDir = dataDir; 
  10.         this.storeThreshold = storeThreshold; 
  11.         this.partSize = partSize; 
  12.         this.indexLock = new ReentrantReadWriteLock(); 
  13.         File dir = new File(dataDir); 
  14.         File[] files = dir.listFiles(); 
  15.         ssTables = new LinkedList<>(); 
  16.         index = new TreeMap<>(); 
  17.         //目錄為空無需加載ssTable 
  18.         if (files == null || files.length == 0) { 
  19.             walFile = new File(dataDir + WAL); 
  20.             wal = new RandomAccessFile(walFile, RW_MODE); 
  21.             return
  22.         } 
  23.  
  24.  
  25.         //從大到小加載ssTable 
  26.         TreeMap<Long, SsTable> ssTableTreeMap = new TreeMap<>(Comparator.reverseOrder()); 
  27.         for (File file : files) { 
  28.             String fileName = file.getName(); 
  29.             //從暫存的WAL中恢復(fù)數(shù)據(jù),一般是持久化ssTable過程中異常才會(huì)留下walTmp 
  30.             if (file.isFile() && fileName.equals(WAL_TMP)) { 
  31.                 restoreFromWal(new RandomAccessFile(file, RW_MODE)); 
  32.             } 
  33.             //加載ssTable 
  34.             if (file.isFile() && fileName.endsWith(TABLE)) { 
  35.                 int dotIndex = fileName.indexOf("."); 
  36.                 Long time = Long.parseLong(fileName.substring(0, dotIndex)); 
  37.                 ssTableTreeMap.put(time, SsTable.createFromFile(file.getAbsolutePath())); 
  38.             } else if (file.isFile() && fileName.equals(WAL)) { 
  39.                 //加載WAL 
  40.                 walFile = file; 
  41.                 wal = new RandomAccessFile(file, RW_MODE); 
  42.                 restoreFromWal(wal); 
  43.             } 
  44.         } 
  45.         ssTables.addAll(ssTableTreeMap.values()); 
  46.     } catch (Throwable t) { 
  47.         throw new RuntimeException(t); 
  48.     } 

寫入操作

寫入操作先加寫鎖,然后把數(shù)據(jù)保存到內(nèi)存表以及 WAL 中,另外還要做判斷:如果超過閾值進(jìn)行持久化。這里為了簡單起見我直接串行執(zhí)行了,沒有使用線程池執(zhí)行,但不影響整體邏輯。set 和 rm 的代碼是類似,這里就不重復(fù)了。

  1. @Override 
  2. public void set(String key, String value) { 
  3.     try { 
  4.         SetCommand command = new SetCommand(key, value); 
  5.         byte[] commandBytes = JSONObject.toJSONBytes(command); 
  6.         indexLock.writeLock().lock(); 
  7.         //先保存數(shù)據(jù)到WAL中 
  8.         wal.writeInt(commandBytes.length); 
  9.         wal.write(commandBytes); 
  10.         index.put(key, command); 
  11.  
  12.  
  13.         //內(nèi)存表大小超過閾值進(jìn)行持久化 
  14.         if (index.size() > storeThreshold) { 
  15.             switchIndex(); 
  16.             storeToSsTable(); 
  17.         } 
  18.     } catch (Throwable t) { 
  19.         throw new RuntimeException(t); 
  20.     } finally { 
  21.         indexLock.writeLock().unlock(); 
  22.     } 

內(nèi)存表持久化過程

切換內(nèi)存表及其關(guān)聯(lián)的 WAL:先對(duì)內(nèi)存表加鎖,然后新建一個(gè)內(nèi)存表和 WAL,把老的內(nèi)存表和 WAL 暫存起來,釋放鎖。這樣新的內(nèi)存表就可以開始寫入,老的內(nèi)存表變成只讀。

執(zhí)行持久化過程:把老內(nèi)存表有序?qū)懭氲揭粋€(gè)新的 ssTable 中,然后刪除暫存內(nèi)存表和臨時(shí)保存的 WAL。

  1. /** 
  2.   * 切換內(nèi)存表,新建一個(gè)內(nèi)存表,老的暫存起來 
  3.   */ 
  4.   private void switchIndex() { 
  5.      try { 
  6.          indexLock.writeLock().lock(); 
  7.          //切換內(nèi)存表 
  8.          immutableIndex = index; 
  9.          index = new TreeMap<>(); 
  10.          wal.close(); 
  11.          //切換內(nèi)存表后也要切換WAL 
  12.          File tmpWal = new File(dataDir + WAL_TMP); 
  13.          if (tmpWal.exists()) { 
  14.              if (!tmpWal.delete()) { 
  15.                  throw new RuntimeException("刪除文件失敗: walTmp"); 
  16.              } 
  17.          } 
  18.          if (!walFile.renameTo(tmpWal)) { 
  19.              throw new RuntimeException("重命名文件失敗: walTmp"); 
  20.          } 
  21.          walFile = new File(dataDir + WAL); 
  22.          wal = new RandomAccessFile(walFile, RW_MODE); 
  23.      } catch (Throwable t) { 
  24.          throw new RuntimeException(t); 
  25.      } finally { 
  26.          indexLock.writeLock().unlock(); 
  27.      } 
  28.  } 
  29.  
  30.  
  31. /** 
  32.  * 保存數(shù)據(jù)到ssTable 
  33.  */ 
  34. private void storeToSsTable() { 
  35.     try { 
  36.         //ssTable按照時(shí)間命名,這樣可以保證名稱遞增 
  37.         SsTable ssTable = SsTable.createFromIndex(dataDir + System.currentTimeMillis() + TABLE, partSize, immutableIndex); 
  38.         ssTables.addFirst(ssTable); 
  39.         //持久化完成刪除暫存的內(nèi)存表和WAL_TMP 
  40.         immutableIndex = null
  41.         File tmpWal = new File(dataDir + WAL_TMP); 
  42.         if (tmpWal.exists()) { 
  43.              if (!tmpWal.delete()) { 
  44.                  throw new RuntimeException("刪除文件失敗: walTmp"); 
  45.             } 
  46.         } 
  47.     } catch (Throwable t) { 
  48.         throw new RuntimeException(t); 
  49.     } 
  50.  } 

查詢操作

查詢的操作就跟算法中描述的一樣:

  • 先從內(nèi)存表中取,如果取不到并且存在不可變內(nèi)存表就從不可變內(nèi)存表中取。

  • 內(nèi)存表中查詢不到就從新到舊的 SSTable 中依次查詢。

 

  1. @Override 
  2. public String get(String key) { 
  3.     try { 
  4.         indexLock.readLock().lock(); 
  5.         //先從索引中取 
  6.         Command command = index.get(key); 
  7.         //再嘗試從不可變索引中取,此時(shí)可能處于持久化sstable的過程中 
  8.         if (command == null && immutableIndex != null) { 
  9.             command = immutableIndex.get(key); 
  10.         } 
  11.         if (command == null) { 
  12.             //索引中沒有嘗試從ssTable中獲取,從新的ssTable找到老的 
  13.             for (SsTable ssTable : ssTables) { 
  14.                 command = ssTable.query(key); 
  15.                 if (command != null) { 
  16.                     break
  17.                 } 
  18.             } 
  19.         } 
  20.         if (command instanceof SetCommand) { 
  21.             return ((SetCommand) command).getValue(); 
  22.         } 
  23.         if (command instanceof RmCommand) { 
  24.             return null
  25.         } 
  26.         //找不到說明不存在 
  27.         return null
  28.     } catch (Throwable t) { 
  29.         throw new RuntimeException(t); 
  30.     } finally { 
  31.         indexLock.readLock().unlock(); 
  32.     } 

總結(jié)

知行合一,方得真知。如果我們不動(dòng)手實(shí)現(xiàn)一個(gè)數(shù)據(jù)庫,就很難理解為什么這么設(shè)計(jì)。 例如日志格式為什么這樣設(shè)計(jì),為什么數(shù)據(jù)庫保存的是數(shù)據(jù)操作而不是數(shù)據(jù)本身等等。

本文實(shí)現(xiàn)的數(shù)據(jù)庫功能比較簡單,有很多地方可以優(yōu)化,例如數(shù)據(jù)持久化異步化,日志文件壓縮,查詢使用布隆過濾器先過濾一下。有興趣的讀者可以繼續(xù)深入研究。

 

責(zé)任編輯:張燕妮 來源: 阿里技術(shù)
相關(guān)推薦

2021-06-30 13:45:49

SQL數(shù)據(jù)庫LSM

2021-11-12 05:00:00

數(shù)據(jù)庫索引技術(shù)

2023-03-29 08:52:58

視覺Vue組件庫

2009-05-19 10:22:29

數(shù)據(jù)庫表格隨機(jī)讀取數(shù)據(jù)庫

2009-01-10 19:25:44

2021-02-06 14:27:00

SQL優(yōu)化運(yùn)維

2021-04-30 15:34:23

Python 開發(fā)編程語言

2021-04-29 22:38:04

Python數(shù)據(jù)庫SQL

2022-10-28 10:18:53

代碼績效Java

2020-05-08 09:30:33

黑客微軟GitHub

2013-07-17 09:42:34

云計(jì)算數(shù)據(jù)庫NoSQL

2022-05-11 09:02:27

Python數(shù)據(jù)庫Excel

2025-03-04 00:20:45

2021-07-09 06:48:29

數(shù)組存儲(chǔ)內(nèi)存

2025-05-29 08:00:00

數(shù)組編程語言

2021-08-04 05:49:40

數(shù)據(jù)庫數(shù)時(shí)序數(shù)據(jù)庫技術(shù)

2022-04-05 13:46:21

日志數(shù)據(jù)庫系統(tǒng)

2021-09-09 09:28:08

面向列數(shù)據(jù)庫面向行

2024-02-06 09:55:33

框架代碼

2022-08-31 14:24:03

數(shù)字化轉(zhuǎn)型小程序平臺(tái)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

岛国在线视频| 特级西西444www大胆免费看| 久久97久久97精品免视看秋霞| 午夜精品一区二区三区电影天堂 | 国产一区二区不卡视频| 久草国产在线观看| 亚洲理论电影| 日韩一区二区视频| 日本中文字幕片| 在线国产91| 99综合电影在线视频| 国产精品美女久久| 国产一级特黄毛片| 日韩欧美二区| 日韩风俗一区 二区| 久久久久久蜜桃一区二区| 欧美xxxx性xxxxx高清| 国产欧美中文在线| 国产一区二区精品在线| 91丨porny丨在线中文 | 不卡中文字幕在线观看| av有码在线观看| 国产精品久久久久久久久久久免费看| 成人免费看片网址| 伊人免费在线观看| 国产人成精品一区二区三| 精品国产拍在线观看| 久久久久久久久久久国产精品| 四虎成人精品一区二区免费网站| 精品福利一区二区| www.国产二区| 日本暖暖在线视频| 久久精品欧美日韩| 精品国产一区二区三区麻豆免费观看完整版 | 99riav1国产精品视频| 日韩在线观看免费av| 不卡一区二区在线观看| 色是在线视频| 国产精品人人做人人爽人人添 | 五月天丁香视频| 国产乱理伦片在线观看夜一区 | 亚洲图片在线播放| 久久精品在线| 欧美最顶级丰满的aⅴ艳星| 一区二区三区免费高清视频| 亚洲天天综合| 久久久久www| 99re6热在线精品视频| 欧洲激情综合| 亚洲无亚洲人成网站77777| 亚洲国产综合视频| 红杏成人性视频免费看| 精品日韩99亚洲| 一起草最新网址| 日本免费精品| 555www色欧美视频| 波多野结衣网页| 日本一区二区三区电影免费观看| 在线播放91灌醉迷j高跟美女 | 91看片在线播放| 韩国精品一区二区三区| 久久久久久久久中文字幕| 国产一级特黄毛片| 一本久久知道综合久久| 欧美一区二区大胆人体摄影专业网站| 精品成人免费视频| 亚洲欧美卡通另类91av| 国产成人精彩在线视频九色| 精品免费囯产一区二区三区| 视频一区二区不卡| 国产精品成人v| 夜夜爽8888| 国产一区三区三区| 国产精品有限公司| 日本福利在线观看| 日本一区二区三区久久久久久久久不 | 中文字幕+乱码+中文乱码www | 国产精品偷伦免费视频观看的| 国产九色91回来了| 国产做a爰片久久毛片| 亚洲一区久久久| 人妻少妇精品无码专区| 久久夜色精品一区| www.午夜色| 91av久久| 在线视频一区二区三区| 国产欧美激情视频| 你懂的在线观看一区二区| 亚洲老头老太hd| 九九这里只有精品视频| 狠狠色丁香久久综合频道| 91大神在线播放精品| 正在播放亚洲精品| 国产91对白在线观看九色| 美脚丝袜一区二区三区在线观看| aaa日本高清在线播放免费观看| 日韩毛片一二三区| 精品国产一二三四区| 美女视频一区| 亚洲黄一区二区| 女教师淫辱の教室蜜臀av软件| 欧美freesex交免费视频| 7m第一福利500精品视频| 91theporn国产在线观看| 成人激情动漫在线观看| 日韩亚洲视频在线| 3344国产永久在线观看视频| 欧美丝袜丝交足nylons| 成人区人妻精品一区二| 日韩欧美中文| 97视频在线播放| 国产孕妇孕交大片孕| 久久综合资源网| 国产精品无码免费专区午夜| 99久久综合国产精品二区| 精品成人免费观看| 肉色超薄丝袜脚交69xx图片| 国产精品亚洲欧美| 亚洲综合日韩在线| 97人人在线| 欧美日韩一区二区在线| a级大片免费看| 欧美日韩国产在线观看网站| 97超级碰在线看视频免费在线看| 97免费观看视频| 日本一区二区免费在线观看视频 | 热久久久久久久| 国内视频一区| 男人添女人下部高潮视频在线观看| 欧美亚洲日本一区| 日韩乱码人妻无码中文字幕久久| 欧美视频一区| 91国产丝袜在线放| 免费av毛片在线看| 在线看国产日韩| 成人免费网站黄| 国产精品免费看| 国产欧美日韩伦理| 激情av在线播放| 欧美一级夜夜爽| 三级黄色免费观看| 麻豆91精品视频| 亚洲不卡中文字幕| 日本美女久久| 中文国产成人精品久久一| 中文字幕一区二区人妻视频| 91热门视频在线观看| 国产a级片网站| 精品久久97| 隔壁老王国产在线精品| 亚洲第一页视频| 亚洲宅男天堂在线观看无病毒| 亚洲av毛片在线观看| 在线精品小视频| 91久久国产自产拍夜夜嗨| 中文字幕免费高清电视剧网站在线观看 | 久久综合一区二区| 久久久久久香蕉| 红桃视频在线观看一区二区| 国产精品第一第二| 永久免费av片在线观看全网站| 欧美亚洲日本国产| 五月婷婷综合激情网| 精品亚洲国产成人av制服丝袜| 中文字幕久精品免| 日本精品一区二区三区在线观看视频| 欧美日韩第一页| 无码精品人妻一区二区| 在线影院国内精品| 91精品少妇一区二区三区蜜桃臀| 国产精品综合一区二区| 成人免费观看在线| 九九免费精品视频在线观看| 国产精品wwwwww| 久久日韩视频| 精品av久久707| 91丝袜一区二区三区| 亚洲国产精品t66y| 男人女人拔萝卜视频| 亚洲激情影院| 日本婷婷久久久久久久久一区二区| 2019年精品视频自拍| 久久精品在线视频| 特黄视频在线观看| 日本丶国产丶欧美色综合| 欧美h片在线观看| av在线这里只有精品| 乱子伦视频在线看| 亚洲一级淫片| 欧美日产一区二区三区在线观看| 国产精品原创视频| 久久久亚洲网站| av免费观看一区二区| 精品国产伦一区二区三区免费| 久久久精品视频网站| 亚洲啪啪综合av一区二区三区| 亚洲图片综合网| 久久精品国产99久久6| 亚洲色成人www永久在线观看| 国产99精品| www.久久草| 久久亚洲精品人成综合网| 欧美精品激情blacked18| 成人在线免费视频| 亚洲成在人线av| 国产男女裸体做爰爽爽| 一本色道久久综合亚洲aⅴ蜜桃| 天天操夜夜操av| 26uuu精品一区二区| 亚欧美一区二区三区| 喷水一区二区三区| 亚洲熟妇国产熟妇肥婆| 亚洲二区三区不卡| 日本在线观看一区二区| 爱高潮www亚洲精品| 国产在线观看91精品一区| 一本大道色婷婷在线| 九色91av视频| 欧美日韩xx| 夜夜嗨av一区二区三区免费区 | jizz久久久久久| 97avcom| 图片区小说区亚洲| 久久精品亚洲精品| 在线免费黄色| 亚洲亚裔videos黑人hd| 婷婷国产在线| 亚洲国产高潮在线观看| www夜片内射视频日韩精品成人| 欧美体内she精视频| 无码人妻av一区二区三区波多野 | 91在线观看视频| 国产吃瓜黑料一区二区| 国产麻豆一精品一av一免费| 日韩爱爱小视频| 秋霞国产午夜精品免费视频| 91av资源网| 亚洲精品资源| 久久人人爽人人爽人人av| 国内综合精品午夜久久资源| 色香蕉在线观看| 99国产**精品****| 制服国产精品| 91亚洲一区| 一区二区免费在线视频| 欧美aaaaaaaaaaaa| 亚洲精品成人自拍| 日韩精品欧美| 亚洲一区二区三区乱码| 日韩一级毛片| 正在播放亚洲| 欧美人成网站| 欧美精品卡一卡二| 99精品福利视频| 免费在线观看亚洲视频| 羞羞答答国产精品www一本| 国产中文字幕免费观看| 久久aⅴ乱码一区二区三区| 日本wwww视频| 秋霞国产午夜精品免费视频| 久久久久久久久久久久91| 美女高潮久久久| 天天色天天综合网| 成人听书哪个软件好| 欧产日产国产精品98| 91性感美女视频| 欧美激情 一区| 亚洲人成精品久久久久久| 欧美激情图片小说| 偷拍与自拍一区| 国产黄网在线观看| 91精品国产综合久久久久| wwwav在线播放| 精品亚洲精品福利线在观看| 国产三级在线| 久久综合久中文字幕青草| 999福利在线视频| 国产精品久久久久久一区二区 | 天海翼在线视频| 亚洲无人区一区| 日韩免费av网站| 日韩一区二区电影| 日韩av高清在线| 久久久久999| 新版的欧美在线视频| 国产日韩在线看| 97se亚洲| 三区精品视频观看| 狠狠噜噜久久| 一区二区三区入口| 成人精品国产免费网站| 欧美丰满美乳xxⅹ高潮www| 亚洲免费视频中文字幕| 国产无遮挡呻吟娇喘视频| 欧美日韩的一区二区| 色婷婷av一区二区三| 中文字幕日韩免费视频| 女人天堂av在线播放| 国产精品com| 99久久香蕉| 亚洲综合欧美日韩| 国产精品五区| 亚洲成人福利视频| 国产精品看片你懂得| 日韩欧美一级视频| 欧美一区二区三区白人| 福利成人在线观看| 78m国产成人精品视频| 国产精一区二区| 日韩精品一区二区三区四区五区 | www.日韩在线观看| 中文字幕在线日韩| 波多视频一区| 国产欧美一区二区视频| 亚洲国产日韩欧美在线| 日韩欧美黄色大片| 成人av电影在线| 久久久久亚洲AV成人| 欧美亚洲尤物久久| 欧美日本韩国一区二区| 久久久久久久香蕉网| 国产精品毛片aⅴ一区二区三区| 青青草国产精品| 亚洲伦伦在线| 在线观看亚洲免费视频| 亚洲人成网站色在线观看 | 日韩免费视频线观看| 在线免费观看黄色av| 国产精品久久久久国产a级| 日韩高清在线免费观看| 国产精品videossex国产高清| 久久99国产精品尤物| 免费黄色国产视频| 欧美自拍偷拍午夜视频| 免费成人av电影| 91产国在线观看动作片喷水| 爱高潮www亚洲精品| 999久久欧美人妻一区二区| 国产一区二区三区视频在线播放| 成人小视频免费看| 欧美性受极品xxxx喷水| 国产在线视频福利| 国产成人午夜视频网址| 国产成人影院| 91视频免费版污| 欧美激情在线一区二区三区| 青青艹在线观看| 国产一区二区精品丝袜| 91福利精品在线观看| 亚洲无玛一区| 狠狠狠色丁香婷婷综合久久五月| 肉色超薄丝袜脚交69xx图片 | 午夜福利123| 亚洲免费视频中文字幕| 丰满人妻一区二区三区四区53| 久久久久久美女| 婷婷精品在线| 毛葺葺老太做受视频| 中文av一区特黄| 国产精品怡红院| 欧美高清无遮挡| 青青久久av| 无人在线观看的免费高清视频| 国产女同互慰高潮91漫画| 一区二区小视频| 欧美成人精品h版在线观看| 中文字幕一区二区三区四区久久| 久久av综合网| 久久久久久久综合日本| 进去里视频在线观看| 久久天堂电影网| 国产欧美啪啪| 男女啪啪网站视频| 日韩理论片一区二区| 老司机午夜福利视频| 国产91久久婷婷一区二区| 天天做天天爱天天综合网2021| 波多野结衣在线免费观看| 亚洲国产精品影院| 国产精品免费观看| 亚洲影院高清在线| 99riav国产精品| 亚洲国产精品一区二区久久hs| 欧美一级搡bbbb搡bbbb| 欧美激情20| 一区二区不卡在线观看| caoporen国产精品视频| 黄色网址中文字幕| 久久6免费高清热精品| 蜜臀av免费一区二区三区 | 欧洲大片精品免费永久看nba| 日本免费黄视频| 中文字幕一区二区三区色视频| 可以免费观看的毛片| 国产精品毛片a∨一区二区三区|国| 欧美阿v一级看视频| 好吊视频在线观看| 欧美xxxx老人做受| 日本免费成人| 人妻有码中文字幕|