尝试在python中向并行API调用添加节流控制

尝试在python中向并行API调用添加节流控制,python,multithreading,threadpool,python-multithreading,python-decorators,Python,Multithreading,Threadpool,Python Multithreading,Python Decorators,我正在使用Google places API,它的每秒查询次数限制为10。这意味着我不能在一秒钟内提出超过10个请求。如果我们使用串行执行,这不会是一个问题,因为API平均响应时间是250毫秒,所以我将能够在一秒钟内进行4次调用 为了利用整个10QPS限制我使用了多线程并进行了并行API调用。但是现在我需要控制一秒钟内可能发生的调用数量,它不应该超过10(如果我超过限制,google API就会抛出错误) 下面是我到目前为止的代码,我无法理解为什么程序有时会卡住,或者比需要的时间长很多 impo

我正在使用Google places API,它的每秒查询次数限制为10。这意味着我不能在一秒钟内提出超过10个请求。如果我们使用串行执行,这不会是一个问题,因为API平均响应时间是250毫秒,所以我将能够在一秒钟内进行4次调用

为了利用整个10QPS限制我使用了多线程并进行了并行API调用。但是现在我需要控制一秒钟内可能发生的调用数量,它不应该超过10(如果我超过限制,google API就会抛出错误)

下面是我到目前为止的代码,我无法理解为什么程序有时会卡住,或者比需要的时间长很多

import time
from datetime import datetime
import random
from threading import Lock
from concurrent.futures import ThreadPoolExecutor as pool
import concurrent.futures
import requests
import matplotlib.pyplot as plt
from statistics import mean
from ratelimiter import RateLimiter

def make_parallel(func, qps=10):
    lock = Lock()
    threads_execution_que = []
    limit_hit = False
    def qps_manager(arg):
        current_second = time.time()
        lock.acquire()
        if len(threads_execution_que) >= qps or limit_hit:
            limit_hit = True
            if current_second - threads_execution_que[0] <= 1:
                time.sleep(current_second - threads_execution_que[0])
        current_time = time.time()
        threads_execution_que.append(current_time)
        lock.release()

        res = func(arg)

        lock.acquire()
        threads_execution_que.remove(current_time)
        lock.release()
        return res

    def wrapper(iterable, number_of_workers=12):
        result = []
        with pool(max_workers=number_of_workers) as executer:
            bag = {executer.submit(func, i): i for i in iterable}
            for future in concurrent.futures.as_completed(bag):
                result.append(future.result())
        return result
    return wrapper

@make_parallel
def api_call(i):
    min_func_time = random.uniform(.25, .3)
    start_time = time.time()
    try:
        response = requests.get('https://jsonplaceholder.typicode.com/posts', timeout=1)
    except Exception as e:
        response = e
    if (time.time() - start_time) - min_func_time < 0:
        time.sleep(min_func_time - (time.time() - start_time))
    return response

api_call([1]*50)
导入时间
从日期时间导入日期时间
随机输入
从线程导入锁
从concurrent.futures导入ThreadPoolExecutor作为池
进口期货
导入请求
将matplotlib.pyplot作为plt导入
从统计数据看进口意味着什么
来自税率限制器进口税率限制器
def使_平行(func,qps=10):
lock=lock()
线程\u执行\u que=[]
限制命中=错误
def qps_经理(arg):
当前秒=时间。时间()
lock.acquire()
如果len(threads\u execution\u que)>=qps或limit\u hit:
极限命中=真
如果当前\u second-threads\u execution\u que[0]看起来像这样,那么:

from ratelimit import limits, sleep_and_retry

@make_parallel
@sleep_and_retry
@limits(calls=10, period=1)
def api_call(i):
    try:
        response = requests.get("https://jsonplaceholder.typicode.com/posts", timeout=1)
    except Exception as e:
        response = e
    return response
编辑:我做了一些测试,看起来
@sleep\u和_retry
有点过于乐观,所以只需将周期稍微延长一点,到1.2秒:

s = datetime.now()
api_call([1] * 50)
elapsed_time = datetime.now() - s
print(elapsed_time > timedelta(seconds=50 / 10))

您的代码有语法和逻辑错误。请修复缩进:
返回包装器
处于全局范围
wrapper
不在任何地方使用,而且
make_parallel
无法工作,因为它返回none抱歉,修复了缩进。代码现在应该有意义了。您能澄清一下qps\U经理正在做什么吗?它没有在任何地方调用,所以我不明白为什么需要它。我在几个场景中测试过它,QPS限制正在发生!!:)但是,我们正在包装make_parallel以及ratelimit装饰器,这难道不意味着我们正在对每个线程进行速率限制,而不是将整个过程作为一个整体进行限制吗???@Scarface,如果我们按照正确的顺序进行装饰,就不会这样。
@make_parallel
的输入是以前装饰程序的结果,因此
@make_parallel
采用了速率受限的函数,并并行运行。谢谢,它解决了我的问题,也解决了我的一些问题。