_潜行者

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
import time


class TokenBucket:
    """
    注意:对于单个key的操作不是线程安全的
    """
    def __init__(self, key, capacity, fill_rate, default_capacity, redis_conn):
        """
        :param capacity: 最大容量
        :param fill_rate: 填充速度/每秒
        :param default_capacity: 初始容量
        :param redis_conn: redis connection
        """
        self._key = key
        self._capacity = capacity
        self._fill_rate = fill_rate
        self._default_capacity = default_capacity
        self._redis_conn = redis_conn

        self._last_capacity_key = "last_capacity"
        self._last_timestamp_key = "last_timestamp"

    def _init_key(self):
        self._last_capacity = self._default_capacity
        now = time.time()
        self._last_timestamp = now
        return self._default_capacity, now

    @property
    def _last_capacity(self):
        last_capacity = self._redis_conn.hget(self._key, self._last_capacity_key)
        if last_capacity is None:
            return self._init_key()[0]
        else:
            return float(last_capacity)

    @_last_capacity.setter
    def _last_capacity(self, value):
        self._redis_conn.hset(self._key, self._last_capacity_key, value)

    @property
    def _last_timestamp(self):
        return float(self._redis_conn.hget(self._key, self._last_timestamp_key))

    @_last_timestamp.setter
    def _last_timestamp(self, value):
        self._redis_conn.hset(self._key, self._last_timestamp_key, value)

    def _try_to_fill(self, now):
        delta = self._fill_rate * (now - self._last_timestamp)
        return min(self._last_capacity + delta, self._capacity)

    def consume(self, num=1):
        """
        消耗 num 个 token,返回是否成功
        :param num:
        :return: result: bool, wait_time: float
        """
        # print("capacity ", self.fill(time.time()))
        if self._last_capacity >= num:
            self._last_capacity -= num
            return True, 0
        else:
            now = time.time()
            cur_num = self._try_to_fill(now)
            if cur_num >= num:
                self._last_capacity = cur_num - num
                self._last_timestamp = now
                return True, 0
            else:
                return False, (num - cur_num) / self._fill_rate

简单使用:

user_bucket = TokenBucket(key=str(request.user.id),
                                  redis_conn=cache, **SysOptions.throttling["user"])
        can_consume, wait = user_bucket.consume()
        if not can_consume:
            return "Please wait %d seconds" % (int(wait))

 

Django-rest-framework的限流原理

class VisitThrottleMixin(object):
    '''
    根据Django-rest-framework的限流原理改写,根据滚动时间分片限流,应对瞬时高并发
    有一定的局限性
    '''
    MAX_CAPACIRY = 6
    TIME_DELTA = 60

    def __init__(self):
        self.capacity = None
        self.visit_key = '__visit_throttle_key'

    def allow_request(self):
        ctime = time.time()

        if self.visit_key not in self.request.session:
            self.request.session[self.visit_key] = [ctime, ]
            return True  # True表示可以访问

        capacity = self.request.session.get(self.visit_key)
        self.capacity = capacity
        # 如果有历史访问记录,并且最早一次的访问记录离当前时间超过60s,就删除最早的那个访问记录,
        # 只要为True,就一直循环删除最早的一次访问记录
        while capacity and capacity[-1] < (ctime - self.TIME_DELTA):
            capacity.pop()
        # 如果访问记录不超过MAX_CAPACIRY次,就把当前的访问记录插到第一个位置(pop删除最后一个)
        if len(capacity) < self.MAX_CAPACIRY:
            capacity.insert(0, ctime)
            self.request.session[self.visit_key] = capacity
            return True
        return False

    def wait(self):
        '''还需要等多久才能访问'''
        ctime = time.time()
        return self.TIME_DELTA - (ctime - self.capacity[-1])

 

 
posted on 2018-11-24 16:25  _潜行者  阅读(330)  评论(0编辑  收藏  举报