Мой текущий выбор - использовать PostgreSQL Advisory Locks. И, похоже, все хорошо.
Ниже представлен класс промежуточного программного обеспечения, который должен быть размещен над всеми другими посредниками, которые могут работать с базой данных. Может быть хорошей идеей использовать разные кластеры postgres, сконфигурированные для большого количества подключений и другого пула соединений в режиме сеанса.
class SessionLock(object):
"""
Prevents users from making simultaneous requests.
"""
def __init__(self):
if not getattr(settings, 'SESSION_LOCK', True):
raise MiddlewareNotUsed
from django.db import connections, DEFAULT_DB_ALIAS
from django.contrib.sessions.middleware import SessionMiddleware
self.connections = connections
self.db_alias = getattr(settings, 'SESSION_LOCK_DB', DEFAULT_DB_ALIAS)
# Get session initialisation function from session middleware.
self.load_session = SessionMiddleware.process_request.__func__
def process_request(self, request):
# Load the session to retrieve user id from it.
self.load_session(None, request)
# Generate a lock id.
user_id = request.session.get(USER_ID_SESSION_KEY)
if user_id is not None:
request.session_lock_id = ('user_lock_%d' % user_id).__hash__()
else:
# If user is anonymous then use meta info for identification.
request.session_lock_id = (
request.META.get('HTTP_HOST', '') + ':' +
request.META.get('HTTP_USER_AGENT', '')
).__hash__()
# Acquire the lock.
cursor = self.connections[self.db_alias].cursor()
cursor.execute('SELECT pg_advisory_lock(%d)' % request.session_lock_id)
def process_response(self, request, response):
self._release_lock(request)
return response
def process_exception(self, request, exception):
self._release_lock(request)
def _release_lock(self, request):
if hasattr(request, 'session_lock_id'):
cursor = self.connections[self.db_alias].cursor()
cursor.execute('SELECT pg_advisory_unlock(%d)' %
request.session_lock_id)
Почему пользователи могут нажать кнопку несколько раз? –
Потому что они используют медленное соединение между собой. Я бы отключил кнопку с помощью js после первого щелчка, но второй щелчок помогает им быстро получать ответ сервера. И это также не помогает тем пользователям, которые достаточно умны, чтобы спамить сервер с несколькими запросами с использованием специальных utils. – raacer
По сути, вам нужно либо каждый раз направлять запросы от одного пользователя через балансировщик нагрузки на один и тот же сервер, или вам потребуется что-то общее, что есть у серверов (database/redis/..). В любом случае вам нужно будет реализовать мьютекс с таймаутом (Redis 'SETEX отлично подходит для этого ..) – thebjorn