網上有很多關于pos機密鑰儲存失敗,在 Golang 中編寫基于磁盤的鍵值存儲的知識,也有很多人為大家解答關于pos機密鑰儲存失敗的問題,今天pos機之家(www.tonybus.com)為大家整理了關于這方面的知識,讓我們一起來看下吧!
本文目錄一覽:
pos機密鑰儲存失敗
我一直在考慮閱讀一篇計算機科學論文,并基于它實施一個項目。分布式系統、網絡和數據庫是讓我著迷的一些東西。但是,我一直在尋求實施一個更平易近人的項目,以避免最初被淹沒。我偶然通過Avinash的項目:CaskDB看到了Bitcask論文。
在快速閱讀了這篇相當短的論文后,我決定編寫一個相同的 Golang 實現,因為它看起來像一個令人興奮的項目。如果您有興趣查看完整的項目,請查看BarrelDB。
Bitcask 是基于磁盤的鍵值存儲引擎,專為快速讀寫操作而設計。它主要由Riak(分布式數據庫)作為存儲引擎之一在生產中使用。引擎蓋下的Bitc桶具有簡單而巧妙的設計。它以僅追加模式寫入文件。這意味著僅通過附加到文件末尾來執行寫入,從而避免了執行任何隨機磁盤 I/O 查找的需要。
讓我們看一下Bitcask的各個組件:
記錄的格式CRC:存儲值的校驗和,保證數據一致性時間戳:UNIX 格式的時間戳,存儲為 int32。到期:如果記錄定義了到期,則 UNIX 格式的時間戳將存儲為 int32。密鑰大?。好荑€的大小(以字節為單位)值大?。褐档拇笮。ㄒ宰止潪閱挝唬╄€匙價值與鍵/值一起存儲的附加元數據用固定寬度的標頭表示。每個字段表示為 ,因此標頭的總大小為 4*5 = 20 字節。下面是對此記錄進行編碼和解碼的代碼:int32
type Record struct { Header Header Key string Value []byte}// Header represents the fixed width="360px",height="auto" />
Decode takes a record object decodes the binary value the buffer.func (h *Header) decode(record []byte) error { return binary.Read(bytes.NewReader(record), binary.LittleEndian, h)}記錄在存儲在磁盤上之前以二進制格式編碼。
數據文件“數據文件”(用于磁盤上的數據庫文件的術語)是所有寫入操作的僅追加記錄。Bitcask 的一個實例可以有多個數據文件。但是,只有一個“活動”數據文件。在 BarrelDB 中,goroutine 定期在后臺運行,以檢查活動數據庫文件的大小是否已超過閾值,然后旋轉活動文件。它將此數據庫文件追加到“過時”數據文件列表中。所有新的寫入只發生在“活動”數據文件上,過時的文件作為“壓縮”過程的一部分進行合并(稍后將在帖子中描述)。
以下是 ais 的表示方式:dataFile
type DataFile struct { sync.RWMutex writer *os.File reader *os.File id int offset int}
它包含用于寫入和讀取文件的不同處理程序。我們有 2 個文件處理程序而不是重用同一個的原因是,它們僅在“僅追加”模式下打開。此外,由于活動文件可以旋轉,因此可以設置編寫器,確保不會在該文件上發生新的寫入。writernil
writer, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return nil, fmt.Errorf("error opening file for writing db: %w", err) } // Create a reader for reading the db file. reader, err := os.Open(path) if err != nil { return nil, fmt.Errorf("error opening file for reading db: %w", err) }鍵目錄
除了將文件存儲在磁盤上之外,Bitcask 還存儲其他元數據,這些元數據定義了如何檢索記錄。此哈希表是具有此元數據的鍵映射,稱為。這里要注意的重要一點是,這張地圖中從未存儲過。這使得Bitcask可以處理比RAM可以容納的更重要的數據集。KeyDirvalue
// KeyDir represents an in-memory hash for faster lookups of the key.// Once the key is found in the map, the additional metadata, like the offset record// and the file ID is used to extract the underlying record from the disk.// Advantage is that this approach only requires a single disk seek of the db file// since the position offset (in bytes) is already stored.type KeyDir map[string]Meta// Meta represents some additional properties for the given key.// The actual value of the key is not stored in the in-memory hashtable.type Meta struct { Timestamp int RecordSize int RecordPos int FileID int}
在這里,告訴記錄在整個文件中的位置偏移量(以字節為單位)。由于記錄的位置與密鑰一起存儲在內存中,因此檢索密鑰不需要超過單個磁盤查找。Bitcask 即使數據庫中有許多密鑰,也能實現非常低的延遲。文件系統預讀緩存還有助于提高性能,并且是免費的 - 無需設計單獨的緩存機制。RecordPos
壓 實正如我們之前看到的,數據文件只是一個僅追加的寫入序列。對鍵的任何修改都只是附加到數據文件的新記錄。KeyDir 使用包含記錄新位置的新元數據覆蓋鍵的條目。因此,所有讀取將自動返回更新的值。
通過為密鑰寫入“邏輯刪除”記錄,以類似的方式處理刪除。當用戶在刪除密鑰后請求密鑰時,BarrelDB 可以檢查該值是否等于邏輯刪除值并返回相應的錯誤。
正如您所猜到的,如果我們不執行任何垃圾清理,我們的數據庫將無限增長。需要修剪數據文件以刪除過期/刪除的記錄并將所有過時的文件合并到單個活動文件中 - 以控制打開的文件數量。所有這些過程統稱為“壓縮”。
讓我們來看看這些壓縮例程中的每一個是如何在后臺工作的:
合并合并過程循環訪問 KeyDir 中的所有鍵并獲取其值。該值也可能來自過時的文件。更新新的鍵/值后,它會將它們寫入新的活動文件。關閉所有舊文件處理程序,并從磁盤中刪除過時的文件。KeyDir 的更新方式類似,因為新記錄位于不同的位置/文件中。
提示文件Bitcask 論文描述了一種創建最初加載到數據庫中的“提示”文件以加快啟動時間的方法。此文件對于在冷啟動后引導 KeyDir 至關重要。這樣可以避免遍歷所有數據文件并按順序讀取其值。在 BarrelDB 中,編碼用于將地圖轉儲為轉儲。gobKeyDirgob
// generateHints encodes the contents of the in-memory hashtable// as `gob` and writes the data to a hints file.func (b *Barrel) generateHints() error { path := filepath.Join(b.opts.dir, HINTS_FILE) if err := b.keydir.Encode(path); err != nil { return err } return nil}
在啟動期間,BarrelDB 會檢查文件是否存在,解碼此 gob 轉儲,然后將數據加載到其中。.hintsKeyDir
刪除過期的密鑰goroutine以可配置的間隔運行,以檢查密鑰的值是否已過期。如果有,它將從 KeyDir 中刪除該條目。在以下合并過程中,由于此條目不會出現在 KeyDir 中,因此在創建新數據文件時會自動刪除該條目。
要檢查密鑰是否已過期,只需進行簡單的檢查,例如以 UNIX 紀元格式比較它們的時間戳,就足夠了:time.Now().Unix() > int64(r.Header.Expiry)
瑞迪斯服務器除了使用 BarrelDB 作為 Go 庫之外,我還實現了一個與 Redis 兼容的服務器。我發現tidwall/redcon是一個易于使用的庫,可以為 Go 應用程序創建一個與 Redis 兼容的服務器。我要做的就是包裝 BarrelDB API 方法并定義 / 的處理程序。SETGET
我能夠使用并連接到 BarrelDB 服務器:redis-cli
127.0.0.1:6379> set hello worldOK127.0.0.1:6379> get hello"world"基準
可以檢查存儲庫中的實際基準。但是,我想指出一些結果的推論。redis-benchmark
首先,讓我們使用 50 個并行客戶端向服務器發送 100000 個請求。此命令為每個操作創建一個唯一鍵。SET
redis-benchmark -p 6379 -c 50 -t set -n 100000 -r 100000000Summary: throughput summary: 145985.41 requests per second latency summary (msec): avg min p50 p95 p99 max 0.179 0.016 0.183 0.207 0.399 1.727
因此,對于基于磁盤的 KV,每秒 140k 個請求一點也不差。但這里要注意的令人興奮的事情是,即使您通過增加客戶端來增加負載,性能也是可預測的:
redis-benchmark -p 6379 -c 200 -t set -n 100000 -r 100000000Summary: throughput summary: 140845.08 requests per second latency summary (msec): avg min p50 p95 p99 max 0.718 0.224 0.711 0.927 1.183 5.775
如果我們也增加請求數量(5 倍),吞吐量看起來幾乎相同:
redis-benchmark -p 6379 -c 200 -t set -n 500000 -r 100000000Summary: throughput summary: 138350.86 requests per second latency summary (msec): avg min p50 p95 p99 max 0.748 0.056 0.711 0.879 1.135 63.135
這種魔力完全是因為Bitcask使用日志結構化哈希表(只是用于寫入數據的僅附加記錄)的方式。即使有大量記錄,它所要做的就是寫入文件的末尾,從而避免任何昂貴的 I/O 操作。
總結總的來說,我對實施感到滿意,因為我涵蓋了論文中描述的所有內容。這個項目對我來說有很好的學習成果。我花了很多時間想出一個設計,用于構建不同的組件及其API方法,并在壓縮過程中處理所有邊緣場景。雖然,完全歸功于Bitcask,因為它保持了如此優雅和簡約的設計,但在基準測試中取得了一些重要的數字。這也提醒我們,簡單不一定意味著不那么強大。BarrelDB
我期待通過添加對通過 Raft 連接的多個 BarrelDB 節點的支持來實現分布式 KV 存儲?,F在,去享受一些茶并將這個項目發布到 WWW :)
翻譯原文: https://mrkaran.dev/posts/barreldb/?hmsr=toutiao.io&utm_campaign=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io
以上就是關于pos機密鑰儲存失敗,在 Golang 中編寫基于磁盤的鍵值存儲的知識,后面我們會繼續為大家整理關于pos機密鑰儲存失敗的知識,希望能夠幫助到大家!
