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

我将用户的文件存储在他们自己的名字目录中

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

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

什么可能是最好的办法做到这一点。 我有一个解决scheme,但要问社区是最好的方法。

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

当任何人想要访问该文件时,我会通过php读取该文件,或者replace名称,或者将在这一点上做一些事情,以便文件被下载为原始文件名。

我只有这个build议的解决scheme。 你们还有其他比这更好的吗?

编辑:

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

你们所有的答案真棒,真的很有帮助。 我想给大家赏金,这就是为什么我离开它,让社区可以自动提供。 谢谢你的答案,我真的很感激。

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

  1. 基于内容的标识
    1. MD5从文件的内容
    2. 命名空间UUID:v5根据用户的uuid和文件的md5生成唯一的标识符。
    3. 自定义函数根据“真实名称”生成路径。
    4. 保存在数据库中:uuid,originalname(上传的名称),realname(生成的名称),filesize和mime。 (可选dateAdded,和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作为基于这个问题标签的文件源的例子。 它可以来自任何文件源或生成的内容。

你可以创建关系型MySQL表吗? 例如:

users表和files表。

你的用户表会跟踪你所有的事情(我假设)已经在跟踪:

idnameemail

然后文件表将存储如下所示:

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

那么当你保存你的文件时,你可以把它保存为idfileExtension并使用查询来拉取与该文件关联的用户或与用户关联的所有文件。

例如:

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

既然你已经使用MongoDB,我会建议检查GridFS。 这是一个规范,允许您将文件(即使大于16mb)存储到MongoDB集合中。

它是可扩展的,所以如果你添加另外一台服务器,它也会存储元数据,所以你不会有任何问题,它有可能以大块的形式读取文件,而且它也有内置的备份功能。

我将根据文件名,日期和上传日期和时间以及文件名的用户名生成GUID,将这些值以及文件的路径保存到数据库中供以后使用。 如果您生成这样一个GUID,文件名不能被猜测。

举例来说,让用户Daniel Steiner(我)在2013年4月23日上午十二点37分将一个名为resume.doc的文件上传到您的服务器。 这将给出Daniel_Steiner + 2013/23/04 + 00:37 + resume.doc的基础值,然后将其作为MD5散列05c2d2f501e738b930885d991d136f1e。 为了确保文件将在正确的程序中打开,我们将在之后添加正确的文件结尾,从而得到像http://link.to/your/site/05c2d2f501e738b930885d991d136f1e.doc这样的东西。如果您的使用帐号已经有用户标识,您可以将这些添加到网址,例如,如果我的用户ID是123145,则网址是http://link.to/your/site/123145/05c2d2f501e738b930885d991d136f1e.doc

如果将原始文件名保存到数据库中,稍后可以提供一个下载文件,该下载文件提供文件的下载文件的原始文件名,甚至可以在服务器上有另一个文件名。

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

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

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

但是由于它的另一个抽象层次,你需要编写自己做低级同步的函数,包括避免名称冲突,长路径名,每个文件夹大文件数,每个实体传输的容易性,水平缩放等等。你的应用程序需要多复杂

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

 /files/jr/andomuser/456.jpg 

为用户'jrandomuser'。

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

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

在这里输入图像描述

在哪里File表至少有:

在这里输入图像描述

IDFile是一个auto_increment列/主键。 UserID是可以为nullable外键。

对于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语句来获得每个用户所使用的文件计数/空间的每个用户的统计信息,而不是分析实际的文件,这可能会更快分散在多个存储设备上。
  3. 您可以为不同的用户申请文件访问权限。 这将不会造成表结构数据库的重大变化。

我不认为作为一个选项,原来的文件名需要在存储,由于两个原因:

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

所以,有一个解决方案:

1)将文件上传到IDFileINSERT重命名到File表中。 这是安全的,没有dublicates。

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中存储大量实际文件作为二进制内容的想法

数据库管理系统是关于数据和分析的,它不是一个文件系统,不应该以这种方式使用,如果我的意见很重要。

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

LDAP像时尚一样在树中组织数据。

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

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

  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造成安全风险。

亚马逊S3是可靠和便宜的,请注意与S3的“最终并发”。

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

“上传/ $ userid_ $文件名。$分机”

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

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

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

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