小程序解决大问题-物流系统磁盘爆满问题处理
晚上七点,煤矿调运的物流调度系统突然磁盘报名导致服务崩溃。系统用的是微服务,没有详细操作说明,也不敢动,运煤车辆排起了长队,只能联系厂家处理。好在经过30多分钟的处理,服务终于启动,系统运行正常。经过排查是一台后端服务器存储了车辆图片,导致磁盘满了,数据库停止运行。以前也有类似的问题,每次都会影响半个小时到一个小时。
作为程序员,分析了该服务器存储的图片都是抓取的原始图片,每张都在3-5M。十几万张图片如果压缩到几百k,磁盘容量问题就应该解决了。跟厂家沟通了下,图片只是最近七天的有用,而且大部分都在一天内已经传到第三方平台,可以压缩。
设计方案:
- 遍历 d://ftp,下面的文件及文件夹,找出 所有jpeg图片;
- 使用thumbnailator将图片压缩,并替换源文件;
- 计算压缩前后的文件夹大小;
- 计算压缩比例;
- 记录处理日志;
- 写入 windows 任务计划程序;
- 使用多线程,考虑生产服务器,严格限制CPU和内存占用。
压缩前:
压缩后:
压缩后,图片车牌依然可以被识别,不影响使用:
程序:
public class App {
private static final Logger logger = LoggerFactory.getLogger(App.class);
private static final int THREAD_POOL_SIZE = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
private static final AtomicInteger filesProcessed = new AtomicInteger(0);
private static int totalFilesToProcess = 0;
public static void main(String[] args) {
// 创建log文件夹
File logDir = new File("log");
if (!logDir.exists()) {
logDir.mkdir();
}
logger.info("线程池大小:{}", THREAD_POOL_SIZE);
Scanner scanner = new Scanner(System.in);
System.out.println("请输入文件夹路径(例如:d:\\ftp):");
String folderPath = scanner.nextLine();
File dir = new File(folderPath);
if (!dir.exists() || !dir.isDirectory()) {
logger.error("文件夹路径不存在: {}", folderPath);
return;
}
logger.info("开始处理文件夹: {}", folderPath);
totalFilesToProcess = countFiles(dir);
long originalSize = calculateDirectorySize(dir);
logger.info("原始文件夹大小: {}", formatBytes(originalSize));
logger.info("开始压缩图片");
long startTime = System.currentTimeMillis();
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
traverseDirectory(dir, executorService);
executorService.shutdown();
try {
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.error("线程池被中断: {}", e.getMessage());
}
long endTime = System.currentTimeMillis();
long compressedSize = calculateDirectorySize(dir);
logger.info("总耗时: {} 秒", (endTime - startTime) / 1000.0);
logger.info("压缩后文件夹总大小: {}", formatBytes(compressedSize));
double compressionRatio = (compressedSize == originalSize) ? 1.0 : (double) compressedSize / originalSize;
logger.info("压缩比例: {}%", String.format("%.2f", compressionRatio * 100));
logger.info("压缩图片完成");
}
public static void traverseDirectory(File dir, ExecutorService executorService) {
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
traverseDirectory(file, executorService);
} else if (isJpegFile(file.getName())) {
executorService.submit(() -> processImage(file));
}
}
}
}
private static boolean isJpegFile(String fileName) {
String lowerCaseName = fileName.toLowerCase();
return lowerCaseName.endsWith(".jpeg") || lowerCaseName.endsWith(".jpg");
}
private static void processImage(File file) {
compressImage(file);
int processed = filesProcessed.incrementAndGet();
if (processed % 100 == 0 || processed == totalFilesToProcess) {
logger.info("已处理 {} / {} 文件", processed, totalFilesToProcess);
}
}
public static void compressImage(File file) {
try {
Thumbnails.Builder<File> builder = Thumbnails.of(file);
java.awt.image.BufferedImage originalImage = builder.scale(1).asBufferedImage();
int originalWidth = originalImage.getWidth();
if (originalWidth > 1280) {
double scale = (double) 1280 / originalWidth;
builder.scale(scale)
.outputQuality(0.6)
.toFile(file);
logger.info("压缩图片: {}", file.getPath());
}
} catch (IOException e) {
logger.error("处理图片时出错: {} - {}", file.getPath(), e.getMessage());
}
}
/**
* 计算文件夹大小
* @param dir
* @return
*/
public static long calculateDirectorySize(File dir) {
long totalSize = 0;
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
totalSize += calculateDirectorySize(file);
} else {
totalSize += file.length();
}
}
return totalSize;
}
/**
* 转换文件大小为可读格式
* @param bytes
* @return
*/
public static String formatBytes(long bytes) {
String[] units = {"B", "KB", "MB", "GB", "TB"};
int i = 0;
while (bytes >= 1024 && i < units.length - 1) {
bytes /= 1024;
i++;
}
return String.format("%.2f %s", bytes / 1.0, units[i]);
}
/**
* 计算文件夹下图片总数量
* @param dir
* @return
*/
public static int countFiles(File dir) {
int count = 0;
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
count += countFiles(file);
} else if (file.getName().toLowerCase().endsWith(".jpeg") ||
file.getName().toLowerCase().endsWith(".jpg")) {
count++;
}
}
return count;
}
}
稳定运行两个月,磁盘空出200G。再也不用手忙脚乱地联系客服,重启服务了。