Golang封装Rocksdb支持主从模式介绍
golang封装rocksdb
rocksdb一般会作为某一个组件嵌入一个系统中,如flink使用rocksdb作为存储后端。rocksdb是C++编写的程序,在golang技术栈下,可以使用cgo,通过c接口调用rocksdb,进而在上层封装程序——如扩展通讯协议如扩展redis协议将rocksdb当成redis调用、支持主从模式读写分离等。
github.com/linxGnu/gro… 这个库封装了cgo调用rocksdb的一系列接口,可以基于此项目编写go程序操作rocksdb。
package main
import (
"log"
"github.com/linxGnu/grocksdb"
)
func main() {
opts := grocksdb.NewDefaultOptions()
opts.SetCreateIfMissing(true)
db, err := grocksdb.OpenDb(opts, "./path/to/db")
if err != nil {
log.Fatalf("Error opening db: %v", err)
}
defer db.Close()
// 进行数据库操作...
// 写入数据
writeOpts := grocksdb.NewDefaultWriteOptions()
defer writeOpts.Destroy()
err = db.Put(writeOpts, []byte("key1"), []byte("value1"))
if err != nil {
log.Fatalf("Error putting data: %v", err)
}
// 读取数据
readOpts := grocksdb.NewDefaultReadOptions()
defer readOpts.Destroy()
value, err := db.Get(readOpts, []byte("key1"))
if err != nil {
log.Fatalf("Error getting data: %v", err)
}
defer value.Free()
log.Printf("Value: %s", value.Data())
// // 批量写入
// batch := grocksdb.NewWriteBatch()
// defer batch.Destroy()
// batch.Put([]byte("key1"), []byte("value1"))
// batch.Put([]byte("key2"), []byte("value2"))
// err = db.Write(writeOpts, batch)
// if err != nil {
// log.Fatalf("Error writing batch: %v", err)
// }
// // 迭代器使用
// it := db.NewIterator(readOpts)
// defer it.Close()
// for it.SeekToFirst(); it.Valid(); it.Next() {
// key := it.Key()
// value := it.Value()
// log.Printf("Key: %s, Value: %s\n", string(key.Data()), string(value.Data()))
// }
}
因为rocksdb的环境依赖较多,安装复杂,我写了开箱即用的dockerFile用来在golang环境下封装rocksdb,具体可以看这篇文章:
主从模式
github.com/apache/kvro… 可以参考这个项目,实现多Rocksdb主从模式做读写分离。
Rocksdb本身是单机引擎,不支持主从,需要手动处理实现主从。可以有多种模式选择:基于日志同步的主从复制、基于共享存储的主从复制、基于外部程序的主从复制。本篇主要介绍基于日志同步的主从模式。
github.com/apache/kvro…
过程
- 主节点将磁盘数据挂载到远程,从节点获取数据的时候,可以通过s3协议从远程拉取数据,也可以通过网络直连主节点接收数据。
- 主节点初始化时,暴露接口给从节点连接,自己作为单机运行,由另外的模块负责从节点连接的管理。
- 从节点初始化时,从节点通过主节点的CheckPonit备份中获取全量同步的数据。具体状态触发可以通过检查manifest的sst信息,或者检查wal的数据信息。
- 运行过程中,需要同步增量数据,增量状态可以从wal日志判断,此时需要双工通讯,一方面主节点通过从节点心跳判断是否需要同步,从节点通过检查主从节点的心跳响应判断是否需要同步。
双方基于wal的版本号来控制数据增量同步,为了防止数据太过频繁传输,可以采用类似TCP传输中的Nagle算法,等待数据满一批后,再按批传输同步给从节点。 - 数据一致性检查
- 管理CLI,提供CLI可以很方便对集群检查、扩容、备份等等。
- 主从灾备。使用ETCD接入,golang技术栈完全可以做到内嵌一个ETCD。
主库从库:
初始化主数据库
打开或创建主数据库路径(master_db_path)
写入数据到主数据库
对于每个键值对(key, value):
将 key 和 value 写入主数据库
记录当前的写入操作到 WAL 日志
获取当前 WAL 日志位置
获取当前 WAL 日志的文件编号或偏移量
将 WAL 日志位置保存到一个持久化的位置(例如一个单独的文件或数据库中)
=======================================================================
初始化从数据库
打开或创建从数据库路径(slave_db_path)
获取主数据库的最新 WAL 日志位置
从持久化位置读取主数据库的最新 WAL 日志位置
同步主数据库的变更到从数据库
从主数据库的 WAL 日志文件中读取变更
解析 WAL 日志文件,提取变更操作(键值对的插入、更新、删除等)
应用变更到从数据库
对于每个解析出的变更操作:
如果是插入或更新操作:
将键值对写入从数据库
如果是删除操作:
从从数据库中删除对应的键
更新从数据库的同步状态
记录当前同步的 WAL 日志位置到一个持久化的位置
全量同步:
在主数据库中:
定期创建一个 Checkpoint
将 Checkpoint 的路径和相关信息发送给从数据库
在从数据库中:
如果需要全量同步:
从主数据库获取最新的 Checkpoint
使用 Checkpoint 恢复从数据库
更新从数据库的同步状态
增量同步:
在主数据库中:
每次写入操作后,记录当前的 WAL 日志位置
定期将最新的 WAL 日志位置同步到从数据库(例如通过网络传输)
在从数据库中:
定期检查主数据库的最新 WAL 日志位置
如果有新的变更(即新的 WAL 日志位置大于上次同步的位置):
从主数据库的 WAL 日志中读取新的变更
解析并应用这些变更到从数据库
更新从数据库的同步状态