5

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

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

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

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

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

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

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

4

1 回答 1

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 等

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

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

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

2.没有数据库

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

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

让我们从 3 开始:

文件夹结构

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

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

    • 我们要保存产品图片。图片名称为: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 存储桶中

于 2020-05-22T14:45:48.933 回答