在 Web 应用程序的文件系统中存储上传的文件时如何处理问题?

san*_*ain 5 database transactions jta xadisk

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

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

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

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

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

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

我希望得到一些关于我应该遵循哪种方法的建议。

Car*_*les 2

我相信你正在努力做到超级正确,也许不需要那么多,但我前段时间也遇到过一些类似的情况,并探索了不同的可能性。我不喜欢与选项 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 以及是否需要暂定最终路径所需的任何信息。
  • 您上传文件
  • 工作人员读取队列中的待处理消息并完成工作。仅当一切顺利时,消息才会被标记为已使用。
  • 如果出现故障,消息自然会返回队列
  • 下次读取消息时,工作人员可以获得足够的信息来查看是否有工作需要恢复,或者如果无法恢复,甚至可以删除文件

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

2. 不带数据库

在某种程度上,如果我们能够将其视为配置设计的适当约定,那么没有数据库的系统将是完全可以接受的。你必须处理三件事:

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

让我们从3开始:

文件夹结构

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

  • 你可以考虑一些更复杂的事情。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 存储桶中