如何处理将上传的文件存储在web应用程序的文件系统中时出现的问题



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

我知道将图像存储在数据库、文件系统或类似amazonS3的服务中的利弊。对于我的应用程序,我倾向于将映像保留在文件系统中,并将映像的路径保留在数据库中。这意味着我必须处理分布式事务管理中出现的问题。我需要一些关于如何处理这些问题的建议。

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

2-我相信我可以解决由于违反我的应用程序的ACID属性而产生的一些问题。上传图像时,我可以先将文件写入磁盘,如果此操作成功,我可以更新数据库中的路径。如果数据库事务失败,我可以从磁盘上删除文件。我知道这仍然不是防弹的;db事务处理后可能会出现电力短缺,或者磁盘可能有一段时间没有响应等等…我知道也存在并发问题,例如,如果一个用户试图修改上传的图像,而另一个用户同时试图删除它,就会出现一些问题。不过,在我的应用程序中进行并发更新的几率相对较低。

如果出现这种特殊情况,我相信我可以使用磁盘上的孤立文件或数据库上的孤立映像路径。如果数据库中存在文件路径,而文件系统中不存在,我可以在报告页面上向用户显示通知,他可能会尝试重新加载图像。孤立文件在文件系统中不会有太大问题,我可能会运行一个进程来不时检测这样的文件。不过,我对这种做法还是不太满意。

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

我希望能就我应该采取哪种方法提供一些建议。

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

让我们首先总结一下关注的问题:

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

以及不同的方法:

1.使用DB

使用任何关系数据库都可以确保数据库中的事务,使用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.无DB

在某种程度上,一个没有数据库的系统是完全可以接受的,如果我们能把它作为一种适当的约定来保护它,而不是配置设计。你必须处理3件事:

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

让我们从3:开始

文件夹结构

  • 一般来说,像report id的一个文件夹这样的东西太简单了,可能很难维护,而且最终也太简单了。这会导致问题,因为如果我们有一个文件夹images,每个报告一个文件夹,而明天你有更少的报告,比如说20万个报告,那么images文件夹将有20万个元素,甚至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}

根据逻辑,微服务将知道在存储中的哪里可以找到它并返回图像。

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

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

最新更新