python分布式环境下的限流器
项目中用到了限流,受限于一些实现方式上的东西,手撕了一个简单的服务端限流器。
服务端限流和客户端限流的区别,简单来说就是:
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 "") > 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 ""))
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分布式环境下的限流器的更多相关文章
- 分布式环境下Unique ID生成方法
ID即标示符,在某个搜索域内能唯一标示其中某个对象.在关系型数据库中每个表都需要定义一个主键来唯一标示一条记录.为了方便一般都会使用一个auto_increment属性的整形数做为ID.因为数据库本身 ...
- 分布式环境下的id生成方法
分布式环境下的id生成方法 前几天研究数据库分表分库的问题,其中有一个关键的地方就是生成唯一键的问题,假如数据表有1亿条数据,而且还在不断的增加,这里我们就需要考虑到分表分库,假设我们采用Hash ...
- 集群/分布式环境下5种session处理策略
转载自:http://blog.csdn.net/u010028869/article/details/50773174?ref=myread 前言 在搭建完集群环境后,不得不考虑的一个问题就是用户访 ...
- 【架构师之路】集群/分布式环境下5种session处理策略
[架构师之路]集群/分布式环境下5种session处理策略 转自:http://www.cnblogs.com/jhli/p/6557929.html 在搭建完集群环境后,不得不考虑的一个问题就是 ...
- 【转】分布式环境下5种session处理策略(大型网站技术架构:核心原理与案例分析 里面的方案)
前言 在搭建完集群环境后,不得不考虑的一个问题就是用户访问产生的session如何处理.如果不做任何处理的话,用户将出现频繁登录的现象,比如集群中存在A.B两台服务器,用户在第一次访问网站时,Ngin ...
- [Done]SnowFlake 分布式环境下基于ZK构WorkId
Twitter 的 Snowflake 大家应该都熟悉的,先上个图: 时间戳 序列号一般不会去改造,主要是工作机器id,大家会进行相关改造,我厂对工作机器进行了如下改造(估计大家都差不多吧,囧~~~ ...
- 【转】集群/分布式环境下5种session处理策略
转载至:http://blog.csdn.net/u010028869/article/details/50773174 在搭建完集群环境后,不得不考虑的一个问题就是用户访问产生的session如何处 ...
- 集群/分布式环境下,Session处理策略
前言 在搭建完集群环境后,不得不考虑的一个问题就是用户访问产生的session如何处理.如果不做任何处理的话,用户将出现频繁登录的现象.比如集中中存在A.B两台服务器,用户在第一次访问网站是,Ngin ...
- Hadoop完全分布式环境下,DataNode进程正常启动,但是网页上不显示DataNode节点
Hadoop完全分布式环境下,上传文件到hdfs上时报错: // :: WARN hdfs.DFSClient: DataStreamer Exception org.apache.hadoop.ip ...
随机推荐
- 201521123008《Java程序设计》第十二周学习总结
1. 本周学习总结 2. 书面作业 将Student对象(属性:int id, String name,int age,double grade)写入文件student.data.从文件读出显示. 1 ...
- 201521123024 java 第十周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常与多线程相关内容. 2. 书面作业 本次PTA作业题集异常.多线程 finally 题目4-2 1.1 截图你的提交结果(出现学 ...
- 快速搭建一个Fabric 1.0的环境
之前笔者写了一篇Fabric1.0 Beta的部署和Fabric 1.0的多机部署.但是很多人在部署Fabric的时候还是很容易出问题,所以我就再把Fabric 1.0的单机环境搭建讲一下.其实很多内 ...
- eastcom——eclipse中运行vtmserver项目
1, vtmserver项目必须在tomcat7上运行. 2, 在Eclipse中vtmserver的截图 3, 在eclipse中配置一个tomcat7并将vtmserver加入其中 4, 在ecl ...
- 14.LINUX-platform机制实现驱动层分离(详解)
版权声明:本文为博主原创文章,未经博主允许不得转载. 本节目标: 学习platform机制,如何实现驱动层分离 1.先来看看我们之前分析输入子系统的分层概念,如下图所示: 如上图所示,分 ...
- mybatis入门篇基——基本配置与参数说明
Mybatis 好吧这是我第一次写这种文章~如果有不足和错误之处欢迎评论,指点.今天想谈谈关于mybatis的一些基础入门知识. 进入正题~~: a.关于mybatis: 我个人觉得mybatis深得 ...
- 【京东账户】——Mysql/PHP/Ajax爬坑之页头页尾加载
一.引言 实现京东的账户项目,有一个小功能,页头页尾加载.要用到的是Apach环境,Mysql.PHP以及Ajax. 二.实现 原理: 用php文件分别写一个的页头和一个页尾,放在前后两个div里. ...
- Linux 安装PHP探针
学习linux系统还是很有意思的事情,下面这个就是探针,想必有人已经看到过类似的界面主要用来查看自己服务器的运行状况,简单看看内存占用及运行时间就可以了 1 首先要安装Apahce 及 php,命令如 ...
- jenkins~集群分发功能的具体实现
前一讲主要说了jenkins分发的好处<jenkins~集群分发功能和职责处理>,它可以让具体的节点干自己具体的事,比如windows环境下的节点,它只负责编译,发布windows的生态环 ...
- base64码转图片
1将图片转换为Base64编码,可以让你很方便地在没有上传文件的条件下将图片插入其它的网页.编辑器中. 这对于一些小的图片是极为方便的,因为你不需要再去寻找一个保存图片的地方. 2.假定生成的代码为& ...