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

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和相关的channelprivate 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; //修改状态为statusByteBuffer 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,文件权限为rwfc = raf.getChannel(); //利用randomAccessFile创建channel通道} catch (FileNotFoundException e) {FaultHandler.forcedStop(e);}//利用buffer和channel进行文件读写//写空的文件头,将byte[]包装为bufferByteBuffer 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.mzph.cn/news/1911.shtml

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

相关文章

UART串口通信协议

一、串行通信 串行通信分为两种方式&#xff1a;同步串行通信和异步串行通信。 同步串行通信需要通信双方在同一时钟的控制下&#xff0c;同步传输数据。 异步串行通信是指通信双方使用各自的时钟控制数据的发送和接收过程。 二、UART 通用异步收发传输器&#xff08;Unive…

【Vue/element】 el-table实现表格动态新增/插入/删除 表格行,可编辑单元格

el-table实现表格动态新增/插入/删除 表格行&#xff0c;可编辑单元格 效果如下&#xff1a; 点击“新增一行”可以在表格最后新增一行&#xff0c;单元格内容可编辑 点击绿色按钮&#xff0c;可在指定行的后面插入一行 点击红色-按钮&#xff0c;可以删除指定行 原理&#…

让小程序动起来-轮播图的两种方式--【浅入深出系列003】

浅入深出系列总目录在000集 如何0元学微信小程序–【浅入深出系列000】 文章目录 本系列校训学习资源的选择啥是轮播图轮播图的关键代码最常见的轮播图代码便于理解的轮播代码两种轮播代码的比较 实际操练第一步&#xff0c;就是找到文件。第二步&#xff0c;先改动一下最显眼…

Docker使用总结

Docker 1.什么是 Docker 官网的介绍是“Docker is the world’s leading software container platform.” 官方给Docker的定位是一个应用容器平台。 Docker 是一个容器平台的领导者 Docker 容器平台 Docker 应用容器平台 application项目 Mysql Redis MongoDB ElasticSeacrh …

分布式运用——存储系统Ceph

分布式运用——存储系统Ceph 一、Ceph 介绍1.Ceph 简介2、存储基础2.1 单机存储设备2.2 单机存储的问题2.3 商业存储解决方案2.4 分布式存储&#xff08;软件定义的存储 SDS&#xff09;2.5 分布式存储的类型 3.Ceph 优势3.1 高扩展性3.2 高可靠性3.3 高性能3.4 功能强大 4.Cep…

hybridCLR热更遇到问题

报错1&#xff1a; No ‘git‘ executable was found. Please install Git on your system then restart 下载Git安装&#xff1a; Git - Downloading Package 配置&#xff1a;https://blog.csdn.net/baidu_38246836/article/details/106812067 重启电脑 unity&#xff1a;…

嵌入式工程师常用的软件工具推荐

前言&#xff1a;常言道&#xff1a;工欲善其事&#xff0c;必先利其器。作为一名合格的嵌入式工程师&#xff0c;日常可能需要接触和处理各种奇奇怪怪的问题&#xff0c;这时候一款高适配性的工具将会令工作效率大大提升。作者根据个人的实际使用情况与粉丝的客观感受&#xf…

MySQL表的约束

目录 前言 1.什么是约束 2.空属性 3.默认值 4.列描述 5.zerofill 6.主键 7.自增长 8.唯一键 9.外键 总结 前言 hello&#xff0c;各位小伙伴大家好&#xff0c;本章内容为大家介绍关于MySQL约束的相关内容&#xff0c;关于约束这个概念&#xff0c;如果是第一次接触可…

JAVA ---- 经典排序算法

目录 一. 插入排序 1. 直接插入排序 代码演示 2.希尔排序( 缩小增量排序 ) 二. 选择排序 1.直接选择排序 代码&#xff1a; 2. 堆排序 代码 三. 交换排序 1. 冒泡排序 代码 2. 快速排序 代码&#xff08;有注释&#xff09;&#xff1a; 动图来自网…

ubuntu创建多用户并使用ssh链接

添加多个同时登录的用户 以下内容中的“username”根据自己需求自己定义 1.创建新用户 sudo useradd username2.给新用户添加管理权限 sudo vim /etc/sudoers打开的文件中添加如下内容 username ALL(ALL:ALL) ALL3.设置密码 输入&#xff1a; sudo passwd username打开的…

「软件测试」最全面试问题和回答,全文背熟不拿下offer算我输

一般要应聘关于测试的工作&#xff0c;面试题会不会很难?下面小编整理了软件测试面试题及答案&#xff0c;欢迎参考! 一、引言 1.1 文档目的 本次文档是为了收集在面试中遇到的一问题与常见的一些答案并不是唯一答案 二、职业规划 2.1 简单的自我介绍下 面试宫&#xff…

点大商城V2_2.5.0 全开源版 商家自营+多商户入驻 百度+支付宝+QQ+头条+小程序端+unipp开源前端安装测试教程

播播资源安装点大商城V2_2.5.0 全开源版测试后发现后台总体体验下来比较简洁&#xff0c;营销功能还是挺多该有的都有了&#xff0c;相比上一版优化很多细节。首页和会员中心均支持DIY装修&#xff0c;底部菜单也一样&#xff0c;安装测试中目前未发现BUG&#xff0c;小程序整体…

etcd实现大规模服务治理应用实战

导读&#xff1a;服务治理目前越来越被企业建设所重视&#xff0c;特别现在云原生&#xff0c;微服务等各种技术被更多的企业所应用&#xff0c;本文内容是百度小程序团队基于大模型服务治理实战经验的一些总结&#xff0c;同时结合当前较火的分布式开源kv产品etcd&#xff0c;…

文献阅读笔记——求解车辆路径问题及其变体的元启发式算法的分类综述

论文题目&#xff1a;A taxonomic review of metaheuristic algorithms for solving the vehicle routing problem and its variants 其他信息&#xff1a;Computers & Industrial Engineering|2020|Raafat Elshaer⁎, Hadeer Awad 文章贡献&#xff1a;1&#xff09;对使…

如何用Python搭建监控平台

监控和运维&#xff0c;是互联网工业链上非常重要的一环。监控的目的就是防患于未然。通过监控&#xff0c;我们能够及时了解到企业网络的运行状态。一旦出现安全隐患&#xff0c;你就可以及时预警&#xff0c;或者是以其他方式通知运维人员&#xff0c;让运维监控人员有时间处…

什么是计算机蠕虫?

计算机蠕虫诞生的背景 计算机蠕虫的诞生与计算机网络的发展密切相关。20世纪60年代末和70年代初&#xff0c;互联网还处于早期阶段&#xff0c;存在着相对较少的计算机和网络连接。然而&#xff0c;随着计算机技术的进步和互联网的普及&#xff0c;计算机网络得以迅速扩张&…

S32 Design Studio for ARM(S32DS)下载和安装

1. S32 Design Studio for ARM 介绍 S32 Design Studio for ARM&#xff08;下面简称S32DS&#xff09;&#xff0c;是 NXP 官方在 2014 年官方推出的&#xff0c;专门面向 S32K、KEA、MAC57D54H等系列微控制器的集成开发环境。 S32DS是由Eclipse和一些插件集成而来的开发平台…

kafka消息队列最常用的两种模式,以及应用场景

目录 一、发布-订阅模式 二、点对点模式 三、应用场景 一、发布-订阅模式 发布-订阅模式是最常见的消息传递模式&#xff0c;其中消息发布者将消息发送到一个或多个主题&#xff08;Topic&#xff09;&#xff0c;而订阅者可以选择订阅一个或多个主题来接收消息。每个订阅者…

实现本地缓存-caffeine

目录 实现caffeine cache CacheManager Caffeine配置说明 创建自定义配置类 配置缓存管理器 编写自动提示配置文件 测试使用 创建测试配置实体类 创建测试配置类 创建注解扫描的测试实体 创建单元测试类进行测试 实现caffeine cache CacheManager SimpleCacheManag…

香橙派4和树莓派4B构建K8S集群实践之七: Jenkins

目录 1. 说明 2. 步骤 2.1 准备工作 2.2 安装 2.2.1 用jenkins原站for k8s的安装仓方法安装 2.2.2 Helm 安装 3. 相关命令 4. 遇到的问题 5. 参考 1. 说明 在k8s上部署jenkins&#xff0c;并用 jenkins.k8s-t2.com访问在namespace为devops下安装在指定节点k8s-master-…