Python 在多服务器设置中防止Django中的单个用户同时请求
我有一个Django应用程序在uWSGI下的多处理+多线程模式下在两个专用服务器上工作。负载平衡通过nGinx完成。还有Postgres和Redis服务器 用户在关键数据处理过程中意外点击两次按钮,并向web服务器发送垃圾邮件以获得更多分数,从而导致竞争状况。我正在考虑在请求处理开始时使用某种用户锁,以防止数据损坏和服务器过载。问题是请求处理是在机器之间分布的Python 在多服务器设置中防止Django中的单个用户同时请求,python,django,locking,multiprocessing,race-condition,Python,Django,Locking,Multiprocessing,Race Condition,我有一个Django应用程序在uWSGI下的多处理+多线程模式下在两个专用服务器上工作。负载平衡通过nGinx完成。还有Postgres和Redis服务器 用户在关键数据处理过程中意外点击两次按钮,并向web服务器发送垃圾邮件以获得更多分数,从而导致竞争状况。我正在考虑在请求处理开始时使用某种用户锁,以防止数据损坏和服务器过载。问题是请求处理是在机器之间分布的 在这种情况下,什么是最稳健和有效的解决方案?如果我只有一台uWSGI服务器,但仍然有多个进程,那该怎么办?我当前的选择是使用。而且似乎效
在这种情况下,什么是最稳健和有效的解决方案?如果我只有一台uWSGI服务器,但仍然有多个进程,那该怎么办?我当前的选择是使用。而且似乎效果很好 下面是一个中间件类,它位于所有其他可能使用数据库的中间件之上。使用为大量连接配置的不同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)
为什么用户可以多次单击按钮?因为他们使用较慢的Internet连接。我会在第一次单击后使用js禁用按钮,但第二次单击会帮助他们不时快速获得服务器响应。对于那些聪明到可以使用特殊UTIL向服务器发送多个请求的用户来说,这也没有什么帮助。从根本上说,您每次都需要将来自一个用户的请求通过负载平衡器路由到同一台服务器,或者您需要服务器的共同点(database/redis/)。无论哪种方式,您都需要使用@thebjorn实现一个带超时的互斥(Redis'SETEX在这方面效果很好)。大多数负载平衡器向请求中注入cookie,以便后续请求由同一服务器处理。这也将解决您的问题,但仍然允许绕过坏用户。我喜欢使用Posgres咨询锁的想法,但恐怕我需要太多的资源来处理来自垃圾邮件发送者的大量数据库连接。Redis要轻得多,但它不提供同步锁,因此我必须使用无限循环来等待资源繁忙。什么是用户\u ID\u会话\u密钥?从django.contrib.auth导入会话\u密钥作为用户\u ID\u会话\u密钥