背景
多线程写入文件,要考虑线程同步问题,实现数据完整落盘磁盘备份。
操作系统:
win10:没问题
centos7:有问题
public static void writeFileLock(String content, String filePath) {
File file = new File(filePath);
RandomAccessFile raf = null;
FileChannel fileChannel = null;
FileLock fileLock = null;
try {
raf = new RandomAccessFile(file, "rw");
fileChannel = raf.getChannel();
while (true) {
try {
fileLock = fileChannel.tryLock();
if (fileLock != null) {
break;
}
} catch (Exception e) {
Thread.sleep(0);
}
}
raf.seek(raf.length());
raf.write(content.getBytes());
fileLock.release();
fileChannel.close();
raf.close();
} catch (Exception e) {
log.error("写文件异常", e);
log.error("写入文件路径:{}, 文件内容:{}", filePath, content);
}
}
RandomAccessFile建立文件连接符,raf获取文件管道,文件管道获取文件锁,tryLock方法有两个特点:第一、非阻塞,调用后立刻返回;第二、没拿到锁可能返回null,也可以能抛出异常,所以if判断循环获取,异常块捕获异常再重新尝试获取锁,注意Thread.sleep(0)的作用并不是睡0秒,而是马上加入到可执行队列,等待cpu的时间分片。
这段代码承载线上的kafka多线程备份消息的任务,用lock协调多线程的写入同步,埋点监控发现,备份数据偶发遗漏,大概2.3亿数据,会有5条偏差,就是漏了。
下面记录压测思路及过程。
准备
压测代码:
private static final ExecutorService FILE_THREADS = Executors.newFixedThreadPool(100);
public void execute(String... strings) throws Exception {
int cnt = 100 * 100 * 100;
int idx = 1;
long begin = 1574305200000L;
while (idx <= cnt) {
Map map = new HashMap<>();
map.put("id", idx);
map.put("time", begin);
String timeDirectory = DateUtil.getBeforeOneHour("yyyyMMddHHmm", 8, begin);
String mm = DateUtil.getBeforeOneHour("mm", 0, begin).concat(".txt");
String json = JsonUtil.getJosnString(map).concat(System.getProperty("line.separator"));
FILE_THREADS.execute(new PersistThread(timeDirectory, mm , json));
if (idx % 10000 == 0) {
begin += 60000L;
}
idx++;
}
}
private class PersistThread extends Thread {
String time;
String filename;
String content;
PersistThread(String time, String filename, String content) {
this.time = time;
this.filename = filename;
this.content = content;
}
@Override
public void run() {
String fold