2017-06-25 83 views
1

我正在開發一個帶有模塊pyTelegramBotAPI的bot,它通過webhooks與安裝的Flask + Gunicorn作爲webhooks的服務器一起工作。 Gunicorn正在與5名工人爲更好的速度,我的項目的結構看起來像這樣:電報bot的API速率限制

app.py 
bot.py 

在bot.py我有處理更新的功能:

def process_the_update(update): 
    logger.info(update) 
    update = types.Update.de_json(update) 
    bot.process_new_updates([update]) 

在app.py我進口這個函數,所以,每當更新到來時,app.py都會調用這個函數,bot將處理更新。在我的機器人用戶可以調用一個命令,它將使用外部API來獲取一些信息。問題是,這個外部API每秒限制3個請求。我需要配置一個具有這樣的速率限制的機器人。首先,我想用隊列的代碼做這樣的:

lock_queue = Queue(1) 
requests_queue = Queue(3) 
def api_request(argument): 
    if lock_queue.empty(): 
     try: 
      requests_queue.put_nowait(time.time()) 
     except queue.Full: 
      lock_queue.put(1) 
      first_request_time = requests_queue.get() 
      logger.info('First request time: ' + str(first_request_time)) 
      current_time = time.time() 
      passed_time = current_time - first_request_time 
      if passed_time >= 1: 
       requests_queue.put_nowait(time.time()) 
       lock_queue.get() 
      else: 
       logger.info(passed_time) 
       time.sleep(1 - passed_time) 
       requests_queue.put_nowait(time.time()) 
       lock_queue.get() 
    else: 
     lock_queue.put(1) 
     first_request_time = vk_requests_queue.get() 
     logger.info('First request time: ' + str(first_request_time)) 
     current_time = time.time() 
     passed_time = current_time - first_request_time 
     if passed_time >= 1: 
      requests_queue.put_nowait(time.time()) 
      lock_queue.get() 
     else: 
      logger.info(passed_time) 
      time.sleep(1 - passed_time) 
      requests_queue.put_nowait(time.time()) 
      lock_queue.get() 
    result = make_api_request(argument) # requests are made too by external module. 
    return result 

的邏輯是,因爲我以爲,由於模塊pyTelegramBotAPI使用更快的更新線程處理,所有線程會檢查requests_queue,這將有充足的時間3最後api_requests,所以3個請求中的第一個請求的時間將與當前時間進行比較(以檢查是否通過了第二個時間)。而且,因爲我需要確定,只有一個線程會同時進行這種比較,所以我做了lock_queue。 但是,問題在於,首先,gunicorn使用5名工作人員,所以總會有可能性,來自用戶的所有消息將在不同的進程中處理,並且這些進程會有自己的隊列。其次,即使我將工人數量設置爲默認值(1名工人),我仍然得到429錯誤,所以我認爲,我的代碼不會按我想要的那樣工作。

我想用redis做速率限制,所以每次在每個線程和進程中bot都會檢查最近3次請求的時間,但我仍然不確定,這是正確的方式,我不確定,如何寫這個。

會很高興,如果有人提出任何意見或(外部API不提供任何的x限速頭)

回答

0

寫這個功能,使用Redis的計算請求(在此基礎上https://www.binpress.com/tutorial/introduction-to-rate-limiting-with-redis/155的代碼,甚至範例教程)

import redis 

r_db = redis.Redis(port=port, db=db) 

def limit_request(request_to_make, limit=3, per=1, request_name='test', **kwargs): 
    over_limit_lua_ = ''' 
    local key_name = KEYS[1] 
    local limit = tonumber(ARGV[1]) 
    local duration = ARGV[2] 

    local key = key_name .. '_num_of_requests' 
    local count = redis.call('INCR', key) 
    if tonumber(count) > limit then 
     local time_left = redis.call('PTTL', key) 
     return time_left 
    end 
    redis.call('EXPIRE', key, duration) 
    return -2 
    ''' 

    if not hasattr(r_db, 'over_limit_lua'): 
     r_db.over_limit_lua = r_db.register_script(over_limit_lua_) 

    request_possibility = int(r_db.over_limit_lua(keys=request_name, args=[limit, per])) 
    if request_possibility > 0: 
     time.sleep(request_possibility/1000.0) 
     return limit_request(request_to_make, limit, per, request_name, **kwargs) 
    else: 
     request_result = request_to_make(**kwargs) 
     return request_result