【问题标题】:Best way to store large amount of data of users存储大量用户数据的最佳方式
【发布时间】:2023-04-08 08:08:02
【问题描述】:

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

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

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

最好的方法是什么?我有一个解决方案,但想问社区这是最好的方法。

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

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

我只考虑这个建议的解决方案。你们还有比这更好的吗?

编辑:

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

各位,你们所有的答案都很棒而且很有帮助。我想给每个人赏金,这就是我离开它的原因,以便社区可以自动提供。 谢谢大家的回答。我真的很感激。

【问题讨论】:

  • 我发现用户 ID(不变的值)是组织上传的更好方法。手动导航更难(查看文件夹不会告诉您谁在上传),但它可以让用户名更改而不会破坏与其对应的资产文件夹。
  • 因为您正在为每个文件创建一个数据库条目,所以您可以存储一个“存储卷 ID”,每次存储卷上的空间不足时您都会递增该 ID。获取文件后,您将获得用户 ID、文件哈希和存储卷名称,您可以将它们组合起来检索资产。我只是使用 Amazon S3,让他们处理这样的事情
  • 您还可以研究基于云的解决方案,例如 AWS S3,它会自动为您处理扩展。我们使用类似的结构(带有 id)来管理 S3 上的用户文件。
  • 我的一个下载解决方案是将所有内容保存在公共目录之外的文件夹中。上传时,它会使用路径保存到数据库中,即:/home/user/files/1/image.png,然后我根据文件名和插入 ID 创建一个哈希,然后将其保存到数据库中。检索只是使用 PHP,因此我们可以控制下载的内容和下载计数器。任何具有相同文件名的东西都没有关系。并且一定要研究 S3。
  • 请注意,如果您有很多(几千个)用户,那么根 i 节点可能会变大,所以一个简单的 ls 命令可能需要很长时间。所以也许创建子目录,比如/a/anakin 等可能是个好主意。

标签: php database linux file file-upload


【解决方案1】:

你能创建关系 MySQL 表吗?例如:

一个users 表和一个files 表。

您的 users 表将跟踪您(我假设)已经跟踪的所有内容:

idnameemail

然后文件表将存储类似:

id, fileExtension, fileSize, userID userID 将是指向files 表中的id 字段的外键。

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

例如:

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

【讨论】:

  • ,嗨,我没有使用 mysql,而是使用 mongodb。但这种方式也更好。表示您赞成数据库处理所有文件的详细信息。
【解决方案2】:

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

  1. 基于内容的识别
    1. 来自文件内容的 MD5
    2. 命名空间 UUID:v5 根据用户的 uuid 和文件的 md5 生成唯一标识符。
    3. 根据“实名”生成路径的自定义函数。
    4. 保存在数据库中:uuid、originalname(上传的名称)、realname(生成的名称)、filesize 和 mime。 (可选的添加日期和 md5)
  2. 文件检索。
    1. 用于检索元数据的 UUID。
    2. 根据实名重新生成文件路径。
    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. 也适用于单个文件夹结构。

注意:我使用 php 的 $_FILE 作为基于此问题标签的文件源示例。它可以来自任何文件源或生成的内容。

【讨论】:

    【解决方案3】:

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

    它是可扩展的,所以如果你添加另一个服务器就没有问题了,它还存储元数据,可以分块读取文件,它还内置了备份功能。

    【讨论】:

      【解决方案4】:

      我会根据文件名的哈希值、上传的日期和时间以及文件名的用户名生成一个 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 如果您的用户帐户已经有用户 ID,您可以将其添加到 URL,例如,如果我的用户 ID 为 123145,则网址为 http://link.to/your/site/123145/05c2d2f501e738b930885d991d136f1e.doc

      如果您将原始文件名保存到数据库中,您以后还可以提供一个下载脚本,为该文件提供其原始文件名以供下载,即使它在您的服务器上还有另一个文件名。

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

      如果您愿意,我也可以提供一个 PHP 示例 - 代码不应过多。

      【讨论】:

        【解决方案5】:

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

        但是由于它是另一个抽象级别,您需要编写自己进行低级别同步的函数,包括避免名称冲突、长路径名、每个文件夹的大文件数、每个实体的传输方便性、水平缩放等。所以这取决于你的应用程序需要有多复杂

        【讨论】:

          【解决方案6】:

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

          /files/jr/andomuser/456.jpg
          

          对于用户“jrandomuser”。

          请注意,由于用户名可能不会像 SHA-1 值那样随机分布,因此您稍后可能需要添加另一个级别。不过怀疑。

          【讨论】:

          • 您的想法确实令人印象深刻。我会考虑的。之后仍然存在的问题仍然是磁盘中的数据存储。如果在用户和保留文件夹的情况下如何解决。由于 amazon s3 不允许使用文件夹。如果他们这样做了,我将毫无问题地获取他们的存储空间,因为他们如何安排我的文件是他们的问题。
          【解决方案7】:

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

          File 表至少有:

          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 类型
          3. 实际存储位置(用于分布式存储系统)
          4. 下载次数(另一个表可能是更好的解决方案)

          等等……

          一些好处:

          1. 您无需计算文件大小、哈希、扩展名或任何文件元数据,因为您可能通过一次数据库操作即可获得它。
          2. 您可以通过单个SELECT ... GROUP BY ... WITH ROLLUP 语句获取每个用户的文件计数/已用空间/您写入File 表的任何内容的统计信息,这将比分析实际文件更快,这可能会分散在多个存储设备。
          3. 您可以为不同的用户应用文件访问权限。不会对表结构数据库进行重大更改。

          我不认为存储时需要原始文件名是一种选择,原因有二:

          1. 文件可能有名称,服务器操作系统文件系统不正确支持该名称,例如西里尔文。
          2. 两个不同的文件可能具有完全相同的名称,因此其中一个可能会被另一个覆盖。

          所以,有一个解决办法:

          1) 将上传到IDFile 的文件从INSERT 重命名为File 表。它是安全的,没有重复。

          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 是关于数据和分析的,它不是文件系统,如果我的拙见很重要,那么永远不应该以这种方式使用。

          【讨论】:

          • 看起来很像我的方法 ;) 是的,我也反对将二进制文件存储在数据库中!
          【解决方案8】:

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

          LDAP 以树状方式组织数据。

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

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

          【讨论】:

            【解决方案9】:
            1. Mongodb 用于存储实际文件名(例如:myImage.jpg)和其他属性(例如:MIME 类型),以及下面 2. 和 3. 中的 $random-text.jpg

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

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

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

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

            【讨论】:

              【解决方案10】:

              假设用户在数据库中有一个唯一的ID(Primary Key),如果ID为73的用户上传文件,保存如下:

              “上传/$userid_$filename.$ext”

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

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

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

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

              【讨论】:

                猜你喜欢
                • 2011-11-26
                • 2012-10-18
                • 2013-09-10
                • 1970-01-01
                • 2021-10-29
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多