当新请求来自同一会话中的同一用户时,如何取消先前的请求

时间:2017-05-29 06:32:45

标签: python http nginx python-asyncio aiohttp

我们正在使用aiohttp建立一个休息api。我们的应用程序旨在让用户比收到回复更频繁地发送请求(因为计算时间)。对于用户来说,重要的只是最新请求的结果。是否可以停止对过时请求的计算?

谢谢

2 个答案:

答案 0 :(得分:3)

你正在构建非常类似于HTTP的东西。 HTTP请求的回答时间不应超过几毫秒,HTTP请求不应相互依赖;如果你需要执行需要相当长时间的计算,可以尝试通过更改架构/模型/缓存/任何内容来加速它们,或者将其明确地视为长期运行的作业,它可以通过HTTP接口进行控制。 / strong>这意味着“作业”是可以通过HTTP查询的“物理资源”。您可以通过POST请求创建资源:

POST /tasks
Content-Type: application/json

{"some": "parameters", "go": "here"}
{"resource": "/tasks/42"}

然后您可以查询任务的状态:

GET /tasks/42
{"status": "pending"}

最终得到结果:

GET /tasks/42
{"status": "done", "results": [...]}

当您发布取代旧任务的新任务时,您的后端可以以其认为合适的任何方式取消旧任务;然后,资源将返回“已取消”或类似状态。在开始新任务后,您的客户端不会再次查询旧资源。

即使您的客户端每秒查询一次资源,它仍然会在服务器上使用更少的资源(一个连接打开10秒,而10个连接在同一时间范围内打开200ms),尤其是如果您申请一些智能缓存。这也可以更加可扩展,因为您可以独立于HTTP前端扩展任务后端,并且HTTP前端可以简单地扩展到多个服务器和负载平衡器。

答案 1 :(得分:1)

我将发布@ Drizzt1991的解决方案:

  嘿那里,阿尔乔姆。你有相当奇怪的要求。   要知道如果1个客户端正在使用保持活动状态的套接字,那么在回答第一个请求之前,它实际上不可能看到下一个请求。这是HTTP的工作原理,它在发送另一个请求之前需要结果。   因此,只有当客户端在2个独立的套接字上运行时,您的情况才会起作用,但同样需要断言,来自同一客户端的2个套接字将在同一台机器上路由。通过实践,这对于故障转移和其他东西并没有那么好。基本上它将是一个有状态的API。   即使你做了所有这些,并非所有的图书馆都支持取消。传统的关系数据库只会忽略结果,但仍会处理挂起的查询。如果你做一些复杂的东西作为图形遍历并且你有很多步骤可以取消,那就行了。

     

但是如果断言,那个客户端使用一个套接字池并且它们被路由到同一台机器并且请求会从取消中获益,这样的事情应该可以解决这个问题:

import asyncio
import random

from aiohttp import web


def get_session_id(request):
    # I don't know how you do session management, so left it out
    return ""


async def handle(request):
    session_id = get_session_id(request)
    request['tr_id'] = tr_id = int(random.random() * 1000000)

    running_tasks = request.app['running_tasks']
    if session_id in running_tasks and not running_tasks[session_id].done():
        running_tasks[session_id].cancel()
        del running_tasks[session_id]

    current_task = asyncio.ensure_future(_handle_impl(request))
    running_tasks[session_id] = current_task

    try:
        resp = await current_task
    except asyncio.CancelledError:
        print("Cancelled request", tr_id)
        resp = web.Response(text="Cancelled {}".format(tr_id))
    finally:
        if running_tasks[session_id] is current_task:
            del running_tasks[session_id]
    return resp


async def _handle_impl(request):
    tr_id = request['tr_id']
    print("Start request", tr_id)
    await asyncio.sleep(10)
    print("Finished request", tr_id)
    return web.Response(text="Finished {}".format(tr_id))


app = web.Application()
app.router.add_get('/', handle)
app.router.add_get('/{name}', handle)

app['running_tasks'] = {}

web.run_app(app, host="127.0.0.1", port=8080)