在多服务器设置中防止Django中单个用户同时发出请求

时间:2013-10-12 18:35:35

标签: python django locking multiprocessing race-condition

我在两个专用服务器上的uWSGI下有一个Django应用程序在多处理+多线程模式下工作。通过nGinx完成负载平衡。还有Postgres和Redis服务器。

用户通过两次意外点击按钮并通过垃圾邮件发送Web服务器来获得更多积分,从而导致关键数据处理的竞争条件。我在考虑在请求处理开始时使用某种用户锁来防止数据损坏和服务器重载。问题是请求处理是在机器之间分配的。

在这种情况下,最强大,最有效的解决方案是什么?如果我有一个uWSGI服务器但仍然有多个进程会怎样?

1 个答案:

答案 0 :(得分:0)

我目前的选择是使用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)