【问题标题】:Using asyncio for doing a/b testing in Python在 Python 中使用 asyncio 进行 a/b 测试
【发布时间】:2022-01-04 17:19:59
【问题描述】:

假设有一些 API 已经在生产环境中运行,并且您创建了另一个 API,您希望使用到达生产 API 的传入请求进行 A/B 测试。现在我想知道,是否有可能做这样的事情,(我知道人们通过为 A/B 测试等保留两个不同的 API 版本来进行流量拆分)

收到生产 API 的传入请求后,您立即向新 API 发出异步请求,然后继续处理生产 API 的其余代码,然后在返回最终响应之前返回给调用者,您检查是否为您之前创建的异步任务计算了结果。如果它可用,则返回它而不是当前 API。

我想知道,做这样的事情最好的方法是什么?我们是否尝试为此或其他东西编写装饰器?如果我们在这里使用异步,我有点担心会发生很多边缘情况。任何人有任何关于使代码或整个方法更好的建议吗?

感谢您的宝贵时间!


上述方法的一些伪代码,

import asyncio

def call_old_api():
    pass

async def call_new_api():
    pass

async def main():
    task = asyncio.Task(call_new_api())

    oldResp = call_old_api()
    resp = await task

    if task.done():
        return resp
    else:
        task.cancel() # maybe
        return oldResp

asyncio.run(main())

【问题讨论】:

    标签: python-3.x api asynchronous python-asyncio ab-testing


    【解决方案1】:

    你不能只在 asyncio 的协程中执行 call_old_api()。有详细解释为什么here。请确保您理解它,因为根据您的服务器的工作方式,您可能无法做您想做的事情(在同步服务器上运行异步 API,保留编写异步代码的点,for example)。

    如果你明白你在做什么,并且你有一个异步服务器,你可以在线程中调用旧的同步 API 并使用一个任务来运行新的 API:

    task = asyncio.Task(call_new_api())
    oldResp = await in_thread(call_old_api())
    
    if task.done():
        return task.result()  # here you should keep in mind that task.result() may raise exception if the new api request failed, but that's probably ok for you
    else:
        task.cancel() # yes, but you should take care of the cancelling, see - https://stackoverflow.com/a/43810272/1113207
        return oldResp
    

    我认为您可以走得更远,而不是总是等待旧 API 完成,您可以同时运行两个 API 并返回第一个完成的 API(以防新 API 比旧 API 运行得更快)。通过上面的所有检查和建议,它应该看起来像这样:

    import asyncio
    import random
    import time
    from contextlib import suppress
    
    
    def call_old_api():
        time.sleep(random.randint(0, 2))
        return "OLD"
    
    
    async def call_new_api():
        await asyncio.sleep(random.randint(0, 2))
        return "NEW"
    
    
    async def in_thread(func):
        loop = asyncio.get_running_loop()
        return await loop.run_in_executor(None, func)
    
    
    async def ensure_cancelled(task):
        task.cancel()
        with suppress(asyncio.CancelledError):
            await task
    
    
    async def main():
        old_api_task = asyncio.Task(in_thread(call_old_api))
        new_api_task = asyncio.Task(call_new_api())
    
        done, pending = await asyncio.wait(
            [old_api_task, new_api_task], return_when=asyncio.FIRST_COMPLETED
        )
    
        if pending:
            for task in pending:
                await ensure_cancelled(task)
    
        finished_task = done.pop()
        res = finished_task.result()
        print(res)
    
    
    asyncio.run(main())
    

    【讨论】:

    • 非常感谢您的回答和解释!加上那些超级酷的链接。我对 asyncio 有点陌生,这对我了解和理解很有帮助!再次感谢。
    • 只是出于学习的好奇心@Mikhail,如果旧的服务器 api 和新的服务器 api 是异步的,那么是否可以以这种方式进行 a/b 测试呢?任何我可以阅读或检查更多关于此的资源,因为我希望学习更多异步!谢谢。
    • @Aditya 如果旧的 API 端点是由它自己的服务器提供的(异步或同步),我想你可以通过 an async http request 来使用它。这是异步服务器方法的优点之一:您不必再担心第三方 I/O 请求,因为它们是异步的并且不会阻塞其他客户端。我没有现成的带有同步/异步解释的资源,但是 this article 我刚刚用谷歌搜索过似乎很不错,例如。
    • 是的,旧的 api 有自己的服务器正在运行,我相信我可以通过 async.gather 并发向他们两个发出异步请求,并希望它能正常工作!非常感谢您的帮助。你实际上已经在堆栈上写了很多惊人的答案?
    猜你喜欢
    • 1970-01-01
    • 2021-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-11
    相关资源
    最近更新 更多