python-令牌桶实现接口限流

令牌桶限流:
再用flask 做项目开发的时候。发现用自带的限流太麻烦了。然后百度到的又是各种问题的。

经测试调整过后。代码如下:
使用方式为:
1、装配器:

class Route(Resource)
	@sleep_and_retry
	@limit_rate_tool
	def post(self):
		pass

2、普通方法使用
		if not can_pass_token_bucket(user, "limit"):
			logger.info("拒绝通过,达到最大流量")
		logger.info("获得令牌,允许通过")
		pass
		
import functools
import math
import time
from datetime import datetime

from flask import request

from exceptions import RateLimitException
from extentions import redis
from utils.common import logger
from utils.date_time import now_datetime

hasinit = False


# 延迟机制
def sleep_and_retry(func):
    @functools.wraps(func)
    def wrapper(*args, **kargs):
        while True:
            try:
                return func(*args, **kargs)
            except RateLimitException as exception:
                time.sleep(exception.period_remaining)

    return wrapper


def limit_rate_tool(func):
    @functools.wraps(func)
    def wrap(*args):
        url = request.url
        period_remaining = 2  # AdogeConsts.LIMIT_RATE_PERIOD_REMAINING
        if not can_pass_token_bucket(url, "limit"):
            logger.info(f"接口{url}达到最大限流,等待{period_remaining}秒")
            raise RateLimitException(message="达到最大限流", period_remaining=period_remaining)

            pass  # if not can_pass_token_bucket(url, "limit", time_zone=5, times=2):
        else:
            return func(args)
            pass  # else-if not can_pass_token_bucket(url, "limit", time_zone=5, times=2):
        pass  # def wrap(*args, **kwargs):

    return wrap
    pass  # def limit_rate_tool(func):


def init_redis_key(key, times):
    redis.hset(key, 'tokens', times)
    last_time = time.mktime(datetime.now().timetuple())  # 记录令牌生成时间
    redis.hset(key, 'last_time', last_time)


#  令牌增加的时机为(当前时间-上一次令牌增加时间) >= (time_zone / times) ,且最短增加时间间隔为1s
#  每秒增加的个数为 times / time_zone,且最大增加个数为times
def can_pass_token_bucket(user, action, time_zone=1, times=3):  # AdogeConsts.LIMIT_RATE_MAX_NUM):
    """
    :param user: 用户唯一标识
    :param action: 用户访问的接口标识(即用户在客户端进行的动作)
    :param time_zone: 接口限制的时间段
    :param times: 限制的时间段内允许多少请求通过
    """
    # 请求来了就倒水,倒水速率有限制
    key = '{}:{}'.format(user, action)
    global hasinit
    if not hasinit:
        init_redis_key(key, times)
        hasinit = True
    rate = times / time_zone  # 令牌生成速度
    capacity = times  # 桶容量
    tokens = redis.hget(key, 'tokens')  # 看桶中有多少令牌
    last_time = redis.hget(key, 'last_time')  # 上次令牌生成时间
    now = time.mktime(datetime.now().timetuple())
    if tokens is not None:
        tokens = int(tokens)
    else:
        logger.info(f"当前令牌为空,重置令牌数量{capacity}")
        tokens = capacity
    if last_time is not None and int(float(last_time.decode())) is not None:
        last_time = int(float(last_time.decode()))
    else:
        last_time = now
    logger.info(f"tokens:{tokens}")
    # 时间戳相差60 代表现实生活相差一秒。所以,如果要求每秒生产的令牌,需要除与
    now_datetime2 = datetime.fromtimestamp(now)
    last_time_datetime = datetime.fromtimestamp(last_time)
    seconds = (now_datetime2 - last_time_datetime).seconds
    logger.info(f"seconds: {seconds}")
    delta_tokens = seconds * rate  # 经过一段时间后生成的令牌
    if delta_tokens >= 1:
        logger.info(f"{key}增加前令牌数量:{tokens}")
        tokens = tokens + math.ceil(delta_tokens)  # 增加令牌
        logger.info(f"当前增加令牌时间{now_datetime()} ,增加个数为:{delta_tokens},当前剩余个数:{tokens}")
        if tokens > capacity:
            logger.info(f"当前增加令牌后大于桶数量,重置令牌数量{capacity}")
            tokens = capacity
        last_time = time.mktime(datetime.now().timetuple())  # 记录令牌生成时间
        redis.hset(key, 'last_time', last_time)
        redis.hset(key, 'tokens', tokens)

    if tokens >= 1:
        logger.info(f"减少前令牌数量:{tokens}")
        tokens = tokens - 1  # 请求进来了,令牌就减少1
        logger.info(f"{key}当前减少令牌时间{now_datetime()} ,当前个数:{tokens}")
        redis.hset(key, 'tokens', tokens)
        return True
    return False

你可能感兴趣的:(python,limit,python,flask,令牌桶,limit_rate,限流)