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
- 上传请求保存到 table 中,其中包含任何需要识别此文件、报告 ID、临时上传文件路径、目标路径 和状态栏的信息
- 您保存文件
- 如果文件保险箱出现故障,您可以更新 table 中的记录,或将其删除
- 如果保存文件成功,在
transaction
中:
- 更新进度table成功状态
- 更新 table 你实际保存关系报告图像的地方
- 有一个 cron,但不检查文件系统,而是检查进程 table。如果文件系统中有任何文件是孤立的,那么它肯定已被添加到 table(这是第 1 点)。在这里你可以决定是否删除文件,或者如果你有足够的信息,你可以继续触发点 4 的中止过程。
相同的报告图像关系 table 具有一些额外的状态列。
1.2 有队列系统
如 RabbitMQ、SQS、AMQ 等
可以使用任何队列系统而不是数据库 table 来完成非常相似的方法。我不会提供太多细节,因为它更多地取决于您的实际基础设施,而只是一般的想法。
- 上传请求进入队列,您发送一条消息,其中包含您可能需要的任何内容,以识别此文件、报告 ID 以及是否需要暂定的最终路径。
- 您上传文件
- 工作人员读取队列中的待处理消息并完成工作。仅当一切顺利时,消息才被标记为已使用。
- 如果出现故障,消息自然会返回队列
- 下一次阅读消息时,工作人员可以有足够的信息来查看是否有要恢复的工作,如果无法恢复,甚至可以删除文件
在这两种情况下,并发问题不会直接管理,但可以管理(在第一种情况下依赖 DB 锁,在第二种情况下依赖 FIFO 队列)但总是有一些应用程序逻辑
2。没有数据库
在某种程度上,没有数据库的系统将是完全可以接受的table,如果我们可以将其捍卫为适当的 convention over configuration 设计。
你必须处理三件事:
- 保存文件
- 读取文件
- 确保文件系统的结构是可管理的
让我们从 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 存储桶中
我正在构建一个网络应用程序,用户可以在其中创建报告,然后为创建的报告上传一些图像。当用户单击报告页面上的按钮时,这些图像将在浏览器中呈现。这些图像是机密的,只有授权用户才能访问它们。
我知道将图像存储在数据库、文件系统或像 amazon S3 这样的服务中的优缺点。对于我的应用程序,我倾向于将图像保存在文件系统中,并将图像的路径保存在数据库中。这意味着我必须处理围绕分布式事务管理产生的问题。我需要一些关于如何处理这些问题的建议。
1- 我认为正确的解决方案之一是使用 JTA 和 XADisk 等技术。我对这些技术不是很了解,但我相信两阶段提交是实现自动化的方式。我正在使用 MySQL 作为数据库,MySQL 似乎支持两阶段提交。这种方法的问题是 XADisk 似乎不是一个活跃的项目,并且没有太多关于它的文档,事实上我对这种方法的来龙去脉不是很了解。我不确定我是否应该投资于这种方法。
2- 我相信我可以避免因我的应用程序违反 ACID 属性而引起的一些问题。上传图像时,我可以先将文件写入磁盘,如果此操作成功,我可以更新数据库中的路径。如果数据库事务失败,我可以从磁盘中删除文件。我知道那仍然不是防弹的;数据库事务之后可能会出现电力短缺,或者磁盘可能有一段时间没有响应等......我知道还存在并发问题,例如,如果一个用户试图修改上传的图像而另一个用户试图删除它同时,也会出现一些问题。我的应用程序中并发更新的机会仍然相对较低。
如果发生此类异常情况,我相信我可以忍受磁盘上的孤立文件或数据库上的孤立图像路径。如果数据库中存在文件路径而不是文件系统中,我可以在报告页面上向用户显示通知,他可能会尝试重新上传图像。文件系统中的孤立文件不会有太大问题,我可能 运行 一个进程会不时检测到此类文件。不过,我对这种方法不是很满意。
3- 最后一个选项可能是根本不在数据库中存储文件路径。我可以构建文件系统,以便我可以推断代码中的文件路径并一次加载所有图像。例如,我可以为每个报告创建一个名为报告 ID 的文件夹。当请求加载报告的图像时,我可以立即加载图像,因为我知道报告 ID。这最终可能会在文件系统中产生大量文件夹,我不确定这样的设计是否可以接受。该方案仍然会存在并发问题
如果我能就应该遵循哪种方法提供一些建议,我将不胜感激。
我相信你是在努力做到极端正确,也许不需要那么多,但我前段时间也遇到过类似的情况,也探索了不同的可能性。我不喜欢与您的选项 1 一致的选项,但关于 2 和 3,我有不同的成功方法。
先总结一下关注点:
- 您要保存文件
- 您希望文件路径链接到相应的实体(即报告)
- 您不希望文件路径链接到不存在的文件
- 您不希望文件系统中的文件不链接到任何报告
以及不同的方法:
1。使用数据库
您几乎可以使用任何关系数据库来确保数据库中的事务,并且使用 S3
您可以确保新对象和新对象上传的读写一致性。如果你 PUT
一个对象并且你得到一个 200 OK
,它将是可读的。现在,如何将所有这些放在一起?您需要跟踪该过程。我可以想出两种方法:
1.1有进步table
- 上传请求保存到 table 中,其中包含任何需要识别此文件、报告 ID、临时上传文件路径、目标路径 和状态栏的信息
- 您保存文件
- 如果文件保险箱出现故障,您可以更新 table 中的记录,或将其删除
- 如果保存文件成功,在
transaction
中:- 更新进度table成功状态
- 更新 table 你实际保存关系报告图像的地方
- 有一个 cron,但不检查文件系统,而是检查进程 table。如果文件系统中有任何文件是孤立的,那么它肯定已被添加到 table(这是第 1 点)。在这里你可以决定是否删除文件,或者如果你有足够的信息,你可以继续触发点 4 的中止过程。
相同的报告图像关系 table 具有一些额外的状态列。
1.2 有队列系统
如 RabbitMQ、SQS、AMQ 等
可以使用任何队列系统而不是数据库 table 来完成非常相似的方法。我不会提供太多细节,因为它更多地取决于您的实际基础设施,而只是一般的想法。
- 上传请求进入队列,您发送一条消息,其中包含您可能需要的任何内容,以识别此文件、报告 ID 以及是否需要暂定的最终路径。
- 您上传文件
- 工作人员读取队列中的待处理消息并完成工作。仅当一切顺利时,消息才被标记为已使用。
- 如果出现故障,消息自然会返回队列
- 下一次阅读消息时,工作人员可以有足够的信息来查看是否有要恢复的工作,如果无法恢复,甚至可以删除文件
在这两种情况下,并发问题不会直接管理,但可以管理(在第一种情况下依赖 DB 锁,在第二种情况下依赖 FIFO 队列)但总是有一些应用程序逻辑
2。没有数据库
在某种程度上,没有数据库的系统将是完全可以接受的table,如果我们可以将其捍卫为适当的 convention over configuration 设计。 你必须处理三件事:
- 保存文件
- 读取文件
- 确保文件系统的结构是可管理的
让我们从 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 存储桶中