You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在自研Java DBMS中实现64位值的原子IO?

实现long型值写入文件的原子性方案(Java DBMS场景)

针对你用MemorySegment映射文件、force()无法保证8字节原子性的问题,结合DBMS的高性能需求,以下几个方案可以实现long值写入的“全或无”语义:

方案一:双写+校验位(高性能首选)

这是DBMS领域实现原子写入的经典轻量方案,额外存储开销小,性能损耗极低。

  • 核心逻辑:给每个目标long值分配两个连续的8字节存储位(记为A、B),再加1字节的校验标记位。写入时先写A、再写B,最后将校验位设为完成标记;读取时先校验标记,再对比A、B的值,确保一致性。
  • 具体实现:
    1. 存储布局:在内存映射段中为每个long值预留17字节连续空间(8+8+1)
    2. 写入流程:
      • 将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字节范围
    3. 读取流程:
      • 读取校验位: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日志的状态决定是否完成修改。
  • 具体实现:
    1. WAL日志条目格式:[日志长度(4字节)][目标offset(8字节)][新long值(8字节)][提交标记(1字节)]
    2. 写入流程:
      • 写入未提交的WAL日志(提交标记设为0),调用WAL的刷盘方法确保日志落盘
      • 修改内存映射段的long值:segment.set(targetOffset, newValue)
      • 调用segment.asSlice(targetOffset, 8).force()刷盘目标区域
      • 写入WAL的提交完成标记(提交标记设为1),再次刷盘WAL
    3. 恢复流程:
      • 遍历WAL日志,找到提交标记为0的条目:检查目标offset的long值是否等于日志中的新值,若不等则重新写入;若已相等则补全提交标记
      • 提交标记为1的日志直接跳过
  • 优缺点:
    • 优点:复用现有WAL实现,无需额外存储开销,原子性依赖WAL的日志完整性校验
    • 缺点:多了两次WAL写入和刷盘操作,性能略低于双写方案

方案三:改用FileChannel原子写入(跨平台兼容)

如果操作系统支持8字节原子写入(主流Linux、Windows都支持),可以绕过MemorySegment的force()原子性问题,直接用FileChannel的写入方法。

  • 核心逻辑:Java的FileChannel.write(ByteBuffer, long)方法,当ByteBuffer中的字节数是操作系统支持的原子写入大小(8字节)时,操作系统会保证写入的原子性,再配合force()刷盘即可。
  • 具体实现:
    1. 准备ByteBuffer:ByteBuffer buf = ByteBuffer.allocate(8).putLong(newValue).flip()
    2. 写入文件:fileChannel.write(buf, targetOffset)(确保buf仅包含这8字节)
    3. 强制刷盘:fileChannel.force(true)(true表示刷盘元数据)
  • 注意事项:需要做跨平台兼容性测试,部分老旧系统可能不支持8字节原子写入
  • 优缺点:
    • 优点:无需额外存储,实现简单
    • 缺点:相比内存映射,写入需要用户态到内核态的拷贝,性能略低,适合写入频率不极高的场景

额外注意事项

  • 所有方案都必须保证刷盘范围的准确性,比如双写时要force()包含A、B和校验位的连续区域,避免部分刷盘导致的不一致
  • 一定要模拟崩溃场景(比如kill进程、模拟断电)验证原子性,不能仅依赖理论
  • 结合你已实现的锁机制,确保同一时间只有一个线程修改目标long值,避免并发写入破坏原子性

内容的提问来源于stack exchange,提问作者user1994405

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 18:33:18