Как решить проблемы при хранении загруженных файлов в файловой системе веб-приложения?

Я создаю веб-приложение, в котором пользователи могут создавать отчеты, а затем загружать изображения для созданных отчетов. Эти изображения будут отображаться в браузере, когда пользователь нажимает кнопку на странице отчета. Изображения являются конфиденциальными, и только авторизованные пользователи смогут получить к ним доступ.

Я знаю плюсы и минусы хранения изображений в базе данных, в файловой системе или в таких сервисах, как amazon S3. Для моего приложения я склонен хранить изображения в файловой системе, а пути к изображениям - в базе данных. Это означает, что мне приходится иметь дело с проблемами, возникающими при управлении распределенными транзакциями. Мне нужен совет, как справиться с этими проблемами.

1- Я считаю, что одним из правильных решений является использование таких технологий, как JTA и XADisk. Я не очень разбираюсь в этих технологиях, но считаю, что двухфазная фиксация - это то, как достигается автоматичность. Я использую MySQL в качестве базы данных, и похоже, что MySQL поддерживает двухфазную фиксацию. Проблема с этим подходом заключается в том, что XADisk не выглядит активным проектом, по нему не так много документации, и есть факт, что я не очень разбираюсь в тонкостях этого подхода. Я не уверен, стоит ли мне инвестировать в этот подход.

2- Я считаю, что могу решить некоторые проблемы, возникающие из-за нарушения свойств ACID для моего приложения. При загрузке изображений я могу сначала записать файлы на диск, если эта операция завершится успешно, я могу обновить пути в базе данных. Если транзакция базы данных не удалась, я могу удалить файлы с диска. Я знаю, что это все еще не пуленепробиваемое; нехватка электроэнергии может возникнуть сразу после транзакции db, или диск может не реагировать на какое-то время и т. д. Я знаю, что есть также проблемы с параллелизмом, например, если один пользователь пытается изменить загруженное изображение, а другой пытается удалить его в В то же время возникнут некоторые проблемы. Тем не менее, шансы на одновременное обновление моего приложения будут относительно низкими.

Я считаю, что могу жить с потерянными файлами на диске или путями к потерянным образам в базе данных, если возникают такие исключительные случаи. Если путь к файлу существует в базе данных, а не в файловой системе, я могу показать пользователю уведомление на странице отчета, и он может попытаться повторно загрузить изображение. Сиротские файлы в файловой системе не будут большой проблемой, я мог бы время от времени запускать процесс для обнаружения таких файлов. Тем не менее, мне не очень нравится такой подход.

3- Последний вариант может заключаться в том, чтобы вообще не сохранять пути к файлам в базе данных. Я могу структурировать файловую систему так, чтобы я мог вывести путь к файлу в коде и загрузить все изображения сразу. Например, я могу создать папку с именем идентификатора отчета для каждого отчета. Когда был сделан запрос на загрузку изображений отчета, я могу загрузить изображения сразу, так как я знаю идентификатор отчета. Это может привести к огромному количеству папок в файловой системе, и я не уверен, приемлем ли такой дизайн. В этой схеме все еще будут существовать проблемы с параллелизмом.

Я был бы признателен за совет, какой подход мне следует использовать.

1 ответ

Решение

Я считаю, что вы пытаетесь быть сверхкорректным, и, возможно, этого не так уж много, но я также столкнулся с подобной ситуацией некоторое время назад и изучал также различные возможности. Мне не нравились варианты, соответствующие вашему варианту 1, но в отношении 2 и 3 у меня были разные успешные подходы.

Подведем итог по списку проблем:

  • Вы хотите, чтобы файл был сохранен
  • Вы хотите, чтобы путь к файлу был связан с соответствующей сущностью (например, с отчетом)
  • Вы не хотите, чтобы путь к файлу был связан с файлом, который не существует
  • Вы не хотите, чтобы файлы в файловой системе не были связаны ни с одним отчетом

И разные подходы:

1. Использование БД

Вы можете гарантировать транзакции в БД практически с любой реляционной базой данных, и с S3вы можете обеспечить согласованность чтения после записи как для новых объектов, так и для загрузки новых объектов. если тыPUT объект, и вы получите 200 OK, он будет читаемым. Как все это собрать воедино? Вам нужно следить за процессом. Я могу придумать 2 способа:

1.1 С таблицей прогресса

  1. Запрос на загрузку сохраняется в таблице со всем необходимым, чтобы идентифицировать этот файл, идентификатор отчета, путь к временному загруженному файлу, путь назначения и столбец статуса.
  2. Вы сохраняете файл
  3. В случае отказа файлового сейфа вы можете обновить запись в таблице или удалить ее.
  4. Если сохранение файла прошло успешно, в transaction:
    • обновить таблицу прогресса с успешным статусом
    • обновите таблицу, в которой вы фактически сохраняете изображение отношения отчета
  5. Имейте cron, но не проверяйте файловую систему, а проверяйте таблицу процессов. Если в файловой системе есть какой-либо файл, который является сиротским, определенно он был добавлен в таблицу (это был пункт 1). Здесь вы можете решить, будете ли вы удалять файл, или, если у вас достаточно информации, вы можете продолжить прерванный процесс, запустив точку 4.

Та же таблица отношений отчета и изображения с некоторыми дополнительными столбцами статуса.

1.2 С системой очередей

Как RabbitMQ, SQS, AMQ и т. Д.

Очень похожий подход может быть реализован с любой системой очередей вместо таблицы db. Я не буду вдаваться в подробности, потому что это больше зависит от вашей реальной инфраструктуры, а только от общей идеи.

  • Запрос на загрузку попадает в очередь, вы отправляете сообщение со всем, что вам может понадобиться для идентификации этого файла, идентификатора отчета и, если вам нужен предварительный окончательный путь.
  • Вы загружаете файл
  • Рабочий читает ожидающие сообщения в очереди и выполняет свою работу. Сообщение помечается как использованное только тогда, когда все идет хорошо.
  • Если что-то не получается, сообщение, естественно, вернется в очередь.
  • В следующий раз, когда сообщение будет прочитано, работник может получить достаточно информации, чтобы увидеть, есть ли работа для возобновления, или даже файл, который нужно удалить, если возобновление невозможно.

В обоих случаях проблемами параллелизма не так просто управлять, но ими можно управлять (полагаясь на блокировки БД в первом случае и очереди FIFO во втором случае), но всегда с некоторой логикой приложения.

2. Без БД

В некоторой степени система без базы данных была бы вполне приемлемой, если бы мы могли защитить ее как правильное соглашение по дизайну конфигурации. Вам предстоит иметь дело с 3 вещами:

  1. Сохранить файлы
  2. Читать файлы
  3. Убедитесь, что структура файловой системы управляема

Начнем с 3:

Структура папки

  • В общем что-то вроде одной папки для report idбудет слишком простым и, возможно, сложным в обслуживании, а также в конечном итоге слишком простым. Это вызовет проблемы, потому что если у нас есть папкаimages с одной папкой на отчет, и завтра у вас будет меньше 200 тысяч отчетов, images в папке будет 200 тыс. элементов, и даже lsзаймет слишком много времени, как и для любого языка программирования, пытающегося получить доступ. Это убьет тебя

  • Вы можете подумать о чем-нибудь более сложном. Лично мне нравится способ, которым я научился уMagento 1 более 10 лет назад, и с тех пор я многое использовал: использовал структуру папок, которая сначала следовала внешним правилам, но была расширена за счет правил, производных от самого имени файла.

    • Мы хотим сохранить изображение продукта. Имя изображения:myproduct.jpg
    • первое правило: для изображений товаров я использую /media/catalog/product
    • затем, чтобы избежать множества изображений в одном, я создаю одну папку для каждой буквы имени изображения, вплоть до некоторого количества букв. Допустим, 3. Итак, моя последняя папка будет примерно такой:/media/catalog/product/m/y/p/myproduct.jpg
    • вот так понятно, куда сохранять любое новое изображение. Вы можете сделать что-то подобное, используя свой идентификатор отчета, категории или что-то еще, что вам подходит. Конечная цель - избежать слишком плоской структуры и создать дерево, которое имеет смысл для вас и которое можно легко автоматизировать.

И это подводит нас к следующей части:

Чтение и запись.

Раньше я довольно успешно реализовал подобную систему. Это позволило мне легко сохранять файлы и легко извлекать их из мест, которые были чисто динамическими. Части здесь были:

  • S3 (но можно использовать любую файловую систему)
  • Небольшой микросервис, действующий как прокси для чтения и записи.
  • Некоторая система пространств имен и присоединенная логика.

Логика довольно проста. Пространство имен позволяет мне узнать, где будет сохранен файл. Например, пространство имен может бытьcompanyname/reports/images.

Скажем, разработаем микросервис для чтения и записи:

Для сохранения файла он получает:

  • пространство имен
  • идентификатор объекта (т.е. вы сообщаете)
  • файл для загрузки

И подойдет:

  • на основе правил, которые у меня есть для этого пространства имен, а идентификатор и имя файла сохранят файл в этой папке
  • он не возвращает физическое местоположение. Это остается неизвестным клиенту.

Затем для чтения клиенты будут использовать URL-адрес, который также использует соглашение. Например, у вас может быть что-то вроде

https://myservice.com/{NAMESPACE}/{entity_id}

И, исходя из логики, микросервис будет знать, где его найти в хранилище, и вернет изображение.

Если у вас есть более одного изображения в отчете, вы можете делать разные вещи, например: - вам может понадобиться добавить в путь третий ярлык, например https://myservice.com/{NAMESPACE}/{entity_id}/1 https://myservice.com/{NAMESPACE}/{entity_id}/2 и т.д... - если это для вашего внутреннего использования приложения, у вас может быть одна конечная точка, которая возвращает список всех подходящих изображений, скажем, https://myservice.com/{NAMESPACE}/{entity_id} возвращает массив со всеми URL-адресами изображений

Я реализовал это с помощью довольно простой конфигурации yml для определения логики и очень простого кода, читающего эту конфигурацию. Это позволило мне проявить большую гибкость. Например, сохранять отчеты с разными путями, серверами или сегментами s3, если они принадлежат разным компаниям или относятся к разным типам отчетов.

Другие вопросы по тегам