如何在 2 天后自动删除上传的文件

How to delete uploaded files automatically after 2 days

提问人:danial 提问时间:11/17/2023 最后编辑:danial 更新时间:11/19/2023 访问量:98

问:

我有一个聊天应用程序,用户可以在其中相互发送文件。每个文件必须在 2 天后删除。

为此,我每 4 小时递归检查一次所有文件,并删除已创建超过 2 天的文件。

但是这项工作会消耗大量服务器的 CPU,这会扰乱应用程序的工作。因为要检查的文件数量非常大,而且所有文件的大小都在 10 GB 左右。

现在,如果每个文件上传后,都会激活一个计时器以在 2 天后删除该文件,这样会更好吗?考虑到几乎每秒都会上传一个文件,并且计时器的数量可能非常高。

我的递归解决方案:

const fs = require('fs')
const path = require('path')
const rootDir = require('../config').rootDir
const log = require('../da/log');

const DIRECTORY_MESSAGES = rootDir + '/file/messages';
const DIRECTORY_AVATARS = rootDir + '/file/avatars';
const INTERVAL_MINUTES_MESSAGES = 240;
const INTERVAL_MINUTES_AVATARS = 265;
const ACCESS_DAYS_MESSAGES = 2;
const ACCESS_DAYS_AVATARS = 10;
/*
* Remove files that access times is older than {fileAccessHours}.
* Runs every {removeEveryMinutes}
* */
module.exports = {
    start: function () {
        setInterval(deleteFiles.bind(this, DIRECTORY_MESSAGES, ACCESS_DAYS_MESSAGES), INTERVAL_MINUTES_MESSAGES * 60_000)
        setInterval(deleteFiles.bind(this, DIRECTORY_AVATARS, ACCESS_DAYS_AVATARS), INTERVAL_MINUTES_AVATARS * 60_000)
    }
}

function deleteFiles(dir, accessDays) {
    walkDir(dir, function (path, isDirectory) {
        try {
            if (isDirectory) {
                if (path === dir) {
                    return;
                }
                return fs.rmdir(path, function (err) {
                    if (err) {
                        return log.error(__filename + ' :deleteFiles, fs.rmdir, ' + err);
                    }
                });
            } else {
                fs.stat(path, function (err, stat) {
                    try {

                        const now = new Date().getTime();
                        const endTime = new Date(stat.atime).getTime() + accessDays * 86_400_000;
                        if (err) {
                            return log.error(__filename + ' :deleteFiles, fs.stat, ' + err);
                        }
                        if (now > endTime) {
                            return fs.unlink(path, function (err) {
                                if (err) {
                                    return log.error(__filename + ' :deleteFiles, fs.unlink, ' + err);
                                }
                            });
                        }
                    } catch (err) {
                        console.log('File Deleter: walkDir: callback: fs.stat: ' + err);
                    }
                });
            }
        } catch (err) {
            console.log('File Deleter: walkDir: callback' + err);
        }
    });
}

function walkDir(dir, callback) {
    try {
        let files = fs.readdirSync(dir);
        if (files.length === 0) {
            callback(dir, /* isDirectory */ true);
        } else {
            files.forEach(f => {
                let dirPath = path.join(dir, f);
                let isDirectory = fs.statSync(dirPath).isDirectory();
                isDirectory ? walkDir(dirPath, callback) : callback(path.join(dir, f), /* isDirectory */ false);
            });
        }
    } catch (err) {
        console.log('File Deleter: walkDir: ' + err);
    }
}
JavaScript 节点 .js 文件 服务器 计时器

评论

3赞 A R K 11/17/2023
我认为最好的方法是添加一个单独的 cron 作业或其他一些定期运行的作业并执行清理逻辑,理想情况下,如果它会影响 CPU/内存,我们不应该在应用程序代码中添加这种逻辑,最好创建一个单独的作业可能是定期运行并进行清理的 cron 作业

答:

2赞 T.J. Crowder 11/17/2023 #1

现在,如果每个文件上传后,都会激活计时器以在 2 天后删除该文件,这会更好吗?

“更好”是一个狡猾的概念,但请考虑一下,如果您的服务器崩溃并重新启动,或者您必须关闭它进行升级等,会发生什么:这些计时器丢失了,您必须再次执行目录遍历操作。

这似乎是一个简单的数据库应用程序。它可以是由文件(例如 DB)支持的进程内数据库,或者现在让适当的数据库进程也非常简单。您可以在上传时记录文件的日期/时间和路径,然后定期查询数据库以查看哪些确切文件已过期并需要删除。无需目录遍历。如果使用单独的数据库(而不是进程内),清理甚至可以像 A R K 建议的那样是一个单独的进程,而不是占用服务器的主 Node.js 线程。sqlite

0赞 Jasen 11/17/2023 #2

做目录遍历,但不要重新发明轮子。例如,使用 GNU find:

 find /path/to/directory -type f -ctime +2 -delete

从 cron 运行它。

0赞 Amit 11/17/2023 #3

(设计视角)

我会创建一个“过期”对象队列 + 一个清空它的“Deleter”工作线程:

struct Expiration
{
    Time expiration_date_and_time; // On construction set Now() + 48h
    String full_path;
}
  1. 在上传的每个文件上,将一个对象添加到队列的后面,并将队列存储在文件中以进行持久化(崩溃/重启方案)。如果队列为空,则在添加后唤醒“Deleter”worker-thread。

  2. “Deleter”worker-thread 重复删除队列的 Front,直到 Front 尚未过期(如果同时上传了多个文件)并更新持久性文件。之后,它进入睡眠状态,直到删除队列的前面。

  3. 启动时:“Deleter”worker-thread 从持久性文件加载队列并转到步骤 2。

--

  • 确保使用互斥锁保护队列的访问,因为它充当文件添加线程和负责文件删除的删除者工作线程的共享资源。