场景/现象描述
生产进行文件同步时,遇到一个文件传输相关的问题,代码逻辑大致如下:
- 从数据中筛选数据
- 将数据写入到指定文件放到本地服务器中
- 将本地服务器中的文件,使用ftp的方式传入到目标服务器中(其他系统)
逻辑很简单对吧,问题是这样:
发现目标服务器中出现较多的文件大小为40960B,且文件最后一行数据被截断了,并不完整。
我方本地服务器中的文件大小是41000+ 到42000+不等
即,文件传输并不完整。
排查过程
- 最开始时,认为是传输时有问题,文件上传是自己封装在jar包里的方法,传入文件本地路径、目标服务器地址+路径、用户名/密码就可以传输,内部逻辑有分片、断点续传等。排查与调试后发现jar是正确的,各种场景都可以正确上传。
- 后来怀疑是文件在传输时没写完,传输结束才写完。又因为系统架构是双机的,文件目录挂载了NAS,且步骤2与步骤3是用feign调用的(即走负载均衡),所以怀疑是NAS同步是否有延迟?就在代码逻辑的步骤2与步骤3前后都打印了一下文件MD5,发现文件MD5也是一致的,说明文件前后并没有发生变化。
- 再后来,发现走了弯路,不是说文件大小不对么,那么在步骤2/3前后都增加以下文件大小打印:
File file = new File();
logger.info("文件大小:"+file.length);
- 结果出乎预料,发现文件在步骤2结束时,feign调用步骤3进行传输前,大小就是40960B。
- 排查代码后发现,步骤2中写文件使用的是FileWriter 处理的文件,一眼看过去好像是没什么问题。
FileWriter fw = null;
try{
//步骤2--写文件
//步骤3--调用feign上传文件
}catch(Exception e){
//异常处理
}finally{
IoUtil.close(fw);
}
- 但仔细看就会发现,步骤2与步骤3都在一个try里面包裹,FileWriter 流关闭是在finally里面,且步骤2与步骤3中,并没有对FileWriter 进行flush处理。
- FileWriter 流关闭时,会自动调用一次flush方法,所以本地文件的大小是正确的,但执行步骤2与步骤3时,文件的大小就是40960B,因为剩下的一千多B还在缓冲区中,并没有被推出到文件中,因为没有调用flush方法。
解决方法
在步骤2与步骤3中间,增加一个flush调用,将缓冲区中最后一个部分的流推出到文件中。
fw.flush();
思考
造成问题的原因:
FileWriter 的缓冲区默认应该是8192(8K),FileWriter写文件时,每达到一次缓冲区大小,自动将缓冲区的内容推到文件中,例如:当文件总大小是42625B时,FileWriter 会自动将前5次推到文件中(40960B),第6次时,因为不满8192B,所以还在缓冲区中,直到调用flush时才推出(close方法会先flush一下再关闭流)。