简介
最近做了一个功能,是读取邮箱的附件文件并保存到本地的功能,使用的Java mail下工具包,使用imap协议进行读取。 在上到生产环境时,偶然会出现保存文件到本地会超时。经过排查发现这些文件都比较大,所以初步就认为是文件过大导致应用性能变差,所以保存超时。 然后就在测试过程中发现:10mb的文件10分钟都无法保存下来,1mb的文件也需要2分钟。而10kb左右的文件基本1秒钟就可保存完成。
排查过程
第一步:排查jvm性能情况
使用ps -ef | grep java查看应用pid(使用jps也可以)
使用jinfo pid 查看jvm的启动参数
使用jstat -gcutil pid 5000(每5s执行一次)
观察发现在保存到本地时,并没有发生YGC, FGC次数都没有增加
总结:说明内存,gc等没有限制文件保存
第二步:查看磁盘io情况
因为项目是docker环境,所以只验证了一下磁盘写速度,指令如下:
dd if=/dev/zero of=test bs=16k count=10000 oflag=direct
写速度基本可以保证在百兆每秒,所以也不是磁盘io的问题
第三步 日志
外部原因都已经排查完了,只好通过日志来查看到底是哪里慢了,先上代码
public static void saveFile(BodyPart fileBodyPart, Path path, Integer timeout) throws IOException {
// 写入文件
long timeoutMillis = timeout * 1000L;
File file = path.toFile();
long startTime = System.currentTimeMillis();
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
BufferedInputStream bis = new BufferedInputStream(fileBodyPart.getInputStream())) {
int len;
long maxTimeMillis = System.currentTimeMillis() + timeoutMillis;
byte[] bytes = new byte[1024 * 8];
while ((len = bis.read(bytes)) != -1 && System.currentTimeMillis() < maxTimeMillis) {
bos.write(bytes, 0, len);
}
log.info("保存文件{}到本地,耗时{}ms",file.getName(), System.currentTimeMillis() - startTime);
if (System.currentTimeMillis() >= maxTimeMillis && file.exists()) {
file.delete();
throw new IOException("文件保存超时");
}
} catch (Exception e) {
log.error("文件[{}]保存失败:[{}]", path.getFileName(), e);
throw new IOException("文件保存失败:", e);
} finally {
try {
if (fileBodyPart.getInputStream() != null) {
fileBodyPart.getInputStream().close();
}
} catch (MessagingException e) {
log.error("");
}
}
}
我在bos.write(bytes, 0, len);语句前后都增加了日志用以记录读取邮件inputStream,写入本地文件outputStream来查看到底是哪里慢。 排查后发现,bisRead会调用次数巨多,并且时间很慢!就此定位到问题:读取邮件的inputStream次数多且慢 这时候就去登上stackOverFlow,果然就查到了问题:stackoverflow.com/questions/4… 定位到mail.imap.partialfetch,mail.imap.fetchsize这两个参数。看下api介绍
mail.imap.fetchsize: int Partial fetch size in bytes. Defaults to 16K.
mail.imap.partialfetch: boolean Controls whether the IMAP partial-fetch capability should be used. Defaults to true.
所以imap协议默认每次都是部分读取,且读取大小为16k,这就能解释为何10k左右的文件读取速度ms级,而mb级别文件保存极慢的原因了。 知道问题了,那解决办法就清晰了,直接上代码了,把fetchSize调整到1m,数据读取立刻就快了
public Store createStore() throws MessagingException {
// 连接邮件服务器
String protocol = "imap";
Properties props = new Properties();
props.setProperty("mail.store.protocol", protocol);
props.setProperty("mail.imap.port", "993");
props.setProperty("mail.imap.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.setProperty("mail.imap.socketFactory.fallback", "false");
props.setProperty("mail.imap.partialfetch","false");
props.setProperty("mail.imap.fetchsize", "1048576");
Session session = Session.getInstance(props);
Store store = session.getStore(protocol);
store.connect("xxhost", 993, "xxuserName", "xxpassword");
return store;
}