rosedb02-wal实现 & 编码方案

原文地址:https://blog.fengqingmo.top/articles/149

前言

WAL(Write-Ahead Logging)技术是一种常见的数据库事务日志技术,用于确保数据库的持久性和恢复能力。

核心思想是在对数据库进行任何修改之前,先将相应的日志记录写入到持久化的日志文件中。

bitcask 里数据和日志存放在一起,

基础概念

wal: 抽象为负责多个数据文件**(segment)**的管理器,

segment: 数据文件,分为 当前正在写的文件 和 已关闭的文件,每个文件内分为多个块

block: 一个文件被切分成多个固定大小的block

chunk: 实际存储的数据,每个键值对为一个chunk

需要实现的内容及问题

  1. 以上四个概念的抽象

  2. wal与segment都需要实现其对应的迭代器

问题:

  1. 如果chunkSize > blockSize 该如何存储chunk,如果chunkSize 大于 SegmentSize 如何存储。

RoseDB Design

68747470733a2f2f73322e6c6f6c692e6e65742f323032342f30352f31392f497a344a4d4c32536f6b466472664e2e706e67.png

RoseDB数据的编码方案

编码方案指定了以何种格式如何存储连续/相关数据的字节

chunk

+----------+-------------+-----------+--- ... ---+
| CRC (4B) | Length (2B) | Type (1B) |  Payload  |
+----------+-------------+-----------+--- ... ---+

CRC = 32-bit hash computed over the payload using CRC
Length = Length of the payload data
Type = Type of record
       (FullType, FirstType, MiddleType, LastType)
       The type is used to group a bunch of records together to represent
       blocks that are larger than BlockSize
Payload = Byte stream as long as specified by the payload size

格式的数据类型实现

// ChunkPosition represents the position of a chunk in a segment file.
// Used to read the data from the segment file.
type ChunkPosition struct {
	// 段文件编号
	SegmentId SegmentID
	// 在第几个 block 内
	BlockNumber uint32
	// ChunkOffset The start offset of the chunk in the segment file.
	ChunkOffset int64
	// ChunkSize How many bytes the chunk data takes up in the segment file.
	ChunkSize uint32
}

写入一个chunk

  1. 先判断当前 block剩下空间能否放下一个 chunk header, 不可以则将block剩余空间填满,新开一个block
  2. 判断整个chunk(dataSize + chunkHeaderSize)能否放到当前block剩下的空间内,可以则直接放入,chunkTypefull
  3. 否则继续写入,通过chunk剩下需要被写的大小判断当前chunk的类型

segment:

       +-----+-------------+--+----+----------+------+-- ... ----+
 File  | r0  |      r1     |P | r2 |    r3    |  r4  |           |
       +-----+-------------+--+----+----------+------+-- ... ----+
       |<---- BlockSize ----->|<---- BlockSize ----->|

  rn = variable size records
  P = Padding
  BlockSize = 32KB

格式的数据类型实现

// Segment represents a single segment file in WAL.
// The segment file is append-only, and the data is written in blocks.
// Each block is 32KB, and the data is written in chunks.
type segment struct {
    id                 SegmentID
    fd                 *os.File
    //当前在第几个block/已使用的block数量
    currentBlockNumber uint32
    //当前block已使用的大小
    currentBlockSize   uint32
    closed             bool
    header             []byte
    startupBlock       *startupBlock
    isStartupTraversal bool
}

new Segment

segmentId 是 自增的

// openSegmentFile a new segment file.
func openSegmentFile(dirPath, extName string, id uint32) (*segment, error) {
	fd, err := os.OpenFile(
		SegmentFileName(dirPath, extName, id),
		os.O_CREATE|os.O_RDWR|os.O_APPEND,
		fileModePerm,
	)

	if err != nil {
		return nil, err
	}

	// set the current block number and block size.
	offset, err := fd.Seek(0, io.SeekEnd)
	if err != nil {
		return nil, fmt.Errorf("seek to the end of segment file %d%s failed: %v", id, extName, err)
	}

	return &segment{
		id:                 id,
		fd:                 fd,
		header:             make([]byte, chunkHeaderSize),
		currentBlockNumber: uint32(offset / blockSize),
		currentBlockSize:   uint32(offset % blockSize),
		startupBlock: &startupBlock{
			block:       make([]byte, blockSize),
			blockNumber: -1,
		},
		isStartupTraversal: false,
	}, nil
}

Segment Reader

用于遍历文件的chunk

// segmentReader is used to iterate all the data from the segment file.
// You can call Next to get the next chunk data,
// and io.EOF will be returned when there is no data.
type segmentReader struct {
	segment     *segment
	//段文件含有的block数量
	blockNumber uint32
	// 当前chunk在文件内的偏移
	chunkOffset int64
}

重要的方法

*func (seg segment)readInternal(blockNumber uint32, chunkOffset int64)… : 从段文件中读取数据。

func (segReader *segmentReader) Next() ([]byte, *ChunkPosition, error): 获取seg的下一个chunk

WAL:用来管理所有文件

数据类型实现

// WAL 代表一个预写日志结构,提供数据的持久性和容错能力。
// 它包含活跃段(当前用于新写入的段文件)和旧段(用于读操作的旧段文件)。
// 选项字段存储各种配置选项。
// 互斥锁(mu)用于并发访问WAL数据结构,确保安全访问和修改。
type WAL struct {
	// 活跃段文件,用于新的写入数据。
	activeSegment     *segment
	// 旧段文件,仅用于读取。
	olderSegments     map[SegmentID]*segment
	// 配置选项。
	options           Options
	// 用于并发访问和修改的互斥锁。
	mu                sync.RWMutex
	// 写入的字节数。
	bytesWrite        uint32
	// 重命名ID列表。
	renameIds         []SegmentID
	// 等待写入的数据数组。
	pendingWrites     [][]byte
	// 等待写入的大小。
	pendingSize       int64
	// 等待写入的锁。
	pendingWritesLock sync.Mutex
}

new wal:加载所有文件

读取目录下的所有文件,segmentId 最大的为当前活跃文件

*func Open(options Options) (WAL, error)

WAL Reader

负责迭代所有segment

// Reader represents a reader for the WAL.
// It consists of segmentReaders, which is a slice of segmentReader
// structures sorted by segment id,
// and currentReader, which is the index of the current segmentReader in the slice.
//
// The currentReader field is used to iterate over the segmentReaders slice.
type Reader struct {
	segmentReaders []*segmentReader
	currentReader  int
}

总结

写好底层的数据结构需要很强的编码能力和抽象能力。

  • 24
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值