Golang封装Rocksdb主从模式介绍

212 阅读4分钟

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,具体可以看这篇文章:

juejin.cn/post/742490…


主从模式

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 日志中读取新的变更
        解析并应用这些变更到从数据库
        更新从数据库的同步状态