45.限流Throttling及源码解析
什么是限流?
- 限流类似于权限机制,它也决定是否接受当前请求,用于控制客户端在某段时间内允许向API发出请求的次数,也就是频率
- 假设有客户端(比如爬虫程序)短时间发起大量请求,超过了服务器能够处理的能力,将会影响其他用户的正常使用
- 为了保证服务的稳定性,并防止接口受到恶意用户的攻击,我们可以对接口进行限流
- 又或者可以对未经身份验证的请求设置访问频率,对经过身份验证的请求不限制访问频率
- 限流也不止单指限制访问次数的措施,例如付费数据服务的特点访问次数
限流的应用场景
- 区分用户场景,比如匿名和已登录,不同权限的用户不同的限流策略
- API的不同,根据不同API设置不同的策略
- 请求的爆发期和持续期不同的限流策略
- 可以同时支持使用多个限流策略
限流的机制
DRF提供的两个常用限流类
全局限流类配置
REST_FRAMEWORK = {
    # 全局限流类的配置
    'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.AnonRateThrottle',  # 对于匿名用户的限流
        'rest_framework.throttling.UserRateThrottle' #对于登录用户的限流
    ),
    # 限流频率的配置
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/day', # 未认证用户一天只许访问100次
        'user': '1000/day' # 认证用户一天可以访问1000次
        }
    }- second: 按秒设置频率次数
- minute:按分钟设置频率次数
- hour:按小时设置频率次数
- day: 按天设置频率次数
视图级别限流类配置
#导入限流模块
from rest_framework import throttling 
class getInfoList(ModelViewSet):
    # 通过throttle_classes 设置该视图的限流类
    # 视图指定会覆盖settings设置的全局限流
    throttle_classes = (throttling.UserRateThrottle,)
    def infoList(self):
        ...识别请求的客户端
- drf利用http报头的 x-forwarded-for 或者wsgi中的remote-addr变量来唯一标识客户端的IP地址
- 如果 存在x-forwarded-for 属性,则使用x-forwarded-for ,否则使用remote-addr
- 可以使用request.user的属性来标识请求,比如使用request.user.id 来标记唯一请求
- 使用IP地址对客户端请求进行限流,需要考虑使用伪造代理IP请求的情况
throttling源码解析
- BaseThrottle: 限流基类
- SimpleRateThrottle:频率校验类
- AnonRateThrottle:匿名用户限流
- UserRateThrottle:认证用户限流
- ScopedRateThrottle:api视图级别的限流
BaseThrottle限流基类
class BaseThrottle:
    # allow_request源码并没有直接实现功能,只是写好了方法占位,待后续继承实现
    # 该方法主要是处理是否允许请求通过
    # 如果后续继承基类实现该方法,允许请求通过返回True,不允许请求通过返回False
    def allow_request(self, request, view):
        raise NotImplementedError('.allow_request() must be overridden')
    # 获取IP地址
    def get_ident(self, request):
        # 获取请求头中真实IP地址
        xff = request.META.get('HTTP_X_FORWARDED_FOR')
        # 获取代理IP地址
        remote_addr = request.META.get('REMOTE_ADDR')
        # 获取设置的允许的最大代理数,默认不设置为None
        num_proxies = api_settings.NUM_PROXIES
       # 如果num_proxies不是None,说明设置了该值
        if num_proxies is not None:
            # 如果设置为0,或者 xff没有值
            if num_proxies == 0 or xff is None:
                # 返回代理IP地址
                return remote_addr
            #使用代理IP的话会有多个地址,使用逗号分割成一个list
            addrs = xff.split(',')
            '''
            通过min函数,拿到允许的代理数和IP地址长度最小的值,使用-变成负数
            在addrs列表中通过该下标取对应值
            '''
            client_addr = addrs[-min(num_proxies, len(addrs))]
            return client_addr.strip()
        # 如果没有设置允许的代理数 并且xff有值则直接返回,否则返回remote_addr
        return ''.join(xff.split()) if xff else remote_addr
    # 等待时间,告诉客户端被限流,等待多久可以访问
    # 后续继承实现,可选
    def wait(self):
        return NoneSimpleRateThrottle
class SimpleRateThrottle(BaseThrottle):
    # 限流需要用到缓存,使用drf默认的缓存
    # 如果其他继承类想修改缓存机制,cache = caches['缓存名'] 进行修改
    cache = default_cache
    # time.time方法,但是并没有()进行实例调用
    # 类似计时器功能,在这里留好,后续调用
    timer = time.time
    # 缓存设置,字符串格式化方法后续传参使用
    cache_format = 'throttle_%(scope)s_%(ident)s'
    # scope默认没有设置,该值是DEFAULT_THROTTLE_RATES中对应限流类的key
    scope = None
    # 限流频率默认的配置值
    THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES
    def __init__(self):
        if not getattr(self, 'rate', None):
            # 从下面get_rate()方法获取访问频率限制的参数
            self.rate = self.get_rate()
        # 通过self.parse_rate方法获取限制的频率及持续时间赋值给num_requests
        self.num_requests, self.duration = self.parse_rate(self.rate)
    # 获取当前请求的标识
    def get_cache_key(self, request, view):
        raise NotImplementedError('.get_cache_key() must be overridden')
    # 获取settings频率设置限流类对应的key
    def get_rate(self):
       # 如果没有scope,抛出异常
        if not getattr(self, 'scope', None):
            msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
                   self.__class__.__name__)
            raise ImproperlyConfigured(msg)
        try:
            # 从 self.THROTTLE_RATES 中获取设置的scope
            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):
         # 如果没有设置频率限制,直接返回None
        if rate is None:
            return (None, None)
        # 在settings设置频率我们使用 num/type 设置值
        # 字符串使用/分割 ,获取两个对应的值
        num, period = rate.split('/')
        num_requests = int(num)
        #settings中设置时间单位以天为单位可以是day也可以是d
        # period[0]获取第一个字符为key,以秒为单位换算,秒就1,分就是60,天就是86400
        # 如果需要扩展月、年等时间,可以扩展源码
        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
        # 返回频率和持续时间
        return (num_requests, duration)
    # 是否允许请求通过,运行返回True,否则返回False
    def allow_request(self, request, view):
        # 如果没有设置限流频率,直接返回True
        if self.rate is None:
            return True
        # 获取用户标识赋值给self.key
        self.key = self.get_cache_key(request, view)
        # 没有用户标识直接返回True
        if self.key is None:
            return True
        # 获取历史访问时间戳
        self.history = self.cache.get(self.key, [])
        # 获取当前时间戳
        self.now = self.timer()
        # while循环,如果历史访问时间戳有值,拿到历史时间戳[-1]的数据,如果小于等于当前时间戳减去持续时间,弹出最后一个时间戳
        # 当前时间-持续时间,就相当于需要限制的时间区间,如果历史时间戳小于等于该区间,才不会继续pop
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()
        # 如果历史访问时间戳的列表长度大于等于我们设置频率数量,说明到了频率上限
        if len(self.history) >= self.num_requests:
            # 返回self.throttle_failure 对应False
            return self.throttle_failure()
        # 返回self.throttle_success 对应True
        return self.throttle_success()
    # 频率未到达上限时返回该方法
    def throttle_success(self):
        # 在历史请求时间戳列表,将当前时间插入该列表
        self.history.insert(0, self.now)
        # 更新缓存内容
        self.cache.set(self.key, self.history, self.duration)
        # 返回True
        return True
    # 频率到达上限时返回该方法
    def throttle_failure(self):
        return False
    # 返回还需要多长时间可以进行下一次请求,可选方法
    def wait(self):
        if self.history:
            # 如果历史请求时间戳有值,剩余时间等于持续时间减去(当前时间-第一次请求)
            remaining_duration = self.duration - (self.now - self.history[-1])
        else:
            # 剩余的时间等于持续时间
            remaining_duration = self.duration
        # 允许请求的次数 等于 允许的次数-已请求的次数+1
        available_requests = self.num_requests - len(self.history) + 1
        # 如果允许请求的次数小于等于0,返回None
        if available_requests <= 0:
            return None
        return remaining_duration / float(available_requests)AnonRateThrottle
- 类的 rate 属性,可以通过继承 AnonRateThrottle 并设置该属性来修改这个值,优先级高
- settings配置文件中 DEFAULT_THROTTLE_RATES['anon'] 配置项的值。优先级低
- anonratetrottle 适用于想限制来自未知用户的请求频率的情况
class AnonRateThrottle(SimpleRateThrottle):
    # 设置频率控制的key为anon
    scope = 'anon'
    # 重写get_cache_key方法
    def get_cache_key(self, request, view):
        # 如果请求用户是经过认证的用户,不需要进行限流,直接返回None
        if request.user.is_authenticated:
            return None
        # 如果用户是未经认证的用户,将该类的scope和 用户的IP地址传入SimpleRateThrottle的self.cache_format类属性
        return self.cache_format % {
            'scope': self.scope,
            'ident': self.get_ident(request)
        }UserRateThrottle
- 类的 rate 属性,可以通过继承 UserRateThrottle 并设置该属性来修改这个值,优先级高
- settings配置文件中 DEFAULT_THROTTLE_RATES['user'] 配置项的值。优先级低
   # 设置频率控制的key位anon
    scope = 'user'
    # 重写get_cache_key方法
    def get_cache_key(self, request, view):
        if request.user.is_authenticated:
            # 如果请求用户是认证用户,设置用户的唯一标识赋值给ident
            ident = request.user.pk
        else:
            #如果请求用户是非认证用户,通过get_ident获取请求ip赋值给ident
            ident = self.get_ident(request)
        # 设置SimpleRateThrottle中self.cache_format的值
        return self.cache_format % {
            'scope': self.scope,
            'ident': ident
        }ScopedRateThrottle
class ScopedRateThrottle(SimpleRateThrottle):
    scope_attr = 'throttle_scope'
    def __init__(self):
        pass
    def allow_request(self, request, view):
        #  从view获取self.scope_attr赋值给scope,如果view中没有指定,设置为None
        self.scope = getattr(view, self.scope_attr, None)
        # 如果没有设置scope,直接返回True
        if not self.scope:
            return True
        # 获取settings频率设置限流类对应的key
        self.rate = self.get_rate()
        # 获取频率限制、持续时长
        self.num_requests, self.duration = self.parse_rate(self.rate)
        # 调用父类的allow_request 返回对应的结果
        return super().allow_request(request, view)
    # 获取用户唯一标识
    def get_cache_key(self, request, view):
         # 如果是认证用户 ident=用户唯一标识
        if request.user.is_authenticated:
            ident = request.user.pk
        else:
            # 非认证用户返回请求的ip
            ident = self.get_ident(request)
        # 设置父类的类属性
        return self.cache_format % {
            'scope': self.scope,
            'ident': ident
        }自定义限流类
自定义限流类的步骤:
- 继承BaseThrottle类或者根据场景继承其他限流类
- 实现allow_request方法,如果请求被允许,那么返回True,否则返回False
- wait方法,是否实现根据自己场景
- 获取唯一标识的方法可以使用源码自由的,也可以自定义
场景案例1
# 每小时的限流类
class UserHourRateThrottle(UserRateThrottle):
    scope = 'userHour'
# 每天的限流类
class UserDayRateThrottle(UserRateThrottle):
    scope = 'userDay' # settings中进行配置
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': (
    # 配置我们自定义的限流类或者再view中进行局部的配置
    'testApi.throttles.UserHourRateThrottle',
    'testApi.throttles.UserDayRateThrottle'
    ),
'DEFAULT_THROTTLE_RATES': {
    'userHour': '100/hour', # 每小时最多100次
    'userDay': '1000/day' # 每天最多100次
}
}场景案例2
import random
class RandomRateThrottle(throttling.BaseThrottle):
    def allow_request(self, request, view):
        # 如果随机的数字 不等于1,返回True,否则返回False
        return random.randint(1, 10) != 1
# 之后在settings进行配置或者局部配置
45.限流Throttling及源码解析的更多相关文章
- CBV流程之View源码解析
		CBV是基于反射实现根据请求方式不同,执行不同的方法. 请求流程:view源码解析 1.urls.py :请求一定来执行视图下的as_view方法.也可以直接点击as_view()来找源码. 2.vi ... 
- Sentinel源码解析四(流控策略和流控效果)
		引言 在分析Sentinel的上一篇文章中,我们知道了它是基于滑动窗口做的流量统计,那么在当我们能够根据流量统计算法拿到流量的实时数据后,下一步要做的事情自然就是基于这些数据做流控.在介绍Sentin ... 
- Django生命周期  URL ----> CBV 源码解析-------------- 及rest_framework APIView 源码流程解析
		一.一个请求来到Django 的生命周期 FBV 不讨论 CBV: 请求被代理转发到uwsgi: 开始Django的流程: 首先经过中间件process_request (session等) 然后 ... 
- OKHttp源码解析
		http://frodoking.github.io/2015/03/12/android-okhttp/ Android为我们提供了两种HTTP交互的方式:HttpURLConnection 和 A ... 
- Ocelot简易教程(七)之配置文件数据库存储插件源码解析
		作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9852711.html 上篇文章给大家分享了如何集成我写的一个Ocelot扩展插件把Ocelot的配置存储 ... 
- spring 源码解析
		1. [文件] spring源码.txt ~ 15B 下载(167) ? 1 springн┤┬вио╬Ш: 2. [文件] spring源码分析之AOP.txt ~ 15KB 下载( ... 
- 死磕 java同步系列之Semaphore源码解析
		问题 (1)Semaphore是什么? (2)Semaphore具有哪些特性? (3)Semaphore通常使用在什么场景中? (4)Semaphore的许可次数是否可以动态增减? (5)Semaph ... 
- Java生鲜电商平台-秒杀系统微服务架构设计与源码解析实战
		Java生鲜电商平台-秒杀系统微服务架构设计与源码解析实战 Java生鲜电商平台- 什么是秒杀 通俗一点讲就是网络商家为促销等目的组织的网上限时抢购活动 比如说京东秒杀,就是一种定时定量秒杀,在规定 ... 
- Java生鲜电商平台-优惠券系统的架构设计与源码解析
		Java生鲜电商平台-优惠券系统的架构设计与源码解析 电商后台:实例解读促销系统 电商后台系统包括商品管理系统.采购系统.仓储系统.订单系统.促销系统.维权系统.财务系统.会员系统.权限系统等,各系统 ... 
随机推荐
- 化整为零优化重用,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang函数的定义和使用EP07
			函数是基于功能或者逻辑进行聚合的可复用的代码块.将一些复杂的.冗长的代码抽离封装成多个代码片段,即函数,有助于提高代码逻辑的可读性和可维护性.不同于Python,由于 Go lang是编译型语言,编译 ... 
- Luogu3435 [POI2006]OKR-Periods of Words (KMP)
			\(next\)应用,将原串视作最长前缀复制后的子串 #include <iostream> #include <cstdio> #include <cstring> ... 
- java学习第七天xml.day18
			反射 在java中,反射主要是指程序可以访问.检测和修改它本身状态或行为的一种能力. 获取字节码的方式: 使用反射获取构造器 : 内省 
- Android Studio 模拟器(AVD)访问互联网
			模拟器默认是不可以直接访问互联网的,需要为模拟器配置 DNS 服务器. (一)找到模拟器安装的位置 模拟器安装位置在安卓 SDK 下面,进入[SDK Path]/emulator. (二)打开终端输入 ... 
- 简易的DragDropCarousel 拖拽轮播控件
			上一篇文章有写到 自动轮播的控件 简易的AutoPlayCarousel 轮播控件 - 黄高林 - 博客园 (cnblogs.com) 本章是基于自动轮播的一种衍生,通过拖拽鼠标进切换 直接上代码 ... 
- Mac系统下Datagrip打不开、点击没反应?
			有没有可能是因为你从网上下载了一些破解软件导致的? 背景 Mac系统下JB公司家的IDEA. Datagrip.PyCharm 或 Goland 打不开点击没反应-- 分析 大概率是之前安装过 汉化插 ... 
- OpenCV读写视频操作
			一.读取视频流 在使用OpenCV读取摄像头,或者处理一些磁盘中保存的视频文件时,通常使用VideoCapture进行读取. std::string video_path("/path/to ... 
- 不建议升级windows11的理由
			此文写于2022年9月13日,用于警告那些蠢蠢欲动想升级win11的伙伴. win11发布后,最亮眼的功能当然是自带"安卓模拟器",但作为一名程序开发者的我其实是不愿意马上尝鲜的, ... 
- 从云AK泄露利用看企业特权管理
			从云AK泄露利用看企业特权管理 目录 - 缘起 - 当前主流AK泄露检测方式 - 防止AK滥用的关键要素? - 哪些算特权账号管理? - 如何做特权账号管理? - 特权管理与堡垒机.IAM.零信任的关 ... 
- Flink基础概念入门
			Flink 概述 什么是 Flink Apache Apache Flink 是一个开源的流处理框架,应用于分布式.高性能.高可用的数据流应用程序.可以处理有限数据流和无限数据,即能够处理有边界和无边 ... 
