如何在自研Java DBMS中实现64位值的原子IO?
实现long型值写入文件的原子性方案(Java DBMS场景)
针对你用MemorySegment映射文件、force()无法保证8字节原子性的问题,结合DBMS的高性能需求,以下几个方案可以实现long值写入的“全或无”语义:
方案一:双写+校验位(高性能首选)
这是DBMS领域实现原子写入的经典轻量方案,额外存储开销小,性能损耗极低。
- 核心逻辑:给每个目标long值分配两个连续的8字节存储位(记为A、B),再加1字节的校验标记位。写入时先写A、再写B,最后将校验位设为完成标记;读取时先校验标记,再对比A、B的值,确保一致性。
- 具体实现:
- 存储布局:在内存映射段中为每个long值预留17字节连续空间(8+8+1)
- 写入流程:
- 将long值写入位置A:
segment.set(offset, newValue) - 将相同long值写入位置B:
segment.set(offset+8, newValue) - 设置校验位为0x01:
segment.set(offset+16, (byte)0x01) - 调用
segment.asSlice(offset, 17).force()刷盘这17字节范围
- 将long值写入位置A:
- 读取流程:
- 读取校验位:
byte flag = segment.get(offset+16) - 若flag为0x01:对比A和B的值,一致则返回;不一致说明刷盘故障,可根据业务规则选择恢复策略(比如取A或触发修复)
- 若flag不为0x01:说明写入未完成,使用之前的有效旧值
- 读取校验位:
- 优缺点:
- 优点:仅多一次long写入和1字节操作,force()范围可控,性能接近原写入逻辑
- 缺点:占用额外17字节/每个long值,需要提前规划存储布局
方案二:复用预写日志(WAL)实现原子提交
既然你已经掌握WAL的实现,可以直接复用这套机制来保证原子性,无需额外调整存储布局。
- 核心逻辑:将long值的修改先写入WAL,待WAL刷盘完成后再修改内存映射段,最后在WAL中标记提交完成。崩溃恢复时,通过WAL日志的状态决定是否完成修改。
- 具体实现:
- WAL日志条目格式:
[日志长度(4字节)][目标offset(8字节)][新long值(8字节)][提交标记(1字节)] - 写入流程:
- 写入未提交的WAL日志(提交标记设为0),调用WAL的刷盘方法确保日志落盘
- 修改内存映射段的long值:
segment.set(targetOffset, newValue) - 调用
segment.asSlice(targetOffset, 8).force()刷盘目标区域 - 写入WAL的提交完成标记(提交标记设为1),再次刷盘WAL
- 恢复流程:
- 遍历WAL日志,找到提交标记为0的条目:检查目标offset的long值是否等于日志中的新值,若不等则重新写入;若已相等则补全提交标记
- 提交标记为1的日志直接跳过
- WAL日志条目格式:
- 优缺点:
- 优点:复用现有WAL实现,无需额外存储开销,原子性依赖WAL的日志完整性校验
- 缺点:多了两次WAL写入和刷盘操作,性能略低于双写方案
方案三:改用FileChannel原子写入(跨平台兼容)
如果操作系统支持8字节原子写入(主流Linux、Windows都支持),可以绕过MemorySegment的force()原子性问题,直接用FileChannel的写入方法。
- 核心逻辑:Java的
FileChannel.write(ByteBuffer, long)方法,当ByteBuffer中的字节数是操作系统支持的原子写入大小(8字节)时,操作系统会保证写入的原子性,再配合force()刷盘即可。 - 具体实现:
- 准备ByteBuffer:
ByteBuffer buf = ByteBuffer.allocate(8).putLong(newValue).flip() - 写入文件:
fileChannel.write(buf, targetOffset)(确保buf仅包含这8字节) - 强制刷盘:
fileChannel.force(true)(true表示刷盘元数据)
- 准备ByteBuffer:
- 注意事项:需要做跨平台兼容性测试,部分老旧系统可能不支持8字节原子写入
- 优缺点:
- 优点:无需额外存储,实现简单
- 缺点:相比内存映射,写入需要用户态到内核态的拷贝,性能略低,适合写入频率不极高的场景
额外注意事项
- 所有方案都必须保证刷盘范围的准确性,比如双写时要force()包含A、B和校验位的连续区域,避免部分刷盘导致的不一致
- 一定要模拟崩溃场景(比如kill进程、模拟断电)验证原子性,不能仅依赖理论
- 结合你已实现的锁机制,确保同一时间只有一个线程修改目标long值,避免并发写入破坏原子性
内容的提问来源于stack exchange,提问作者user1994405
相关产品推荐
相关产品推荐

