BoltDB 使用入门实践

【存储】etcd的存储是如何实现的(3)-blotdb etcd的底层kv存储 boltdb 阅读详情

BoltDB 使用入门实践

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.

Go-boltBrowser是一个基于Web的boltDB浏览器 boltBrowser是一个基于Web的boltDB浏览器 立即下载

相关推荐

用PyTorch实战清华SSVEP数据集:手把手教你搭建第一个脑机接口分类模型(附完整代码)

本文详细介绍了如何使用PyTorch实战清华大学SSVEP数据集,从数据预处理到CNN模型构建的全流程。通过手把手教学,帮助读者搭建第一个脑机接口分类模型,特别针对数据维度转换等关键难点提供可视化解析,并附完整代码实现。

weixin_42566121的博客 353

什么是 bbolt?

bbolt 是一个用 Go 语言实现的嵌入式键值存储数据库,是对 BoltDB 的一个 fork。BoltDB 是一个高性能、简单易用的键值存储库,但其作者在 2017 年停止维护。bbolt 由 etcd 团队接手维护,修复了 BoltDB 的一些 bug,增加了性能优化和新功能,同时保持与 BoltDB API 的向后兼容。纯 Go 实现:无需外部依赖,易于集成。嵌入式:直接嵌入到 Go 应用程序中,数据存储在单一文件中。ACID 事务:支持完全可序列化的事务,保证数据一致性。B+树存储。

小诸葛的博客 1169

PyTorch多机训练Loss不一致问题排查指南:基于算子级一致性验证

比较二次训练过程中所有算子的误差,定位存在一致性问题的pytorch算子

Hi20240217的博客 1159

windows 映射文件会释放内存吗_keyvalue数据库bbolt深入分析:文件映射与神奇的unsafe包...

欢迎点击上方蓝色"零君聊软件"关注零君。上一篇文章主要讲述了bbolt如何通过天衣无缝的巧妙设计来保证了事务的原子性。这篇文章主要是讲述bbolt如何通过文件映射以及golang神奇的unsafe包来读取、解析数据库文件中的数据。本文是此系列的第五篇文章。文件映射考虑到有的同学可能不知道文件映射是什么东西,这里就先简单的介绍一下。文件映射是将文件中部分或全部数据直接映射到内存中的一段连续...

weixin_39653764的博客 310

DVWA靶场实战:从搭建到渗透测试的完整指南

本文详细介绍了DVWA靶场的搭建与渗透测试全流程,涵盖LAMP环境搭建、Docker部署及集成环境安装三种方式。通过SQL注入、文件上传漏洞等实战案例,帮助读者掌握Web安全测试技巧,并提供防御方案与进阶调优建议,是网络安全学习的实用指南。

weixin_28312391的博客 490

财务惊呼“不可能”!Python 自动识别 PDF 发票,数据秒级录入系统

电子发票(原生 PDF):这种最爽,文字是可以选中的。我们用 PyPDF2 直接提取文本,速度快,准确率 100%。 纸质扫描/拍照发票:这种是图片。必须上 OCR(光学字符识别)。我们要用百度开源的 PaddleOCR,这是目前中文识别效果最好的开源库之一,吊打早已过气的 Tesseract。

BUG猿的博客 1024

探索高效数据存储:bbolt——轻量级的Go语言数据库引擎

探索高效数据存储:bbolt——轻量级的Go语言数据库引擎 项目地址:https://gitcode.com/gh_mirrors/bb/bbolt 在开发中寻找一个简单、可靠且高效的本地数据存储解决方案是至关重要的。今天,我们来深入了解一下bbolt,一个由etcd团队维护的Go语言实现的键值存储系统。bbolt以其易于使用、稳定性高和性能卓越的特点,已成为许多开发者的选择。 项目简介 bbol...

gitblog_00067的博客 1010

【语音识别】基于MFCC特征实现说话人语音识别matlab源码

1 模型 采用能够反映人对语音的感知特性的Mel频率倒谱系数(MFCC)作为特征参数,以及为避免时间规整问题采用矢量量化技术开发的说话人识别系统.MFCC主要的是模拟人耳的听觉过程,相对于其它参数它对语音波形的变化不敏感,更加稳定,系统取得很好的识别结果,实验表明系统训练和识别的计算量和存储量都比较低. 2 部分代码 functionvarargout=Main(varargin) % MAIN M-file for Main.fig % MAIN, by itself...

qq_59747472的博客 850

golang中 boltdb的学习和实践

golang boltdb的学习和实践1. 安装go get github.com/boltdb/bolt2.创建和启动数据库db,err:=bolt.Open("my.db",0600,nil)其中open的第一个参数为路径,如果数据库不存在则会创建名为my.db的数据库, 第二个为文件操作,第三个参数是可选参数, 内部可以配置只读和超时时间等,特别需要注意的地方...

weixin_33795093的博客 935

2021年03月 C/C++(五级)真题解析#中国电子学会#全国青少年软件编程等级考试

给定一个十进制正整数n(0 < n < 1000000000),每个数位上数字均不为0。n的位数为m。现在从m位中删除k位(0

人生就是一个不断学习的过程 1012

zynq的vitis工程烧录到Winbond(华邦)型号W25Q256JVEQ的QSPI FLAHS

vitis2023.1直接支持W25Q256JVEQ。

FPGA艺术家的博客 679

boltdb 学习和实践

golang boltdb的学习和实践 1. 安装 go get github.com/boltdb/bolt 2.创建和启动数据库 db, err := bolt.Open("my.db", 0600, nil) 其中open的第一个参数为路径,如果数据库不存在则会创建名为my.db的数据库, 第二个为文件操作,第三个参数是可选参数, ...

weixin_33966365的博客 714

适航规章、标准,书籍资料下载汇总(增加DO-297 IMA)

从事IMA平台适航工作的朋友看过来,DO-297英文版PDF可以下载了!更新日志:2024-9-28:初版。2024-10-21:增加DO-330英文版PDF下载,后台回复关键词”330“获取下载链接。2024-10-30:增加DO-160G中文版(非机翻)和英文版PDF下载,后台回复关键词”160“获取下载链接。2024-11-10:增加DO-297英文版PDF下载,后台回复关键词”297“获取下载链接。以下是一些收集到的适航相关的资料,分享给大家。

qq_41854911的博客 1579

从卡顿到丝滑:深入解析etcd存储引擎BoltDB的优化实践

etcd作为分布式系统中最关键的数据存储解决方案,其性能优化一直是开发者关注的焦点。本文将深入探讨etcd底层存储引擎BoltDB的工作原理与优化实践,帮助你解决分布式系统中的存储性能瓶颈问题。 ## etcd存储架构全景解析 etcd的高性能离不开其精心设计的存储架构,BoltDB作为核心存储引擎扮演着至关重要的角色。从整体架构来看,etcd采用分层设计,将数据处理与持久化存储分离,形成高效

gitblog_00020的博客 536

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

两个表比对使用update_BoltDB 使用入门实践

Getting Started安装go get go.etcd.io/bbolt/... 会 get 两项go package -> $GOPATHbolt command line -> $GOBINOpen Database使用 kv 数据库都很简单,只需要一个文件路径即可搭建完成环境。package main import ( "log" bolt "go....

weixin_39637256的博客 278

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

raft-boltdb/v2完全迁移指南:基于BBolt的性能优化实践

raft-boltdb/v2是基于BBolt数据库实现的Raft后端存储解决方案,相比v1版本带来了显著的性能提升和稳定性改进。本指南将详细介绍从v1到v2的迁移步骤、核心优化点及最佳实践,帮助开发者快速完成升级并充分利用BBolt的技术优势。 ## 为什么选择迁移到raft-boltdb/v2? raft-boltdb/v2作为Raft协议的存储后端实现,采用了更高效的BBolt数据库(`g

gitblog_00173的博客 802

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

Dism-Multi-language:Dism++ 多语言、清理规则与官方文档完全指南

Dism-Multi-language 是系统维护工具 Dism++ 的官方多语言资源仓库,收录 16 种语言文件、清理规则文件 Data.xml 与完整官方帮助文档。适合 Dism++ 使用者、翻译贡献者与希望定制清理规则的人。 ## Dism++ 项目概览:这个资源仓库里有什么 Dism++ 是构建于底层 CBS(Component Based Servicing)接口上的图形化系统维护工

gitblog_00837的博客 740

从BoltDB到BBolt:raft-boltdb版本演进与技术选型分析

raft-boltdb是一个基于BoltDB实现的Raft协议存储后端,为分布式系统提供可靠的日志存储和状态持久化能力。随着项目发展,其核心存储引擎从BoltDB迁移到BBolt,这一技术选型背后蕴含着对稳定性、维护性和性能的深度考量。本文将深入分析这一演进过程,为开发者提供版本选择的实用指南。 ## 🌱 初识raft-boltdb:BoltDB时代的奠基 raft-boltdb的诞生源于对

gitblog_00892的博客 545

Storm与BoltDB集成指南:无缝连接现有项目的完整方案

Storm是一个针对BoltDB的简单而强大的工具包,它为开发者提供了便捷的API来操作BoltDB数据库。本指南将详细介绍如何将Storm与BoltDB无缝集成到现有项目中,帮助你快速实现数据持久化功能。 ## 为什么选择Storm与BoltDB组合 BoltDB作为一个嵌入式键值数据库,以其高效、可靠的特性受到广泛关注。而Storm在此基础上提供了更友好的操作界面,让开发者可以更专注于业务

gitblog_00117的博客 676

etcd存储引擎:BoltDB后端存储原理与优化

在分布式系统的世界里,etcd作为云原生生态的核心组件,承载着Kubernetes、OpenStack等关键平台的服务发现和配置管理重任。其稳定性和性能直接关系到整个分布式集群的可靠性。而这一切的背后,正是基于BoltDB构建的高效存储引擎在默默支撑。 你是否曾遇到过: - etcd集群响应延迟突然飙升? - 存储空间快速增长却找不到原因? - 数据压缩(defrag)操作耗时过长影响服务? ...

gitblog_00216的博客 952

libkv多后端支持详解:Consul、Etcd、Zookeeper、BoltDB如何选择

libkv是一个强大的分布式键值存储抽象库,为Go开发者提供了统一的多后端支持解决方案。无论您需要构建分布式系统、服务发现机制还是分布式锁,libkv都能让您在Consul、Etcd、Zookeeper和BoltDB之间无缝切换。本文将深入解析这四种后端的特性差异,并提供实用的选择指南。 ## 📊 为什么需要多后端支持的键值存储? 在现代分布式系统中,键值存储已成为核心基础设施组件。libk

gitblog_00510的博客 861

Boltdbweb 开源项目教程

Boltdbweb 开源项目教程 项目介绍 Boltdbweb 是一个基于 Web 的界面,用于管理 BoltDB 数据库。BoltDB 是一个简单、快速、可靠的键值存储数据库,适用于 Go 语言环境。Boltdbweb 提供了一个用户友好的界面,使得开发者可以通过浏览器轻松地查看和修改 BoltDB 数据库的内容。 项目快速启动 安装 首先,确保你已经安装了 Go 语言环境。然后,通过以下命令克...

gitblog_00309的博客 415

etcd 查看所有key_etcd 的性能怎么样?需要优化吗?

作者 |陈星宇(宇慕)阿里云基础技术中台技术专家关注“Go语言中文网”公众号,回复关键词“入门”,即可下载从零入门 K8s 系列文章 PPT。导读:etcd 是容器云平台用于存储关键元信息的组件。阿里巴巴使用 etcd 已经有 3 年的历史, 在今年 双11 过程中它又一次承担了关键角色,接受了 双11 大压力的检验。本文作者从etcd性能背景出发,带领我们了解了 etcd s...

weixin_32267059的博客 398

服务计算修炼 boltdb使用心得

boltdb使用心得 前后端使用心得 首先这一次的实验我们组最后选取的主题是复制star war api 的网站,原因就是这一次实验的主要目的还是希望我们能够体验什么是前后端分离的开发和通过好的api设计来方便之后的迭代和提高交互体验。 具体的分工就是我复制服务器端的数据库移植和数据操作的接口的设计。 虽然说是对于数据库的操作但是其实和想像过去的什么数据库工程师的日常生活还是很不一样的,主要还是因...

huangjundashuaige的博客 777

BoltBrowser 使用教程

BoltBrowser 是一个用于 BoltDB 文件的命令行浏览器。BoltDB 是一个嵌入式的键值存储数据库,适用于 Go 语言。BoltBrowser 提供了一个简单的界面来浏览和管理 BoltDB 文件。 ## 项目快速启动 ### 安装 首先,确保你已经安装了 Go 语言环境。然后,通过以下命令安装 BoltBrowser: ```bash go get github.com/b

gitblog_00944的博客 1133

Storm 事务实战:3 步从 Begin 走到 Commit,守住 BoltDB 数据一致性

Storm 是架在 BoltDB 之上的一层事务封装。你在 Go 里做的多步写操作——扣库存、落订单、改计数——中间任何一步失败,前几步的改动如果没人收场,磁盘上就只剩一半的数据,查询端看到的账目从此对不上。数据一致性不是一句口号,就是这条链路上的每一步都有人兜底。 ## 🚨 先算清一笔"写一半"的账:原子性保住了什么 Storm 对事务的契约写在 `transaction.go` 的 **

gitblog_00598的博客 155
上一篇: 加入开源学习社区一起学习 Go
下一篇: 零基础入门 go 语言
杨鼎睿
博客等级 码龄9年 7粉丝 27原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值