45.限流Throttling及源码解析

什么是限流?

  1. 限流类似于权限机制 , 它也决定是否接受当前请求,用于控制客户端在某段时间内允许向API发出请求的次数 , 也就是频率
  2. 假设有客户端(比如爬虫程序)短时间发起大量请求 , 超过了服务器能够处理的能力 , 将会影响其他用户的正常使用
  3. 为了保证服务的稳定性 , 并防止接口受到恶意用户的攻击 , 我们可以对接口进行限流
  4. 又或者可以对未经身份验证的请求设置访问频率 , 对经过身份验证的请求不限制访问频率
  5. 限流也不止单指限制访问次数的措施,例如付费数据服务的特点访问次数
限流的应用场景
  1. 区分用户场景,比如匿名和已登录 , 不同权限的用户不同的限流策略
  2. API的不同,根据不同API设置不同的策略
  3. 请求的爆发期和持续期不同的限流策略
  4. 可以同时支持使用多个限流策略
  限流的机制限流和权限一样 , 执行视图前会依次检查所有的限流类,全部通过会执行View , 任何一个检查失败,会抛出Exceptions.Throttled异常在settings中 , 通过 DEFAULT_THROTTLE_CLASSES 设置限流类,通过DEFAULT_THROTTLE_RATES设置限流频率  DRF提供的两个常用限流类AnonRateThrottle:对于匿名用户的限流 , 使用anon设置频率UserRateThrottle:对于登录用户的限流, 使用user设置频率 全局限流类配置REST_FRAMEWORK = {# 全局限流类的配置'DEFAULT_THROTTLE_CLASSES': ('rest_framework.throttling.AnonRateThrottle',# 对于匿名用户的限流'rest_framework.throttling.UserRateThrottle' #对于登录用户的限流),# 限流频率的配置'DEFAULT_THROTTLE_RATES': {'anon': '100/day', # 未认证用户一天只许访问100次'user': '1000/day' # 认证用户一天可以访问1000次}}DEFAULT_THROTTLE_RATES设置限流频率格式 次数/时间单位
  • second: 按秒设置频率次数
  • minute:按分钟设置频率次数
  • hour:按小时设置频率次数
  • day: 按天设置频率次数
 视图级别限流类配置#导入限流模块from rest_framework import throttling class getInfoList(ModelViewSet):# 通过throttle_classes 设置该视图的限流类# 视图指定会覆盖settings设置的全局限流throttle_classes = (throttling.UserRateThrottle,)def infoList(self):...  识别请求的客户端我们既然要对请求进行限流 , 那么肯定要失识 别是谁发来的请求,然后进行对应的措施,不然无法确定请求者身份 , 那么就无法得知是不是需要限制的请求 , 常见的方法有三种
  1. drf利用http报头的 x-forwarded-for 或者wsgi中的remote-addr变量来唯一标识客户端的IP地址
  2. 如果 存在x-forwarded-for 属性,则使用x-forwarded-for ,否则使用remote-addr
  3. 可以使用request.user的属性来标识请求 , 比如使用request.user.id 来标记唯一请求
  4. 使用IP地址对客户端请求进行限流,需要考虑使用伪造代理IP请求的情况
  throttling源码解析throttling源码一共有五个类
  1. BaseThrottle: 限流基类
  2. SimpleRateThrottle:频率校验类
  3. AnonRateThrottle:匿名用户限流
  4. UserRateThrottle:认证用户限流
  5. ScopedRateThrottle:api视图级别的限流
 BaseThrottle限流基类没有去具体实现某些功能 , 跟权限类基类似 , 只是提供了占位方法class BaseThrottle:# allow_request源码并没有直接实现功能 , 只是写好了方法占位 , 待后续继承实现# 该方法主要是处理是否允许请求通过# 如果后续继承基类实现该方法 , 允许请求通过返回True , 不允许请求通过返回Falsedef 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')# 获取设置的允许的最大代理数 , 默认不设置为Nonenum_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的话会有多个地址 , 使用逗号分割成一个listaddrs = xff.split(',')'''通过min函数 , 拿到允许的代理数和IP地址长度最小的值 , 使用-变成负数在addrs列表中通过该下标取对应值'''client_addr = addrs[-min(num_proxies, len(addrs))]return client_addr.strip()# 如果没有设置允许的代理数 并且xff有值则直接返回 , 否则返回remote_addrreturn ''.join(xff.split()) if xff else remote_addr# 等待时间,告诉客户端被限流 , 等待多久可以访问# 后续继承实现 , 可选def wait(self):return None

经验总结扩展阅读