BoltDB 入門實踐

Abser發表於2019-10-02

Abser Cat

Abser Cat

TechCats 成員/朋克程式設計師:看看,這就叫專業

Getting Started

安裝

go get go.etcd.io/bbolt/...

會 get 兩項

  1. go package -> $GOPATH
  2. bolt command line -> $GOBIN

Open Database

使用 kv 資料庫都很簡單,只需要一個檔案路徑即可搭建完成環境。

package main

import (
    "log"

    bolt "go.etcd.io/bbolt"
)

func main() {
    // Open the my.db data file in your current directory.
    // It will be created if it doesn't exist.
    db, err := bolt.Open("my.db", 0600, nil)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    ...
}

這裡到 db 不支援多連結。這是因為對於 database file 一個連結保持了一個檔案鎖 file lock

如果併發,後續連結會阻塞。

可以為單個連結新增 超時控制

db, err := bolt.Open("my.db", 0600, &bolt.Options{Timeout: 1 * time.Second})

Transaction

本文無關

與 google 的 levelDB 不同,bbolt 支援事務。 detail bolt 優缺點:detail 同時 bbolt 出自 bolt ,沒太多不同,只是 bbolt 目前還在維護。

事務

併發讀寫

同時只能有

  • 一個讀寫事務
  • 多個只讀事務

actions⚠️:在事務開始時,會保持一個資料檢視 這意味著事務處理過程中不會由於別處更改而改變

執行緒安全

單個事務和它所建立的所有物件(桶,鍵)都不是執行緒安全的。

建議加鎖 或者 每一個 goroutine 併發都開啟 一個 事務

當然,從 db 這個 bbolt 的頂級結構建立 事務 是 執行緒安全 的

死鎖

前面提到的 讀寫事務 和 只讀事務 拒絕相互依賴。當然也不能在同一個 goroutine 裡。

死鎖原因是 讀寫事務 需要週期性重新對映 data 檔案(即database)。這在開啟只讀事務時是不可行的。

讀寫事務

使用 db.Update開啟一個讀寫事務

err := db.Update(func(tx *bolt.Tx) error{
    ···
    return nil
})

上文提過,在一個事務中 ,資料檢視是一樣的。 (詳細解釋就是,在這個函式作用域中,資料對你呈現最終一致性)

返回值

bboltdb 根據你的返回值判斷事務狀態,你可以新增任意邏輯並認為出錯時返回 return err bboltdb 會回滾,如果 return nil 則提交你的事務。

建議永遠檢查 Update 的返回值,因為他會返回如 硬碟壓力 等造成事務失敗的資訊(這是在你的邏輯之外的情況)

⚠️:你自定義返回 error 的 error 資訊同樣會被傳遞出來。

只讀事務

使用 db.View 來新建一個 只讀事務

err := db.View(func(tx *bolt.Tx) error {
    ···
    return nil
})

同上,你會獲得一個一致性的資料檢視。

當然,只讀事務 只能檢索資訊,不會有任何更改。(btw,但是你可以 copy 一個 database 的副本,畢竟這隻需要讀資料)

批量讀寫事務

讀寫事務 db.Update 最後需要對 database提交更改,這會等待硬碟就緒。

每一次檔案讀寫都是和磁碟互動。這不是一個小開銷。

你可以使用 db.Batch 開啟一個 批處理事務。他會在最後批量提交(其實是多個 goroutines 開啟 db.Batch 事務時有機會合並之後一起提交)從而減小了開銷。 ⚠️:db.Batch 只對 goroutine 起效

使用 批處理事務 需要做取捨,用 冪等函式 換取 速度 ⚠️: db.Batch 在一部分事務失敗的時候會嘗試多次呼叫那些事務函式,如果不是冪等會造成不可預知的非最終一致性。

例:使用事務外的變數來使你的日誌不那麼奇怪

var id uint64
err := db.Batch(func(tx *bolt.Tx) error {
    // Find last key in bucket, decode as bigendian uint64, increment
    // by one, encode back to []byte, and add new key.
    ...
    id = newValue
    return nil
})
if err != nil {
    return ...
}
fmt.Println("Allocated ID %d", id)

手動事務

可以手動進行事務的 開啟 ,回滾,新建物件,提交等操作。因為本身 db.Update 和 db.View 就是他們的包裝 ⚠️:手動事務記得 關閉 (Close)

開啟事務使用 db.Begin(bool) 同時引數代表著是否可以寫操作。如下:

  • true - 讀寫事務
  • false - 只讀事務
// Start a writable transaction.
tx, err := db.Begin(true)
if err != nil {
    return err
}
defer tx.Rollback()

// Use the transaction...
_, err := tx.CreateBucket([]byte("MyBucket"))
if err != nil {
    return err
}

// Commit the transaction and check for error.
if err := tx.Commit(); err != nil {
    return err
}

Using Store ?

Using Buckets

桶是鍵值對的集合。在一個桶中,鍵值唯一。

建立

使用 Tx.CreateBucket() 和 Tx.CreateBucketIfNotExists() 建立一個新桶(推薦使用第二個) 接受引數是 桶的名字

刪除

使用 Tx.DeleteBucket() 根據桶的名字來刪除

例子

func main() {
    db, err := bbolt.Open("./data", 0666, nil)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    db.Update(func(tx *bbolt.Tx) error {
        b, err := tx.CreateBucketIfNotExists([]byte("MyBucket"))
        if err != nil {
            return fmt.Errorf("create bucket: %v", err)
        }

        if err = tx.DeleteBucket([]byte("MyBucket")); err != nil {
            return err
        }

        return nil
    })

}

Using key/value pairs ?

最重要的部分,就是 kv 儲存怎麼使用了,首先需要一個 桶 來儲存鍵值對。

Put

使用Bucket.Put()來儲存鍵值對,接收兩個 []byte 型別的引數

db.Update(func(tx *bolt.Tx) error {
    b := tx.Bucket([]byte("MyBucket"))
    err := b.Put([]byte("answer"), []byte("42"))
    return err
})

很明顯,上面的例子設定了 Pair: key:answer value:42

Get

使用 Bucket.Get() 來查詢鍵值。引數是一個 []byte(別忘了這次我們只是查詢,可以使用 只讀事務)

db.View(func(tx *bolt.Tx) error {
    b := tx.Bucket([]byte("MyBucket"))
    v := b.Get([]byte("answer"))
    fmt.Printf("The answer is: %s\n", v)
    return nil
})

細心會注意到,Get是不會返回 error 的,這是因為 Get() 一定能正常工作(除非系統錯誤),相應的,當返回 nil 時,查詢的鍵值對不存在。 ⚠️:注意 0 長度的值 和 不存在鍵值對 的行為是不一樣的。(一個返回是 nil, 一個不是)

func main() {
    db, err := bolt.Open("./data.db", 0666, nil)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    err = db.Update(func(tx *bolt.Tx) error {
        b, err := tx.CreateBucketIfNotExists([]byte("MyBucket"))
        if err != nil {
            return fmt.Errorf("create bucket: %v", err)
        }

        if err = b.Put([]byte("answer"), []byte("42")); err != nil {
            return err
        }

        if err = b.Put([]byte("zero"), []byte("")); err != nil {
            return err
        }

        return nil
    })

    db.View(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte("MyBucket"))
        v := b.Get([]byte("noexists"))
        fmt.Println(reflect.DeepEqual(v, nil)) // false
        fmt.Println(v == nil)                  // true

        v = b.Get([]byte("zero"))
        fmt.Println(reflect.DeepEqual(v, nil)) // false
        fmt.Println(v == nil)                  // true
        return nil
    })
}

Delete

使用 Bucket.Delete() 刪除鍵值對

db.View(func(tx *bolt.Tx) error {
    b := tx.Bucket([]byte("MyBucket"))
    fmt.Println(b.Get([]byte("answer")))

    err := b.Delete([]byte("answer"))
    if err != nil {
        return err
    }
    return nil
})

⚠️: Get() 獲取到的位元組切片值只在當前事務(當前函式作用域)有效,如果要在其他事務中使用需要使用 copy() 將其拷貝到其他的位元組切片

Tricks

桶的自增鍵

使用 NextSequence()來建立自增鍵,見下例

// CreateUser saves u to the store. The new user ID is set on u once the data is persisted.
func (s *Store) CreateUser(u *User) error {
    return s.db.Update(func(tx *bolt.Tx) error {
        // Retrieve the users bucket.
        // This should be created when the DB is first opened.
        b := tx.Bucket([]byte("users"))

        // Generate ID for the user.
        // This returns an error only if the Tx is closed or not writeable.
        // That can't happen in an Update() call so I ignore the error check.
        id, _ := b.NextSequence()
        u.ID = int(id)

        // Marshal user data into bytes.
        buf, err := json.Marshal(u)
        if err != nil {
            return err
        }

        // Persist bytes to users bucket.
        return b.Put(itob(u.ID), buf)
    })
}

// itob returns an 8-byte big endian representation of v.
func itob(v int) []byte {
    b := make([]byte, 8)
    binary.BigEndian.PutUint64(b, uint64(v))
    return b
}

type User struct {
    ID int
    ...
}

巢狀桶

很簡單的,桶可以實現巢狀儲存

func (*Bucket) CreateBucket(key []byte) (*Bucket, error)
func (*Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error)
func (*Bucket) DeleteBucket(key []byte) error

例子

假設您有一個多租戶應用程式,其中根級別儲存桶是帳戶儲存桶。該儲存桶內部有一系列帳戶的序列,這些帳戶本身就是儲存桶。在序列儲存桶(子桶)中,可能有許多相關的儲存桶(Users,Note等)。

\

// createUser creates a new user in the given account.
func createUser(accountID int, u *User) error {
    // Start the transaction.
    tx, err := db.Begin(true)
    if err != nil {
        return err
    }
    defer tx.Rollback()

    // Retrieve the root bucket for the account.
    // Assume this has already been created when the account was set up.
    root := tx.Bucket([]byte(strconv.FormatUint(accountID, 10)))

    // Setup the users bucket.
    bkt, err := root.CreateBucketIfNotExists([]byte("USERS"))
    if err != nil {
        return err
    }

    // Generate an ID for the new user.
    userID, err := bkt.NextSequence()
    if err != nil {
        return err
    }
    u.ID = userID

    // Marshal and save the encoded user.
    if buf, err := json.Marshal(u); err != nil {
        return err
    } else if err := bkt.Put([]byte(strconv.FormatUint(u.ID, 10)), buf); err != nil {
        return err
    }

    // Commit the transaction.
    if err := tx.Commit(); err != nil {
        return err
    }

    return nil
}

遍歷鍵值

在桶中,鍵值對根據 鍵 的 值是有位元組序的。 使用 Bucket.Cursor()對其進行迭代

db.View(func(tx *bolt.Tx) error {
    // Assume bucket exists and has keys
    b := tx.Bucket([]byte("MyBucket"))

    c := b.Cursor()

    for k, v := c.First(); k != nil; k, v = c.Next() {
        fmt.Printf("key=%s, value=%s\n", k, v)
    }

    return nil
})

Cursor 有 5 種方法進行迭代

  1. First() Move to the first key.

  2. Last() Move to the last key.

  3. Seek() Move to a specific key.\

  4. Next() Move to the next key.\

  5. Prev() Move to the previous key.

每一個方法都返回 (key []byte, value []byte) 兩個值 當方法所指值不存在時返回 兩個 nil 值,發生在以下情況:

  1. 迭代到最後一個鍵值對時,再一次呼叫 Cursor.Next()
  2. 當前所指為第一個鍵值對時,呼叫 Cursor.Prev()
  3. 當使用 4.Next() 和 5. Prev()方法而未使用 1.First() 2.Last() 3. Seek()指定初始位置時

⚠️特殊情況:當 key 為 非 nil 但 value 是 nil 是,說明這是巢狀桶,value 值是子桶,使用 Bucket.Bucket() 方法訪問 子桶,引數是 key 值

db.View(func(tx *bolt.Tx) error {
    c := b.Cursor()
    fmt.Println(c.First())
    k, v := c.Prev()
    fmt.Println(k == nil, v == nil) // true,true

    if k != nil && v == nil {
        subBucket := b.Bucket()
        // doanything
    }
    return nil
})

字首遍歷

通過使用 Cursor我們能夠做到一些特殊的遍歷,如:遍歷擁有特定字首的 鍵值對

db.View(func(tx *bolt.Tx) error {
    // Assume bucket exists and has keys
    c := tx.Bucket([]byte("MyBucket")).Cursor()

    prefix := []byte("1234")
    for k, v := c.Seek(prefix); k != nil && bytes.HasPrefix(k, prefix); k, v = c.Next() {
        fmt.Printf("key=%s, value=%s\n", k, v)
    }

    return nil
})

範圍遍歷

在一個範圍裡遍歷,如:使用可排序的時間編碼(RFC3339)可以遍歷特定日期範圍的資料

db.View(func(tx *bolt.Tx) error {
    // Assume our events bucket exists and has RFC3339 encoded time keys.
    c := tx.Bucket([]byte("Events")).Cursor()

    // Our time range spans the 90's decade.
    min := []byte("1990-01-01T00:00:00Z")
    max := []byte("2000-01-01T00:00:00Z")

    // Iterate over the 90's.
    for k, v := c.Seek(min); k != nil && bytes.Compare(k, max) <= 0; k, v = c.Next() {
        fmt.Printf("%s: %s\n", k, v)
    }

    return nil
})

⚠️:Golang 實現的 RFC3339Nano 是不可排序的

ForEach

在桶中有值的情況下,可以使用 ForEach()遍歷

db.View(func(tx *bolt.Tx) error {
    // Assume bucket exists and has keys
    b := tx.Bucket([]byte("MyBucket"))

    b.ForEach(func(k, v []byte) error {
        fmt.Printf("key=%s, value=%s\n", k, v)
        return nil
    })
    return nil
})

⚠️:在 ForEach()中遍歷的鍵值對需要copy()到事務外才能在事務外使用

Advance

Backup

boltdb 是一個單一的檔案,所以很容易備份。你可以使用Tx.writeto()函式寫一致的資料庫。如果從只讀事務呼叫這個函式,它將執行熱備份,而不會阻塞其他資料庫的讀寫操作。

預設情況下,它將使用一個常規檔案控制程式碼,該控制程式碼將利用作業系統的頁面快取。

有關優化大於RAM資料集的資訊,請參見[Tx](https://link.zhihu.com/?target=https%3A//godoc.org/go.etcd.io/bbolt%23Tx)文件。

一個常見的用例是在HTTP上進行備份,這樣您就可以使用像cURL這樣的工具來進行資料庫備份:

func BackupHandleFunc(w http.ResponseWriter, req *http.Request) {
    err := db.View(func(tx *bolt.Tx) error {
        w.Header().Set("Content-Type", "application/octet-stream")
        w.Header().Set("Content-Disposition", `attachment; filename="my.db"`)
        w.Header().Set("Content-Length", strconv.Itoa(int(tx.Size())))
        _, err := tx.WriteTo(w)
        return err
    })
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }
}

然後您可以使用此命令進行備份:

$ curl http://localhost/backup > my.db

或者你可以開啟你的瀏覽器以http://localhost/backup,它會自動下載。

如果你想備份到另一個檔案,你可以使用TX.copyfile()輔助功能。

Statistics

資料庫對執行的許多內部操作保持一個執行計數,這樣您就可以更好地瞭解發生了什麼。通過捕捉兩個時間點資料的快照,我們可以看到在這個時間範圍內執行了哪些操作。

例如,我們可以用一個 goroutine 裡記錄統計每一個 10 秒:

go func() {
    // Grab the initial stats.
    prev := db.Stats()
    for {
        // Wait for 10s.
        time.Sleep(10 * time.Second)
        // Grab the current stats and diff them.
        stats := db.Stats()
        diff := stats.Sub(&prev)
        // Encode stats to JSON and print to STDERR.
        json.NewEncoder(os.Stderr).Encode(diff)
        // Save stats for the next loop.
        prev = stats
    }
}()

將這些資訊通過管道輸出到監控也很有用。

Read-Only Mode

可以開啟只讀模式防止錯誤更改

db, err := bolt.Open("my.db", 0666, &bolt.Options{ReadOnly: true})
if err != nil {
    log.Fatal(err)
}

現在使用 db.Update() 等開啟讀寫事務 將會阻塞

Mobile Use

移動端支援由 gomobile 工具提供

Create a struct that will contain your database logic and a reference to a *bolt.DB with a initializing constructor that takes in a filepath where the database file will be stored. Neither Android nor iOS require extra permissions or cleanup from using this method.

func NewBoltDB(filepath string) *BoltDB {
 db, err := bolt.Open(filepath+"/demo.db", 0600, nil)
 if err != nil {
    log.Fatal(err)
 }
 return &BoltDB{db}
}
type BoltDB struct {
 db *bolt.DB
 ...
}
func (b *BoltDB) Path() string {
 return b.db.Path()
}
func (b *BoltDB) Close() {
 b.db.Close()
}

Database logic should be defined as methods on this wrapper struct. To initialize this struct from the native language (both platforms now sync their local storage to the cloud. These snippets disable that functionality for the database file):

Android

String path;
if (android.os.Build.VERSION.SDK_INT >=android.os.Build.VERSION_CODES.LOLLIPOP){
    path = getNoBackupFilesDir().getAbsolutePath();
} else{
    path = getFilesDir().getAbsolutePath();
}
Boltmobiledemo.BoltDB boltDB = Boltmobiledemo.NewBoltDB(path)

iOS

- (void)demo {
    NSString* path = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
                                                          NSUserDomainMask,
                                                          YES) objectAtIndex:0];
 GoBoltmobiledemoBoltDB * demo = GoBoltmobiledemoNewBoltDB(path);
 [self addSkipBackupAttributeToItemAtPath:demo.path];
 //Some DB Logic would go here
 [demo close];
}
- (BOOL)addSkipBackupAttributeToItemAtPath:(NSString *) filePathString
{
    NSURL* URL= [NSURL fileURLWithPath: filePathString];
    assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
    NSError *error = nil;
    BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
                                  forKey: NSURLIsExcludedFromBackupKey error: &error];
    if(!success){
        NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
    }
    return success;
}

擴充套件閱讀

更多指導

For more information on getting started with Bolt, check out the following articles:

與其他資料庫的比較

Postgres,MySQL和其他關聯式資料庫

關聯式資料庫將資料組織成行,並且只能通過使用SQL進行訪問。這種方法在儲存和查詢資料方面提供了靈活性,但是在解析和計劃SQL語句時也會產生開銷。Bolt通過位元組切片鍵訪問所有資料。這使得Bolt可以快速地通過鍵讀取和寫入資料,但是不提供將值連線在一起的內建支援。 大多數關聯式資料庫(SQLite除外)都是獨立於應用程式執行的獨立伺服器。這使您的系統具有將多個應用程式伺服器連線到單個資料庫伺服器的靈活性,但同時也增加了在網路上序列化和傳輸資料的開銷。Bolt作為應用程式中包含的庫執行,因此所有資料訪問都必須經過應用程式的過程。這使資料更接近您的應用程式,但限制了對資料的多程式訪問。

LevelDB,RocksDB

LevelDB及其派生類(RocksDB,HyperLevelDB)與Bolt類似,因為它們是捆綁到應用程式中的庫,但是它們的底層結構是日誌結構的合併樹(LSM樹)。LSM樹通過使用預寫日誌和稱為SSTables的多層排序檔案來優化隨機寫入。Bolt在內部使用B +樹,並且僅使用一個檔案。兩種方法都需要權衡。 如果您需要較高的隨機寫入吞吐量(> 10,000 w / sec),或者需要使用旋轉磁碟,那麼LevelDB可能是一個不錯的選擇。如果您的應用程式是大量讀取或進行大量範圍掃描,那麼Bolt可能是一個不錯的選擇。 另一個重要的考慮因素是LevelDB沒有事務。它支援鍵/值對的批量寫入,並且支援讀取快照,但不能使您安全地執行比較和交換操作。Bolt支援完全可序列化的ACID事務。

LMDB

Bolt最初是LMDB的埠,因此在架構上相似。兩者都使用B +樹,具有ACID語義和完全可序列化的事務,並支援使用單個寫入器和多個讀取器的無鎖MVCC。 這兩個專案有些分歧。LMDB專注於原始效能,而Bolt專注於簡單性和易用性。例如,出於效能考慮,LMDB允許執行幾種不安全的操作,例如直接寫入。Bolt選擇禁止可能使資料庫處於損壞狀態的操作。Bolt唯一的例外是DB.NoSync。 API也有一些區別。開啟LMDB時需要最大的mmap大小,mdb_env而Bolt會自動處理增量mmap的大小。LMDB使用多個標誌來過載getter和setter函式,而Bolt將這些特殊情況拆分為自己的函式。

注意事項和侷限性

選擇合適的工具來完成這項工作很重要,而Bolt也不例外。在評估和使用Bolt時,需要注意以下幾點:

  • Bolt非常適合讀取密集型工作負載。順序寫入效能也很快,但是隨機寫入可能會很慢。您可以使用DB.Batch()或新增預寫日誌來幫助緩解此問題。\

  • Bolt在內部使用B + tree,因此可以有很多隨機頁面訪問。與旋轉磁碟相比,SSD可以顯著提高效能。\

  • 嘗試避免長時間執行的讀取事務。Bolt使用寫時複製功能,因此在舊事務使用舊頁時無法回收這些舊頁。\

  • 從Bolt返回的位元組片僅在事務期間有效。一旦事務被提交或回滾,它們所指向的記憶體就可以被新頁面重用,或者可以從虛擬記憶體中取消對映,unexpected fault address訪問時會出現恐慌。\

  • Bolt在資料庫檔案上使用排他寫鎖定,因此不能被多個程式共享。\

  • 使用時要小心Bucket.FillPercent。為具有隨機插入的儲存桶設定較高的填充百分比將導致資料庫的頁面利用率非常差。\

  • 通常使用較大的水桶。較小的儲存桶一旦超過頁面大小(通常為4KB),就會導致頁面利用率下降。\

  • 批量載入大量隨機寫入新的儲存桶可能很慢,因為在提交事務之前頁面不會拆分。建議不要在單個事務中將100,000個以上的鍵/值對隨機插入到一個新的儲存桶中。\

  • Bolt使用記憶體對映檔案,因此底層作業系統可以處理資料的快取。通常,作業系統將在記憶體中快取儘可能多的檔案,並根據需要將記憶體釋放給其他程式。這意味著在使用大型資料庫時,Bolt可能會顯示很高的記憶體使用率。但是,這是預料之中的,作業系統將根據需要釋放記憶體。只要Bolt的記憶體對映適合程式虛擬地址空間,它就可以處理比可用物理RAM大得多的資料庫。在32位系統上可能會出現問題。\

  • Bolt資料庫中的資料結構是記憶體對映的,因此資料檔案將是特定於位元組序的。這意味著您無法將Bolt檔案從小位元組序計算機複製到大位元組序計算機並使其正常工作。對於大多數使用者而言,這不是問題,因為大多數現代CPU的位元組序都很少。\

  • 由於頁面在磁碟上的佈局方式,Bolt無法截斷資料檔案並將可用頁面返回到磁碟。取而代之的是,Bolt會在其資料檔案中維護未使用頁面的空閒列表。這些空閒頁面可以被以後的事務重用。由於資料庫通常會增長,因此這在許多用例中效果很好。但是,請務必注意,刪除大塊資料將不允許您回收磁碟上的該空間。 有關頁面分配的更多資訊,請參見此註釋。\

閱讀資料

對於嵌入式,可序列化的事務性鍵/值資料庫,Bolt是一個相對較小的程式碼庫(<5KLOC),因此對於那些對資料庫的工作方式感興趣的人來說,Bolt可能是一個很好的起點。

最佳起點是Bolt的主要切入點:

  • Open()-初始化對資料庫的引用。它負責建立資料庫(如果不存在),獲得檔案的排他鎖,讀取元頁面以及對檔案進行記憶體對映。\

  • DB.Begin()-根據writable引數的值啟動只讀或讀寫事務。這需要短暫獲得“元”鎖以跟蹤未結交易。一次只能存在一個讀寫事務,因此在讀寫事務期間將獲得“ rwlock”。\

  • Bucket.Put()-將鍵/值對寫入儲存桶。驗證引數之後,使用游標將B +樹遍歷到將鍵和值寫入的頁面和位置。找到位置後,儲存桶會將基礎頁面和頁面的父頁面具體化為“節點”到記憶體中。這些節點是在讀寫事務期間發生突變的地方。提交期間,這些更改將重新整理到磁碟。\

  • Bucket.Get()-從儲存桶中檢索鍵/值對。這使用游標移動到鍵/值對的頁面和位置。在只讀事務期間,鍵和值資料將作為對基礎mmap檔案的直接引用返回,因此沒有分配開銷。對於讀寫事務,此資料可以引用mmap檔案或記憶體節點值之一。\

  • Cursor-該物件僅用於遍歷磁碟頁或記憶體節點的B +樹。它可以查詢特定的鍵,移至第一個或最後一個值,也可以向前或向後移動。游標對終端使用者透明地處理B +樹的上下移動。\

  • Tx.Commit()-將記憶體中的髒節點和可用頁面列表轉換為要寫入磁碟的頁面。然後寫入磁碟分為兩個階段。首先,髒頁被寫入磁碟並fsync()發生。其次,寫入具有遞增的事務ID的新元頁面,然後fsync()發生另一個頁面 。這兩個階段的寫入操作確保崩潰時會忽略部分寫入的資料頁,因為指向它們的元頁不會被寫入。部分寫入的元頁面是無效的,因為它們是用校驗和寫入的。\

如果您還有其他可能對他人有用的註釋,請通過請求請求將其提交。

其他使用螺栓的專案

以下是使用Bolt的公共開源專案的列表:

  • Algernon - A HTTP/2 web server with built-in support for Lua. Uses BoltDB as the default database backend.
  • Bazil - A file system that lets your data reside where it is most convenient for it to reside.
  • bolter - Command-line app for viewing BoltDB file in your terminal.
  • boltcli - the redis-cli for boltdb with Lua script support.
  • BoltHold - An embeddable NoSQL store for Go types built on BoltDB
  • BoltStore - Session store using Bolt.
  • Boltdb Boilerplate - Boilerplate wrapper around bolt aiming to make simple calls one-liners.
  • BoltDbWeb - A web based GUI for BoltDB files.
  • bleve - A pure Go search engine similar to ElasticSearch that uses Bolt as the default storage backend.
  • btcwallet - A bitcoin wallet.
  • buckets - a bolt wrapper streamlining simple tx and key scans.
  • cayley - Cayley is an open-source graph database using Bolt as optional backend.
  • ChainStore - Simple key-value interface to a variety of storage engines organized as a chain of operations.
  • Consul - Consul is service discovery and configuration made easy. Distributed, highly available, and datacenter-aware.
  • DVID - Added Bolt as optional storage engine and testing it against Basho-tuned leveldb.
  • dcrwallet - A wallet for the Decred cryptocurrency.
  • drive - drive is an unofficial Google Drive command line client for *NIX operating systems.
  • event-shuttle - A Unix system service to collect and reliably deliver messages to Kafka.
  • Freehold - An open, secure, and lightweight platform for your files and data.
  • Go Report Card - Go code quality report cards as a (free and open source) service.
  • GoWebApp - A basic MVC web application in Go using BoltDB.
  • GoShort - GoShort is a URL shortener written in Golang and BoltDB for persistent key/value storage and for routing it's using high performent HTTPRouter.
  • gopherpit - A web service to manage Go remote import paths with custom domains
  • Gitchain - Decentralized, peer-to-peer Git repositories aka "Git meets Bitcoin".
  • InfluxDB - Scalable datastore for metrics, events, and real-time analytics.
  • ipLocator - A fast ip-geo-location-server using bolt with bloom filters.
  • ipxed - Web interface and api for ipxed.
  • Ironsmith - A simple, script-driven continuous integration (build - > test -> release) tool, with no external dependencies
  • Kala - Kala is a modern job scheduler optimized to run on a single node. It is persistent, JSON over HTTP API, ISO 8601 duration notation, and dependent jobs.
  • Key Value Access Langusge (KVAL) - A proposed grammar for key-value datastores offering a bbolt binding.
  • LedisDB - A high performance NoSQL, using Bolt as optional storage.
  • lru - Easy to use Bolt-backed Least-Recently-Used (LRU) read-through cache with chainable remote stores.
  • mbuckets - A Bolt wrapper that allows easy operations on multi level (nested) buckets.
  • MetricBase - Single-binary version of Graphite.
  • MuLiFS - Music Library Filesystem creates a filesystem to organise your music files.
  • NATS - NATS Streaming uses bbolt for message and metadata storage.
  • Operation Go: A Routine Mission - An online programming game for Golang using Bolt for user accounts and a leaderboard.
  • photosite/session - Sessions for a photo viewing site.
  • Prometheus Annotation Server - Annotation server for PromDash & Prometheus service monitoring system.
  • reef-pi - reef-pi is an award winning, modular, DIY reef tank controller using easy to learn electronics based on a Raspberry Pi.
  • Request Baskets - A web service to collect arbitrary HTTP requests and inspect them via REST API or simple web UI, similar to RequestBin service
  • Seaweed File System - Highly scalable distributed key~file system with O(1) disk read.
  • stow - a persistence manager for objects backed by boltdb.
  • Storm - Simple and powerful ORM for BoltDB.
  • SimpleBolt - A simple way to use BoltDB. Deals mainly with strings.
  • Skybox Analytics - A standalone funnel analysis tool for web analytics.
  • Scuttlebutt - Uses Bolt to store and process all Twitter mentions of GitHub projects.
  • tentacool - REST api server to manage system stuff (IP, DNS, Gateway...) on a linux server.
  • torrent - Full-featured BitTorrent client package and utilities in Go. BoltDB is a storage backend in development.
  • Wiki - A tiny wiki using Goji, BoltDB and Blackfriday.

If you are using Bolt in a project please send a pull request to add it to the list.

@free to work for free

相關文章