【问题标题】:How to deal with issues when storing uploaded files in the file system for a web app?Web应用程序在文件系统中存储上传的文件时如何处理问题?
【发布时间】:2020-05-14 12:29:54
【问题描述】:

我正在构建一个 Web 应用程序,用户可以在其中创建报告,然后为创建的报告上传一些图像。当用户单击报告页面上的按钮时,这些图像将在浏览器中呈现。这些图像是机密的,只有授权用户才能访问它们。

我知道将图像存储在数据库、文件系统或亚马逊 S3 等服务中的优缺点。对于我的应用程序,我倾向于将图像保留在文件系统中,并将图像的路径保留在数据库中。这意味着我必须处理围绕分布式事务管理出现的问题。我需要一些关于如何处理这些问题的建议。

1- 我认为正确的解决方案之一是使用 JTA 和 XADisk 等技术。我对这些技术不是很了解,但我相信 2 阶段提交是如何实现自动性的。我使用 MySQL 作为数据库,似乎 MySQL 支持 2 阶段提交。这种方法的问题是 XADisk 似乎不是一个活跃的项目,并且没有太多关于它的文档,而且事实上我对这种方法的来龙去脉不是很了解。我不确定是否应该投资这种方法。

2- 我相信我可以解决一些因违反我的应用程序的 ACID 属性而引起的问题。上传图片时,我可以先将文件写入磁盘,如果此操作成功,我可以更新数据库中的路径。如果数据库事务失败,我可以从磁盘中删除文件。我知道这仍然不是防弹的;在数据库事务之后可能会发生电力短缺,或者磁盘可能暂时没有响应等......我知道还有并发问题,例如,如果一个用户试图修改上传的图像而另一个用户试图在同时,也会出现一些问题。我的应用程序中并发更新的机会仍然相对较低。

如果发生这种异常情况,我相信我可以忍受磁盘上的孤立文件或数据库上的孤立图像路径。如果文件路径存在于 db 而不是文件系统中,我可以在报告页面上向用户显示通知,他可能会尝试重新上传图像。文件系统中的孤立文件不会有太大问题,我可能会不时运行一个进程来检测这些文件。不过,我对这种方法不是很满意。

3- 最后一个选项可能是根本不在数据库中存储文件路径。我可以构建文件系统,以便可以在代码中推断文件路径并一次加载所有图像。例如,我可以为每个报告创建一个名为报告 ID 的文件夹。当请求加载报告的图像时,我可以立即加载图像,因为我知道报告 ID。这最终可能会导致文件系统中有大量文件夹,我不确定这样的设计是否可以接受。在这个方案中仍然存在并发问题。

对于我应该遵循哪种方法,我将不胜感激。

【问题讨论】:

  • 任何解决方案都有利有弊。我会选择你可以先开始工作并从那里适应的解决方案。我也不会担心文件上的事务,只需存储到磁盘并偶尔清理一次。 Linux 可以告诉您上次访问文件的时间。
  • @lfmunoz 感谢您的建议。实际上,我现在最终将图像存储为 LOB,我可以非常快速地完成这项工作。如果我看到需要,我会更改实现。

标签: database transactions jta xadisk


【解决方案1】:

我相信你正在努力做到超正确,也许不需要那么多,但我前段时间也遇到过类似的情况,也探索了不同的可能性。我不喜欢与您的选项 1 一致的选项,但关于 2 和 3,我有不同的成功方法。

让我们先总结一下关注点:

  • 您希望保存文件
  • 您希望文件路径链接到相应的实体(即报表)
  • 您不希望将文件路径链接到不存在的文件
  • 您不希望文件系统中的文件未链接到任何报告

以及不同的方法:

1。使用数据库

您几乎可以使用任何关系数据库来确保数据库中的事务,并且使用S3 您可以确保新对象和新对象上传的读写一致性。如果你PUT 一个对象并且你得到一个200 OK,它将是可读的。现在,如何将所有这些放在一起?您需要跟踪该过程。我可以想出两种方法:

1.1 带进度表

  1. 上传请求被保存到一个表中,其中包含任何需要标识此文件、报告 ID、临时上传文件路径、目标路径和状态列
  2. 您保存文件
  3. 如果文件保险箱出现故障,您可以更新表中的记录,或将其删除
  4. 如果保存文件成功,在transaction
    • 使用成功状态更新进度表
    • 更新您实际保存关系报告图像的表格
  5. 有一个 cron,但不是检查文件系统,而是检查进程表。如果文件系统中有任何文件是孤立的,则肯定它已被添加到表中(这是第 1 点)。在这里,您可以决定是否删除文件,或者如果您有足够的信息,您可以继续触发第 4 点的中止进程。

具有一些额外状态列的相同报表图像关系表。

1.2 使用队列系统

如 RabbitMQ、SQS、AMQ 等

一种非常相似的方法可以用任何队列系统而不是数据库表来完成。我不会提供太多细节,因为它更多地取决于您的实际基础设施,而只是一般的想法。

  • 上载请求进入队列,您发送一条消息,其中包含您可能需要识别此文件、报告 ID 以及是否需要暂定最终路径的任何内容。
  • 你上传文件
  • 工作人员读取队列中的待处理消息并执行工作。仅当一切顺利时,该消息才被标记为已使用。
  • 如果发生故障,消息自然会回到队列中
  • 在下一次读取消息时,工作人员可以获得足够的信息来查看是否有工作要恢复,如果无法恢复,甚至可以删除文件

在这两种情况下,并发问题都不容易管理,但可以管理(在第一种情况下依赖 DB 锁,在第二种情况下依赖 FIFO 队列)但总是需要一些应用程序逻辑

2。没有数据库

在某种程度上,没有数据库的系统是完全可以接受的,如果我们可以将其作为正确的convention over configuration 设计进行辩护。 你必须处理 3 件事:

  1. 保存文件
  2. 读取文件
  3. 确保文件系统的结构是可管理的

让我们从 3 开始:

文件夹结构

  • 一般来说,像report id 这样的文件夹太简单了,可能难以维护,而且最终也太简单了。这会导致问题,因为如果我们有一个文件夹 images,每个报告一个文件夹,而明天你有更少的 200k 报告,images 文件夹将有 200k 个元素,即使是 ls 也会花费太多时间,对于任何试图访问的编程语言都是一样的。那会杀了你

  • 你可以考虑一些更复杂的东西。个人喜欢 10 多年前从 Magento 1 学到的一种方法,从那时起我就使用了很多:使用遵循第一个外部规则的文件夹结构,但扩展了使用文件名本身扩展的规则。

    • 我们要保存产品图片。图片名称为:myproduct.jpg
    • 第一条规则是:对于产品图片,我使用/media/catalog/product
    • 然后,为了避免在同一张照片中出现很多图像,我为图像名称的每个字母创建一个文件夹,最多可以包含一些字母。可以说 3。所以我的最终文件夹将类似于 /media/catalog/product/m/y/p/myproduct.jpg
    • 这样,保存新图像的位置就很清楚了。您可以使用您的报告 ID、类别或任何对您有意义的东西来做类似的事情。最终目标是避免过于扁平的结构,并创建一个对您有意义且易于自动化的树。

这将我们带到下一部分:

读写。

我之前非常成功地实现了一个类似的系统。它使我可以轻松地保存文件并轻松地检索它们,并且位置完全是动态的。这里的部分是:

  • S3(但您可以使用任何文件系统)
  • 充当读取和写入代理的小型微服务。
  • 一些命名空间系统和附加逻辑。

逻辑很简单。命名空间让我知道文件将保存在哪里。例如,命名空间可以是companyname/reports/images

假设开发一个用于读写的微服务:

为了保存文件,它接收:

  • 命名空间
  • 实体 ID(即您报告)
  • 要上传的文件

它会做的:

  • 根据我对该命名空间的规则,id 和文件名会将文件保存在此文件夹中
  • 它不返回物理位置。这对客户来说仍然是未知的。

然后,为了阅读,客户端将使用同样使用约定的 URL。例如你可以有类似的东西

https://myservice.com/{NAMESPACE}/{entity_id}

并且基于逻辑,微服务将知道在存储中的何处找到它并返回图像。

如果每个报告有多个图像,您可以执行不同的操作,例如: - 您可能希望在路径中添加第三个 slug,例如 https://myservice.com/{NAMESPACE}/{entity_id}/1 https://myservice.com/{NAMESPACE}/{entity_id}/2 等... - 如果是为了您的内部应用程序使用,您可以有一个端点来返回所有符合条件的图像的列表,比如说https://myservice.com/{NAMESPACE}/{entity_id} 返回一个包含所有图像 url 的数组

我的实现方式是使用非常简单的 yml 配置来定义逻辑,以及读取该配置的非常简单的代码。这让我有很大的灵活性。例如,如果报表属于不同的公司或属于不同的报表类型,则将报表保存在完全不同的路径或服务器或 s3 存储桶中

【讨论】:

  • 感谢您的详细回答,非常感谢。我最终将图像作为 LOB 存储在数据库中。实现是如此简单,我无法抗拒。我将运行系统一段时间,看看一切如何。如果事情没有像我预期的那样工作,我将实施类似于 1.1 和 1.2 的解决方案。我需要澄清以下几点: 1.1 中进度表的唯一目的是便于快速查找 cron,对吗?否则,我相信我们可以只用一张桌子就能达到同样的效果; cron 作业可以简单地检查文件系统是否存在。
  • 是的,该表的目的是为了方便 cron 的工作,并对状态有一个整体的了解,因为您想确保整个过程是事务性的。但是你不需要这么复杂的东西,你可以相信快乐的情况总是有效的:如果文件被保存,那么将路径保存到数据库中。
  • 我不会在数据库中存储图像,除非是非常小的图像和非常特殊的情况。对图像使用 BLOB 或 LOB 会导致表大、难以维护、系统功能有限(缩略图、不同设备的版本等),通常一开始你不会受苦,但后来它开始成为问题,当你有很多图像,有很多新的需求,所以问题会在这些问题更难解决的时候出现,因为一切都已经太先进了。
猜你喜欢
  • 2010-12-12
  • 1970-01-01
  • 1970-01-01
  • 2015-01-03
  • 2023-03-06
  • 2011-04-10
  • 2011-02-22
  • 2021-10-12
  • 2023-04-04
相关资源
最近更新 更多