rest framework之限流组件

一、自定义限流

限流组件又叫做频率组件,用于控制客户端可以对API进行的请求频率,比如说1分钟访问3次,如果在1分钟内超过3次就对客户端进行限制。

1、自定义限流

假设现在对一个API访问,在30s内访问不能超过3次,应该如何实现?

VISIT_RECORD = {} #定义全局变量,用于存放访问记录
class VisitThrottle(object):

    def __init__(self):

     #用于await计算剩余访问时间
        self.history = None

    def allow_request(self,request,view):
        #获取用户ip作为唯一的标示
        remote_addr = request.META.get(‘REMOTE_ADDR‘)

        # 获取当前访问的时刻
        ctime = time.time()
        # 这是用户第一次访问,将其进行记录,并且返回True,允许继续访问
         if remote_addr not in VISIT_RECORD:
             VISIT_RECORD[remote_addr] = [ctime,]
             return True

        # 如果不是第一次访问,获取所有的记录
        history = VISIT_RECORD.get(remote_addr)

        self.history = history
        # 判断最开始的时刻与现在的时刻的差值是否在规定的时间范围内,比如在60s内,如果不在,
        # 可以去除最开始的时刻记录
        while history and history[-1] < ctime - 30:
            history.pop()
        # 此时列表中的时刻记录都是在规定的时间范围内,判断时刻的个数也就是访问的次数

        if len(history) < 3:
            history.insert(0,ctime)
            return True

    def wait(self):
        # 还需要等多少秒才能访问
        ctime = time.time()
        return 60 - (ctime - self.history[-1])

在对应的视图中进行配置:

class BookView(ListAPIView):
    throttle_classes = [VisitThrottle,] #配置限流组件
    queryset = models.Book.objects.all()
    serializer_class = BookModelSerializer

2、限流原理

  在rest framework框架中,限流定义为类的列表,只需要全局配置或者局部配置即可。上述限流的原理就是以客户端的唯一标示作为键,以访问的时刻形成的列表作为值形成的字典,然后通过对字典进行操作:

{
    http://127.0.0.1:8020/ :[11:43:30,11:42:22,11:42:20,11:42:09]
}

  如上面的字典所示,后面的访问时间放插入到列表的最左侧,加入当前访问时间是11:43::30,那么与最开始访问时间11:42:09进行做差,然后与规定时间30s进行比较,如果不在30s内,那么就去除最左边的记录,同理使用while循环依次比较,最后在规定时间范围内的记录:

{
  http://127.0.0.1:8020/ :[11:43:30,]
}

再计算访问次数,也就是列表的个数,显然如果列表的个数小于3可以继续访问,否则不可以。

  上面使用全局变量来进行记录,当然也是可以使用缓存来进行记录的存储,需要使用django的缓存API,from django.core.cache import cache,导入这个API后就可以使用set和get方法,设置和获取cache中存储的对象,只需要在操作全局变量除进行替换即可:

from django.core.cache import cache as default_cache
import time

class VisitThrottle(object):

    cache = default_cache

    def allow_request(self,request,view):
         ...
         ...
        # 这是用户第一次访问,将其进行记录,并且返回True,允许继续访问

        if not self.cache.get(remote_addr):
            self.cache.set(remote_addr,[ctime,])
            return True
        # 如果不是第一次访问,获取所有的记录

        history = self.cache.get(remote_addr)
        self.history = history
        ...
        ...

rest framework的限流组件就是基于cache来完成的。  

  上述的wait方法表示还需要多长时间可以进行访问这个API,对客户端的提示:

{
    "detail": "Request was throttled. Expected available in 56 seconds."
}

二、内置限流

在rest framework中已经有一些限流的API可以使用:

1、SimpleRateThrottle

class SimpleRateThrottle(BaseThrottle):
    """
    A simple cache implementation, that only requires `.get_cache_key()`
    to be overridden.

    The rate (requests / seconds) is set by a `rate` attribute on the View
    class.  The attribute is a string of the form ‘number_of_requests/period‘.

    Period should be one of: (‘s‘, ‘sec‘, ‘m‘, ‘min‘, ‘h‘, ‘hour‘, ‘d‘, ‘day‘)

    Previous request information used for throttling is stored in the cache.
    """
    cache = default_cache
    timer = time.time
    cache_format = ‘throttle_%(scope)s_%(ident)s‘
    scope = None
    THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES

    def __init__(self):
        if not getattr(self, ‘rate‘, None):
            self.rate = self.get_rate()
        self.num_requests, self.duration = self.parse_rate(self.rate)

    def get_cache_key(self, request, view):
        """
        Should return a unique cache-key which can be used for throttling.
        Must be overridden.

        May return `None` if the request should not be throttled.
        """
        raise NotImplementedError(‘.get_cache_key() must be overridden‘)

    def get_rate(self):
        """
        Determine the string representation of the allowed request rate.
        """
        if not getattr(self, ‘scope‘, None):
            msg = ("You must set either `.scope` or `.rate` for ‘%s‘ throttle" %
                   self.__class__.__name__)
            raise ImproperlyConfigured(msg)

        try:
            return self.THROTTLE_RATES[self.scope]
        except KeyError:
            msg = "No default throttle rate set for ‘%s‘ scope" % self.scope
            raise ImproperlyConfigured(msg)

    def parse_rate(self, rate):
        """
        Given the request rate string, return a two tuple of:
        <allowed number of requests>, <period of time in seconds>
        """
        if rate is None:
            return (None, None)
        num, period = rate.split(‘/‘)
        num_requests = int(num)
        duration = {‘s‘: 1, ‘m‘: 60, ‘h‘: 3600, ‘d‘: 86400}[period[0]]
        return (num_requests, duration)

    def allow_request(self, request, view):
        """
        Implement the check to see if the request should be throttled.

        On success calls `throttle_success`.
        On failure calls `throttle_failure`.
        """
        if self.rate is None:
            return True

        self.key = self.get_cache_key(request, view)
        if self.key is None:
            return True

        self.history = self.cache.get(self.key, [])
        self.now = self.timer()

        # Drop any requests from the history which have now passed the
        # throttle duration
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()
        if len(self.history) >= self.num_requests:
            return self.throttle_failure()
        return self.throttle_success()

    def throttle_success(self):
        """
        Inserts the current request‘s timestamp along with the key
        into the cache.
        """
        self.history.insert(0, self.now)
        self.cache.set(self.key, self.history, self.duration)
        return True

    def throttle_failure(self):
        """
        Called when a request to the API has failed due to throttling.
        """
        return False

    def wait(self):
        """
        Returns the recommended next request time in seconds.
        """
        if self.history:
            remaining_duration = self.duration - (self.now - self.history[-1])
        else:
            remaining_duration = self.duration

        available_requests = self.num_requests - len(self.history) + 1
        if available_requests <= 0:
            return None

        return remaining_duration / float(available_requests)

SimpleRateThrottle

如果需要借助这个API来实现功能,自己也需要进行一些配置:

  • 继承SimpleRateThrottle

自己定义的限流类需要继承SimpleRateThrottle:

class VisitThrottle(SimpleRateThrottle):
    ...
  • 设置scope
class VisitThrottle(SimpleRateThrottle):
    scope = ‘book‘
    ...

在自定义类中设置scope参数,并且还需要在settings中配置DEFAULT_THROTTLE_RATES

REST_FRAMEWORK = {

    "DEFAULT_THROTTLE_RATES": {
        "book": ‘6/m‘,  #每分钟访问6次

    }
  • 重写get_cache_key方法
class VisitThrottle(SimpleRateThrottle):
    scope = ‘book‘

    def get_cache_key(self,request,view):
        """
        获取访问的标示,比如以ip作为标示
        :param request:
        :param view:
        :return:
        """
        remote_addr = request.META.get(‘REMOTE_ADDR‘)
        return remote_addr

获取访问的唯一标示ip,当然SimpleRateThrottle继承了BaseThrottle,在BaseThrottle中有获取ip的方法,只需要调用即可。

class VisitThrottle(SimpleRateThrottle):
    scope = ‘book‘

    def get_cache_key(self,request,view):
        return self.get_ident(request)
  • 局部配置

只需要在对应的视图中添加对应限流类的列表即可:

class BookView(ListAPIView):
    ...
    throttle_classes = [VisitThrottle,] #配置节流组件
    ...
  • 全局配置

当然也可以在settings中进行全局配置:

REST_FRAMEWORK = {

"DEFAULT_THROTTLE_CLASSES":["app01.utils.throttle.VisitThrottle"],

    "DEFAULT_THROTTLE_RATES": {
        "book": ‘6/m‘,

    }

这样也就完成了相对应的功能,另外内部还提供了其它的API可以使用。

2、AnonRateThrottle

class AnonRateThrottle(SimpleRateThrottle):
    """
    Limits the rate of API calls that may be made by a anonymous users.

    The IP address of the request will be used as the unique cache key.
    """
    scope = ‘anon‘

    def get_cache_key(self, request, view):
        if request.user.is_authenticated:
            return None  # Only throttle unauthenticated requests.

        return self.cache_format % {
            ‘scope‘: self.scope,
            ‘ident‘: self.get_ident(request)
        }

AnonRateThrottle

限制未认证的用户。通过传入请求的 IP 地址生成一个唯一的密钥来进行限制。

3、UserRateThrottle

class UserRateThrottle(SimpleRateThrottle):
    """
    Limits the rate of API calls that may be made by a given user.

    The user id will be used as a unique cache key if the user is
    authenticated.  For anonymous requests, the IP address of the request will
    be used.
    """
    scope = ‘user‘

    def get_cache_key(self, request, view):
        if request.user.is_authenticated:
            ident = request.user.pk
        else:
            ident = self.get_ident(request)

        return self.cache_format % {
            ‘scope‘: self.scope,
            ‘ident‘: ident
        }

UserRateThrottle

  通过 API 将用户请求限制为给定的请求频率。用户标识用于生成一个唯一的密钥来加以限制。未经身份验证的请求将回退到使用传入请求的 IP 地址生成一个唯一的密钥来进行

限制。

4、ScopedRateThrottle

class ScopedRateThrottle(SimpleRateThrottle):
    """
    Limits the rate of API calls by different amounts for various parts of
    the API.  Any view that has the `throttle_scope` property set will be
    throttled.  The unique cache key will be generated by concatenating the
    user id of the request, and the scope of the view being accessed.
    """
    scope_attr = ‘throttle_scope‘

    def __init__(self):
        # Override the usual SimpleRateThrottle, because we can‘t determine
        # the rate until called by the view.
        pass

    def allow_request(self, request, view):
        # We can only determine the scope once we‘re called by the view.
        self.scope = getattr(view, self.scope_attr, None)

        # If a view does not have a `throttle_scope` always allow the request
        if not self.scope:
            return True

        # Determine the allowed request rate as we normally would during
        # the `__init__` call.
        self.rate = self.get_rate()
        self.num_requests, self.duration = self.parse_rate(self.rate)

        # We can now proceed as normal.
        return super(ScopedRateThrottle, self).allow_request(request, view)

    def get_cache_key(self, request, view):
        """
        If `view.throttle_scope` is not set, don‘t apply this throttle.

        Otherwise generate the unique cache key by concatenating the user id
        with the ‘.throttle_scope` property of the view.
        """
        if request.user.is_authenticated:
            ident = request.user.pk
        else:
            ident = self.get_ident(request)

        return self.cache_format % {
            ‘scope‘: self.scope,
            ‘ident‘: ident
        }

ScopedRateThrottle

  可用于限制对 API 特定部分的访问。只有当正在访问的视图包含 .throttle_scope 属性时才会应用此限制。然后通过将请求的 “范围” 与唯一的用户标识或 IP 地址连接起来形成唯一的限流密钥

三、源码剖析

限流组件和权限组件、认证组件等类似,还是从路由对应的视图函数的as_view方法着手,可以看到最终走到的还是APIView的dispatch方法。

1、dispatch

   def dispatch(self, request, *args, **kwargs):
        """
        `.dispatch()` is pretty much the same as Django‘s regular dispatch,
        but with extra hooks for startup, finalize, and exception handling.
        """
        self.args = args
        self.kwargs = kwargs
        #rest-framework重构request对象
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?
        try:
            self.initial(request, *args, **kwargs)

            # Get the appropriate handler method
            #这里和CBV一样进行方法的分发
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed

            response = handler(request, *args, **kwargs)

        except Exception as exc:
            response = self.handle_exception(exc)

        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

这里的dispatch方法是APIView中的dispatch方法,在这里对原先的request进行了重构,以及通过self.initial(request, *args, **kwargs)加入了限流组件。

2、initial

    def initial(self, request, *args, **kwargs):
        """
        Runs anything that needs to occur prior to calling the method handler.
        """
        self.format_kwarg = self.get_format_suffix(**kwargs)

        # Perform content negotiation and store the accepted info on the request
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg

        # Determine the API version, if versioning is in use.
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

        # Ensure that the incoming request is permitted
        self.perform_authentication(request) #进行认证
        self.check_permissions(request) #权限校验
        self.check_throttles(request) #限流组件

3、check_throttles

    def check_throttles(self, request):
        """
        Check if request should be throttled.
        Raises an appropriate exception if the request is throttled.
        """
        for throttle in self.get_throttles():
            if not throttle.allow_request(request, self):
                self.throttled(request, throttle.wait())

    def get_throttles(self):
        """
        Instantiates and returns the list of throttles that this view uses.
        """
        return [throttle() for throttle in self.throttle_classes]

get_throttles

  可以看到循环的是视图中配置的限流类的列表,而且显然每一个限流类都必须要有allow_request和wait方法,如果allow_request返回的False就是说明已经限制访问了,执行self.throttled(request, throttle.wait())。

4、throttled

    def throttled(self, request, wait):
        """
        If request is throttled, determine what kind of exception to raise.
        """
        raise exceptions.Throttled(wait)

也就是如果已经限流了,就会抛出异常,给客户端限流提示。

详情参考:https://q1mi.github.io/Django-REST-framework-documentation/api-guide/throttling/#anonratethrottle

原文地址:https://www.cnblogs.com/shenjianping/p/11494092.html

时间: 2024-07-30 19:11:41

rest framework之限流组件的相关文章

Django Rest framework的限流实现流程

目录 一 什么是throttle 二 Django REST framework是如何实现throttle的 三 Django REST framework中throttle源码流程 一 什么是throttle 节流也类似于权限,它用来决定一个请求是否被授权.节流表示着一种临时的状态,常常用来控制客户端对一个 API的请求速率.例如,你可以通过限流来限制一个用户在每分钟对一个API的最多访问次数为60次,每天的访问次数为1000次. 二 Django REST framework是如何实现thr

【分布式架构】(10)---基于Redis组件的特性,实现一个分布式限流

分布式---基于Redis进行接口IP限流 场景 为了防止我们的接口被人恶意访问,比如有人通过JMeter工具频繁访问我们的接口,导致接口响应变慢甚至崩溃,所以我们需要对一些特定的接口进行IP限流,即一定时间内同一IP访问的次数是有限的. 实现原理 用Redis作为限流组件的核心的原理,将用户的IP地址当Key,一段时间内访问次数为value,同时设置该Key过期时间. 比如某接口设置相同IP10秒内请求5次,超过5次不让访问该接口. 1. 第一次该IP地址存入redis的时候,key值为IP地

使用springcloud gateway搭建网关(分流,限流,熔断)

Spring Cloud Gateway Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式. Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter

Spring Cloud微服务Sentinel+Apollo限流、熔断实战

在Spring Cloud微服务体系中,由于限流熔断组件Hystrix开源版本不在维护,因此国内不少有类似需求的公司已经将眼光转向阿里开源的Sentinel框架.而以下要介绍的正是作者最近两个月的真实项目实践过程,这中间被不少网络Demo示例级别水文误导过,为了以正视听特将实践过程加以总结,希望能够帮到有类似需要的朋友! 一.Sentinel概述 在基于Spring Cloud构建的微服务体系中,服务之间的调用链路会随着系统的演进变得越来越长,这无疑会增加了整个系统的不可靠因素.在并发流量比较高

程序员修神之路--高并发优雅的做限流(有福利)

菜菜哥,有时间吗? YY妹,什么事? 我最近的任务是做个小的秒杀活动,我怕把后端接口压垮,X总说这可关系到公司的存亡 简单呀,你就做个限流呗 这个没做过呀,菜菜哥,帮妹子写一个呗,事成了,以后有什么要求随便说 那好呀,先把我工资涨一下 那算了,我找别人帮忙吧 跟你开玩笑呢,给哥2个小时时间 谢谢菜菜哥,以后你什么要求我都答应你 好嘞,年轻人就是豪爽 ◆◆ 技术分析 ◆◆ 如果你比较关注现在的技术形式,就会知道微服务现在火的一塌糊涂,当然,事物都有两面性,微服务也不是解决技术,架构等问题的万能钥匙

高并发的限流例子

原文:高并发的限流例子 总体思路是这样: 1.  用一个环形来代表通过的请求容器. 2.  用一个指针指向当前请求所到的位置索引,来判断当前请求时间和当前位置上次请求的时间差,依此来判断是否被限制. 3.  如果请求通过,则当前指针向前移动一个位置,不通过则不移动位置 4.  重复以上步骤 直到永远....... 以下代码的核心思路是这样的:指针当前位置的时间元素和当前时间的差来决定是否允许此次请求,这样通过的请求在时间上表现的比较平滑. //限流组件,采用数组做为一个环    class Li

Spring Cloud微服务安全实战_4-10_用spring-cloud-zuul-ratelimit做限流

本篇讲网关上的限流 用开源项目spring-cloud-zuul-ratelimit 做网关上的限流 (项目github:https://github.com/marcosbarbero/) 1,在网关项目里,引入限流组件的maven依赖: 2,在网关项目yml配置里,配限流相关配置 github也有相关配置说明:https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit 限流框架限流需要存一些信息,可以存在数据库里,也可以存在red

spring cloud gateway整合sentinel作网关限流

说明: sentinel可以作为各微服务的限流,也可以作为gateway网关的限流组件. spring cloud gateway有限流功能,但此处用sentinel来作为替待. 说明:sentinel流控可以放在gateway网关端,也可以放在各微服务端. 1,以父工程为基础,创建子工程 2,添加pom依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>sprin

drf 其他功能组件 - 限流-过滤-排序-分页-异常处理-生成接口文档-Xadmin

目录 限流Throttling 使用 可选限流类 实例 过滤Filtering 排序 分页Pagination 可选分页器 异常处理 Exceptions REST framework定义的异常 自动生成接口文档 安装依赖 设置接口文档访问路径 文档描述说明的定义位置 访问接口文档网页 Xadmin 安装 使用 限流Throttling 可以对接口访问的频次进行限制,以减轻服务器压力. 一般用于付费购买次数,投票等场景使用. 使用 可以在配置文件中,使用DEFAULT_THROTTLE_CLAS