当前位置: 首页 > news >正文

[Golang]K-V存储引擎的学习 从零实现 (RoseDB mini版本)

文章目录

  • 项目的简单介绍
    • 详情
  • 代码分析
    • 项目结构
    • db.go
    • db_file.go
    • entry.go

项目的简单介绍

mini-bitcask的学习,从零实现一个k-v存储引擎
原项目的github地址,感谢Rose大佬
mini-bitcaskrosedbmini版本,博主借此了解k-v存储,该项目通过对一个数据文件进行读写以进行简单示意,而非rosedb多个数据文件的机制,这里也提供rosedb的github地址

详情

此处为rose大佬提供的项目的相关知识的介绍,可供查阅

简而言之的话,minidb设计了数据在内存、磁盘二者中的存放,使用类似LSM的存储结构。
实现了数据PUT、GET、DELETE的功能
核心思想为利用顺序IO来提升性能。

代码分析

这里博主为了学习,由于原main.go固定流程,将其改为可自定义的流程,这里附学习项目的github地址,只对main.go进行了修改,并将数据文件的地址改到项目根目录中。

项目结构

.
├── db_file.go
├── db.go
├── db_test.go
├── entry.go
├── errors.go
├── example
│   └── main.go
├── go.mod
├── minidb.iml
└── README.md

接下来对主要部分进行注释和分析
db_file.go

db.go

定义了MiniBitcask结构体,index使用map在内存中进行key-value的存储,dbfile和dirpath是数据文件的相关信息,mu则用来进行并发的保护

type MiniBitcask struct {indexes map[string]int64 // 内存中的索引信息dbFile  *DBFile          // 数据文件dirPath string           // 数据目录mu      sync.RWMutex
}

db.go主要功能为提供数据库的集成功能创建数据库实例OPEN

实现方法写入数据PUT、取出数据GET、删除数据DEL、合并数据文件merge、关闭db实例CLOSE,也是我们主要使用的功能

实现过程从内存中获取索引exist从数据文件加载索引loadIndexesFromFile

这一部分在原文和源文件中就有代码的分析,这里不再赘述

db_file.go

该部分有关数据文件定义
DBFile结构体包括对应数据文件的句柄,以及下次写入的偏移量、

// DBFile 数据文件定义
type DBFile struct {File          *os.FileOffset        int64HeaderBufPool *sync.Pool
}

newInternal函数创建一个DBFile实例NewDBFile创建一个新的数据文件NewMergeDBFile新建一个合并时的数据文件

func newInternal(fileName string) (*DBFile, error) {// 打开文件,如果不存在则创建file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR, 0644)if err != nil {return nil, err}// 获取文件的状态信息,这里目的是为了获取文件的大小,以设置偏移量offsetstat, err := os.Stat(fileName)if err != nil {return nil, err}// 创建一个对象池,用于存储 Entry 头部数据的缓冲区pool := &sync.Pool{New: func() interface{} {return make([]byte, entryHeaderSize)}}// 返回一个新的 DBFile 对象,其中包括文件的偏移量、打开的文件句柄和对象池return &DBFile{Offset: stat.Size(), File: file, HeaderBufPool: pool}, nil
}

DBFile实现以下方法,Read读取数据Write写入Entry

// Read 从 offset 处开始读取,在引擎初始化时,这里被循环调用
func (df *DBFile) Read(offset int64) (e *Entry, err error) {// 从对象池中获取一个用于存储数据的缓冲区buf := df.HeaderBufPool.Get().([]byte)defer df.HeaderBufPool.Put(buf) // 确保在函数返回时将缓冲区归还给对象池// 从文件中读取数据到缓冲区if _, err = df.File.ReadAt(buf, offset); err != nil {return}// 解码缓冲区中的数据为 Entry 对象if e, err = Decode(buf); err != nil {return}// 计算键和值在文件中的偏移量,并读取键和值的数据offset += entryHeaderSize // 将偏移量移到键值对数据开始的位置// 如果键的大小大于 0,则读取键的数据if e.KeySize > 0 {key := make([]byte, e.KeySize) // 创建一个存储键数据的切片if _, err = df.File.ReadAt(key, offset); err != nil {return}e.Key = key // 将读取的键数据赋值给 Entry 对象}// 更新偏移量,准备读取值的数据offset += int64(e.KeySize) // 将偏移量移到值数据开始的位置// 如果值的大小大于 0,则读取值的数据if e.ValueSize > 0 {value := make([]byte, e.ValueSize) // 创建一个存储值数据的切片if _, err = df.File.ReadAt(value, offset); err != nil {return}e.Value = value // 将读取的值数据赋值给 Entry 对象}// 返回读取的 Entry 对象及可能的错误return
}

write方法的实现可以参考read

entry.go

Entry定义这里的一条记录,该包即为如何包装一条记录
一条entry包括key、value的值和大小标志位mark

// Entry 写入文件的记录
type Entry struct {Key       []byteValue     []byteKeySize   uint32ValueSize uint32Mark      uint16
}

NewEntry根据传入的参数返回一条Entry实例
并实现了Entry的编码Encode解码Decode 方法
encode定义了entry编码的顺序,decode可以参考这部分

// Encode 编码 Entry,返回字节数组
func (e *Entry) Encode() ([]byte, error) {buf := make([]byte, e.GetSize())binary.BigEndian.PutUint32(buf[0:4], e.KeySize)binary.BigEndian.PutUint32(buf[4:8], e.ValueSize)binary.BigEndian.PutUint16(buf[8:10], e.Mark)copy(buf[entryHeaderSize:entryHeaderSize+e.KeySize], e.Key)copy(buf[entryHeaderSize+e.KeySize:], e.Value)return buf, nil
}

相关文章:

  • Java实现简单的通讯录
  • JavaWeb--HTML
  • 浏览器同源策略及跨域问题
  • 安卓UI面试题 45-50
  • zookeeper基础学习之六: zookeeper java客户端curator
  • Javascript抓取京东、淘宝商品数据(商品采集商品详情图片抓取)
  • 算法思想总结:滑动窗口算法
  • DeformableAttention的原理解读和源码实现
  • QML与C++通信
  • Python电梯楼层数字识别
  • STM32第九节(中级篇):RCC(第一节)——时钟树讲解
  • Tomcat的部署及调优,jvm调优
  • Java8 新特性
  • Java-并发编程--ThreadLocal、InheritableThreadLocal
  • 《LeetCode热题100》笔记题解思路技巧优化_Part_3
  • 网络传输文件的问题
  • 【140天】尚学堂高淇Java300集视频精华笔记(86-87)
  • 78. Subsets
  • GraphQL学习过程应该是这样的
  • java正则表式的使用
  • maven工程打包jar以及java jar命令的classpath使用
  • MySQL Access denied for user 'root'@'localhost' 解决方法
  • PAT A1120
  • SegmentFault 技术周刊 Vol.27 - Git 学习宝典:程序员走江湖必备
  • Traffic-Sign Detection and Classification in the Wild 论文笔记
  • 区块链技术特点之去中心化特性
  • 深入浅出webpack学习(1)--核心概念
  • 自动记录MySQL慢查询快照脚本
  • No resource identifier found for attribute,RxJava之zip操作符
  • 树莓派用上kodexplorer也能玩成私有网盘
  • ​flutter 代码混淆
  • ​软考-高级-系统架构设计师教程(清华第2版)【第20章 系统架构设计师论文写作要点(P717~728)-思维导图】​
  • !!java web学习笔记(一到五)
  • # Panda3d 碰撞检测系统介绍
  • # 达梦数据库知识点
  • ###51单片机学习(2)-----如何通过C语言运用延时函数设计LED流水灯
  • #{} 和 ${}区别
  • #AngularJS#$sce.trustAsResourceUrl
  • #define、const、typedef的差别
  • #WEB前端(HTML属性)
  • ()、[]、{}、(())、[[]]命令替换
  • (06)金属布线——为半导体注入生命的连接
  • (4)logging(日志模块)
  • (MonoGame从入门到放弃-1) MonoGame环境搭建
  • (八)c52学习之旅-中断实验
  • (免费领源码)python#django#mysql校园校园宿舍管理系统84831-计算机毕业设计项目选题推荐
  • (四)linux文件内容查看
  • (算法)N皇后问题
  • (转)平衡树
  • .NET MVC第三章、三种传值方式
  • .NET/C# 的字符串暂存池
  • .NET/C# 中你可以在代码中写多个 Main 函数,然后按需要随时切换
  • .vollhavhelp-V-XXXXXXXX勒索病毒的最新威胁:如何恢复您的数据?
  • /etc/apt/sources.list 和 /etc/apt/sources.list.d
  • /usr/local/nginx/logs/nginx.pid failed (2: No such file or directory)