本文转载自Java如何保证文件落盘?

导语

在之前的文章Linux/UNIX编程如何保证文件落盘中,我们聊了从应用到操作系统,我们要如何保证文件落盘,来确保掉电等故障不会导致数据丢失。JDK也封装了对应的功能,并且为我们做好了跨平台的保证。

JDK中有三种方式可以强制文件数据落盘:

  1. 调用FileDescriptor#sync函数
  2. 调用FileChannel#force函数
  3. 使用RandomAccessFilerws或者rwd模式打开文件

FileDescriptor#sync

FileDescriptor类提供了sync方法,可以用于保证数据保存到持久化存储设备后返回:

FileOutputStream outputStream = new FileOutputStream("/Users/mazhibin/b.txt");
outputStream.getFD().sync();

可以看一下JDK是如何实现FileDescriptor#sync的:

public native void sync() throws SyncFailedException;
// jdk/src/solaris/native/java/io/FileDescriptor_md.c
JNIEXPORT void JNICALL
Java_java_io_FileDescriptor_sync(JNIEnv *env, jobject this) {
// 获取文件描述符
FD fd = THIS_FD(this);
// 调用IO_Sync来执行数据同步
if (IO_Sync(fd) == -1) {
JNU_ThrowByName(env, "java/io/SyncFailedException", "sync failed");
}
}

IO_Sync在UNIX系统上的定义就是fsync

// jdk/src/solaris/native/java/io/io_util_md.h
#define IO_Sync fsync

FileChannel#force

之前的文章提到了,操作系统提供了fsync/fdatasync两个用户同步数据到持久化设备的系统调用,后者尽可能的会不同步文件元数据,来减少一次磁盘IO,提高性能。但是Java IO的FileDescriptor#sync只是对fsync的封装,JDK中没有对于fdatasync的封装,这是一个特性缺失。

Java NIO对这一点也做了增强,FileChannel类的force方法,支持传入一个布尔参数metaData,表示是否需要确保文件元数据落盘,如果为true,则调用fsync。如果为false,则调用fdatasync

使用范例:

FileOutputStream outputStream = new FileOutputStream("/Users/mazhibin/b.txt");

// 强制文件数据与元数据落盘
outputStream.getChannel().force(true); // 强制文件数据落盘,不关心元数据是否落盘
outputStream.getChannel().force(false);

我们来看看其实现:

public class FileChannelImpl extends FileChannel {
private final FileDispatcher nd;
private final FileDescriptor fd;
private final NativeThreadSet threads = new NativeThreadSet(2); public final boolean isOpen() {
return open;
} private void ensureOpen() throws IOException {
if(!this.isOpen()) {
throw new ClosedChannelException();
}
} // 布尔参数metaData用于指定是否需要文件元数据也确保落盘
public void force(boolean metaData) throws IOException {
// 确保文件是已经打开的
ensureOpen();
int rv = -1;
int ti = -1;
try {
begin();
ti = threads.add(); // 再次确保文件是已经打开的
if (!isOpen())
return;
do {
// 调用FileDispatcher#force
rv = nd.force(fd, metaData);
} while ((rv == IOStatus.INTERRUPTED) && isOpen());
} finally {
threads.remove(ti);
end(rv > -1);
assert IOStatus.check(rv);
}
}
}

实现中有许多线程同步相关的代码,不属于我们要关注的部分,就不分析了。FileChannel#force调用FileDispatcher#force

FileDispatcher是NIO内部实现用的一个类,封装了一些文件操作方法,其中包含了刷新文件的方法:

abstract class FileDispatcher extends NativeDispatcher {

    abstract int force(FileDescriptor fd, boolean metaData) throws IOException;

    // ...
}

FileDispatcher#force的实现:

class FileDispatcherImpl extends FileDispatcher
{ int force(FileDescriptor fd, boolean metaData) throws IOException {
return force0(fd, metaData);
} static native int force0(FileDescriptor fd, boolean metaData) throws IOException; // ...
}

FileDispatcher#force的本地方法实现:

JNIEXPORT jint JNICALL
Java_sun_nio_ch_FileDispatcherImpl_force0(JNIEnv *env, jobject this,
jobject fdo, jboolean md)
{
// 获取文件描述符
jint fd = fdval(env, fdo);
int result = 0; if (md == JNI_FALSE) {
// 如果调用者认为不需要同步文件元数据,调用fdatasync
result = fdatasync(fd);
} else {
#ifdef _AIX
/* On AIX, calling fsync on a file descriptor that is opened only for
* reading results in an error ("EBADF: The FileDescriptor parameter is
* not a valid file descriptor open for writing.").
* However, at this point it is not possibly anymore to read the
* 'writable' attribute of the corresponding file channel so we have to
* use 'fcntl'.
*/
int getfl = fcntl(fd, F_GETFL);
if (getfl >= 0 && (getfl & O_ACCMODE) == O_RDONLY) {
return 0;
}
#endif
// 如果调用者认为需要同步文件元数据,调用fsync
result = fsync(fd);
}
return handle(env, result, "Force failed");
}

可以看出,其实就是简单的通过metaData参数来区分调用fsyncfdatasync

RandomAccessFile结合rws/rwd模式

RandomAccessFile打开文件支持4中模式:

  • “r” 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
  • “rw” 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
  • “rws” 打开以便读取和写入,对于 “rw”,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
  • “rwd” 打开以便读取和写入,对于 “rw”,还要求对文件内容的每个更新都同步写入到底层存储设备。

其中rws模式会在open文件时传入O_SYNC标志位。rwd模式会在open文件时传入O_DSYNC标志位。

具体的源码分析参考:JDK源码阅读-RandomAccessFile

参考资料

Java如何保证文件落盘?的更多相关文章

  1. Linux/UNIX编程如何保证文件落盘

    本文转载自Linux/UNIX编程如何保证文件落盘 导语 我们编写程序write数据到文件中时,其实数据不会立马写入磁盘,而是会经过层层缓存.每层缓存都有自己的刷新时机,每层缓存都刷新后才会写入磁盘. ...

  2. Linux系统:保证数据安全落盘

    在很多IO场景中,我们经常需要确保数据已经安全的写到磁盘上,以便在系统宕机重启之后还能读到这些数据.但是我们都知道,linux系统的IO路径还是很复杂的,分为很多层,每一层都可能会有buffer来加速 ...

  3. Linux:保证数据安全落盘

    背景 在很多IO场景中,我们经常需要确保数据已经安全的写到磁盘上,以便在系统宕机重启之后还能读到这些数据.但是我们都知道,linux系统的IO路径还是很复杂的,分为很多层,每一层都可能会有buffer ...

  4. .net core 微服务之日志落盘设计

    原文:.net core 微服务之日志落盘设计 目录 1.设计目标 2.日志流程 3.串联请求事务 3.1 请求ID 3.2 处理服务器.服务 3.3 处理接口名 3.4 日志的发生时间 3.5 接口 ...

  5. java根据模板文件导出pdf

    原文:https://www.cnblogs.com/wangpeng00700/p/8418594.html 在网上看了一些Java生成pdf文件的,写的有点乱,有的不支持写入中文字体,有的不支持模 ...

  6. Java FtpClient 实现文件上传服务

    一.Ubuntu 安装 Vsftpd 服务 1.安装 sudo apt-get install vsftpd 2.添加用户(uftp) sudo useradd -d /home/uftp -s /b ...

  7. java中的文件读取和文件写出:如何从一个文件中获取内容以及如何向一个文件中写入内容

    import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.Fi ...

  8. java filechannel大文件的读写

    java读取大文件 超大文件的几种方法 转自:http://wgslucky.blog.163.com/blog/static/97562532201332324639689/   java 读取一个 ...

  9. java写入excel文件poi

    java写入excel文件 java写入excel文件poi,支持xlsx与xls,没有文件自动创建 package com.utils; import java.io.File; import ja ...

随机推荐

  1. js创建javaMap

    /** * Simple Map * var m = new Map(); * m.put('key','value'); * var v_otherMap = v_m.toMapString();* ...

  2. python--函数、参数、名称空间与作用域、匿名函数、内置函数、闭包

    python函数 函数定义 def welcome(): print('hello world!!') welcome() #函数调用 ...运行结果 hello world!! 函数定义和编写原则: ...

  3. linux系统权限管理拓展:特殊权限

    文件与目录权限设置不止读.写.执行这些,还有所谓的特殊权限,由于特殊权限会拥有一些"特权": 1 2 3 4 本章内容 SUID SGID SBIT 文件扩展权限ACL 1.SUI ...

  4. Linux-yum安装和相关命令

    Linux-yum安装和相关命令 一 yum yum命令是在Fedora和RedHat以及SUSE中基于rpm的软件包管理器,它可以使系统管理人员交互和自动化地更细与管理RPM软件包,能够从指定的服务 ...

  5. 手把手教你在容器服务 TKE 中使用动态准入控制器

    在 TKE 中使用动态准入控制器 原理概述 动态准入控制器 Webhook 在访问鉴权过程中可以更改请求对象或完全拒绝该请求,其调用 Webhook 服务的方式使其独立于集群组件,具有非常大的灵活性, ...

  6. 翻译:《实用的Python编程》01_01_Python

    目录 | 下一节 (1.2 第一个程序) 1.1 Python Python 是什么? Python 是一种解释型(译者注:区别于编译型)的高级语言, 通常被归类为 "脚本语言"  ...

  7. 【poj 1961】Period(字符串--KMP 模版题)

    题意:给你一个字符串,求这个字符串到第 i 个字符为止的重复子串的个数. 解法:判断重复子串的语句很重要!!if (p && i%(i-p)==0) printf("%d % ...

  8. 2019HDU多校 Round6

    Solved:2 02 Nonsense Time (LIS) 题意:给定一个全排列 最开始为空的 每秒中一个位置上的数出现 求每秒的LIS 题解:题解说 考虑时光倒流 倒着消掉 因为数据随机 所以期 ...

  9. Network of Schools POJ - 1236 有向强连通图

    //题意://给你n个学校,其中每一个学校都和一些其他学校有交流,但是这些边都是单向的.你至少需要给几个学校//传递消息可以使全部学校都收到消息,第二问你最少添加几条边可以使它变成一个强连通图//题解 ...

  10. Incorrect string value: '\xF0\x9F\x93\xB7</...' for column 'content' at row 1

    出现原因:当insert数据中有表情时发生.而这些表情是按照4个字节一个单位进行编码的,而我们使用的utf-8编码在mysql数据库中默认是按照3个字节一个单位进行编码的. 解决方法:将表字段字符集设 ...