badger (一個高效能的LSM K/V store)使用指南

Gundy發表於2019-04-08

badfer是一個純Go實現的快速的嵌入式K/V資料庫,針對LSM tree做了優化。

安裝

$ go get github.com/dgraph-io/badger/...

資料庫

開啟一個資料庫

opts := badger.DefaultOptions
opts.Dir = "/tmp/badger"
opts.ValueDir = "/tmp/badger"
db, err := badger.Open(opts)
if err != nil {
	log.Fatal(err)
}
defer db.Close()
複製程式碼

儲存

儲存kv

使用 Txn.Set()方法

err := db.Update(func(txn *badger.Txn) error {
  err := txn.Set([]byte("answer"), []byte("42"))
  return err
})
複製程式碼

批量設定

wb := db.NewWriteBatch()
defer wb.Cancel()

for i := 0; i < N; i++ {
  err := wb.Set(key(i), value(i), 0) // Will create txns as needed.
  handle(err)
}
handle(wb.Flush()) // Wait for all txns to finish.
複製程式碼

WriteBatch不允許任何讀取。對於讀-修改-寫,應該使用事務API。

設定生存時間 TTL

Badger允許在鍵上設定一個可選的生存時間(TTL)值。一旦TTL結束,KEY將不再是可檢索的,並且將進行垃圾收集。TTL可以使用Txn.SetWithTTL() 設定為一個time.Duration的值

設定後設資料

Txn.SetWithMeta() 設定使用者後設資料

使用 Txn.SetEntry() 可以一次性設定key, value, user metatadata and TTL

遍歷keys

要遍歷鍵,我們可以使用迭代器,可以使用 Txn.NewIterator()`方法獲得迭代器。迭代按位元組字典排序順序進行。

err := db.View(func(txn *badger.Txn) error {
  opts := badger.DefaultIteratorOptions
  opts.PrefetchSize = 10
  it := txn.NewIterator(opts)
  defer it.Close()
  for it.Rewind(); it.Valid(); it.Next() {
    item := it.Item()
    k := item.Key()
    err := item.Value(func(v []byte) error {
      fmt.Printf("key=%s, value=%s\n", k, v)
      return nil
    })
    if err != nil {
      return err
    }
  }
  return nil
})
複製程式碼

字首掃描

要遍歷鍵字首,可以將Seek()和ValidForPrefix()組合使用:

db.View(func(txn *badger.Txn) error {
  it := txn.NewIterator(badger.DefaultIteratorOptions)
  defer it.Close()
  prefix := []byte("1234")
  for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
    item := it.Item()
    k := item.Key()
    err := item.Value(func(v []byte) error {
      fmt.Printf("key=%s, value=%s\n", k, v)
      return nil
    })
    if err != nil {
      return err
    }
  }
  return nil
})

複製程式碼

鍵的遍歷

Badger支援一種獨特的迭代模式,稱為只有鍵的迭代。它比常規迭代快幾個數量級,因為它只涉及對lsm樹的訪問,而lsm樹通常完全駐留在RAM中。要啟用只有鍵的迭代,您需要設定IteratorOptions。PrefetchValues欄位為false。這還可以用於在迭代期間對選定的鍵執行稀疏讀取,只在需要時呼叫item.Value()。

err := db.View(func(txn *badger.Txn) error {
  opts := badger.DefaultIteratorOptions
  opts.PrefetchValues = false
  it := txn.NewIterator(opts)
  defer it.Close()
  for it.Rewind(); it.Valid(); it.Next() {
    item := it.Item()
    k := item.Key()
    fmt.Printf("key=%s\n", k)
  }
  return nil
})
複製程式碼

資料流

Badger提供了一個流框架,它可以併發地遍歷資料庫的全部或部分,將資料轉換為自定義鍵值,並連續地將資料流輸出,以便通過網路傳送、寫入磁碟,甚至寫入Badger。這是比使用單個迭代器更快的遍歷Badger的方法。Stream在管理模式和正常模式下都支援Badger。

stream := db.NewStream()
// db.NewStreamAt(readTs) for managed mode.

// -- Optional settings
stream.NumGo = 16                     // Set number of goroutines to use for iteration.
stream.Prefix = []byte("some-prefix") // Leave nil for iteration over the whole DB.
stream.LogPrefix = "Badger.Streaming" // For identifying stream logs. Outputs to Logger.

// ChooseKey is called concurrently for every key. If left nil, assumes true by default.
stream.ChooseKey = func(item *badger.Item) bool {
  return bytes.HasSuffix(item.Key(), []byte("er"))
}

// KeyToList is called concurrently for chosen keys. This can be used to convert
// Badger data into custom key-values. If nil, uses stream.ToList, a default
// implementation, which picks all valid key-values.
stream.KeyToList = nil

// -- End of optional settings.

// Send is called serially, while Stream.Orchestrate is running.
stream.Send = func(list *pb.KVList) error {
  return proto.MarshalText(w, list) // Write to w.
}

// Run the stream
if err := stream.Orchestrate(context.Background()); err != nil {
  return err
}
// Done.

複製程式碼

刪除一個key

使用Txn.Delete() 方法刪除一個key

獲取key value

通過 txn.Get獲取value

err := db.View(func(txn *badger.Txn) error {
  item, err := txn.Get([]byte("answer"))
  handle(err)

  var valNot, valCopy []byte
  err := item.Value(func(val []byte) error {
    // This func with val would only be called if item.Value encounters no error.

    // Accessing val here is valid.
    fmt.Printf("The answer is: %s\n", val)

    // Copying or parsing val is valid.
    valCopy = append([]byte{}, val...)

    // Assigning val slice to another variable is NOT OK.
    valNot = val // Do not do this.
    return nil
  })
  handle(err)

  // DO NOT access val here. It is the most common cause of bugs.
  fmt.Printf("NEVER do this. %s\n", valNot)

  // You must copy it to use it outside item.Value(...).
  fmt.Printf("The answer is: %s\n", valCopy)

  // Alternatively, you could also use item.ValueCopy().
  valCopy, err = item.ValueCopy(nil)
  handle(err)
  fmt.Printf("The answer is: %s\n", valCopy)

  return nil
})
複製程式碼

如果不存在 Txn.Get() 將會返回一個 ErrKeyNotFound 錯誤

請注意,Get()返回的值只在事務開啟時有效。如果需要在事務外部使用值,則必須使用copy()將其複製到另一個位元組片。

事務

只讀事務

只讀事務使用 DB.View()方法

err := db.View(func(txn *badger.Txn) error {
  // Your code here…
  return nil
})
複製程式碼

讀寫事務鎖

讀寫事務可以使用 DB.Update()方法

err := db.Update(func(txn *badger.Txn) error {
  // Your code here…
  return nil
})
複製程式碼

手動管理事務

直接使用DB.NewTransaction()函式,手動建立和提交事務。它接受一個布林引數來指定是否需要讀寫事務。對於讀寫事務,需要呼叫Txn.Commit()來確保事務已提交。對於只讀事務,呼叫 txn.reject()就可以了。commit()也在內部呼叫 txn .reject()來清除事務,因此只需呼叫Txn.Commit()就足以執行讀寫事務。

但是,如果您的程式碼由於某種原因(出錯)沒有呼叫Txn.Commit()。就需要在defer中呼叫 txn . reject()

// Start a writable transaction.
txn := db.NewTransaction(true)
defer txn.Discard()

// Use the transaction...
err := txn.Set([]byte("answer"), []byte("42"))
if err != nil {
    return err
}

// Commit the transaction and check for error.
if err := txn.Commit(); err != nil {
    return err
}
複製程式碼

相關文章