我有一个 Django 应用程序在两台专用服务器上的 uWSGI 下以多处理+多线程模式工作。负载均衡是通过 nGinx 完成的。还有 Postgres 和 Redis 服务器。
用户通过意外单击按钮两次和向 Web 服务器发送垃圾邮件以获得更多积分,从而导致关键数据处理的竞争条件。我正在考虑在请求处理开始时使用某种每用户锁定来防止数据损坏和服务器过载。问题是请求处理是分布在机器之间的。
在这种情况下,最强大和最有效的解决方案是什么?如果我只有一个 uWSGI 服务器但仍然有多个进程怎么办?
我有一个 Django 应用程序在两台专用服务器上的 uWSGI 下以多处理+多线程模式工作。负载均衡是通过 nGinx 完成的。还有 Postgres 和 Redis 服务器。
用户通过意外单击按钮两次和向 Web 服务器发送垃圾邮件以获得更多积分,从而导致关键数据处理的竞争条件。我正在考虑在请求处理开始时使用某种每用户锁定来防止数据损坏和服务器过载。问题是请求处理是分布在机器之间的。
在这种情况下,最强大和最有效的解决方案是什么?如果我只有一个 uWSGI 服务器但仍然有多个进程怎么办?
我目前的选择是使用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)