時間:2023-08-26|瀏覽:290
1. 寫入數據的提交 一個快照塊(SnapshotBlock)和關聯的賬戶塊(AccountBlock)列表對相關的數據結構進行序列化,并寫入存儲。
2. ringBuffer 序列化后的數據不會直接寫入文件,而是先寫入一個名為ringBuffer的內存緩沖區。這個緩沖區由一系列連續的10M子數據段(Segment)構成,每個數據段都有一個遞增的序列號fileId。對于每個塊(Block),首先會寫入該塊序列化后的字節長度,然后再是實際的數據內容。因為塊和段不是一一對應的,所以會存在一個塊的數據需要跨越多個段的情況。為了能夠定位和存儲一個塊的數據段,可以通過[fileId, offset]二元組來確定塊的起始位置,其中offset表示該塊在段中的偏移量。
為了提高效率并減少開辟和回收緩存區的開銷,這里將這些連續段拼接成一個虛擬環,稱為ringBuffer。新數據追加到環的末尾(Tail),舊數據從隊頭(Head)彈出。追加和彈出操作通過移動段下標的方式完成。隊頭和隊尾之間是待寫入文件的已使用段,其他部分是可以被覆蓋的空閑段。
已使用段的數據既可以被用作寫緩存,又可以做讀緩存。空閑段的數據如果之前寫入過有效數據,也可以被當做讀緩存。因此,整個ringBuffer都可以作為讀緩存。整個ringBuffer就相當于一個最近寫入數據的讀寫緩沖區。
當短期寫入ringBuffer的數據超過flush速度時,會導致數據超過ringBuffer現有容量。此時,ringBuffer會自動擴容。待數據逐步寫入文件列表后,ringBuffer會自動收縮到初始容量。
3. 文件列表、隨機讀取和賬本同步 blockDB使用固定大小的小文件列表來存儲塊數據。每個文件對應ringBuffer中的一個段,文件名即為ringBuffer中的fileId。通過定期的flush操作,ringBuffer中的已使用段會依次寫入文件系統,已flush的段會變為空閑段。
進行隨機讀取操作時,首先通過blockDB索引獲取[fileId, offset]二元組,然后根據fileId在ringBuffer中定位段。如果定位失敗,則通過fileId打開對應的小文件,并進行seek到offset位置。從該位置的開頭讀取數據大小后,就可以連續讀取該塊對應的數據塊。可能需要跨越多個文件讀取fileId+1的下一個小文件。由于采用小文件存儲,相較于大文件,seek操作速度更快,也對系統的頁緩存更友好。
小文件列表在順序寫入和批量順序讀取上具有良好的性能,這個特性在"賬本同步"場景中非常有用。
4. 數據回滾 blockDB僅支持從最新狀態回滾數據到某個歷史狀態,不允許刪除中間的歷史數據,即數據是連續的片段,不允許存在數據空洞。
數據回滾分為預刪除和刪除兩個階段。在預刪除階段,先在ringBuffer中刪除相應的數據,然后標記要回滾到的目標位置。標記完畢后,這段數據變得不可讀,但并未真正刪除。下次進行"異步批量Flush"操作時,會進入刪除階段,此時會真正刪除文件列表中的數據。
5. 數據壓縮 目前使用snappy算法對每個塊進行數據壓縮。