我相信你正在努力做到超正确,也许不需要那么多,但我前段时间也遇到过类似的情况,也探索了不同的可能性。我不喜欢与您的选项 1 一致的选项,但关于 2 和 3,我有不同的成功方法。
让我们首先总结一下关注点:
- 您希望保存文件
- 您希望文件路径链接到相应的实体(即报表)
- 您不希望将文件路径链接到不存在的文件
- 您不希望文件系统中的文件未链接到任何报告
以及不同的方法:
1.使用数据库
您几乎可以使用任何关系数据库来确保数据库中的事务,并且S3
可以确保新对象和新对象上传的读写一致性。如果你PUT
是一个对象并且你得到一个200 OK
,它将是可读的。现在,如何将所有这些放在一起?您需要跟踪该过程。我可以想出两种方法:
1.1 带进度表
- 上传请求被保存到一个表中,其中包含任何需要识别此文件、报告 ID、临时上传文件路径、目标路径和状态列的内容
- 你保存文件
- 如果文件保险箱失败,您可以更新表中的记录,或将其删除
- 如果保存文件成功,在
transaction
:
- 使用成功状态更新进度表
- 更新您实际保存关系报告图像的表
- 有一个cron,但不是检查文件系统,而是检查进程表。如果文件系统中有任何文件是孤立的,则肯定它已被添加到表中(这是第 1 点)。在这里,您可以决定是否删除文件,或者如果您有足够的信息,您可以继续触发第 4 点的中止进程。
具有一些额外状态列的相同报表图像关系表。
1.2 使用队列系统
像 RabbitMQ、SQS、AMQ 等
可以使用任何队列系统而不是 db 表来完成非常相似的方法。我不会提供太多细节,因为它更多地取决于您的实际基础设施,而只是一般的想法。
- 上传请求进入队列,您发送一条消息,其中包含您可能需要识别此文件、报告 ID 以及是否需要暂定最终路径的任何内容。
- 你上传文件
- 工作人员读取队列中的待处理消息并完成工作。仅当一切顺利时,该消息才被标记为已使用。
- 如果某事失败了,消息自然会回到队列中
- 在下一次读取消息时,worker 可以有足够的信息来查看是否有工作要恢复,或者如果无法恢复,甚至可以删除文件
在这两种情况下,并发问题都不容易管理,但可以管理(在第一种情况下依赖 DB 锁,在第二种情况下依赖 FIFO 队列)但总是需要一些应用程序逻辑
2.没有数据库
在某种程度上,没有数据库的系统是完全可以接受的,如果我们可以将其作为配置设计的适当约定进行辩护。你必须处理 3 件事:
- 保存文件
- 读取文件
- 确保文件系统的结构是可管理的
让我们从 3 开始:
文件夹结构
一般来说,像一个文件夹这样的东西report id
太简单了,可能很难维护,而且最终也太简单了。这会导致问题,因为如果我们有一个文件images
夹,每个报告一个文件夹,而明天你有更少的 200k 报告,该images
文件夹将有 200k 元素,甚至ls
会花费太多时间,对于任何试图访问的编程语言都是如此. 那会杀了你
你可以考虑一些更复杂的东西。个人喜欢我从Magento 1
10 多年前学到的一种方法,从那以后我用了很多:使用遵循第一个外部规则的文件夹结构,但使用扩展文件名本身扩展的规则进行扩展。
- 我们要保存产品图片。图片名称为:
myproduct.jpg
- 第一条规则是:对于我使用的产品图片
/media/catalog/product
- 然后,为了避免在同一张照片中出现许多图像,我为图像名称的每个字母创建一个文件夹,最多有一些字母。让我们说 3。所以我的最终文件夹将类似于
/media/catalog/product/m/y/p/myproduct.jpg
- 像这样,保存任何新图像的位置就很清楚了。您可以使用您的报告 ID、类别或任何对您有意义的东西来做类似的事情。最终目标是避免过于扁平的结构,并创建一个对您有意义的树,并且可以轻松实现自动化。
这将我们带到下一部分:
读和写。
我之前非常成功地实现了一个类似的系统。它使我可以轻松地保存文件并轻松地检索它们,并且位置完全是动态的。这里的部分是:
- S3(但您可以使用任何文件系统)
- 充当读取和写入代理的小型微服务。
- 一些命名空间系统和附加逻辑。
逻辑很简单。命名空间让我知道文件将保存在哪里。例如,命名空间可以是companyname/reports/images
.
假设开发一个用于读写的微服务:
为了保存文件,它接收:
它会做:
- 根据我对该命名空间的规则,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 存储桶中