【问题标题】:Thread Safe Storage in DjangoDjango中的线程安全存储
【发布时间】:2019-06-14 05:27:22
【问题描述】:

我正在自定义 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

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

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

【问题讨论】:

  • 对我的回答做了一些修改,请查看:)
  • 如果回答有帮助,请将其标记为正确或评论它为什么不正确。

标签: python django thread-safety


【解决方案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')

【讨论】:

    【解决方案2】:

    Redis 非常适合限速实现,例如:

    我们需要每个用户唯一的密钥,这里我们使用会话密钥或用户的 ip 地址 + 用户代理字符串的哈希值,但如果您想全局限制速率,则返回一个常量(例如 @ 987654321@ 参数)会这样做:

    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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-11-29
      • 1970-01-01
      • 2013-09-03
      • 1970-01-01
      • 1970-01-01
      • 2012-07-11
      • 2013-11-30
      相关资源
      最近更新 更多