345 lines
12 KiB
Go
345 lines
12 KiB
Go
// Package pipelinedb provides an integrated pipeline database system
|
||
// 集成了数据库存储和业务管道处理的一体化解决方案
|
||
package pipelinedb
|
||
|
||
import (
|
||
"encoding/binary"
|
||
"sync"
|
||
)
|
||
|
||
// FreePageManager 空闲页面管理器
|
||
//
|
||
// 核心职责:
|
||
// 1. 管理数据库文件中的空闲页面
|
||
// 2. 提供页面分配和回收功能
|
||
// 3. 维护空闲页面的持久化存储
|
||
// 4. 确保页面分配的线程安全性
|
||
//
|
||
// 设计思想:
|
||
// - 使用切片维护空闲页面列表,提供O(1)的分配性能
|
||
// - 支持空闲页面链表的持久化存储和恢复
|
||
// - 使用互斥锁保证并发安全
|
||
// - 防重复释放机制,避免空闲页面重复添加
|
||
//
|
||
// 适用场景:
|
||
// - 数据库页面空间管理
|
||
// - 文件系统空闲块管理
|
||
// - 任何需要页面级别资源管理的系统
|
||
//
|
||
// 性能特征:
|
||
// - 分配:O(1) - 从切片末尾取出
|
||
// - 释放:O(n) - 需要检查重复(n为空闲页数量)
|
||
// - 空间:O(n) - n为空闲页面数量
|
||
type FreePageManager struct {
|
||
freePages []uint16 // 空闲页面编号列表(使用切片实现栈结构)
|
||
mu sync.Mutex // 互斥锁,保护空闲页面列表的并发访问
|
||
}
|
||
|
||
// NewFreePageManager 创建一个新的空闲页面管理器实例
|
||
//
|
||
// 初始化内容:
|
||
// 1. 创建空的空闲页面切片
|
||
// 2. 初始化互斥锁(自动完成)
|
||
//
|
||
// 返回值:
|
||
// - *FreePageManager: 初始化完成的空闲页面管理器
|
||
//
|
||
// 使用示例:
|
||
//
|
||
// fpm := NewFreePageManager()
|
||
// pageNo, ok := fpm.AllocPage() // 分配页面
|
||
// fpm.FreePage(pageNo) // 释放页面
|
||
func NewFreePageManager() *FreePageManager {
|
||
return &FreePageManager{
|
||
freePages: make([]uint16, 0), // 初始化空的切片,容量为0
|
||
// mu 会自动初始化为零值(未锁定状态)
|
||
}
|
||
}
|
||
|
||
// AllocPage 从空闲页面池中分配一个页面
|
||
//
|
||
// 核心算法:栈式分配(LIFO - Last In First Out)
|
||
// 时间复杂度:O(1) - 直接从切片末尾取出
|
||
// 空间复杂度:O(1) - 只修改切片长度
|
||
//
|
||
// 返回值:
|
||
// - uint16: 分配的页面编号(如果成功)
|
||
// - bool: 是否分配成功(true=成功,false=无空闲页面)
|
||
//
|
||
// 执行流程:
|
||
// 1. 获取互斥锁(保证线程安全)
|
||
// 2. 检查是否有空闲页面可用
|
||
// 3. 如果有:从切片末尾取出页面编号,缩短切片
|
||
// 4. 如果没有:返回失败标志
|
||
//
|
||
// 设计考虑:
|
||
// - 使用LIFO策略提高缓存局部性
|
||
// - 最近释放的页面可能仍在缓存中
|
||
// - O(1)时间复杂度确保高性能分配
|
||
//
|
||
// 并发安全:使用互斥锁保护,支持多线程并发调用
|
||
func (fpm *FreePageManager) AllocPage() (uint16, bool) {
|
||
fpm.mu.Lock() // 获取互斥锁,保证线程安全
|
||
defer fpm.mu.Unlock() // 确保函数退出时释放锁
|
||
|
||
// 检查是否有空闲页面可用
|
||
if len(fpm.freePages) == 0 {
|
||
return 0, false // 无空闲页面,分配失败
|
||
}
|
||
|
||
// 从切片末尾取出最后一个空闲页面(LIFO策略)
|
||
// 这样可以提高缓存局部性,最近释放的页面可能仍在内存中
|
||
pageNo := fpm.freePages[len(fpm.freePages)-1]
|
||
|
||
// 缩短切片,移除已分配的页面
|
||
// 注意:这里使用切片操作,不会重新分配内存
|
||
fpm.freePages = fpm.freePages[:len(fpm.freePages)-1]
|
||
|
||
return pageNo, true // 返回分配的页面编号和成功标志
|
||
}
|
||
|
||
// FreePage 将一个页面释放回空闲页面池
|
||
//
|
||
// 核心算法:防重复释放的页面回收
|
||
// 时间复杂度:O(n) - n为当前空闲页面数量(需要检查重复)
|
||
// 空间复杂度:O(1) - 只添加一个页面编号
|
||
//
|
||
// 参数说明:
|
||
// - pageNo: 要释放的页面编号
|
||
//
|
||
// 执行流程:
|
||
// 1. 获取互斥锁(保证线程安全)
|
||
// 2. 遍历现有空闲页面列表,检查是否重复
|
||
// 3. 如果不重复:将页面编号添加到列表末尾
|
||
// 4. 如果重复:静默忽略(幂等操作)
|
||
//
|
||
// 设计考虑:
|
||
// - 防重复机制避免空闲页面列表污染
|
||
// - 幂等操作确保多次释放同一页面不会出错
|
||
// - 添加到末尾保持LIFO分配顺序
|
||
//
|
||
// 性能权衡:
|
||
// - 检查重复需要O(n)时间,但保证了数据一致性
|
||
// - 对于大量空闲页面的场景,可考虑使用Set数据结构优化
|
||
//
|
||
// 并发安全:使用互斥锁保护,支持多线程并发调用
|
||
func (fpm *FreePageManager) FreePage(pageNo uint16) {
|
||
fpm.mu.Lock() // 获取互斥锁,保证线程安全
|
||
defer fpm.mu.Unlock() // 确保函数退出时释放锁
|
||
|
||
// 检查页面是否已经在空闲列表中(防重复释放)
|
||
for _, p := range fpm.freePages {
|
||
if p == pageNo {
|
||
return // 页面已存在,静默忽略(幂等操作)
|
||
}
|
||
}
|
||
|
||
// 将页面添加到空闲列表末尾
|
||
// 这样下次分配时会优先使用(LIFO策略)
|
||
fpm.freePages = append(fpm.freePages, pageNo)
|
||
}
|
||
|
||
// FreeCount 获取当前空闲页面的数量
|
||
//
|
||
// 用途:
|
||
// - 监控数据库空间使用情况
|
||
// - 评估是否需要扩展数据库文件
|
||
// - 性能调优和容量规划
|
||
//
|
||
// 核心算法:直接返回切片长度
|
||
// 时间复杂度:O(1) - 直接读取切片长度
|
||
// 空间复杂度:O(1) - 不创建额外数据
|
||
//
|
||
// 返回值:
|
||
// - int: 当前空闲页面数量(>=0)
|
||
//
|
||
// 使用示例:
|
||
//
|
||
// count := fpm.FreeCount()
|
||
// if count < 100 {
|
||
// // 空闲页面不足,考虑扩展数据库
|
||
// }
|
||
//
|
||
// 并发安全:使用互斥锁保护,支持多线程并发调用
|
||
func (fpm *FreePageManager) FreeCount() int {
|
||
fpm.mu.Lock() // 获取互斥锁,保证读取一致性
|
||
defer fpm.mu.Unlock() // 确保函数退出时释放锁
|
||
|
||
// 返回空闲页面列表的长度
|
||
return len(fpm.freePages)
|
||
}
|
||
|
||
// LoadFromHeader 从数据库文件头加载空闲页面信息
|
||
//
|
||
// 核心功能:将持久化存储的空闲页面链表加载到内存中
|
||
// 时间复杂度:O(n) - n为空闲页面数量(需要遍历链表)
|
||
// 空间复杂度:O(n) - 需要存储所有空闲页面编号
|
||
//
|
||
// 参数说明:
|
||
// - freeHead: 空闲页面链表的头页面编号(0表示无空闲页面)
|
||
// - readPage: 页面读取函数,用于从磁盘读取页面数据
|
||
// 函数签名:func(pageNo uint16) ([]byte, error)
|
||
//
|
||
// 返回值:
|
||
// - error: 加载过程中的错误(nil表示成功)
|
||
//
|
||
// 执行流程:
|
||
// 1. 获取互斥锁(保证线程安全)
|
||
// 2. 清空当前内存中的空闲页面列表
|
||
// 3. 从链表头开始遍历空闲页面链表:
|
||
// a. 读取当前页面数据
|
||
// b. 解析下一页面指针(存储在页面偏移4-6字节)
|
||
// c. 将当前页面添加到空闲列表
|
||
// d. 移动到下一页面
|
||
// 4. 遍历完成后返回结果
|
||
//
|
||
// 链表结构:
|
||
// - 每个空闲页面的4-6字节存储下一个空闲页面的编号
|
||
// - 链表末尾页面的下一页面指针为0
|
||
// - 支持最多65535个空闲页面
|
||
//
|
||
// 错误处理:
|
||
// - 页面读取失败时立即返回错误
|
||
// - 防止无限循环(限制最大页面数)
|
||
//
|
||
// 并发安全:使用互斥锁保护,确保加载过程的原子性
|
||
func (fpm *FreePageManager) LoadFromHeader(freeHead uint16, readPage func(uint16) ([]byte, error)) error {
|
||
fpm.mu.Lock() // 获取互斥锁,保证线程安全
|
||
defer fpm.mu.Unlock() // 确保函数退出时释放锁
|
||
|
||
// 清空现有的空闲页面列表,准备重新加载
|
||
fpm.freePages = fpm.freePages[:0]
|
||
|
||
// 从链表头开始遍历空闲页面链表
|
||
current := freeHead
|
||
for current != 0 {
|
||
// 读取当前页面的数据
|
||
p, err := readPage(current)
|
||
if err != nil {
|
||
return err // 页面读取失败,返回错误
|
||
}
|
||
|
||
// 从页面数据中解析下一页面指针
|
||
// 空闲页面的4-6字节存储下一个空闲页面的编号
|
||
nextPage := binary.LittleEndian.Uint16(p[4:6])
|
||
|
||
// 将当前页面添加到空闲列表中
|
||
fpm.freePages = append(fpm.freePages, current)
|
||
|
||
// 移动到链表中的下一个页面
|
||
current = nextPage
|
||
|
||
// 防止无限循环的安全检查
|
||
// 理论上最多支持65535个页面(uint16的最大值)
|
||
if len(fpm.freePages) > 65535 {
|
||
break // 超出合理范围,停止遍历
|
||
}
|
||
}
|
||
|
||
return nil // 加载成功
|
||
}
|
||
|
||
// SaveToHeader 将空闲页面信息保存到数据库文件
|
||
//
|
||
// 核心功能:将内存中的空闲页面列表持久化为磁盘上的链表结构
|
||
// 时间复杂度:O(n) - n为空闲页面数量(需要写入所有页面)
|
||
// 空间复杂度:O(1) - 只创建单个页面的缓冲区
|
||
//
|
||
// 参数说明:
|
||
// - writePage: 页面写入函数,用于将页面数据写入磁盘
|
||
// 函数签名:func(pageNo uint16, data []byte) error
|
||
//
|
||
// 返回值:
|
||
// - uint16: 空闲页面链表的头页面编号(0表示无空闲页面)
|
||
// - error: 保存过程中的错误(nil表示成功)
|
||
//
|
||
// 执行流程:
|
||
// 1. 获取互斥锁(保证线程安全)
|
||
// 2. 检查是否有空闲页面需要保存
|
||
// 3. 遍历所有空闲页面,构建链表结构:
|
||
// a. 创建页面数据缓冲区
|
||
// b. 设置下一页面指针(存储在4-6字节)
|
||
// c. 写入页面数据到磁盘
|
||
// 4. 返回链表头页面编号
|
||
//
|
||
// 链表结构:
|
||
// - 每个页面的4-6字节存储下一页面编号
|
||
// - 最后一个页面的下一页面指针设为0
|
||
// - 链表头是第一个空闲页面的编号
|
||
//
|
||
// 错误处理:
|
||
// - 页面写入失败时立即返回错误
|
||
// - 部分写入成功的情况下,链表可能处于不一致状态
|
||
//
|
||
// 并发安全:使用互斥锁保护,确保保存过程的原子性
|
||
func (fpm *FreePageManager) SaveToHeader(writePage func(uint16, []byte) error) (uint16, error) {
|
||
fpm.mu.Lock() // 获取互斥锁,保证线程安全
|
||
defer fpm.mu.Unlock() // 确保函数退出时释放锁
|
||
|
||
// 检查是否有空闲页面需要保存
|
||
if len(fpm.freePages) == 0 {
|
||
return 0, nil // 无空闲页面,返回0作为链表头
|
||
}
|
||
|
||
// 遍历所有空闲页面,构建链表结构
|
||
for i, pageNo := range fpm.freePages {
|
||
// 创建页面大小的数据缓冲区
|
||
p := make([]byte, PageSize)
|
||
|
||
// 设置下一页面指针(存储在页面的4-6字节位置)
|
||
if i < len(fpm.freePages)-1 {
|
||
// 不是最后一个页面,设置指向下一个空闲页面
|
||
nextPage := fpm.freePages[i+1]
|
||
binary.LittleEndian.PutUint16(p[4:6], nextPage)
|
||
} else {
|
||
// 最后一个页面,设置下一页面指针为0(链表结束标志)
|
||
binary.LittleEndian.PutUint16(p[4:6], 0)
|
||
}
|
||
|
||
// 将页面数据写入磁盘
|
||
if err := writePage(pageNo, p); err != nil {
|
||
return 0, err // 写入失败,返回错误
|
||
}
|
||
}
|
||
|
||
// 返回链表头页面编号(第一个空闲页面)
|
||
return fpm.freePages[0], nil
|
||
}
|
||
|
||
// GetFreePages 获取所有空闲页面编号的副本(主要用于调试和监控)
|
||
//
|
||
// 用途:
|
||
// - 调试空闲页面管理逻辑
|
||
// - 监控空闲页面分布情况
|
||
// - 数据库诊断和分析
|
||
//
|
||
// 核心算法:创建空闲页面列表的副本
|
||
// 时间复杂度:O(n) - n为空闲页面数量(需要复制所有元素)
|
||
// 空间复杂度:O(n) - 创建完整的副本
|
||
//
|
||
// 返回值:
|
||
// - []uint16: 所有空闲页面编号的副本切片
|
||
//
|
||
// 设计考虑:
|
||
// - 返回副本而不是原始切片,防止外部修改
|
||
// - 提供只读访问,不影响内部状态
|
||
// - 适用于调试、监控和诊断场景
|
||
//
|
||
// 使用示例:
|
||
//
|
||
// freePages := fpm.GetFreePages()
|
||
// fmt.Printf("空闲页面: %v\n", freePages)
|
||
// fmt.Printf("空闲页面数量: %d\n", len(freePages))
|
||
//
|
||
// 并发安全:使用互斥锁保护,确保读取一致性
|
||
func (fpm *FreePageManager) GetFreePages() []uint16 {
|
||
fpm.mu.Lock() // 获取互斥锁,保证读取一致性
|
||
defer fpm.mu.Unlock() // 确保函数退出时释放锁
|
||
|
||
// 创建空闲页面列表的完整副本
|
||
result := make([]uint16, len(fpm.freePages))
|
||
copy(result, fpm.freePages)
|
||
|
||
return result // 返回副本,防止外部修改原始数据
|
||
}
|