国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Python > 正文

python分布式環(huán)境下的限流器的示例

2020-01-04 16:40:12
字體:
供稿:網(wǎng)友

項目中用到了限流,受限于一些實現(xiàn)方式上的東西,手撕了一個簡單的服務(wù)端限流器。

服務(wù)端限流和客戶端限流的區(qū)別,簡單來說就是:

1)服務(wù)端限流

對接口請求進(jìn)行限流,限制的是單位時間內(nèi)請求的數(shù)量,目的是通過有損來換取高可用。

例如我們的場景是,有一個服務(wù)接收請求,處理之后,將數(shù)據(jù)bulk到Elasticsearch中進(jìn)行索引存儲,bulk索引是一個很耗費資源的操作,如果遭遇到請求流量激增,可能會壓垮Elasticsearch(隊列阻塞,內(nèi)存激增),所以需要對流量的峰值做一個限制。

2)客戶端限流

限制的是客戶端進(jìn)行訪問的次數(shù)。

例如,線程池就是一個天然的限流器。限制了并發(fā)個數(shù)max_connection,多了的就放到緩沖隊列里排隊,排隊擱不下了>queue_size就扔掉。

本文是服務(wù)端限流器。

我這個限流器的優(yōu)點:

1)簡單
2)管事

缺點:

1)不能做到平滑限流

例如大家嘗嘗說的令牌桶算法和漏桶算法(我感覺這兩個算法本質(zhì)上都是一個事情)可以實現(xiàn)平滑限流。什么是平滑限流?舉個栗子,我們要限制5秒鐘內(nèi)訪問數(shù)不超過1000,平滑限流能做到,每秒200個,5秒鐘不超過1000,很平衡;非平滑限流可能,在第一秒就訪問了1000次,之后的4秒鐘全部限制住。•2)不靈活

只實現(xiàn)了秒級的限流。

支持兩個場景:

1)對于單進(jìn)程多線程場景(使用線程安全的Queue做全局變量)

這種場景下,只部署了一個實例,對這個實例進(jìn)行限流。在生產(chǎn)環(huán)境中用的很少。

2)對于多進(jìn)程分布式場景(使用redis做全局變量)

多實例部署,一般來說生產(chǎn)環(huán)境,都是這樣的使用場景。

在這樣的場景下,需要對流量進(jìn)行整體的把控。例如,user服務(wù)部署了三個實例,對外暴露query接口,要做的是對接口級的流量限制,也就是對query這個接口整體允許多大的峰值,而不去關(guān)心到底負(fù)載到哪個實例。

題外話,這個可以通過nginx做。 

下面說一下限流器的實現(xiàn)吧。 

1、接口BaseRateLimiter

按照我的思路,先定義一個接口,也可以叫抽象類。

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

提供一個抽象方法,acquire(),調(diào)用這個方法,返回是否限制流量。

class BaseRateLimiter(object):  __metaclass__ = abc.ABCMeta  @abc.abstractmethod  def __init__(self, rate):    self.rate = rate  @abc.abstractmethod  def acquire(self, count):    return

2、單進(jìn)程多線程場景的限流ThreadingRateLimiter

繼承BaseRateLimiter抽象類,使用線程安全的Queue作為全局變量,來消除競態(tài)影響。

后臺有個進(jìn)程每秒鐘清空一次queue;

當(dāng)請求來了,調(diào)用acquire函數(shù),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

為了解耦和靈活性,我們實現(xiàn)了Cache類。提供一個抽象方法getToken()

如果你使用redis的話,你就繼承Cache抽象類,實現(xiàn)通過redis獲取令牌的方法。

如果使用mysql的話,你就繼承Cache抽象類,實現(xiàn)通過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的實現(xiàn)RedisTokenCache

每秒鐘創(chuàng)建一個key,并且對請求進(jìn)行計數(shù)incr,當(dāng)這一秒的計數(shù)值已經(jīng)超過了限速rate,就拿不到token了,也就是限制流量。

對每秒鐘創(chuàng)建出的key,讓他超時expire。保證key不會持續(xù)占用存儲空間。

沒有什么難點,這里使用redis事務(wù),保證incr和expire能同時執(zhí)行成功。

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次請求。有可能出現(xiàn)這一秒的前100ms,就來了400次請求,后900ms就全部限制住了。也就是不能平滑限流。

不過如果你后臺的邏輯有隊列,或者線程池這樣的緩沖,這個不平滑的影響其實不大。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持VEVB武林網(wǎng)。

 
發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 额尔古纳市| 抚松县| 鄂托克前旗| 伽师县| 永寿县| 台北市| 永和县| 渭南市| 广饶县| 寿光市| 潼关县| 两当县| 灵寿县| 彭水| 宝坻区| 西乌珠穆沁旗| 漾濞| 正安县| 安阳县| 句容市| 专栏| 卢氏县| 乌鲁木齐市| 惠来县| 大宁县| 邵阳市| 盐城市| 青岛市| 潜山县| 松阳县| 庐江县| 衡水市| 天津市| 称多县| 湖北省| 商河县| 商河县| 新民市| 左贡县| 桃园市| 拉孜县|