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

Я работаю над REST API для веб-службы, используя Pyramid и Cornice; данные на стороне сервера обрабатываются с помощью 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

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

Вопрос

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

Решения (?)

Ответы

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 2

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

Например:

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

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