【cfengDB】自己实现数据库第0节 ---整体介绍及事务管理层实现

news2024/9/23 3:18:06

LearnProj

内容管理

    • MySQL系统结构
      • 一条SQL执行流程
    • cfengDB整体结构
    • 事务管理TM模块
      • TID文件规则定义
      • 文件读写 -- NIO
      • RandomAccessFile、FileChannel、ByteBuffer
      • 接口实现
        • 文件合法检测
        • begin()
        • commit(tid)
        • rollback(tid)
        • tid文件创建


本文作为数工底层的项目CfengDB开始篇章,介绍开发缘由和实现思路


cfeng之前对数据库研究不深入,之前只是能够做到基本的SQL查询和基本的慢SQL优化,之前拿到数据库系统工程师证书还是只在业务上对于DB系统使用更深入,但是cfeng基于work的理解,当作为一个优秀的产品使用者之后,再来作为产品的开发者,二者是互相促进的, 现在信息时代的发展大数据量变得非常普遍,了解DB的设计开发对于我们的code是非常有帮助的,从SQLboy变成engineer

MySQL系统结构

工作中最常使用的DB系统应该就是MySQL了,当然随着信创生态,一些国产数据库也广泛普及,如何让我们的SQL更高效,查询更迅速,一致性问题等都是需要考虑的问题,MySQL最常见的几个概念应该就是索引,锁 + 事务MVCC了, 本文就不探究这些业务层面的实现了,这里主要介绍一下MySQL的架构。

img

这是网络上一个比较简化的结构图, 整体SQL的执行来说就4个部分:

  • 连接器: 管理相关MySQL客户端与服务端的连接同时会进行旋前的验证
  • 分析器: 词法分析和语法分析,也就是MySQL需要读懂整条SQL的意思
  • 优化器: 客户端传送的SQL可能非常臃肿繁杂,server端需要进行简单的优化
  • 执行器: MySQL读懂SQL含义并优化之后,开始执行,底层的存储引擎是插件式的,可供选择

一条SQL执行流程

上面的各个模块都是比较简化版的,这里再实例详述一遍。

img

后台应用和MySQL之间都维护了连接池,用于管理所有的连接,通过Connect就可以将待执行的SQL传送给MySQL服务端执行。

SQL语句到达MySQL服务端之后

  1. server的线程会将接收到的语句交给SQL接口(组件),该interface专门由于执行SQL语句
  2. SQL接口将语句传递给SQL解析器parser,进行词法语法分析,理解SQL语句的意思(从哪张表,什么过滤条件,提取哪些字段…)
  3. 之后将语句传送给查询优化器,选择最优的路径,达到最佳的性能(比如select x from student where id =1, 可以先查找所有的x再过滤,或者先过滤再取x)这类的路径选择就是优化器的工作
  4. 选择最佳路径后,再传递给执行器将SQL语句按逻辑执行(比如调用存储引擎的接口,获取user表的数据,判断,继续…)
  5. 调用存储引擎,执行,执行器会调用存储引擎完成 SQL的操作,存储引擎按照一定的步骤查询内存缓存数据,更新磁盘数据。

为了提高效率,MySQL中也存在缓存,如果SQL命中缓存,就不会再走流程,节约时间

cfengDB整体结构

就模块划分来说,MySQL是十分优秀的,cfengDB最多只能说是帮助us更加理解数据库这种产品的一种最简单的设计而已。

因为cfengDB需要完成的最基础的任务就是识别并正确执行SQL,因此1.0.0就只针对这一过程进行结构设计(其余的向连接池化,用户管理…都后续再设计)

cfengDB整体上也是分为前端和后端,前后端通过网络Socket同学,前端的职责就是读取用户的SQL并发送给后端server指向,输出返回的结果,后台和MySQL流程一样需要识别合法的SQL并执行

为了整体的可扩展性和实现的难度,采用分层和分治的思想进行模块划分,整体上划分为事务管理模块Transaction Manager, 数据管理模块Data Manager, 版本锁管理Version Manager,索引管理Index Manager,表管理Table Manager

最终需要实现的就是表管理Table Manager,通过表管理模块就可以提供server的相关服务,模块分层和依赖关系如下:在这里插入图片描述

  • 事务管理TM: 维护TID文件来维护事务的状态,提供接口供其他的模块来查询事务(参考的MySQL的MVCC)
  • 数据管理DM: 直接管理数据库DB文件和日志文件,需要分页管理DB文件并且需要缓存,同时需要管理日志文件以供故障回复,抽象DB文件类提供上层使用
  • 锁管理VM: 并发管理模块,基于2PL协议实现调度可串行化,利用MVCC实现隔离级别
  • 索引管理IM: 基于B+树实现索引
  • 表管理TBM: 整体上的表和字段管理,完成SQL解析和执行

事务管理TM模块

从模块层级来看,TM作为底层是最基础的部分,所以先从最基本的模块开始讲解:

TM主要是通过维护TID来维护事务的状态,提供接口供其他的模块查询状态

TID文件规则定义

cfengDB中每一个事务都会有一个TID进行表示,事务ID从1开始自增,不重复; 特殊规定 ----- TID为0表示无事务noneTransaction,代表事务不申请事务直接执行,该类型事务状态永远都是committed

TM中维护一个TID格式文件,记录Transaction的状态,其中cfengDB中规定事务的状态为三种:

  • 0 running: 正在执行,还没有结束
  • 1 commited: 已提交
  • 3 rollback: 撤销回滚

TID中,每一个事务有1字节空间保存状态,同时,TID头部还有8字节的数字,记录TID文件管理的事务的个数,那么事务tid在文件中的状态就存储在8 +(tid-1) 字节位置,【从0开始计数,并且TID为0代表none】

文件读写 – NIO

IO种类很多,有磁盘IO和网络IO, BIO、NIO、IO多路复用、AIO的概念大多用于网络IO — 用户程序从网络中获取数据socket

  1. 用户进程系统调用进入内核态
  2. OS等待远程客户端发送数据(TCP建立连接),OS从网卡设备获取数据,从Socket协议栈拷贝到内核缓冲区
  3. 把内核缓冲区的数据拷贝到用户缓冲区
  4. 用户进程获取到数据,继续执行

img

NIO相比BIO,AIO的区别就是:

步骤1,用户进程要进行IO了,是阻塞挂起,还是非阻塞

步骤3: 内核缓冲区数据拷贝到用户缓冲区,用户进程是阻塞还是非阻塞

BIO是同步阻塞,数据的读写都会阻塞在一个线程中

NIO是同步非阻塞,通过Selector监听不同的channel上的数据变化,当channel数据发生变化时,通知该线程进行读写操作,然后线程就会自己进行读写

AIO是异步,接收到客户端管道后,交给底层处理IO通信,自己本身做其余的事情

(同步和异步的意思是是否需要自己处理,而阻塞和非阻塞就是点餐后是否需要一直等着饭做好)

【步骤一只是通知(点餐),3就是真正开始读写】,1,3都阻塞,就是BIO,1不阻塞,3阻塞,就是NIO; 1,3都不阻塞,就是AIO

  • BIO:阻塞IO,包括常见的ServerSocket/Socket, accept(); //连接阻塞; InputStream/OutputStream; IO读写阻塞
  • NIO: new IO,noBlock; Selector复路器,缓冲区Buffer,ServerSocketChannel; IO未就绪的时候都是非阻塞的; 通过Linux提供的epoll + Selector + Channel实现多路复用【一个线程处理N个连接】

NIO核心组件:

  • Channel: 通道,NIO数据源头/目的地,缓冲区Buffer的唯一接口: 双向读写【可读出和写入】、数据in/out总是先到缓冲区; {文件IO: FileChannel 从文件中读取数据, DataChannel: UDP读写网络数据,SocketChannel: TCP读写 ServerSocketChannel:服务端监听新TCP连接,每进入一个创建SocketChannel}
  • Buffer: 缓冲区, NIO数据读写的中转地【一块连续内存块】,Buffer和传入的数组共享相同的数据存储区域; 可以简单理解两指针指向相同的块,作为数据缓存, 适用于除了boolean之外所有基本类型, 比如ByteBuffer、IntBuffer… allocate()动态分配yi

文件读写采用的是NIO方式,(NIO非阻塞,支持基于通道的IO操作,区别传统的Input/OutputStream),使用FileChannel,创建TxManager后,需要对TID文件校验,保证TID文件合法

校验方式: 文件头的8byte数字推断文件的理论长度,如果不同就认为不合法,不合法的,直接panic强制停机【对于无法回复的错误只能先粗暴停机】

//事务tid在tid文件中的位置
LEN_TID_HEADER_LEN + (tid - 1)* TID_FIELD_SIZE  [头长度 + (序号 -1* 每个tid长度]

所有的文件操作,执行后立刻刷入文件,防止崩溃后丢失, 使用NIO的fileChannel的force方法,和BIO的flush类似

【为了方便,java8开始支持在interface中定义静态方法体,static的方法: 调用时直接采用接口名称调用即可】

create()和open() 创建TID文件,从TID文件创建TransactionManager,创建XID文件时需要写一个空的TID的header,设置tidCounter为0(数量为0),否则后续会不合法

RandomAccessFile、FileChannel、ByteBuffer

IO的底层涉及的概念: 缓冲区操作内核空间与用户空间虚拟内存分页技术

在这里插入图片描述

磁盘操作属于内存管理,是OS系统级功能,需要在内核态执行,所以读取的数据是到内核缓冲区,之后再拷贝到用户态缓冲区

java中的IO常见操作read()和write()完成的作用: 数据在内核缓冲区和用户缓冲区进行交换, 传统IO是面向流(按字节读取),而NIO则是面向Buffer的

在java的内存结构中, 直接内存不受JVM管理, 而堆heap是受JVM用户进程管理; NIO中的Selector可以监听channel,【channel是数据源的抽象】,只有当某个channel准备好之后,线程才会阻塞去取数据操作,而不需要一直等待【BIO从最开始准备数据就开始阻塞】

  • RandomAccessFile允许随机读写文件,也就是可以按照设定的位置开始读取(部分读取); 访问模式包括: r: 只读; rw: 读写; rws:读写,每次文件内容和元数据修改都同步磁盘; rwd: 读写,每次文件内容同步磁盘
  • FileChannel: 通道, 通过文件位置指定开始读写, instance可以通过RandomAccessFile.getChannel()获得, 读写buffer后会自动向后移动pos
  • ByteBuffer: 缓冲区,对byte数组的一种封装, position表示当前的小标,limit结束标记,capacity就是底层的byte数组的容量, 可以通过wrap或者allocate进行内存的分配,通过getXXX方法可以进行类型转换; 每次进行write的时候可以进行force来避免数据丢失

在这里为了方便进行各种类型的转换, 实现了一个byte[] 和 类型的转换工具类:

	public static byte[] long2Byte(long value) {
        return ByteBuffer.allocate(Long.SIZE/Byte.SIZE).putLong(value).array();
    }

    public static long parseLong(byte[] buf) {
        ByteBuffer buffer = ByteBuffer.wrap(buf,0,8);
        return buffer.getLong();
    }

接口实现

首先TM中都是对于事务的操作,NO Transaction使用TID为0, TID的状态变化和事务的新增都是通过操作TID文件实现的, 所以TM的实现就是对于TID文件的创建和修改

//事务管理大多是对tid文件的管理,所以需要nio的RandomAccessFile和相关的channel
    private RandomAccessFile randomAccessFile;
    private FileChannel fileChannel;
    private long tidCounter; //tid计数器
    private Lock counterLock; //使用Lock锁保证线程安全

再进行TM初始化的时候就会进行TIdCounter的检查,检查的方式就是先检查头的长度,之后再整体计算文件长度

文件合法检测

getTidPosition就可以获取tid对应事务的状态字节开始的位置, 而实际的长度就需要再加上一个TID状态的长度 再和 fileLen相比即可

fileLen = randomAccessFile.length(); //文件实际长度

if(fileLen < TransactionConstant.LEN_TID_HEADER_LEN)
    
this.tidCounter = ByteBufferParser.parseLong(buffer.array()); //tid就是头8个字节表示的数据
long end = getTidPosition(this.tidCounter) + TransactionConstant.TID_FIELD_SIZE; //getTidPosition相当于取得的是tid该状态byte开始的位置,结束位置需要
if(end != fileLen)
    
	private long getTidPosition(long tid) {
        return  TransactionConstant.LEN_TID_HEADER_LEN + (tid - 1) * TransactionConstant.TID_FIELD_SIZE;
    }

而修改事务TID的状态的方法也很简单, 直接获取到TID的开始位置,之后将待写入的byte进行wrap为Buffer,写入再force即可

	long offset = getTidPosition(tid);
        byte[] tmp = new byte[TransactionConstant.TID_FIELD_SIZE];
        tmp[0] = status; //修改状态为status
        ByteBuffer buffer = ByteBuffer.wrap(tmp);
        try {
            fileChannel.position(offset);
            fileChannel.write(buffer);
        } catch (IOException e) {
            FaultHandler.forcedStop(e);
        }
        //每次写buffer时候强制刷新一下,避免丢失
        try {
            fileChannel.force(false);

而检查状态就是读取对应位置的Buffer通过buffer.array()获取之后再进行比较即可

对于定义的相关的接口对于事务的相关操作,就是修改TID文件中的TID的状态; eg:

begin()

开始一个新的事务,所以首先tid ++,之后将该新事务的状态设置为运行RUNNING, 再将头部tidCounter数量加一写入头部

因为需要修改类属性,在并发状态下存在安全问题,所以使用ReentrantLock进行加锁修改

	tidCounter ++;
        //修改数量
        ByteBuffer buffer = ByteBuffer.wrap(ByteBufferParser.long2Byte(tidCounter));
        try {
            fileChannel.position(0);
            fileChannel.write(buffer);
        } catch (IOException e) {
            FaultHandler.forcedStop(e);
        }
        //每次写buffer时候强制刷新一下,避免丢失
        try {
            fileChannel.force(false);
            
            
           this.counterLock.lock();
        try {
            long tid = tidCounter + 1; //当前事务
            updateStatus(tid, TransactionConstant.FIELD_TRAN_RUNNING);  //事务激活
            incrTidCounter(); //头部size增加
            return tid;
        } finally {
            this.counterLock.unlock();
        }

commit(tid)

提交事务,有了之前的操作,这里就很好实现 — 直接修改事务TID文件中tid的状态为commited即可

rollback(tid)

和commit同理, 直接修改TID的状态(文件中)

isXXXX(tid)

状态检查,直接buffer读取对应tid位置的事务的状态再进行比较即可

tid文件创建

tid的文件创建可以直接利用File即可,因为TM的类属性有RandomAccessFile和FileChannel,所以再初始化一下

	//创建文件
        File file = new File(path + TransactionConstant.TID_SUFFIX);
        try {
            if(!file.createNewFile()) {
                //文件创建失败已存在,直接粗暴处理,因为不能进行后续操作了
                FaultHandler.forcedStop(new DatabaseException(ErrorCode.FILE_EXISTS));
            }
        }catch (Exception e) {
            FaultHandler.forcedStop(e);
        }
        //查看文件是否可读写,直接调用File的接口
        if(!file.canRead() || !file.canWrite()) {
            FaultHandler.forcedStop(new DatabaseException(ErrorCode.FILE_CANNOT_READ_OR_WRITE));
        }
        //使用NIO进行文件读写
        FileChannel fc = null;
        RandomAccessFile raf = null; //与简单IO不同,可以调转到文件任何位置进行IO,访问文件部分内容
        try {
            raf = new RandomAccessFile(file,"rw"); //将file转为随机读写File,文件权限为rw
            fc = raf.getChannel(); //利用randomAccessFile创建channel通道
        } catch (FileNotFoundException e) {
            FaultHandler.forcedStop(e);
        }

        //利用buffer和channel进行文件读写
        //写空的文件头,将byte[]包装为buffer
        ByteBuffer buf = ByteBuffer.wrap(new byte[TransactionConstant.LEN_TID_HEADER_LEN]);
        try {
            fc.position(0); //RandomAccessFile定位到0处开始
            fc.write(buf); //缓冲区写入
        } catch (IOException e) {
            FaultHandler.forcedStop(e);
        }
        return new TransactionManagerImpl(raf, fc);
    }

整个TM的实现都是依靠的tid文件的操作,主要是定义好规则,实现不难,这里的两重点技术: 可重入锁保证线程安全 + NIO方式进行文件操作提升性能🎄

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/759064.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue 升级3 +vite+antdv4

目录 一、安装包相关升级 二、vite.config 三、 入口文件修改 四、App.vue 及相关升级 五、路由 六、状态管理VUEX 一、安装包相关升级 升级pakage.json相关安装包 vue2插件vue3替换插件vue2使用vue3使用vue-ls vuex-persistedstate或vuex-persistVue.ls.get() Vue.ls.…

C++ 多线程学习总结

C 多线程 创建线程 thread jion与detach方式的区别 jion方式&#xff1a;必须等待创建并启动的子线程任务执行完毕&#xff0c;才会继续往下执行。 示例&#xff1a; #include <stdio.h> #include <unistd.h> #include <iostream> #include <string&g…

【企业架构实践】要避免的 7 个企业架构错误

颠覆性时代需要有弹性、前瞻性的企业架构。不要让错误的框架破坏您的组织实现当前和未来目标的能力。 企业架构为成功的业务 IT 计划奠定了基础。如果设计和实施得当&#xff0c;企业架构将帮助业务领导者实现他们的目标&#xff0c;使组织变得更具响应性、效率和竞争力。 不幸…

matlab使用教程(2)—数组索引、工作区与字符

1数组索引 MATLAB 中的每个变量都是一个可包含许多数字的数组。如果要访问数组的选定元素&#xff0c;请使用索引。 例如&#xff0c;假设有 44 矩阵 A &#xff1a; A [1 2 3 4; 5 6 7 8; 9 10 11 12; 13 14 15 16] A 44 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 引…

python selenium.webdriver 爬取政策文件

文章目录 获取文章链接批量爬取政策文件应用selenium爬取文件信息数据处理导出为excel 获取文章链接 获取中央人民政府网站链接&#xff0c;进入国务院政策文件库&#xff0c;分为国务院文件和部门文件&#xff08;发改委、工信部、交通运输部、市场监督局、商务部等&#xff…

JavaScript 中 五种迭代数组的方法 every some map filter forEach

JavaScript 中 五种迭代数组的方法 every some map filter forEach 1.every 和 some2.filter (重点常用)3.map (灵活常用&#xff09;4.forEach (重点常用) ECMAScript 提供了 5个对数组的迭代方法 1.every() 2.some() 3.filter() 4.forEach() 5.map() 1.every 和 some every…

串联型PI和并联型PI调节器的比较

一、PI调节器的种类 图3-4 仿真波形变化情况&#xff08;串联型PI调节器&#xff0c;1500r/min&#xff09; 从图3-1到3-4比较可知&#xff0c;与并联型PI调节器相比&#xff0c;串联型PI调节器的超调量很小&#xff08;速度环&#xff09;&#xff0c;且动态过程时间短&…

从新的角度看待大模型微调

一、前言 一切要从最近大火的Lora(《LORA: LOW-RANK ADAPTATION OF LARGE LANGUAGE MODELS》&#xff09;说起&#xff0c;该文章在ICLR2022中提出。说的是利用低秩适配(low-rank adaptation)的方法&#xff0c;可以在使用大模型适配下游任务时只需要训练少量的参数即可达到一…

【SQL应知应会】表分区(二)• MySQL版

欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 本文收录于SQL应知应会专栏,本专栏主要用于记录对于数据库的一些学习&#xff0c;有基础也有进阶&#xff0c;有MySQL也有Oracle 分区表 • MySQL版 前言一、分区表1.非分区表2.分区…

【运维】第03讲(下):Nginx 负载均衡常见架构及问题解析

Nginx 负载均衡常见问题 那么,Nginx 负载均衡的通常配置会出现哪些问题呢?这里列出几种比较常见的问题: 客户端 IP 地址获取问题域名携带问题负载均衡导致 session 丢失问题动态负载均衡问题真实的 Realserver 状态检测接下来,我们就重点讲解下 Nginx 作为负载均衡的这几个…

从代码角度戳一下springMVC的运行过程-spring16

1、首先页面需要输入地址&#xff1a; 2、画张图------ 这个地址先找Tomcat,而Tomcat帮你找你的工程 3、 4、每次都要过这个地方 这句servlet代码 具体流程是&#xff1a; SpringMVC的执行流程 这个Handler干嘛&#xff1f;负责对你的请求进行解析&#xff0c;知道我最终要找…

WAIC2023丨AI图像内容安全“黑科技”如何助力科技发展?

〇、前言 7月7日下午&#xff0c;2023世界人工智能大会&#xff08;WAIC&#xff09;“聚焦大模型时代AIGC新浪潮—可信AI”论坛在上海世博中心红厅举行。人工智能等技术前沿领域的著名专家与学者、投资人和领军创业者汇聚一堂&#xff0c;共同探索中国科技创新的驱动力量。 在…

latex3【排版】

多行公式排版&#xff1a;&#xff08;gather、align、split、cases&#xff09; \section{多行公式}%gather环境\begin{gather} abba \\ abcbaccbacab\end{gather}\begin{gather*} abba \\ abcbaccbacab\end{gather*}​\begin{gather} abba \\ 123 \notag …

贪吃蛇游戏制作

目录 前言 游戏设计 游戏三部曲 函数说明 优化设计 1 前言 终极目标&#xff1a;打造酷炫贪吃蛇游戏 制作环境: VS2015(支持VC2010,VS各个版本) easyx图形库(稍微改下VC6.0也可以实现) 2 游戏设计 贪吃蛇的制作思路就是蛇头带动蛇尾移动&#xff0c;主要…

记一次linux服务器k8s服务失败,重新安装centos系统并部署k8s以及服务【2023年7月15日】

周五&#xff0c;突然收到微信消息说&#xff1a;兄嘚~ 网站无法访问了&#xff01; 我以为就是普通的小问题&#xff0c;连上服务器看报错呢&#xff0c;执行kubectl get nodes [rootnode101 ~]# kubectl get nodes The connection to the server 127.0.0.1:6443 was refused…

FL Studio 21具有哪些功能?flstudio21会有什么新功能

FL Studio 21 是一个功能完备的音乐制作环境&#xff0c;能够进行多轨道音频录制、音序处理和混音&#xff0c;可以帮助用户创作专业质量的音乐轨道。 借助 VST 托管、灵活的混音器、高级 MIDI 和 ReWire 支持&#xff0c;您将轻松驾驭各种音乐风格。 歌曲或循环可以导出为 .wa…

[QT编程系列-15]: 基础框架 - 信号与槽,connect函数详解

目录 一、线程内不同对象的通信 1.1 connect函数介绍 1.2 connect的用法 1.3 代码示例 二、不同线程间不同对象的通信 2.1 概述 2.2 代码案例 一、线程内不同对象的通信 1.1 connect函数介绍 在使用 Qt 框架进行开发时&#xff0c;connect 函数是用于建立信号与槽机…

hadoop -- Hbase

HBase是一个分布式、可扩展、面向列的数据存储&#xff08;百万级别列&#xff09;、可伸缩、高可靠性、实时读写的NoSQL 数据库。 HBase利用 Hadoop的 HDFS作为其文件存储系统&#xff0c; 利用MapReduce 来处理HBase中的海量数据&#xff0c; 利用Zookeeper作为分布式协同服…

AIGC之文本内容生成概述(下)——Transformer

在上一篇文章中&#xff0c;我们一口气介绍了LSTM、Word2Vec、GloVe、ELMo等四种模型的技术发展&#xff0c;以及每种模型的优缺点与应用场景&#xff0c;全文超过一万字&#xff0c;显得冗长且繁杂&#xff0c;在下文部分我们将分开介绍Transformer、BERT、GPT1/GPT2/GPT3/Cha…

electron+vue3全家桶+vite项目搭建【23】url唤醒应用,并传递参数

文章目录 引入实现效果实现步骤测试代码 引入 demo项目地址 很多场景下我们都希望通过url快速唤醒应用&#xff0c;例如百度网盘&#xff0c;在网页中唤醒应用&#xff0c;并传递下载链接&#xff0c;在electron中要实现这样的效果&#xff0c;就需要针对不同的平台做对应的处…