记一次文件删除磁盘空间未释放问题

故事发生

公司和某知名数据服务商合作进行联合建模数据采购,需要将海量数据通过文件方式外发给数据服务商。合作方有文件大小要求,我们将文件切割成很多个小文件发送给合作方。受限于带宽我们没有并行发送,直接串行发送。步骤:遍历小文件列表->FTP下载小文件->合作方sdk切割校验小文件->小文件切割后的文件外发合作方oss->删除文件。晚上8点触发任务执行,发现磁盘空间使用率有明显上升趋势,以为是文件发送前文件校验log过多导致的,和SRE确定了文件清理策略,也明确了日志确实有在被清除。10点多去看发现log日志对比8点数据量没有太大变化占用约24G,但是磁盘剩余空间20分钟内就减少了3个G,很是异常

排查

因为我们是文件操作,第一个怀疑就是文件外发完之后没有删除,然后去看代码,文件删除代码看不出问题,外发文件操作的文件流也都被正常关闭了。
于是到机器上看倒是是哪里增加了文件占用磁盘;
用du命令一路查询,发现是占用磁盘最大的目录除了日志文件,就是我们下载FTP文件的目录,难道真的是没有删除文件吗?
···
du -h --max-depth=1
···
打开下载FTP文件目录发现是空的,文件已经找不到了,但是占用了大量磁盘空间;

lsof | grep deleted

在这里插入图片描述
发现存在大量删除文件的僵尸进程;到这里就基本可以确定还是代码的问题了。虽然代码的file.delete方法没有打印delete的结果,那么为什么会有删除失败情况?
一般文件删除失败都是文件流没有正常关闭之类的原因导致的。
进一步排查,发现切割小文件的代码都是正常的,使用的try with resources处理的IO流
在这里插入图片描述
怀疑这段代码try with resources close流的顺序会影响文件关闭;
但是PrintWriter利用的资源是String资源 不会持有占用上层的BufferedReader流资源,也没有关闭顺序问题。
然后继续去找。脑子已经不够用了,oss上传直接调用的文件全路径上传的,有没有可能阿里云的oss组件占用了文件流没有释放?
这个可能是有的,但是如果是阿里云oss的问题,那早该被发现了,不甘心追了一下源码
com.aliyun.oss.internal.OSSUploadOperation.Task#call这个线程类使用的也是try-with-resources 关闭流操作也没有问题

@Override
        public PartResult call() throws Exception {
            PartResult tr = null;
            InputStream instream = null;

            try {
                UploadPart uploadPart = uploadCheckPoint.uploadParts.get(partIndex);
                tr = new PartResult(partIndex + 1, uploadPart.offset, uploadPart.size);

                instream = new FileInputStream(uploadCheckPoint.uploadFile);
                instream.skip(uploadPart.offset);

                UploadPartRequest uploadPartRequest = new UploadPartRequest();
                uploadPartRequest.setBucketName(uploadFileRequest.getBucketName());
                uploadPartRequest.setKey(uploadFileRequest.getKey());
                uploadPartRequest.setUploadId(uploadCheckPoint.uploadID);
                uploadPartRequest.setPartNumber(uploadPart.number);
                uploadPartRequest.setInputStream(instream);
                uploadPartRequest.setPartSize(uploadPart.size);

                Payer payer = uploadFileRequest.getRequestPayer();
                if (payer != null) {
                    uploadPartRequest.setRequestPayer(payer);
                }
                
                int limit = uploadFileRequest.getTrafficLimit();
                if (limit > 0) {
                    uploadPartRequest.setTrafficLimit(limit);
                }

                UploadPartResult uploadPartResult  = uploadPartWrap(uploadCheckPoint, uploadPartRequest);

                if(multipartOperation.getInnerClient().getClientConfiguration().isCrcCheckEnabled()) {
                    OSSUtils.checkChecksum(uploadPartResult.getClientCRC(), uploadPartResult.getServerCRC(), uploadPartResult.getRequestId());
                    tr.setPartCRC(uploadPartResult.getClientCRC());
                    tr.setLength(uploadPartResult.getPartSize());
                    uploadPart.crc = uploadPartResult.getClientCRC();
                }
                PartETag partETag = new PartETag(uploadPartResult.getPartNumber(), uploadPartResult.getETag());
                uploadCheckPoint.update(partIndex, partETag, true);
                if (uploadFileRequest.isEnableCheckpoint()) {
                    uploadCheckPoint.dump(uploadFileRequest.getCheckpointFile());
                }
                ProgressPublisher.publishRequestBytesTransferred(progressListener, uploadPart.size);
            } catch (Exception e) {
                tr.setFailed(true);
                tr.setException(e);
                logException(String.format("Task %d:%s upload part %d failed: ", id, name, partIndex + 1), e);
            } finally {
                if (instream != null) {
                    instream.close();
                }
            }

            return tr;
        }

那么问题到底出在哪?还有哪里操作了文件?
找到所有操作文件的地方,还有1个:文件切割前遍历文件格式;
在这里插入图片描述
这段代码看起来也很简单,也没有啥问题。
那问题在哪?难道是操作File的对象的时候file对象不是只保留了一个指针,直接操作了文件?
File对象肯定是只保留了文件的指针地址的!不要瞎猜
那么问题出在哪?想到了代码中还有计算文件总数量的操作,于是去看了计算总数量的代码:
···
long fileLineNum = Files.lines(file).count()
···
啊哈 Files.lines(file)返回了一个Stream,但是这个Stream流没有释放
于是将代码改为

try (Stream<String> stringStream = Files.lines(file)) {
                    fileLineNum = stringStream.count();
                } catch (Exception e) {
                    logger.error("获取数据文件行数失败 msg:{}", e.getMessage(), e);
                    return null;
                }

搞定!再次触发外发果然没有问题了
所以问题的本质是Files.lines没有关闭流

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

游语

对你有帮助,可以请我喝杯奶哦

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值