激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

腳本之家,腳本語言編程技術及教程分享平臺!
分類導航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務器之家 - 腳本之家 - Python - python分布式環境下的限流器的示例

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

2020-12-13 00:52扎心了老鐵 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(),調用這個方法,返回是否限制流量。

?
1
2
3
4
5
6
7
8
9
10
11
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一次,如果大于限速了,就返回限制。否則就允許訪問。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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。

?
1
2
3
4
5
6
7
8
9
10
11
12
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抽象類

?
1
2
3
4
5
6
7
8
9
10
11
12
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能同時執行成功。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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

多線程場景下測試代碼 

?
1
2
3
4
5
6
7
8
9
10
11
12
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()

分布式場景下測試代碼

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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就全部限制住了。也就是不能平滑限流。

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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。

原文鏈接:http://www.cnblogs.com/kangoroo/p/7700758.html?utm_source=tuicool&utm_medium=referral

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: av免费片 | 国产欧美成人精品第二区 | 护士xxxx| 国内毛片视频 | 亚洲欧美日韩精品久久亚洲区色播 | 亚洲第一精品在线 | 成人短视频在线播放 | 在线a毛片免费视频观看 | 国产手机在线视频 | 天天干天天碰 | 欧美××××黑人××性爽 | 黑人操穴 | 国产合集91合集久久日 | 中国大陆高清aⅴ毛片 | av在线1 | 国产毛片毛片 | 亚洲天堂在线电影 | 久久亚洲网 | 久章草影院 | 成人小视频在线播放 | 日本免费不卡一区二区 | 欧美一级电影在线观看 | 免费在线观看中文字幕 | 日本成人午夜视频 | 久久在线免费视频 | 国产剧情在线观看一区二区 | 国产色爱综合网 | 有兽焉免费动画 | 一本免费视频 | 爽爽淫人综合网网站 | 欧美日韩一 | 女人久久久www免费人成看片 | 亚洲视频观看 | chengrenyingshi| 久久九九热re6这里有精品 | 欧美城网站地址 | 一级做a爱片毛片免费 | 久久综合给合久久狠狠狠97色69 | 久久成人激情视频 | 狠狠久久伊人中文字幕 | 亚洲第一成人av |