存储大量用户数据的最佳方式

时间:2013-04-03 21:55:23

标签: php database linux file file-upload

我将用户的文件存储在自己的名称目录中,如

/username/file01.jpg
/username/file02.mp4
/username/file03.mp3

但是如果有更多用户来上传更多文件,那么这会产生问题,因为这会导致一些或多个用户迁移到另一个驱动器。我首先选择用户名目录解决方案,因为我不希望混合文件名。我也不想改变文件名。此外,如果另一个用户上传相同的文件名,那么如果文件以原始名称存储,则会产生问题。

这可能是最好的方法。我有一个解决方案,但想问社区这是最好的方式。

我将使用顺序文件夹,然后将文件名散列为非常独特的东西并存储到目录中。 我将做的是将文件和用户名的原始名称存储到数据库中,并将文件的哈希值存储在磁盘中。

当有人想要访问该文件时,我将通过php读取该文件,或者替换名称或者在该点执行某些操作,以便将文件作为原始文件名下载。

我只考虑了这个建议的解决方案。你们有没有比这更好的其他人。

编辑:

我也使用文件夹系统,可能第二种方式我将使用虚拟文件夹。 我的数据库是MongoDB

伙计们你所有的答案都很棒,非常有帮助。我想给每个人一个赏金,这就是为什么我离开它以便社区可以自动提供。 谢谢大家的回答。我真的很感激。

10 个答案:

答案 0 :(得分:9)

你能创建关系MySQL表吗? e.g:

users表和files表。

您的用户表会跟踪您已经跟踪的所有内容(我假设):

idnameemail

然后文件表将存储如下内容:

idfileExtensionfileSizeuserID< ---- userID将是指向id的外键files表中的字段。

然后当您保存文件时,可以将其保存为idfileExtension并使用查询来提取与该文件关联的用户或与用户关联的所有文件。

e.g:

SELECT users.name, files.id, files.extension
FROM `users`
INNER JOIN `files` on users.id = files.userID;

答案 1 :(得分:7)

我处理数据库上的文件元数据并使用UUID检索文件。我所做的是:

  1. 基于内容的识别
    1. 来自文件内容的MD5
    2. Namespaced UUID:v5根据用户的uuid和文件的md5生成唯一标识符。
    3. 根据'realname'生成路径的自定义功能。
    4. 保存在数据库上:uuid,originalname(上传的名称),realname(生成的名称),filesize和mime。 (可选dateAdded和md5)
  2. 文件回溯。
    1. UUID以检索元数据。
    2. 根据realname重新生成文件路径。
    3. 原始名称用于向下载文件的用户显示熟悉的名称。
  3. 我处理文件的名称,为其分配一个命名空间的UUID作为数据库主键,并根据用户和文件名生成路径。前提条件是您的用户已分配给他的uuid。以下代码将帮助您避免数据库中的ID冲突,并帮助您通过其内容识别文件(如果您需要找到重复内容而不是必需的文件名)。

    $fileInfo = pathinfo($_FILE['file']['name']);
    $extension = (isset($fileInfo['extension']))?".".$fileInfo['extension']:"";
    
    $md5Name = md5_file($_FILE['file']['tmp_name']); //you could use other hash algorithms if you are so inclined.
    
    $realName = UUID::v5($user->uuid, $md5Name) . $extension; //UUID::v5(namespace, value).
    

    我使用函数根据一些自定义参数生成文件路径,您可以使用$ username和$ realname。如果您实现可能已在文件命名方案或任何自定义方案上进行分区的分布式文件夹结构,这将非常有用。

    function generateBasePath($realname, $customArgsArray){
        //Process Args as your requirements.
        //might as well be  "$FirstThreeCharsFromRealname/"
        //or a checksum that helps you decide which drive/volume/mountpoint to use.
        //like some files on the local disk and some other from an Amazon::S3 mountpoint.
        return $mountpoint.'/'.$generatedPath; 
    }
    

    作为额外奖励,这也是:

    1. 可帮助您维护版本化文件存储库,如果您在文件的记录中添加了已替换的文件(uuid)的属性。
    2. 如果添加“所有者”和/或“群组”
    3. 的属性,请创建应用程序访问控制列表
    4. 也适用于单个文件夹结构。
    5. 注意:我使用php的$ _FILE作为基于此问题标签的文件源示例。它可以来自任何文件源或生成的内容。

答案 2 :(得分:5)

由于您已经使用过MongoDB,我建议您查看GridFS。它是一个允许您将文件(即使它们大于16mb)存储到MongoDB集合中的规范。

它是可扩展的,因此如果您添加另一台服务器,它也会没有问题,它还可以存储元数据,可以读取块中的文件,还具有内置的备份功能。

答案 3 :(得分:3)

我会根据文件名的散列,上传的日期和时间以及文件名的用户名生成GUID,保存这些值,以及数据库中文件的路径供以后使用。如果生成这样的GUID,则无法猜到文件名。

作为示例,让用户Daniel Steiner(我)在2013年4月23日上午12点37分将一个名为resume.doc的文件上传到您的服务器。这将给出一个基值 Daniel_Steiner + 2013/23/04 + 00:37 + resume.doc然后将作为MD5哈希05c2d2f501e738b930885d991d136f1e。为了确保文件将在正确的程序中打开,我们将在之后添加正确的文件结尾,因此将获得类似http://link.to/your/site/05c2d2f501e738b930885d991d136f1e.doc的内容如果您的useraccounts已经有用户ID,您可以将这些添加到URL,例如,如果我的用户ID为123145,则网址为http://link.to/your/site/123145/05c2d2f501e738b930885d991d136f1e.doc

如果您将原始文件名保存到数据库,以后您还可以提供一个下载文件,为文件提供其原始文件名以供下载,即使服务器上有另一个文件名也很难。

如果您可以使用符号链接,则将文件重新定位到另一个硬盘上也不是问题。

如果你愿意,我也可以提出一个PHP示例 - 不应该是太多的代码。

答案 4 :(得分:2)

由于文件系统是树,而不是图形(分面分类),因此很难想出一些方法来轻松表示多个实体,如用户,媒体类型,日期,事件,图像裁剪类型等。这就是为什么使用关系数据库更容易 - 它可以转换为图形。

但是由于它是另一个抽象层次,你需要编写自己进行低级同步的函数,包括避免名称冲突,长路径名,每个文件夹的大文件数,每个实体的转移容易程度,水平扩展等。因此,这取决于您的应用程序需要多么复杂

答案 5 :(得分:2)

另一种策略是创建一个二维结构,其中第一级目录是用户名的前2个字符,然后第二级是剩余字符(类似于Git如何存储其SHA-1对象ID)。例如:

/files/jr/andomuser/456.jpg

用户'jrandomuser'。

请注意,由于用户名可能不会像SHA-1值一样随机分发,因此您可能需要稍后再添加其他级别。不过对此表示怀疑。

答案 6 :(得分:2)

我建议使用以下数据库结构:

enter image description here

File表至少有:

enter image description here

IDFileauto_increment列/主键。 UserIDnullable外键。

对于FK_File_User我建议:

ON UPDATE NO ACTION -- IDUser is auto_increment too. No changes need to be tracked.
ON DELETE SET NULL  -- If user deleted, then File is not owned. Might be deleted
                    -- with CRON job or something else.

但是,可能会在File表中添加其他列:

  1. 实际上传日期和时间
  2. 实际mime-type
  3. 实际存储位置(适用于分布式存储系统)
  4. 下载次数(另一张表可能是更好的解决方案)
  5. 等...

    一些好处:

    1. 您不需要计算文件大小,散列,扩展名或任何文件元数据,因为您可以通过一次数据库操作来获取它。
    2. 您可以通过单个File语句获取每个用户使用的文件计数/空间/您写入SELECT ... GROUP BY ... WITH ROLLUP表的任何内容的统计信息,并且它比分析实际文件更快可能分布在多个存储设备上。
    3. 您可以为不同的用户应用文件访问权限。表格结构数据库的成本不会很大。

    4. 我不认为存储需要原始文件名,因为有两个原因:

      1. 文件可能具有名称,服务器操作系统文件系统未正确支持该名称,如Cyrillic文件系统。
      2. 两个不同的文件可能具有完全相同的名称,因此其中一个可能会被另一个文件覆盖。
      3. 所以,有一个解决方案:

        1)将文件从IDFile上传到INSERTFile表格时重命名文件。这是安全的,没有共和党人。

        2)在需要/下载时恢复文件的名称,如:

        // peform query to "File" table by given ID
        
        list($name, $ext, $size, $md5) = $result->fetch_row();
        
        $result->free();
        
        header('Content-Length: ' . $size);
        header('Content-MD5: ' . $md5);
        header('Accept-Ranges: bytes');
        header('Connection: close');
        header('Content-Type: application/force-download');
        header('Content-Disposition: attachment; filename="' . $name . '.' . $ext . '"');
        
        // flush file content
        

        3)实际文件可能存储在单个目录中(因为IDFile是安全的)而IDUser - 命名子目录 - 取决于具体情况。

        4)由于IDFile是直接序列,如果某些文件丢失,您可以通过评估实际文件名序列的缺失段来获取其数据库元。然后,您可以“通知所有者”,“删除文件元”或这两个操作。


        我反对将大型实际文件存储在DBMS本身作为二进制内容的想法。

        DBMS是关于数据和分析的,它不是文件系统,如果我的拙见很重要,就不应该以这种方式使用。

答案 7 :(得分:1)

您可以安装LDAP服务器。 LDAP查找速度非常快,因为它针对繁重的读取操作进行了高度优化。您甚至可以查询数据

LDAP以类似时尚的方式组织数据。

您可以按照以下示例组织数据“user-> IP地址 - >文件夹 - >文件名”。这种方式文件可以在物理/地理上分散,您可以非常快速地获取位置。

您也可以使用标准LDAP查询进行查询,例如获取特定用户的所有文件列表或获取文件夹中的文件列表等。

答案 8 :(得分:0)

  1. Mongodb用于存储实际文件名(例如:myImage.jpg)和其他属性(例如:MIME类型),以及来自2的$random-text.jpg。 3.下面

  2. 生成一些$random-text,例如:base_convert(mt_rand(), 10, 36)uniqid($username, true);

  3. 将文件实际存储为$random-text.jpg - 始终保持相同的扩展名

  4. 注意:使用filter_var()确保输入文件名不会给Mongodb带来安全风险。

  5. Amazon S3可靠且便宜,请注意S3的“最终并发”。

答案 9 :(得分:0)

假设用户在数据库中有唯一ID(主键),如果ID为73的用户上传文件,请将其保存为:

“上传/ $ $ userid_文件名。$ EXT”

例如,73_resume.doc,73_myphoto.jpg

现在,在获取文件时,请使用以下代码:

foreach (glob("uploads/$userid_*.*") as $filename) {
    echo $filename;
}

这可以与散列解决方案(存储在数据库中)结合使用,因此获取下载路径为73_photo.jpg的用户不会在浏览器地址栏中随机尝试74_photo.jpg。