我目前的选择是使用PostgreSQL Advisory Locks。它似乎工作正常。
下面是一个中间件类,放置在可能与数据库一起使用的所有其他中间件之上。在会话模式下使用为大量连接配置的不同 postgres 集群和不同的连接池可能是一个好主意。
from django.contrib.auth import SESSION_KEY as USER_ID_SESSION_KEY
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)