web app上传的文件存储到文件系统时出现问题怎么办?

How to deal with issues when storing uploaded files in the file system for a web app?

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

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

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

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

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

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

如果我能就应该遵循哪种方法提供一些建议,我将不胜感激。

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

先总结一下关注点:

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

以及不同的方法:

1。使用数据库

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

1.1有进步table

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

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

1.2 有队列系统

如 RabbitMQ、SQS、AMQ 等

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

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

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

2。没有数据库

在某种程度上,没有数据库的系统将是完全可以接受的table,如果我们可以将其捍卫为适当的 convention over configuration 设计。 你必须处理三件事:

  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、类别或任何对您有意义的东西来做类似的事情。最后的 objective 是为了避免过于扁平的结构,并创建一个对你有意义的树,并且可以很容易地自动化。

这将我们带到下一部分:

读写。

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

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

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

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

为了保存一个文件,它收到:

  • 命名空间
  • 实体id(即你举报)
  • 要上传的文件

它会做:

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

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

的东西

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

并且根据逻辑,微服务将知道在存储和 return 图像中的何处找到它。

如果每个报告有不止一张图片,您可以做不同的事情,例如: - 您可能希望在路径中有第三个 slug,例如 https://myservice.com/{NAMESPACE}/{entity_id}/1 https://myservice.com/{NAMESPACE}/{entity_id}/2 等... - 如果它是供您的内部应用程序使用,您可以有一个端点 returns 所有符合条件的图像的列表,假设 https://myservice.com/{NAMESPACE}/{entity_id} returns 是一个包含所有图像 urls

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