一次恶心的删除minio文件之旅

一次恶心的删除minio文件之旅,第1张

刚入职一家公司,需求下来了

需求

由于minio占用空间极速扩大,目前已有3.5T,其中有一个桶buket1下的images目录(就是存放图片的)所占空间为1.5T,只保留最近几天的文件,两天内删除以前全部的文件。

分析

image目录下都是1KB-1MB的小文件,每天按日期yyyyMMdd产生一个目录,并且文件都放在各自的md5目录下,这就导致一个目录下存在几万甚至十几万个目录文件。
举个栗子:http://ip:port/buket1/images/20210601/526F6BCD5661D393CADE4E832523B5F8/wdfr543265ffd%26.jpg 就是minio文件的网络地址。
当然这些是我后来的分析。

时间紧,任务重,先不考虑删除2022年4月24日之前的文件

第一次尝试

最快的方式,一定是在服务器上用linux命令删除
于是我先是测试环境试了试
先在测试服务器上找到minio存放文件的根目录,发现在/lcn目录下有data1,data2,data3,data4四个目录,并且目录下的内容都相同,然后cd /lcn/data1/buket1/ | ls,果然有个images目录。
在生产服务器上也是一样的情况。
我果断在测试环境登录了4个窗口,分别cd 到images目录,rm -rf 20210601/,不一会儿就删除了20210601目录,在minio浏览器客户端看确实是删除了。
可是领导说,不能直接在服务器上删,因为以前有人这样 *** 作过,结果minio服务不能用了。
第一次尝试失败

第二次尝试

java代码
可是minio的java客户端只支持按完整的对象路径删除
这就需要先遍历对象,再删除对象
一顿 *** 作,写出一个遍历接口和一个批量删除接口

    /**
     * 遍历minio文件目录
     * @param bucketName 桶名称
     * @param prefix 限定文件目录前缀
     * @return
     */
    public Iterable<Result<Item>> iterateObjects(String bucketName, String prefix) {
        try {
            boolean exists = minIoClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            if (!exists) {
                return null;
            }
            Builder builder = ListObjectsArgs.builder().bucket(bucketName).recursive(true).prefix(prefix);
            Iterable<Result<Item>> iterable = minIoClient.listObjects(builder.build());
            return iterable;
        } catch (Exception e) {
            throw new MyMinioException(MyMinioErrorType.GET_FILEPATH_FAIL, bucketName, prefix, e.getMessage());
        }
    }
    
    /**
     * 批量删除minio文件
     * @param bucketName 桶名称
     * @param filePaths 文件目录
     */
    public void removeObjects(String bucketName, List<String> filePaths) {
        validateBucketName(bucketName);
        List<DeleteObject> list = new ArrayList<>();
        for (String filePath : filePaths) {
            validateFileLocation(filePath);
            list.add(new DeleteObject(filePath));
        }
        
        Iterable<Result<DeleteError>> iterable = minIoClient.removeObjects(
                RemoveObjectsArgs.builder().bucket(bucketName).objects(list).build()
                );
        try {
            for (Result<DeleteError> result : iterable) {
                DeleteError error = result.get();
                log.info("minio删除错误->bucketName={},objectName={},message={}", error.bucketName(), error.objectName(), error.message());
            }
        } catch (Exception e) {
            log.error("读取minio删除错误的数据时异常", e);
        }
    }

部分代码

public void deleteImages() {
    log.info("删除minio的buket1/images/文件:开始......");
    
    LocalDate startDate = LocalDate.of(2021, 6, 1);
    LocalDate endDate = LocalDate.of(2022, 4, 23);


    List<String> list = new ArrayList<>();
    for (;;) {
        if (startDate.isAfter(endDate)) {
            break;
        }
        String format = startDate.format(DateTimeFormatter.BASIC_ISO_DATE);
        list.add(format);
        startDate = startDate.plusDays(1L);
    }
    long count = 0L;
    for (String date : list) {
        long c = removeImages(date);
        count += c;
        log.info("images/{}/文件删除完毕,共{},总计{}", date, c, count);
    }
    log.info("删除minio的buket1/images/文件:完成......");
}

private long removeImages(String time) {
    final int BATCH_NUM = 200;
    long n = 0L;
    String prefix = "images/" + time + "/";
    String path = prefix;
    try {
        log.info("遍历->path->{}", path);
        Iterable<Result<Item>> iterable = myFileService.iterateObjects("buket1", path);
        if (iterable == null) {
            return n;
        }
        Iterator<Result<Item>> it = iterable.iterator();
        List<String> list = new ArrayList<>();
        while (it.hasNext()) {
            Item item = it.next().get();
            if (item.isDeleteMarker() || item.isDir()) {
                continue;
            }
            list.add(item.objectName());
            if (list.size() == BATCH_NUM) {
                myFileService.removeObjects("buket1", list);
                n += BATCH_NUM;
                list = new ArrayList<>();
                log.info("已删除buket1/{},[{}]个文件", path, n);
            }
        }
        if (!list.isEmpty()) {
            myFileService.removeObjects("buket1", list);
            n += list.size();
            log.info("已删除buket1/{},[{}]个文件", path, n);
        }
     } catch (Exception e) {
         log.error("minio-{}文件删除异常", path, e);
     }
    return n;
}

结果可想而知,一个对象也没遍历出来,超时了。
第二次尝试失败

第三次尝试

在生产服务器上 cd images/20210601 | ls
等啊等,等了5分钟,终于显示出满满一屏蓝色的目录,还溢出屏幕了
在prefix参数上搞一搞事情吧

private static final String[] PREFIX_STR = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};

private long removeImages(String time) {
    final int BATCH_NUM = 200;
    long n = 0L;
    String prefix = "images/" + time + "/";
    String path = "";
    for (int i = 0; i < PREFIX_STR.length; i++) {
        try {
            path = prefix + PREFIX_STR[i];
            log.info("遍历->path->{}", path);
            Iterable<Result<Item>> iterable = myFileService.iterateObjects("buket1", path);
            if (iterable == null) {
                return n;
            }
            
            Iterator<Result<Item>> it = iterable.iterator();
            List<String> list = new ArrayList<>();
            while (it.hasNext()) {
                Item item = it.next().get();
                if (item.isDeleteMarker() || item.isDir()) {
                    continue;
                }
                list.add(item.objectName());
                if (list.size() == BATCH_NUM) {
                    myFileService.removeObjects("buket1", list);
                    n += BATCH_NUM;
                    list = new ArrayList<>();
                    log.info("已删除buket1/{},[{}]个文件", path, n);
                }
            }
            if (!list.isEmpty()) {
                myFileService.removeObjects("buket1", list);
                n += list.size();
                log.info("已删除buket1/{},[{}]个文件", path, n);
            }
        } catch (Exception e) {
            log.error("minio-{}文件删除异常", path, e);
        }
    }
    return n;
}

这次虽然很慢,但好歹一些文件成功删除了,然而还是有很多超时。
第三次尝试算是失败了

第四次尝试

在prefix参数是再加一位

private static final String[] PREFIX_STR = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};

private long removeImages(String time) {
        final int BATCH_NUM = 200;
        long n = 0L;
        String prefix = "images/" + time + "/";
        String path = "";
        for (int i = 0; i < PREFIX_STR.length; i++) {
            try {
                for (int j = 0; j < PREFIX_STR.length; j++) {
                	path = prefix + PREFIX_STR[i] + PREFIX_STR[j];
                    log.info("遍历->path->{}", path);
                    Iterable<Result<Item>> iterable = myFileService.iterateObjects("buket1", path, startAfter);
                    if (iterable == null) {
                        return n;
                    }
                    
                    Iterator<Result<Item>> it = iterable.iterator();
                    List<String> list = new ArrayList<>();
                    while (it.hasNext()) {
                        Item item = it.next().get();
                        if (item.isDeleteMarker() || item.isDir()) {
                            continue;
                        }
                        list.add(item.objectName());
                        if (list.size() == BATCH_NUM) {
                            myFileService.removeObjects("buket1", list);
                            n += BATCH_NUM;
                            list = new ArrayList<>();
                            log.info("已删除buket1/{},[{}]个文件", path, n);
                        }
                    }
                    if (!list.isEmpty()) {
                        myFileService.removeObjects("buket1", list);
                        n += list.size();
                        log.info("已删除buket1/{},[{}]个文件", path, n);
                    }
                }
            } catch (Exception e) {
                log.error("minio-{}文件删除异常", path, e);
            }
        }
        return n;
    }

这次仍然很慢,好歹大多数没有超时,按这样的速度,没有几个月是删不完的/滑稽

第五次尝试

十个线程同时跑。
结果像第二次一样,都超时了。

第六次尝试

第四次日志显示已经删除20210601的所有文件了,到服务器上看看,哎,怎么还有这么多文件?
经过多番测试,终于找到原因了。
原来,minio遍历对象默认是编码url的,%被编码成了%25,而删除对象不会解码,所以只要对象名称带有%的都没删除掉。
改遍历代码,设置useUrlEncodingType为false

    /**
     * 遍历minio文件目录
     * @param bucketName 桶名称
     * @param prefix 限定文件目录前缀
     * @return
     */
    public Iterable<Result<Item>> iterateObjects(String bucketName, String prefix) {
        try {
            boolean exists = minIoClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            if (!exists) {
                return null;
            }
            Builder builder = ListObjectsArgs.builder().bucket(bucketName).recursive(true).prefix(prefix).useUrlEncodingType(false);
            Iterable<Result<Item>> iterable = minIoClient.listObjects(builder.build());
            return iterable;
        } catch (Exception e) {
            throw new MyMinioException(MyMinioErrorType.GET_FILEPATH_FAIL, bucketName, prefix, e.getMessage());
        }
    }
    
    /**
     * 批量删除minio文件
     * @param bucketName 桶名称
     * @param filePaths 文件目录
     */
    public void removeObjects(String bucketName, List<String> filePaths) {
        validateBucketName(bucketName);
        List<DeleteObject> list = new ArrayList<>();
        for (String filePath : filePaths) {
            validateFileLocation(filePath);
            list.add(new DeleteObject(filePath));
        }
        
        Iterable<Result<DeleteError>> iterable = minIoClient.removeObjects(
                RemoveObjectsArgs.builder().bucket(bucketName).objects(list).build()
                );
        try {
            for (Result<DeleteError> result : iterable) {
                DeleteError error = result.get();
                log.info("minio删除错误->bucketName={},objectName={},message={}", error.bucketName(), error.objectName(), error.message());
            }
        } catch (Exception e) {
            log.error("读取minio删除错误的数据时异常", e);
        }
    }

这次都可以删除了。但是好慢呀

第七次尝试

从数据库查询并解析html,提取对象路径,直接删除

private static final Pattern SRC_PATTERN = Pattern.compile("(?<=src=\"/bucket1/)images/[\S\s]+?(?=\")", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);

private static List<String> queryHtmlSpiderSrc(String html) {
    List<String> srcList = new ArrayList<>();
    if (StringUtils.isBlank(html)) {
        return srcList;
    }
    Matcher matcher = SRC_PATTERN.matcher(html);
    matcher.reset();
    while (matcher.find()) {
        srcList.add(StringUtils.deleteWhitespace(matcher.group()));
    }
    return srcList.stream().distinct().collect(Collectors.toList());
}

一个小时后,日志显示已经删除了好几天的。
终于完成了。
在服务器上看看,哎,怎么还有很多文件?
在确定从数据库解析的对象真的都删除了后,扒开屎一样的代码看看,呃,估计数据库里记录的对象只占三分之一。

第八次尝试

要是先在服务器上遍历出对象路径存放到文件里,java代码读取文件批量删除就好了。
说干就干,编写shell脚本,现学现用
最终写出了一坨屎

ceshi.sh

#! /bin/bash
# 遍历六月份的minio对象
month='202106'
arr=(0 1 2 3 4 5 6 7 8 9 A B C D E F)
parent_dir='images'
root_dir='/lcn/data1/buket1/images'
dir0=$(ls $root_dir | grep "^${month}")
for i in $dir0
do
	# 输出日志到文件
    echo $i >> /home/resultpath/ceshi-${month}.log
    d1=${root_dir}/${i}
    for ele in ${arr[@]}
    do
    # 输出日志到文件
	echo ${i}/${ele} >> /home/resultpath/ceshi-${month}.log
        dir1=$(ls $d1 | grep "^$ele")
	for j in $dir1
	do
	    d2=${root_dir}/${i}/$j
	    # ls -A 遍历除.和..以外的所有目录
	    dir2=$(ls -A $d2)
	    for p in $dir2
	    do
	    	# 输出对象路径到文件
	        echo ${parent_dir}/${i}/${j}/${p} >> /home/resultpath/ceshi-${month}.txt
	    done
	done
    done
done
echo '完成' >> /home/resultpath/ceshi-${month}.log

执行命令在后台运行

nohup sh ceshi.sh >/dev/null 2>&1 &

最后输出到/home/resultpath/ceshi-${month}.txt文件的内容格式为

images/20210601/526F6BCD5661D393CADE4E832523B5F8/wdfr543265ffd%26.jpg

改成这样,两天也删不完啊,怎么也得7天。

后面,我把minio的md5目录改为了取md5的前两位,为以后写正式的定时删除任务做准备吧。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/801536.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-07
下一篇 2022-05-07

发表评论

登录后才能评论

评论列表(0条)

保存