Вопрос

Вот простой пример представления django с потенциальным условием гонки:

# myapp/views.py
from django.contrib.auth.models import User
from my_libs import calculate_points

def add_points(request):
    user = request.user
    user.points += calculate_points(user)
    user.save()

Условие гонки должно быть достаточно очевидным:Пользователь может сделать этот запрос дважды, и приложение потенциально может быть выполнено user = request.user одновременно, заставляя один из запросов переопределять другой.

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

Итак, вот мой вопрос:Какие механизмы блокировки доступны django для решения ситуаций, подобных этой?

Это было полезно?

Решение

Django 1.4+ поддерживает select_for_update , в более ранних версиях вы можете выполнять необработанные SQL-запросы, например select ... for update , который в зависимости от базовой БД будет блокировать строку от любых обновлений, вы можете делать с этой строкой все, что захотите, до конца транзакции. например.

from django.db import transaction

@transaction.commit_manually()
def add_points(request):
    user = User.objects.select_for_update().get(id=request.user.id)
    # you can go back at this point if something is not right 
    if user.points > 1000:
        # too many points
        return
    user.points += calculate_points(user)
    user.save()
    transaction.commit()

Другие советы

Начиная с Django 1.1, вы можете использовать выражения ORM F () для решения этой конкретной проблемы.

from django.db.models import F

user = request.user
user.points  = F('points') + calculate_points(user)
user.save()

Для получения дополнительной информации см. документацию:

https: / /docs.djangoproject.com/en/1.8/ref/models/instances/#updating-attributes-based-on-existing-fields

https: //docs.djangoproject .com / ы / 1,8 / исй / модель / выражение / # django.db.models.F

Блокировка базы данных - это то, что нужно. Есть планы добавить " выбрать для обновления " поддержка Django ( здесь ), но на данный момент самым простым будет использование необработанного SQL для UPDATE пользовательский объект, прежде чем начать рассчитывать балл.

<Ч>

Пессимистическая блокировка теперь поддерживается ORM в Django 1.4, когда ее поддерживает базовая БД (например, Postgres). См. заметки о выпуске Django 1.4a1. .

У вас есть много способов сделать что-то подобное в одном потоке.

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

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

  • Ваше веб-приложение может создать очередь запросов на оценку, запустить отдельный процесс, а затем записать запросы на оценку в эту очередь.Порождение может быть помещено в Django urls.py так что это происходит при запуске веб-приложения.Или его можно поместить в отдельный manage.py скрипт администратора.Или это может быть сделано "по мере необходимости" при попытке первого запроса оценки.

  • Вы также можете создать отдельный веб-сервер с поддержкой WSGI, используя Werkzeug, который принимает запросы WS через urllib2.Если у вас есть один номер порта для этого сервера, запросы ставятся в очередь по протоколу TCP /IP.Если ваш обработчик WSGI имеет один поток, значит, вы достигли сериализованной однопоточности.Это немного более масштабируемо, поскольку механизм подсчета очков является запросом WS и может быть запущен где угодно.

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

  • Одноэлементный объект в базе данных.Одна строка в уникальной таблице может быть обновлена идентификатором сеанса для захвата контроля;обновить с идентификатором сеанса None чтобы ослабить контроль.Существенное обновление должно включать в себя WHERE SESSION_ID IS NONE фильтруйте, чтобы убедиться, что обновление завершается неудачно, когда блокировка удерживается кем-то другим.Это интересно, потому что по своей сути не зависит от гонки - это одиночное обновление, а не последовательность ВЫБОРА ОБНОВЛЕНИЙ.

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

Возможно, это упрощает вашу ситуацию, но как насчет замены ссылки JavaScript? Другими словами, когда пользователь щелкает ссылку или кнопку, оборачивает запрос в функцию JavaScript, которая немедленно отключает / " отключает " ссылка и заменяет текст на "Загрузка ..." или " Отправка запроса ... " информация или что-то подобное. Будет ли это работать для вас?

Теперь вы должны использовать:

Model.objects.select_for_update().get(foo=bar)
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top