【MyDB】3-DataManager数据管理 之 4-数据页缓存

embedded/2025/1/18 23:38:28/

【MyDB】3-DataManager数据管理 之 3-数据页管理

  • 页面缓存设计与实现
    • PageImpl页面定义
    • getForCache() 文件中读取页面数据
    • releaseForCache() 驱逐页面
    • AtomicInteger 记录当前打开数据库文件页
    • recoverInsert()和recoverUpdate()
  • 参考资料

本章涉及代码:top/xianghua/mydb/server/dm/pageCache/PageCacheImpl.java

页面缓存设计与实现

这里参考大部分数据库的设计,将默认数据页大小定为 8K。如果想要提升向数据库写入大量数据情况下的性能的话,也可以适当增大这个值。

上一节我们已经实现了一个通用的缓存框架,并且定义了页面。

那么这一节我们需要缓存页面,就可以直接借用那个缓存的框架了。但是首先,需要定义出页面的结构。注意这个页面是存储在内存中的,与已经持久化到磁盘的抽象页面有区别。

PageImpl页面定义

定义一个页面如下:

java">public class PageImpl implements Page {private int pageNumber; // 页面页号,从1开始计数private byte[] data; // 页面实际包含的字节数据private boolean dirty; // 标志页面是否是脏页面。脏页面在缓存驱逐时需要被写回磁盘private Lock lock; // 页面锁,用于并发控制private PageCache pc; // 页面所属的缓存
}

这里保存了一个 PageCache(还未定义)的引用,用来方便在拿到 Page 的引用时可以快速对这个页面的缓存进行释放操作。

定义页面缓存的接口如下:

java">public interface PageCache {int newPage(byte[] initData);Page getPage(int pgno) throws Exception;void close();void release(Page page);void truncateByBgno(int maxPgno);int getPageNumber();void flushPage(Page pg);
}

页面缓存的具体实现类,需要继承抽象缓存框架,并且实现 getForCache()releaseForCache() 两个抽象方法。

  • getForCache():从文件中读取页面数据并包装成Page对象
  • releaseForCahce():驱逐页面时根据页面是否为脏页面,决定是否将其写回文件系统。

getForCache() 文件中读取页面数据

getForCache.png

由于数据源就是文件系统,getForCache() 直接从文件中读取,并包裹成 Page 即可:

java">    /*** 根据pageNumber从数据库文件中读取页数据,并包裹成Page*/@Overrideprotected Page getForCache(long key) throws Exception {int pgno = (int)key; // 获取pageNumberlong offset = PageCacheImpl.pageOffset(pgno); // 计算页偏移量,(pgno-1) * PAGE_SIZE;// 分配一个页大小的ByteBuffer,8KByteBuffer buf = ByteBuffer.allocate(PAGE_SIZE);fileLock.lock(); // 文件加锁try {// 定位到文件中并读取数据到ByteBuffer中fc.position(offset);fc.read(buf);} catch(IOException e) {Panic.panic(e);}fileLock.unlock(); // 文件解锁return new PageImpl(pgno, buf.array(), this); // 返回新的Page}

releaseForCache() 驱逐页面

releaseForCache() 驱逐页面时,也只需要根据页面是否是脏页面,来决定是否需要写回文件系统:

releaseForCache.png

java">    @Overrideprotected void releaseForCache(Page pg) {// 是脏页面,则写回到文件中if(pg.isDirty()) {flush(pg);pg.setDirty(false);}}public void release(Page page) {release((long)page.getPageNumber());}public void flushPage(Page pg) {flush(pg);}private void flush(Page pg) {int pgno = pg.getPageNumber();long offset = pageOffset(pgno);fileLock.lock();try {ByteBuffer buf = ByteBuffer.wrap(pg.getData());fc.position(offset);fc.write(buf);fc.force(false);} catch(IOException e) {Panic.panic(e);} finally {fileLock.unlock();}}

AtomicInteger 记录当前打开数据库文件页

PageCache 还使用了一个 AtomicInteger,来记录了当前打开的数据库文件有多少页。这个数字在数据库文件被打开时就会被计算,并在新建页面时自增。

java">public int newPage(byte[] initData) {int pgno = pageNumbers.incrementAndGet();Page pg = new PageImpl(pgno, initData, null);flush(pg);  // 新建的页面需要立刻写回return pgno;
}

提一点,同一条数据是不允许跨页存储的,这一点会从后面的章节中体现。这意味着,单条数据的大小不能超过数据库页面的大小。

剩余两个函数 recoverInsert()recoverUpdate() 用于在数据库崩溃后重新打开时,恢复例程直接插入数据以及修改数据使用。

recoverInsert()和recoverUpdate()

用于在数据库崩溃后重新打开时,恢复例程直接插入数据以及修改数据使用。

  • recoverInsert():将数据插入到指定的偏移位置,并更新空闲空间的偏移量。
  • recoverUpdate():在指定的偏移位置直接更新数据,而不更新空闲空间的偏移量。

recoverInsertAndUpdate.png

java">// 将raw插入pg中的offset位置,并将pg的offset设置为较大的offset
public static void recoverInsert(Page pg, byte[] raw, short offset) {pg.setDirty(true); // 将pg的dirty标志设置为true,表示pg的数据已经被修改System.arraycopy(raw, 0, pg.getData(), offset, raw.length); // 将raw的数据复制到pg的数据中的offset位置short rawFSO = getFSO(pg.getData()); // 获取pg的当前空闲空间偏移量if (rawFSO < offset + raw.length) { // 如果当前的空闲空间偏移量小于offset + raw.lengthsetFSO(pg.getData(), (short) (offset + raw.length)); // 将pg的空闲空间偏移量设置为offset + raw.length}
}// 将raw插入pg中的offset位置,不更新update
public static void recoverUpdate(Page pg, byte[] raw, short offset) {pg.setDirty(true); // 将pg的dirty标志设置为true,表示pg的数据已经被修改System.arraycopy(raw, 0, pg.getData(), offset, raw.length); // 将raw的数据复制到pg的数据中的offset位置
}

参考资料

MYDB 3. 数据页的缓存与管理 | 信也のブログ (shinya.click)

数据页缓存 | EasyDB (blockcloth.cn)


http://www.ppmy.cn/embedded/155077.html

相关文章

几个Linux系统安装体验(续): 中科方德服务器系统

本文介绍中科方德服务器系统&#xff08;NFSDesktop&#xff09;的安装。 下载 下载地址&#xff1a; https://www.nfschina.com/index.php?catid68 下载文件&#xff1a;本文下载的文件名称为NFSCNS-4.0-G330-x86_64-241128.iso。 下载注意事项&#xff1a;无法直接下载&…

【Rust自学】13.3. 闭包 Pt.3:使用泛型参数和fn trait来存储闭包

13.3.0. 写在正文之前 Rust语言在设计过程中收到了很多语言的启发&#xff0c;而函数式编程对Rust产生了非常显著的影响。函数式编程通常包括通过将函数作为值传递给参数、从其他函数返回它们、将它们分配给变量以供以后执行等等。 在本章中&#xff0c;我们会讨论 Rust 的一…

【Vue】vue3 video 保存视频进度,每次进入加载上次的视频进度

使用 localStorage 存储每个视频的播放进度在组件加载时恢复上次的播放进度在视频播放过程中实时保存进度在组件卸载前保存最终进度使用 timeupdate 事件来监听视频播放进度的变化 在模板中为视频元素添加事件监听&#xff1a; <videoloopautoplaycontrols:id"video_…

Gitlab搭建npm仓库

由于图片和格式解析问题&#xff0c;为了更好阅读体验可前往 阅读原文 使用gitlab的仓库注册表特性需要版本14.0&#xff0c;如果你的版本比较低&#xff0c;请先根据自己的需求合理升级后再使用 npm私有仓库的搭建方式有很多种&#xff0c;比如使用docker(阅读此篇)&#xff…

Python爬虫学习前传 —— Python从安装到学会一站式服务

早上好啊&#xff0c;大佬们。我们的python基础内容的这一篇终于写好了&#xff0c;啪唧啪唧啪唧…… 说实话&#xff0c;这一篇确实写了很久&#xff0c;一方面是在忙其他几个专栏的内容&#xff0c;再加上生活学业上的事儿&#xff0c;确实精力有限&#xff0c;另一方面&…

C 语言运算符的优先级和结合性

运算符的结合性和优先级 优先级运算符描述结合性1()[]->.函数调用、数组下标、结构体 / 联合体成员通过指针访问、结构体 / 联合体成员访问从左到右2!~ (前缀)-- (前缀) (一元)- (一元)* (间接寻址)& (取地址)sizeof(type)逻辑非、按位取反、前缀自增、前缀自减、一元正…

微服务学习-快速搭建

1. 速通版 1.1. git clone 拉取项目代码&#xff0c;导入 idea 中 git clone icoolkj-microservices-code: 致力于搭建微服务架构平台 1.2. git checkout v1.0.1版本 链接地址&#xff1a;icoolkj-microservices-code 标签 - Gitee.com 2. 项目服务结构 3. 实现重点步骤 …

以太坊(概念与原理)

特点 以太坊是”世界计算机“&#xff0c;开源的、全球分布的计算机基础设施。执行称为智能合约的程序使用区块链来同步和存储系统状态以及名为以太币的加密货币&#xff0c;以计量和约束执行资源成本本质是一个基于交易的状态机以太坊平台使开发人员能够构建具有内置经济功能…