事务管理器设计与实现
约 1665 字大约 6 分钟
2025-01-15
1.设计思路
事务管理器通过维护 .xid 文件来维护事务的状态的,并提供接口供其他模块来查询某个事务的状态
.xid文件包含了所有事务,每个事务都有唯一的事务ID,即XID
2.xid文件
2.1 XID定义
每个事务都有一个唯一的事务标识符 XID,从 1 开始递增。其中XID 0
被特殊定义为超级事务(Super Transaction),用于表示在没有申请事务的情况下进行的操作,其状态永远是committed
2.2 事务状态
active
:正在进行,尚未结束。committed
:已提交。aborted
:已撤销或回滚
2.3 文件结构
- 每个事务在 .xid文件中分配一个字节的空间,用于保存其状态
- .xid文件的头部保存一个 8 字节的数字,用于记录这个 .xid文件管理的事务的个数
- 事务 XID 在文件中的状态存储在
(XID-1) + 8
字节的位置处。例如,事务2在文件中的状态存储在(2-1)+8=9字节的位置
3.TransactionManager接口定义
TM提供接口,供上层模块调用
public interface TransactionManager {
long begin(); // 开启一个新事务
void commit(long xid); // 提交一个事务
void abort(long xid); // 取消一个事务
boolean isActive(long xid); // 查询一个事务的状态是否是正在进行的状态
boolean isCommitted(long xid); // 查询一个事务的状态是否是已提交
boolean isAborted(long xid); // 查询一个事务的状态是否是已取消
void close(); // 关闭TM
}
4.TM模块的实现
4.1 常量定义
public class TransactionManagerImpl implements TransactionManager {
// XID文件头长度
static final int LEN_XID_HEADER_LENGTH = 8;
// 每个事务的占用长度
private static final int XID_FIELD_SIZE = 1;
// 事务的三种状态
private static final byte FIELD_TRAN_ACTIVE = 0;
private static final byte FIELD_TRAN_COMMITTED = 1;
private static final byte FIELD_TRAN_ABORTED = 2;
// 超级事务,永远为commited状态
public static final long SUPER_XID = 0;
// XID文件后缀
static final String XID_SUFFIX = ".xid";
}
4.2 xid文件的校验与读取
4.2.1 校验xid文件
- 在创建
TransactionManager
的构造函数后,首先需要对 XID 文件进行校验,以确保其合法性。 - 校验过程相对简单,通过读取文件头的 8 字节数字推算出文件的理论长度,并将其与实际长度进行对比。
- 如果两者不一致,则认为该 XID 文件存在问题,不符合规范。对于校验未通过的情况,系统会立即触发 panic 方法,强制终止运行。
- 在一些关键基础模块中,一旦发生无法恢复的错误,系统会直接停机,以确保整体系统的安全性和稳定性。
4.2.2 开始一个事务
开始一个事务:为事务分配一个编号,并标记为“进行中”,保存到.xid文件
public long begin() {
counterLock.lock();
try {
// 为事务分配一个编号
long xid = xidCounter + 1;
// 标记为“进行中”
updateXID(xid, FIELD_TRAN_ACTIVE);
// 将事务信息 保存到.xid文件
incrXIDCounter();
return xid;
} finally {
counterLock.unlock();
}
}
4.2.3 更新事务状态
// 更新xid事务的状态为status
private void updateXID(long xid, byte status) {
// 获取事务xid在xid文件中对应的位置
long offset = getXidPosition(xid);
// 创建一个长度为XID_FIELD_SIZE的字节数组
byte[] tmp = new byte[XID_FIELD_SIZE];
// 将事务状态设置为status
tmp[0] = status;
// 使用字节数组创建一个ByteBuffer
ByteBuffer buf = ByteBuffer.wrap(tmp);
try {
// 将文件通道的位置设置为offset
fc.position(offset);
// 将ByteBuffer中的数据写入到文件通道
fc.write(buf);
} catch (IOException e) {
// 如果出现异常,调用Panic.panic方法处理
Panic.panic(e);
}
try {
// 强制将文件通道中的所有未写入的数据写入到磁盘
fc.force(false);
} catch (IOException e) {
// 如果出现异常,调用Panic.panic方法处理
Panic.panic(e);
}
}
4.2.4 增加事务计数
// 将XID加一,并更新XID Header
private void incrXIDCounter() {
// 事务总数加一
xidCounter++;
// 将新的事务总数转换为字节数组,并用ByteBuffer包装
ByteBuffer buf = ByteBuffer.wrap(Parser.long2Byte(xidCounter));
try {
// 将文件通道的位置设置为0,即文件的开始位置
fc.position(0);
// 将ByteBuffer中的数据写入到文件通道,即更新了XID文件的头部信息
fc.write(buf);
} catch (IOException e) {
// 如果出现异常,调用Panic.panic方法处理
Panic.panic(e);
}
try {
// 强制将文件通道中的所有未写入的数据写入到磁盘
fc.force(false);
} catch (IOException e) {
// 如果出现异常,调用Panic.panic方法处理
Panic.panic(e);
}
}
4.2.5 事务状态检查
// 定义一个方法,接收一个事务ID(xid)和一个状态(status)作为参数
private boolean checkXID(long xid, byte status) {
// 计算事务ID在XID文件中的位置
long offset = getXidPosition(xid);
// 创建一个新的字节缓冲区(ByteBuffer),长度为XID_FIELD_SIZE
ByteBuffer buf = ByteBuffer.wrap(new byte[XID_FIELD_SIZE]);
try {
// 将文件通道的位置设置为offset
fc.position(offset);
// 从文件通道读取数据到字节缓冲区
fc.read(buf);
} catch (IOException e) {
// 如果出现异常,调用Panic.panic方法处理
Panic.panic(e);
}
// 检查字节缓冲区的第一个字节是否等于给定的状态
// 如果等于,返回true,否则返回false
return buf.array()[0] == status;
}
4.2.6 关闭事务管理器
public void close() {
try {
fc.close();
file.close();
} catch (IOException e) {
Panic.panic(e);
}
}
5.总结
- TM 模块的主要功能是 管理事务,包括事务的开始、提交、回滚以及状态检查。
- 为实现这些功能,模块中定义了一系列关键常量,如
LEN_XID_HEADER_LENGTH
、XID_FIELD_SIZE
、FIELD_TRAN_ACTIVE
、FIELD_TRAN_COMMITTED
、FIELD_TRAN_ABORTED
、SUPER_XID
和XID_SUFFIX
,分别表示 XID 文件头长度、每个事务的占用长度、事务的三种状态、超级事务标识以及 XID 文件的后缀。 - 在实现上,TM 模块通过
RandomAccessFile
类型的file
和FileChannel
类型的fc
操作 XID 文件,并使用xidCounter
来记录事务的数量,同时通过Lock
类确保线程安全。 - 构造函数中,首先对
file
和fc
进行初始化,并调用checkXIDCounter
方法校验 XID 文件的合法性,确保文件的正确性。 - 在事务管理方面,
begin()
方法用于开始一个新事务,通过调用updateXID()
方法将事务 ID 和状态写入 XID 文件,并通过incrXIDCounter()
方法更新 XID 计数器。commit()
和abort()
方法分别用于提交和回滚事务,依赖updateXID()
方法来更新事务状态。isActive()
、isCommitted()
和isAborted()
方法用于检查事务状态,通过checkXID()
方法确认事务的当前状态。close()
方法则负责关闭文件通道和文件,确保资源的正确释放。