Djang的线程安全存储

2024-06-09 15:16:19 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在定制Django的管理电子邮件处理程序,并为其添加一些突发保护,以便在一分钟内出现多个错误时,只发送一个错误电子邮件。你知道吗

    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中存储时间戳值?你知道吗

我听说可以将时间戳值存储在数据库中,但我更希望避免为此访问数据库。你知道吗


Tags: django数据库returntime进程enable电子邮件错误
2条回答

Redis非常适合于速率限制的实现,例如:

我们需要每个用户都有一个唯一的密钥,这里我们使用会话密钥或用户的ip地址+用户代理字符串的哈希值,但是如果您想全局地进行速率限制,那么返回一个常量(例如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

用法:

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

一个严格的速率限制器通常不是你想要的,但是,通常你想让人们有小爆发(只要在一个时间间隔内没有太多)。 一个“漏桶”(google-it)算法可以做到这一点(与上面的用法相同):

def leaky_bucket(name, interval=30, size=3, message="Too many request."):
    """Rate limiter that allows bursts.

    Args:
        name:     the service to limit (several views can share a service)
        interval: the timperiod (in seconds)
        size:     maximum number of activities in a timeperiod
        message:  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):
                val = r.hgetall(key)
                value = float(val['value'])
                now = time.time()

                # leak the bucket
                elapsed = now - float(val['timestamp'])
                value -= max(0.0, elapsed / float(interval) * size)

                if value + 1 > size:
                    r.hmset(key, dict(timestamp=now, value=value))
                    r.expire(key, interval)
                    return http.HttpResponse(message, status=409)
                else:
                    value += 1.0
                    r.hmset(key, dict(timestamp=now, value=value))
                    r.expire(key, interval)
                    return fn(request, *args, **kwargs)

            else:
                r.hmset(key, dict(timestamp=time.time(), value=1.0))
                r.expire(key, interval)
                return fn(request, *args, **kwargs)

        return wrap
    return decorator

有一种方法,但是:

To provide thread-safety, a different instance of the cache backend will be returned for each thread.

可以使用缓存,如果不想访问数据库,可以使用内存缓存存储。你知道吗

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

查看本地内存缓存部分

示例(来自文档):

你知道吗设置.py你知道吗

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

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

你知道吗视图.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')

相关问题 更多 >