python分布式环境下的限流器的示例


Posted in Python onOctober 26, 2017

项目中用到了限流,受限于一些实现方式上的东西,手撕了一个简单的服务端限流器。

服务端限流和客户端限流的区别,简单来说就是:

1)服务端限流

对接口请求进行限流,限制的是单位时间内请求的数量,目的是通过有损来换取高可用。

例如我们的场景是,有一个服务接收请求,处理之后,将数据bulk到Elasticsearch中进行索引存储,bulk索引是一个很耗费资源的操作,如果遭遇到请求流量激增,可能会压垮Elasticsearch(队列阻塞,内存激增),所以需要对流量的峰值做一个限制。

2)客户端限流

限制的是客户端进行访问的次数。

例如,线程池就是一个天然的限流器。限制了并发个数max_connection,多了的就放到缓冲队列里排队,排队搁不下了>queue_size就扔掉。

本文是服务端限流器。

我这个限流器的优点:

1)简单
2)管事

缺点:

1)不能做到平滑限流

例如大家尝尝说的令牌桶算法和漏桶算法(我感觉这两个算法本质上都是一个事情)可以实现平滑限流。什么是平滑限流?举个栗子,我们要限制5秒钟内访问数不超过1000,平滑限流能做到,每秒200个,5秒钟不超过1000,很平衡;非平滑限流可能,在第一秒就访问了1000次,之后的4秒钟全部限制住。•2)不灵活

只实现了秒级的限流。

支持两个场景:

1)对于单进程多线程场景(使用线程安全的Queue做全局变量)

这种场景下,只部署了一个实例,对这个实例进行限流。在生产环境中用的很少。

2)对于多进程分布式场景(使用redis做全局变量)

多实例部署,一般来说生产环境,都是这样的使用场景。

在这样的场景下,需要对流量进行整体的把控。例如,user服务部署了三个实例,对外暴露query接口,要做的是对接口级的流量限制,也就是对query这个接口整体允许多大的峰值,而不去关心到底负载到哪个实例。

题外话,这个可以通过nginx做。 

下面说一下限流器的实现吧。 

1、接口BaseRateLimiter

按照我的思路,先定义一个接口,也可以叫抽象类。

初始化的时候,要配置rate,限流器的限速。

提供一个抽象方法,acquire(),调用这个方法,返回是否限制流量。

class BaseRateLimiter(object):

  __metaclass__ = abc.ABCMeta

  @abc.abstractmethod
  def __init__(self, rate):
    self.rate = rate

  @abc.abstractmethod
  def acquire(self, count):
    return

2、单进程多线程场景的限流ThreadingRateLimiter

继承BaseRateLimiter抽象类,使用线程安全的Queue作为全局变量,来消除竞态影响。

后台有个进程每秒钟清空一次queue;

当请求来了,调用acquire函数,queue incr一次,如果大于限速了,就返回限制。否则就允许访问。

class ThreadingRateLimiter(BaseRateLimiter):

  def __init__(self, rate):
    BaseRateLimiter.__init__(self, rate)
    self.queue = Queue.Queue()
    threading.Thread(target=self._clear_queue).start()

  def acquire(self, count=1):
    self.queue.put(1, block=False)
    return self.queue.qsize() < self.rate

  def _clear_queue(self):
    while 1:
      time.sleep(1)
      self.queue.queue.clear()

2、分布式场景下的限流DistributeRateLimiter

继承BaseRateLimiter抽象类,使用外部存储作为共享变量,外部存储的访问方式为cache。

class DistributeRateLimiter(BaseRateLimiter):

  def __init__(self, rate, cache):
    BaseRateLimiter.__init__(self, rate)
    self.cache = cache

  def acquire(self, count=1, expire=3, key=None, callback=None):
    try:
      if isinstance(self.cache, Cache):
        return self.cache.fetchToken(rate=self.rate, count=count, expire=expire, key=key)
    except Exception, ex:
      return True

为了解耦和灵活性,我们实现了Cache类。提供一个抽象方法getToken()

如果你使用redis的话,你就继承Cache抽象类,实现通过redis获取令牌的方法。

如果使用mysql的话,你就继承Cache抽象类,实现通过mysql获取令牌的方法。

cache抽象类

class Cache(object):

  __metaclass__ = abc.ABCMeta

  @abc.abstractmethod
  def __init__(self):
    self.key = "DEFAULT"
    self.namespace = "RATELIMITER"

  @abc.abstractmethod
  def fetchToken(self, rate, key=None):
    return

给出一个redis的实现RedisTokenCache

每秒钟创建一个key,并且对请求进行计数incr,当这一秒的计数值已经超过了限速rate,就拿不到token了,也就是限制流量。

对每秒钟创建出的key,让他超时expire。保证key不会持续占用存储空间。

没有什么难点,这里使用redis事务,保证incr和expire能同时执行成功。

class RedisTokenCache(Cache):

  def __init__(self, host, port, db=0, password=None, max_connections=None):
    Cache.__init__(self)
    self.redis = redis.Redis(
      connection_pool=
        redis.ConnectionPool(
          host=host, port=port, db=db,
          password=password,
          max_connections=max_connections
        ))

  def fetchToken(self, rate=100, count=1, expire=3, key=None):
    date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    key = ":".join([self.namespace, key if key else self.key, date])
    try:
      current = self.redis.get(key)
      if int(current if current else "0") > rate:
        raise Exception("to many requests in current second: %s" % date)
      else:
        with self.redis.pipeline() as p:
          p.multi()
          p.incr(key, count)
          p.expire(key, int(expire if expire else "3"))
          p.execute()
          return True
    except Exception, ex:
      return False

多线程场景下测试代码 

limiter = ThreadingRateLimiter(rate=10000)

def job():
  while 1:
    if not limiter.acquire():
      print '限流'
    else:
      print '正常'

threads = [threading.Thread(target=job) for i in range(10)]
for thread in threads:
  thread.start()

分布式场景下测试代码

token_cache = RedisTokenCache(host='10.93.84.53', port=6379, password='bigdata123')
limiter = DistributeRateLimiter(rate=10000, cache=token_cache)
r = redis.Redis(connection_pool=redis.ConnectionPool(host='10.93.84.53', port=6379, password='bigdata123'))

def job():
  while 1:
    if not limiter.acquire():
      print '限流'
    else:
      print '正常'

threads = [multiprocessing.Process(target=job) for i in range(10)]
for thread in threads:
  thread.start()

可以自行跑一下。 

说明:

我这里的限速都是秒级别的,例如限制每秒400次请求。有可能出现这一秒的前100ms,就来了400次请求,后900ms就全部限制住了。也就是不能平滑限流。

不过如果你后台的逻辑有队列,或者线程池这样的缓冲,这个不平滑的影响其实不大。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python使用numpy实现BP神经网络
Mar 10 Python
Python多继承原理与用法示例
Aug 23 Python
Python sorted函数详解(高级篇)
Sep 18 Python
解决Python下json.loads()中文字符出错的问题
Dec 19 Python
基于PyQt4和PySide实现输入对话框效果
Feb 27 Python
Django后端接收嵌套Json数据及解析详解
Jul 17 Python
python按行读取文件并找出其中指定字符串
Aug 08 Python
django中间键重定向实例方法
Nov 10 Python
PyCharm刷新项目(文件)目录的实现
Feb 14 Python
python argparse传入布尔参数false不生效的解决
Apr 20 Python
Python深度学习之实现卷积神经网络
Jun 05 Python
教你怎么用Python selenium操作浏览器对象的基础API
Jun 23 Python
Python Nose框架编写测试用例方法
Oct 26 #Python
Python面向对象编程基础解析(二)
Oct 26 #Python
Python面向对象编程基础解析(一)
Oct 26 #Python
获取Django项目的全部url方法详解
Oct 26 #Python
Python探索之ModelForm代码详解
Oct 26 #Python
启动targetcli时遇到错误解决办法
Oct 26 #Python
Mac中Python 3环境下安装scrapy的方法教程
Oct 26 #Python
You might like
在线竞拍系统的PHP实现框架(一)
2006/10/09 PHP
基于CI框架的微信网页授权库示例
2016/11/25 PHP
yii2简单使用less代替css示例
2017/03/10 PHP
ThinkPHP实现简单登陆功能
2017/04/28 PHP
PHP实现绘制二叉树图形显示功能详解【包括二叉搜索树、平衡树及红黑树】
2017/11/16 PHP
Yii Framework框架使用PHPExcel组件的方法示例
2019/07/24 PHP
详解phpstorm2020最新破解方法
2020/09/17 PHP
JavaScript RegExp方法获取地址栏参数(面向对象)
2009/03/10 Javascript
js常用代码段收集
2011/10/28 Javascript
Javascript中的for in循环和hasOwnProperty结合使用
2013/06/05 Javascript
jquery插件tooltipv顶部淡入淡出效果使用示例
2013/12/05 Javascript
javascript数组快速打乱重排的方法
2014/01/02 Javascript
运用jQuery定时器的原理实现banner图片切换
2014/10/22 Javascript
node.js中的fs.readdir方法使用说明
2014/12/17 Javascript
JavaScript数据操作_浅谈原始值和引用值的操作本质
2016/08/23 Javascript
JS简单实现浮动窗口效果示例
2016/09/07 Javascript
微信小程序 wxapp导航 navigator详解
2016/10/31 Javascript
nodejs实现一个word文档解析器思路详解
2018/08/14 NodeJs
jQuery实现获取及设置CSS样式操作详解
2018/09/05 jQuery
three.js实现炫酷的全景3D重力感应
2018/12/30 Javascript
js用正则表达式筛选年月日的实例方法
2021/01/04 Javascript
[02:17]2016国际邀请赛中国区预选赛VG战队领队采访
2016/06/26 DOTA
对python指数、幂数拟合curve_fit详解
2018/12/29 Python
详解Django项目中模板标签及模板的继承与引用(网站中快速布置广告)
2019/03/27 Python
python通过实例讲解反射机制
2019/10/17 Python
使用Matplotlib 绘制精美的数学图形例子
2019/12/13 Python
Python基于stuck实现scoket文件传输
2020/04/02 Python
ALDI奥乐齐官方海外旗舰店:德国百年超市
2017/12/27 全球购物
Joie官方网上商店:购买服装和女装配饰
2018/06/05 全球购物
Love, Bonito国际官网:新加坡女装品牌
2021/03/13 全球购物
创先争优演讲稿
2014/09/15 职场文书
通知函的格式
2015/04/27 职场文书
教师继续教育反思周记
2015/06/25 职场文书
《搭石》教学反思
2016/02/18 职场文书
2016年社区中秋节活动总结
2016/04/05 职场文书
vue项目支付功能代码详解
2022/02/18 Vue.js