229 lines
4.9 KiB
Go
229 lines
4.9 KiB
Go
package bolt
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
|
|
"git.pyer.club/kingecg/gotidb/pkg/engine"
|
|
bolt "go.etcd.io/bbolt"
|
|
)
|
|
|
|
const (
|
|
// bucket 名称
|
|
seriesBucketPrefix = "series_" // 序列数据 bucket 前缀
|
|
metaBucketName = "meta" // 元数据 bucket
|
|
indexBucketName = "index" // 索引 bucket
|
|
|
|
// 元数据键
|
|
statsKey = "stats" // 统计信息键
|
|
)
|
|
|
|
// BoltEngine 实现基于 BoltDB 的存储引擎
|
|
type BoltEngine struct {
|
|
mu sync.RWMutex
|
|
config *engine.BoltEngineConfig
|
|
db *bolt.DB
|
|
stats engine.EngineStats
|
|
opened bool
|
|
closed bool
|
|
}
|
|
|
|
// 确保 BoltEngine 实现了 Engine 接口
|
|
var _ engine.Engine = (*BoltEngine)(nil)
|
|
|
|
// NewBoltEngine 创建一个新的 Bolt 引擎
|
|
func NewBoltEngine(config *engine.BoltEngineConfig) (engine.Engine, error) {
|
|
if config == nil {
|
|
return nil, fmt.Errorf("bolt engine config cannot be nil")
|
|
}
|
|
|
|
// 确保数据目录存在
|
|
if err := os.MkdirAll(filepath.Dir(config.Path), 0755); err != nil {
|
|
return nil, fmt.Errorf("failed to create data directory: %v", err)
|
|
}
|
|
|
|
return &BoltEngine{
|
|
config: config,
|
|
stats: engine.EngineStats{
|
|
LastWriteTime: time.Now(),
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// Open 实现 Engine 接口
|
|
func (b *BoltEngine) Open() error {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
if b.opened {
|
|
return fmt.Errorf("bolt engine already opened")
|
|
}
|
|
|
|
if b.closed {
|
|
return fmt.Errorf("bolt engine already closed")
|
|
}
|
|
|
|
// 打开数据库
|
|
db, err := bolt.Open(b.config.Path, 0600, &bolt.Options{
|
|
Timeout: 1 * time.Second,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open bolt database: %v", err)
|
|
}
|
|
|
|
// 创建必要的 bucket
|
|
err = db.Update(func(tx *bolt.Tx) error {
|
|
// 创建元数据 bucket
|
|
if _, err := tx.CreateBucketIfNotExists([]byte(metaBucketName)); err != nil {
|
|
return fmt.Errorf("failed to create meta bucket: %v", err)
|
|
}
|
|
|
|
// 创建索引 bucket
|
|
if _, err := tx.CreateBucketIfNotExists([]byte(indexBucketName)); err != nil {
|
|
return fmt.Errorf("failed to create index bucket: %v", err)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
db.Close()
|
|
return fmt.Errorf("failed to create buckets: %v", err)
|
|
}
|
|
|
|
b.db = db
|
|
b.opened = true
|
|
|
|
// 加载统计信息
|
|
if err := b.loadStats(); err != nil {
|
|
return fmt.Errorf("failed to load stats: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Close 实现 Engine 接口
|
|
func (b *BoltEngine) Close() error {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
if !b.opened {
|
|
return fmt.Errorf("bolt engine not opened")
|
|
}
|
|
|
|
if b.closed {
|
|
return fmt.Errorf("bolt engine already closed")
|
|
}
|
|
|
|
// 保存统计信息
|
|
if err := b.saveStats(); err != nil {
|
|
return fmt.Errorf("failed to save stats: %v", err)
|
|
}
|
|
|
|
// 关闭数据库
|
|
if err := b.db.Close(); err != nil {
|
|
return fmt.Errorf("failed to close bolt database: %v", err)
|
|
}
|
|
|
|
b.closed = true
|
|
return nil
|
|
}
|
|
|
|
// Write 实现 Engine 接口
|
|
func (b *BoltEngine) Write(ctx context.Context, points []engine.DataPoint) error {
|
|
if len(points) == 0 {
|
|
return nil
|
|
}
|
|
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
if !b.opened || b.closed {
|
|
return fmt.Errorf("bolt engine not open")
|
|
}
|
|
|
|
startTime := time.Now()
|
|
|
|
// 写入数据点
|
|
err := b.db.Update(func(tx *bolt.Tx) error {
|
|
for _, point := range points {
|
|
seriesID := point.GetSeriesID()
|
|
if seriesID == "" {
|
|
b.stats.WriteErrors++
|
|
continue
|
|
}
|
|
|
|
// 获取或创建序列 bucket
|
|
bucketName := seriesBucketPrefix + seriesID
|
|
bucket, err := tx.CreateBucketIfNotExists([]byte(bucketName))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create series bucket: %v", err)
|
|
}
|
|
|
|
// 序列化数据点
|
|
value, err := json.Marshal(point)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal data point: %v", err)
|
|
}
|
|
|
|
// 使用时间戳作为键
|
|
key := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(key, uint64(point.Timestamp))
|
|
|
|
// 写入数据点
|
|
if err := bucket.Put(key, value); err != nil {
|
|
return fmt.Errorf("failed to write data point: %v", err)
|
|
}
|
|
|
|
// 更新索引
|
|
indexBucket := tx.Bucket([]byte(indexBucketName))
|
|
if err := indexBucket.Put([]byte(seriesID), []byte(seriesID)); err != nil {
|
|
return fmt.Errorf("failed to update index: %v", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to write points: %v", err)
|
|
}
|
|
|
|
// 更新统计信息
|
|
b.stats.PointsCount += int64(len(points))
|
|
b.stats.LastWriteTime = time.Now()
|
|
b.stats.WriteLatency = time.Since(startTime) / time.Duration(len(points))
|
|
|
|
// 更新序列数
|
|
if err := b.updateSeriesCount(); err != nil {
|
|
return fmt.Errorf("failed to update series count: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Stats 实现 Engine 接口
|
|
func (b *BoltEngine) Stats() engine.EngineStats {
|
|
b.mu.RLock()
|
|
defer b.mu.RUnlock()
|
|
|
|
stats := b.stats
|
|
|
|
// 获取数据库大小
|
|
if b.db != nil {
|
|
stats.DiskUsage = b.db.Stats().TxStats.PageCount * int64(b.db.Info().PageSize)
|
|
}
|
|
|
|
return stats
|
|
}
|
|
|
|
// 注册 Bolt 引擎创建函数
|
|
func init() {
|
|
engine.RegisterBoltEngine(NewBoltEngine)
|
|
}
|