Pyramid REST API: как безопасно обрабатывать одновременный доступ к данным?

Я работаю над REST API для веб-службы, используя Pyramid и Карниз; данные на стороне сервера обрабатываются с помощью SQLAlchemy и MySQL. Веб-сервер — это nginx, использующий uwsgi и настроен на запуск нескольких процессов Python:

[uwsgi]
socket = localhost:6542
plugins = python34
...
processes = 2 # spawn the specified number of workers/processes
threads = 2 # run each worker in prethreaded mode with the specified number of threads

Проблема

Предположим, таблица customers на стороне сервера. С помощью API можно читать данные о клиентах, изменять их или удалять. В дополнение к этому есть другие функции API, которые считывают данные клиентов.

Я мог бы одновременно выполнять несколько вызовов API, которые затем конкурировали бы за один и тот же ресурс клиента:

# Write/modify the customer {id} data
curl --request POST ... https://some.host/api/customer/{id}
# Delete customer {id} and all of its associated data
curl --request DELETE https://some.host/api/customer/{id}
# Perform some function which reads customer {id}
curl --request GET ... https://some.host/api/do-work

По сути, это Проблема читателей-писателей, но поскольку задействован процесс, традиционная синхронизация потоков с использованием блокировок/мьютексов/семафоров не будет работать здесь.

Вопрос

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

Решения (?)

  • Я не думаю, что имеет смысл отмечать/отмечать клиента {id} как locked, потому что SQLAlchemy кэширует такие изменения и flush() не кажется достаточно атомарным в этом контексте?
  • В этой статье описывается использование HTTP ETag для управления общими ресурсами.
  • Можно также использовать Redis в качестве ссылки диспетчер распределенных блокировок для спин-блокировки для переноса функции просмотра?
  • Как насчет менеджера транзакций от Pyramid?

person Jens    schedule 26.11.2015    source источник


Ответы (2)


Я предполагаю, что вы имеете дело с одной базой данных MySQL, и ваши блокировки не должны охватывать другие ресурсы (Redis, сторонние API и т. д.). Я также предполагаю, что ваши клиентские функции сами по себе не должны работать с данными транзакций (поддерживать сеанс в течение нескольких вызовов API), вы просто хотите предотвратить одновременный доступ к API, чтобы испортить вашу базу данных.

Существует два вида блокировки: пессимистическая блокировка и оптимистическая блокировка.

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

Оптимистическая блокировка — это то, что вы можете довольно легко избежать с базами данных SQL. Если две транзакции конкурируют с одним и тем же ресурсом, база данных эффективно обрекает одну из транзакций, а инфраструктура приложения (в данном случае Pyramid + пирамида_tm) может повторить транзакцию N раз, прежде чем сдаться.

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

pyramid_tm реализует решение для оптимистичной блокировки, и я бы порекомендовал вам использовать его или какое-либо другое решение для оптимистичной блокировки, если вы не знаете очень конкретной причины, по которой вы этого не хотите.

  • pyramid_tm связывает жизненный цикл транзакции с HTTP-запросом, что очень естественно с точки зрения веб-разработчика.

  • pyramid_tm может связать другие события с успешными транзакциями, например. pyramid_mailer отправляет электронную почту пользователям, только если транзакции фиксируются

  • pyramid_tm хорошо протестирован и основан на диспетчере транзакций ZODB transaction, который используется в производстве с начала 2000 года.

  • Убедитесь, что ваш сеанс SQLAlchemy установлен на уровень SERIALIZABLE SQL — вы начинаете с модель максимальной согласованности. Вы можете снизить это требование к производительности, если знаете, что вызовы API допускают это, например. звонки делают анализ статистики только для чтения.

  • Оптимистическая блокировка обычно работает лучше при «нормальном» большом количестве операций чтения — мало операций записи, где редко возникает конфликт (два вызова API обновляют одного и того же пользователя один раз). Штраф за повторную попытку транзакции применяется только в случае конфликта.

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

дальнейшее чтение

person Mikko Ohtamaa    schedule 30.11.2015

Обычно вы начинаете с определения того, какая модель согласованности является приемлемой. Чем слабее ваши требования к согласованности, тем проще становится эта проблема на стороне сервера.

Например:

Можно ли обойтись оптимистичным параллелизмом? т.е. предположить, что у вас есть блокировка, выполнить свою операцию, но определить, когда возникает ситуация параллелизма, чтобы вы могли правильно восстановиться? Это может быть хорошим вариантом, если вы не ожидаете большого количества столкновений. Например, Sqlalchemy должна обнаруживать, что она обновляет строку, которая уже была изменена.

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

person Erwin Jansen    schedule 26.11.2015
comment
Спасибо! WRT SQLAlchemy, этот ответ кажется жизнеспособным решением. Просто нужно найти способ интегрировать это с обработкой сеанса Pyramid. - person Jens; 26.11.2015