本意是想解读Hashicorp Raft,看了一下,发现其引用了一些包,也是这个组织开源的模块,所以干脆先看看这些模块是如何实现的,就当是为了Hashicorp Raft做前期的了解
hashicorp/raft-boltdb
该库引用了BoltDB。BoltDB受到LMDB的启发,是一个纯Go实现的简易key/value存储库。
目录结构
以下基于tag:v2.2.0
https://github.com/hashicorp/raft-boltdb/tree/v2.2.0
既然我们是从源码的角度去学习,那么就要先了解清楚这个包的目录结构是如何的。
├── bench_test.go
├── bolt_store.go
├── bolt_store_test.go
├── go.mod
├── go.sum
├── LICENSE
├── Makefile
├── metrics.go
├── README.md
├── util.go
└── v2
├── bench_test.go
├── bolt_store.go
├── bolt_store_test.go
├── go.mod
├── go.sum
├── metrics.go
├── README.md
└── util.go
bolt_store.go
:该库的主要实现地方bench_test.go
:压测文件metrics.go
:指标收集util.go
:工具类库v2目录
:v2是对go.etcd.io/bbolt的实现,没有破坏性的API更改。然而,可能存在磁盘格式不兼容的问题,因此保守地将其设置为单独的导入路径。这是’ raft-boltdb '的主要版本,应该尽可能使用v2。
util.go
先看看util文件做了什么,整个文件都不到50行,很简单,就是对库方法的加了一层封装
package raftboltdb
import (
"bytes"
"encoding/binary"
"github.com/hashicorp/go-msgpack/codec"
)
// msg信息解码
func decodeMsgPack(buf []byte, out interface{
}) error {
r := bytes.NewBuffer(buf)
hd := codec.MsgpackHandle{
}
dec := codec.NewDecoder(r, &hd)
return dec.Decode(out)
}
// 将已编码的对象写入新字节缓冲区
func encodeMsgPack(in interface{
}) (*bytes.Buffer, error) {
buf := bytes.NewBuffer(nil)
hd := codec.MsgpackHandle{
}
enc := codec.NewEncoder(buf, &hd)
err := enc.Encode(in)
return buf, err
}
// 将字节进行大端序转换为整数
func bytesToUint64(b []byte) uint64 {
return binary.BigEndian.Uint64(b)
}
// 把数字转换为大端序写入字节切片
func uint64ToBytes(u uint64) []byte {
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, u)
return buf
}
metrics.go
该文件是对指标的收集,可根据需要去采集数据,在工程实践上指标数据还是十分重要的,监控项目的运行情况
import (
"context"
"time"
metrics "github.com/armon/go-metrics"
"github.com/boltdb/bolt"
)
const (
defaultMetricsInterval = 5 * time.Second
)
// RunMetrics should be executed in a go routine and will periodically emit
// metrics on the given interval until the context has been cancelled.
func (b *BoltStore) RunMetrics(ctx context.Context, interval time.Duration) {
if interval == 0 {
interval = defaultMetricsInterval
}
tick := time.NewTicker(interval)
defer tick.Stop()
stats := b.emitMetrics(nil)
for {
select {
case <-ctx.Done():
return
case <-tick.C:
stats = b.emitMetrics(stats)
}
}
}
func (b *BoltStore) emitMetrics(prev *bolt.Stats) *bolt.Stats {
newStats := b.conn.Stats()
stats := newStats
if prev != nil {
stats = newStats.Sub(prev)
}
// freelist metrics
metrics.SetGauge([]string{
"raft", "boltdb", "numFreePages"}, float32(newStats.FreePageN))
metrics.SetGauge([]string{
"raft", "boltdb", "numPendingPages"}, float32(newStats.PendingPageN))
metrics.SetGauge([]string{
"raft", "boltdb", "freePageBytes"}, float32(newStats.FreeAlloc))
metrics.SetGauge([]string{
"raft", "boltdb", "freelistBytes"}, float32(newStats.FreelistInuse))
// txn metrics
metrics.IncrCounter([]string{
"raft", "boltdb", "totalReadTxn"}, float32(stats.TxN))
metrics.SetGauge([]string{
"raft", "boltdb", "openReadTxn"}, float32(newStats.OpenTxN))
// tx