python调用接口限流_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就全部限制住了。也就是不能平滑限流。

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

分布式环境下Unique ID生成方法

ID即标示符,在某个搜索域内能唯一标示其中某个对象.在关系型数据库中每个表都需要定义一个主键来唯一标示一条记录.为了方便一般都会使用一个auto_increment属性的整形数做为ID.因为数据库本身 ...

分布式环境下的id生成方法

分布式环境下的id生成方法   前几天研究数据库分表分库的问题,其中有一个关键的地方就是生成唯一键的问题,假如数据表有1亿条数据,而且还在不断的增加,这里我们就需要考虑到分表分库,假设我们采用Hash ...

集群&sol;分布式环境下5种session处理策略

转载自:http://blog.csdn.net/u010028869/article/details/50773174?ref=myread 前言 在搭建完集群环境后,不得不考虑的一个问题就是用户访 ...

【架构师之路】集群&sol;分布式环境下5种session处理策略

[架构师之路]集群/分布式环境下5种session处理策略   转自:http://www.cnblogs.com/jhli/p/6557929.html 在搭建完集群环境后,不得不考虑的一个问题就是 ...

【转】分布式环境下5种session处理策略(大型网站技术架构:核心原理与案例分析 里面的方案)

前言 在搭建完集群环境后,不得不考虑的一个问题就是用户访问产生的session如何处理.如果不做任何处理的话,用户将出现频繁登录的现象,比如集群中存在A.B两台服务器,用户在第一次访问网站时,Ngin ...

&lbrack;Done&rsqb;SnowFlake 分布式环境下基于ZK构WorkId

Twitter 的 Snowflake  大家应该都熟悉的,先上个图: 时间戳 序列号一般不会去改造,主要是工作机器id,大家会进行相关改造,我厂对工作机器进行了如下改造(估计大家都差不多吧,囧~~~ ...

【转】集群&sol;分布式环境下5种session处理策略

转载至:http://blog.csdn.net/u010028869/article/details/50773174 在搭建完集群环境后,不得不考虑的一个问题就是用户访问产生的session如何处 ...

集群&sol;分布式环境下,Session处理策略

前言 在搭建完集群环境后,不得不考虑的一个问题就是用户访问产生的session如何处理.如果不做任何处理的话,用户将出现频繁登录的现象.比如集中中存在A.B两台服务器,用户在第一次访问网站是,Ngin ...

Hadoop完全分布式环境下,DataNode进程正常启动,但是网页上不显示DataNode节点

Hadoop完全分布式环境下,上传文件到hdfs上时报错: // :: WARN hdfs.DFSClient: DataStreamer Exception org.apache.hadoop.ip ...

随机推荐

ABAP READ TABLE语句注意

READ TABLE 后注意判断 sy-subrc 是否等于0

Ext&period;Net&lowbar;1 配置ext&period;net所需的环境

一.配置ext.net有两种方法,一是通过自动配置,即:工具--->Nuget包管理器--->管理解决方案的Nuget程序包--->搜索EXT.NET--->安装,安装完后,环 ...

Visual Event插件----查看html元素绑定的事件与方法的利器

WEB标准提倡结构.表现和行为相 分离,现在越来越多采用这种表现和行为的方式,但它也为我们开发调试带来一些问题,网页载入一堆JavaScript,,我们很难搞清楚最后在哪些元素的哪个动作绑定了事件,尤 ...

cocos2d-x 头文件中添加方法变量导致编译报错

代码如下: HelloWorldScene.h #ifndef __HELLOWORLD_SCENE_H__#define __HELLOWORLD_SCENE_H__ #include " ...

STL源码剖析 迭代器(iterator)概念与编程技法(三)

1 STL迭代器原理 1.1  迭代器(iterator)是一中检查容器内元素并遍历元素的数据类型,STL设计的精髓在于,把容器(Containers)和算法(Algorithms)分开,而迭代器(i ...

Django用普通user对象登录的必须准备步骤

zt from http://segmentfault.com/q/1010000000343563 在stackoverflow找到了解答(http://stackoverflow.com/ques ...

Java 并行与并发

Java 并行与并发 注意两个词:并行(Concurrent) 并发(Parallel) 并行:是逻辑上同时发生,指在某一个时间内同时运行多个程序 并发:是物理上同时发生,指在某一个时间点同时运行多个 ...

加密算法:DigestUtils与java MessageDigest

1.使用Spring的DigestUtils public class StringUtilTest { static final String TARGET = "changeme&quo ...

洛谷 P4168 &lbrack;Violet&rsqb; 蒲公英

历尽千辛万苦终于AC了这道题目... 我们考虑1个区间\([l,r]\), 被其完整包含的块的区间为\([L,R]\) 那么众数的来源? 1.\([l,L)\)或\((R,r]\)中出现的数字 2.\ ...

webpack4

本地安装: npm init -y cnpm install webpack webpack-cli webpack-dev-server --save-dev 然后装一些所需要的loader和插件: ...

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值