Подтвердить что ты не робот

Условия гонки в джанго

Вот простой пример представления 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, чтобы справиться с ситуациями, подобными этому?

4b9b3361

Ответ 1

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()

Ответ 2

Как и в 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/en/1.8/ref/models/expressions/#django.db.models.F

Ответ 3

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


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

Ответ 4

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

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

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

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

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

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

  • Объект Singleton в базе данных. Одна строка в уникальной таблице может быть обновлена ​​с идентификатором сеанса, чтобы захватить управление; обновите идентификатор сеанса None, чтобы освободить управление. Важное обновление должно включать фильтр WHERE SESSION_ID IS NONE, чтобы гарантировать, что обновление не удастся, если блокировка удерживается кем-то другим. Это интересно, потому что он по своей природе не годится - это одно обновление, а не последовательность SELECT-UPDATE.

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

Ответ 5

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

Ответ 6

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

Model.objects.select_for_update().get(foo=bar)