简体   繁体   English

API 电报机器人的速率限制

[英]API rate limit for telegram bot

I am developing a bot with module pyTelegramBotAPI, which is working via webhooks with installed Flask+Gunicorn as server for webhooks.我正在开发一个带有模块 pyTelegramBotAPI 的机器人,它通过 webhooks 工作,并安装了 Flask+Gunicorn 作为 webhooks 服务器。 Gunicorn is working with 5 workers for better speed, structure of my project looks like that: Gunicorn 正在与 5 名工人合作以提高速度,我的项目结构如下所示:

app.py
bot.py

In bot.py i have a function for processing updates:在 bot.py 中,我有一个 function 用于处理更新:

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

In app.py i imported this function, so, whenever an update comes, app.py will call this function, and bot will handle the update.在 app.py 中,我导入了这个 function,因此,每当更新到来时,app.py 都会调用这个 function,bot 将处理更新。 In my bot user can call a command, which will use an external api to get some information.在我的机器人中,用户可以调用一个命令,该命令将使用外部 api 来获取一些信息。 The problem is, that this external api has limit for 3 requests per second.问题是,这个外部 api 限制为每秒 3 个请求。 I need to configure a bot with such rate limit.我需要配置一个具有这种速率限制的机器人。 First i thought of doing it with Queue with code like this:首先,我想到用 Queue 来做,代码如下:

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 

The logic was that, as i thought, because module pyTelegramBotAPI uses threads for faster updates handling, all threads would check requests_queue, which will have time of 3 last api_requests, and so the time of the first of 3 requests made will be compared to the current time (to check, if a second passed).逻辑是,正如我所想的那样,因为模块 pyTelegramBotAPI 使用线程来更快地处理更新,所有线程都会检查 requests_queue,这将有 3 个最后 api_requests 的时间,因此 3 个请求中的第一个的时间将与当前时间(检查是否过了一秒)。 And, because i needed to be sure, that only one thread would do this kind of comparison simultaneously, i made lock_queue.而且,因为我需要确定只有一个线程会同时进行这种比较,所以我创建了 lock_queue。 But, the problem is that, firstly, gunicorn uses 5 workers, so there will be always possibility, that all messages from users will be handled in different processes, and these processes will have their own queues.但是,问题在于,首先,gunicorn 使用了 5 个 worker,因此总是有可能来自用户的所有消息将在不同的进程中处理,而这些进程将有自己的队列。 And, secondly, even if i set number of workers to default (1 worker), i still get 429 error, so i think, that my code won't work as i wanted at all.其次,即使我将工人数量设置为默认值(1 名工人),我仍然会收到 429 错误,所以我认为我的代码根本无法按我想要的方式工作。

I thought of making rate limit with redis, so every time in every thread and process bot will check the time of last 3 requests, but still i am not sure, that this is the right way, and i am not sure, how to write this.我想用 redis 进行速率限制,所以每次在每个线程和进程中机器人都会检查最后 3 个请求的时间,但我仍然不确定,这是正确的方法,我不确定如何写这个。

Would be glad, if someone suggests any ideas or even examples of code (external api does not provide any x-rate-limit header)很高兴,如果有人提出任何想法甚至代码示例(外部 api 不提供任何 x-rate-limit 标头)

Wrote this function, using redis to count requests (based on this https://www.binpress.com/tutorial/introduction-to-rate-limiting-with-redis/155 tutorial)写了这个function,使用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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM