Django中的线程安全存储

时间:2019-06-13 20:33:13

标签: python django thread-safety

我正在自定义Django的Admin Email Handler,并为其添加了一些突发保护,因此,如果在一分钟内出现多个错误,只会发送一封错误电子邮件。

    def burst_protection(self, lag=60):
        """
        :param lag:
        :return:
        """
        current_time = int(time.time())
        global timestamp

        if current_time - timestamp > lag:
            timestamp = current_time
            enable_burst_protection = False
        else:
            enable_burst_protection = True

        return enable_burst_protection

最初,我将timestamp实现为类变量,但这不能防止生产环境中的消息突发,因为我假设服务器上有多个线程或进程同时访问和写入timestamp 。是否有线程和进程安全的方式将时间戳值存储在Python / Django中?

我听说可以通过将时间戳记值存储在数据库中来实现,但是我宁愿避免为此访问数据库。

2 个答案:

答案 0 :(得分:1)

有一种方法,但是:

  

为了提供线程安全,将为每个线程返回不同的缓存后端实例。

您可以使用缓存,如果您不想访问数据库,则可以进行内存缓存存储。

https://docs.djangoproject.com/en/2.2/topics/cache/

查看本地内存缓存部分

示例(来自文档):

settings.py

    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'unique-snowflake',
    }
}

然后您可以使用低级缓存,在同一页面部分低级缓存API

views.py-或连发对象所在的位置

from django.core.cache import caches
cache1 = caches['unique-snowflake'] # we get our cache handle
cache1.set('my_key', time())
...
cache1.get('my_key')

答案 1 :(得分:1)

Redis非常适合速率限制实施,例如:

我们需要每个用户一个唯一的密钥,在这里我们使用会话密钥或用户的ip地址+用户代理字符串的哈希,但是如果您想对全局进行限制,则返回一个常数(例如{ {1}}参数)将执行以下操作:

servicename

然后可以将速率限制器实现为装饰器:

import hashlib

def _ratelimit_key(servicename, request):
    """Return a key that identifies one visitor uniquely and is durable.
    """
    sesskey = request.session.session_key
    if sesskey is not None:
        unique = sesskey
    else:
        ip = request.get_host()
        ua = request.META.get('HTTP_USER_AGENT', 'no-user-agent')
        digest = hashlib.md5(ua).hexdigest()
        unique = '%s-%s' % (ip, digest)
    return '%s-%s' % (servicename, unique)

用法:

import time
from django.conf import settings
from django import http
import redis

def strict_ratelimit(name, seconds=10, message="Too many requests."):
    """Basic rate-limiter, only lets user through 10 seconds after last attempt.

    Args:
        name: the service to limit (in case several views share a service)
        seconds: the length of the quiet period
        message: the message to display to the user when rate limited

    """
    def decorator(fn):
        def wrap(request, *args, **kwargs):
            r = redis.Redis()
            key = _ratelimit_key(name, request)
            if r.exists(key):
                r.expire(key, seconds)  # refresh timeout
                return http.HttpResponse(message, status=409)
            r.setex(key, seconds, "nothing")
            return fn(request, *args, **kwargs)
        return wrap
    return decorator

严格的速率限制器通常不是您想要的,但是,通常,您希望允许人们进行小的突发事件(只要在一个时间间隔内没有太多)。 “泄漏存储桶”(google it)算法可以做到这一点(与上面的用法相同):

@strict_ratelimit('search', seconds=5)
def my_search_view(request):
    ...