刚入职一家公司,需求下来了
需求由于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的前两位,为以后写正式的定时删除任务做准备吧。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)