golang中 boltdb的學習和實踐

golang boltdb的學習和實踐

1. 安裝

go get github.com/boltdb/bolt

2.創建和啓動數據庫

db, err := bolt.Open("my.db", 0600, nil)

其中open的第一個參數爲路徑,如果數據庫不存在則會創建名爲my.db的數據庫, 第二個爲文件操作,第三個參數是可選參數, 內部可以配置只讀和超時時間等,
特別需要注意的地方就是因爲boltdb是文件操作類型的數據庫,所以只能單點寫入和讀取,如果多個同時操作的話後者會被掛起直到前者關閉操作爲止, boltdb一次只允許一個讀寫事務,但一次允許多個只讀事務。所以數據具有較強的一致性。

因此單個事務和從它們創建的所有對象(例如桶、鍵)都不是線程安全的。與數據在多個概念你必須爲每一個或使用鎖機制來保證只有一個goroutine裏操作改變數據。
只讀事務和讀寫事物通常不應該在同一個goroutine裏同時打開。由於讀寫事務需要週期性地重新映射數據文件,這可能導致死鎖。

3.讀寫事務

boltdb的讀寫事務操作我們可以使用DB.Update()來完成形如:

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

在閉包fun中,在結束時返回nil來提交事務。您還可以通過返回一個錯誤在任何點回滾事務。所有數據庫操作都允許在讀寫事務中進行。
始終要關注err返回,因爲它將報告導致您的事務不能完成的所有磁盤故障。

4.批量讀寫事物

每一次新的事物都需要等待上一次事物的結束,這種開銷我們可以通過DB.Batch()批處理來完成

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

在批處理過程中如果某個事務失敗了,批處理會多次調用這個函數函數返回成功則成功。如果中途失敗了,則整個事務會回滾。

5.只讀事務

只讀事務可以使用DB.View()來完成

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

不改變數據的操作都可以通過只讀事務來完成, 您只能檢索桶、檢索值,或在只讀事務中複製數據庫。

6.啓動事務

DB.Begin()啓動函數包含在db.update和db.batch中,該函數啓動事務開始執行事務並返回結果關閉事務,這是boltdb推薦的方式,有時候你可能需要手動啓動事物你可以使用Tx.Begin()來開始,切記不要忘記關閉事務。

// 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
}

7.使用桶

桶是數據庫中鍵/值對的集合。桶中的所有鍵必須是唯一的。您可以使用DB.CreateBucket()創建一個桶:

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

你也可以是實用Tx.CreateBucketIfNotExists()來創建桶,該函數會先判斷是否已經存在該桶不存在即創建, 刪除桶可以使用Tx.DeleteBucket()來完成

8.使用k-v對

存儲鍵值對到桶裏可以使用Bucket.Put()來完成:

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

獲取鍵值Bucket.Get()

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

get()函數不返回一個錯誤,因爲它的運行是保證工作(除非有某種系統故障)。如果鍵存在,那麼它將返回它的值。如果它不存在,那麼它將返回nil。
還需要注意的是當事務打開都get返回的值時唯一有效的,如果你需要將該值用於其他事務,你可以通過copy拷貝到其他的byte slice中

9.桶的自增

利用nextsequence()功能,你可以讓boltdb生成序列作爲你鍵值對的唯一標識。見下面的示例。

func (s *Store) CreateUser(u *User) error {
    return s.db.Update(func(tx *bolt.Tx) error {
        // 創建users桶
        b := tx.Bucket([]byte("users"))

        // 生成自增序列
        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
    ...
}

10. 迭代鍵

boltdb以桶中的字節排序順序存儲鍵。這使得在這些鍵上的順序迭代非常快。要遍歷鍵,我們將使用遊標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()允許您移動到鍵列表中的特定點,並一次一個地通過操作鍵前進或後退。
光標上有以下函數:

First()  移動到第一個健.
Last()   移動到最後一個健.
Seek()   移動到特定的一個健.
Next()   移動到下一個健.
Prev()   移動到上一個健.

這些函數中的每一個都返回一個包含(key []byte, value []byte)的簽名。當你有光標迭代結束,next()將返回一個nil。在調用next()或prev()之前,你必須尋求一個位置使用first(),last(),或seek()。如果您不尋求位置,則這些函數將返回一個nil鍵。
在迭代過程中,如果鍵爲非零,但值爲0,則意味着鍵指向一個桶而不是一個值。用桶.bucket()訪問子桶。

11.前綴掃描

遍歷一個key的前綴,你可以結合seek()bytes.hasprefix()

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); bytes.HasPrefix(k, prefix); k, v = c.Next() {
        fmt.Printf("key=%s, value=%s\n", k, v)
    }

    return nil
})

12.範圍掃描

另一個常見的用例是掃描範圍,例如時間範圍。如果你使用一個合適的時間編碼,如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
})

13.循環遍歷每一個

如果你知道所在桶中擁有鍵,你也可以使用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
})

14.嵌套桶

還可以在一個鍵中存儲一個桶,以創建嵌套的桶:

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

15.數據庫備份

boltdb是一個單一的文件,所以很容易備份。你可以使用TX.writeto()函數寫一致的數據庫。如果從只讀事務調用這個函數,它將執行熱備份,而不會阻塞其他數據庫的讀寫操作。
默認情況下,它將使用一個常規文件句柄,該句柄將利用操作系統的頁面緩存。有關優化大於RAM數據集的信息,請參見Tx文檔。
一個常見的用例是在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()輔助功能。

16.統計

數據庫對運行的許多內部操作保持一個運行計數,這樣您就可以更好地瞭解發生了什麼。通過捕捉這些數據的快照,我們可以看到在這個時間範圍內執行了哪些操作。
例如,我們可以開始一個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
    }

17.只讀模式

有時創建一個共享的只讀boltdb數據庫是有用的。對此,設置options.readonly國旗打開數據庫時。只讀模式使用共享鎖允許多個進程從數據庫中讀取,但它將阻塞任何以讀寫方式打開數據庫的進程。

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

18.移動端支持(ios/android)

boltdb能夠運行在移動設備上利用的工具結合特徵GoMobile。創建一個結構體,包含您的數據庫邏輯和參考一個bolt.db與初始化contstructor需要在文件路徑,數據庫文件將存儲。使用這種方法,Android和iOS都不需要額外的權限或清理。

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()
}

數據庫邏輯應定義爲此包裝器結構中的方法。
要從本機語言初始化此結構(兩個平臺現在都將本地存儲與雲同步)。這些片段禁用數據庫文件的功能):
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;
}

19.查看工具

1.下載工具
go get github.com/boltdb/boltd
然後編譯cmd下的main文件生成可執行文件改名爲boltd
拷貝boltd到 *.db同級目錄,執行如下:

然後打開網站:

2.命令行工具
https://github.com/hasit/bolter


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章