简体   繁体   English

与Tornado服务器的多个异步HTTP连接

[英]Multiple Async HTTP connections to Tornado Server

I have a tornado server which I am trying to make synchronous. 我有一个试图使之同步的龙卷风服务器。 I have a client which makes asynchronous requests to the server simultaneously. 我有一个客户端,它同时向服务器发出异步请求。 It pings the server every 5 seconds with a heartbeat and secondly, it makes a GET request for a job whenever it can. 它每5秒钟使用一次心跳ping服务器一次,其次,它会在可能的情况下发出GET请求。

On the server side, there is a thread-safe queue which contains jobs. 在服务器端,有一个包含作业的线程安全队列。 It blocks for 20 seconds if the queue is empty. 如果队列为空,它将阻塞20秒。 I want it to hold the connection and block for that 20 seconds and when it returns, it writes "No job" to the client. 我希望它保持该连接并阻塞20秒钟,当它返回时,它会向客户端写入“无作业”。 As soon as a job is available, it should immediately write it to the client since queue.get() would return. 作业可用后,应立即将其写入客户端,因为queue.get()将返回。 I want the heartbeats to continue happening in the background while this request is blocked. 我希望在此请求被阻止的同时,心跳继续在后台发生。 Here I am making two asynchronous requests to the server from the same client. 在这里,我正在从同一客户端向服务器发出两个异步请求。

Here is a sample project I build which kind of simulates my issue. 这是我构建的一个示例项目,可以模拟我的问题。

Server: 服务器:

import tornado.ioloop
import tornado.web
from queue import Queue
from tornado import gen

q = Queue()


class HeartBeatHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def post(self):
        print("Heart beat")


class JobHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self):
        print("Job")
        try:
            job = yield q.get(block=True, timeout=20)
            self.write(job)
        except Exception as e:
            self.write("No job")


def make_app():
    return tornado.web.Application([
        (r"/heartbeat", HeartBeatHandler),
        (r"/job", JobHandler),
    ])


if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    try:
        tornado.ioloop.IOLoop.current().start()
    except KeyboardInterrupt:
        tornado.ioloop.IOLoop.current().stop()

Client: 客户:

import asyncio
from tornado import httpclient, gen


@gen.coroutine
def heartbeat_routine():
    while True:
        http_client = httpclient.AsyncHTTPClient()
        heartbeat_request = httpclient.HTTPRequest("http://{}/heartbeat".format("127.0.0.1:8888"), method="POST",
                                                   body="")
        try:
            yield http_client.fetch(heartbeat_request)
            yield asyncio.sleep(5)
        except httpclient.HTTPError as e:
            print("Heartbeat failed!\nError: {}".format(str(e)))

        http_client.close()


@gen.coroutine
def worker_routine():
    while True:
        http_client = httpclient.AsyncHTTPClient(defaults=dict(request_timeout=180))
        job_request = httpclient.HTTPRequest("http://{}/job".format("127.0.0.1:8888"), method="GET")
        try:
            response = yield http_client.fetch(job_request)
            print(response.body)
        except httpclient.HTTPError as e:
            print("Heartbeat failed!\nError: {}".format(str(e)))

        http_client.close()


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    asyncio.ensure_future(heartbeat_routine())
    asyncio.ensure_future(worker_routine())
    loop.run_forever()

Questions: 问题:

  1. The problem is that the heartbeats also block for those 20 seconds while the queue.get() blocks. 问题在于,当queue.get()阻塞时,心跳也会阻塞这20秒。 Which I do not want. 我不要。
  2. As you can see in my client I set request timeout to 180 seconds. 如您所见,在我的客户端中,我将请求超时设置为180秒。 But that never seems to work with tornado. 但这似乎与龙卷风无关。 If you increase queue.get() timeout above 20 seconds, it returns error code saying request timed out. 如果将queue.get()超时增加到20秒以上,它将返回错误代码,说明请求已超时。
  1. If you use a thread-safe queue, you must use not use blocking operations from the IOLoop thread. 如果使用线程安全队列,则必须不使用IOLoop线程的阻塞操作。 Instead, run them in a thread pool: 而是在线程池中运行它们:

     job = yield IOLoop.current().run_in_executor(None, lambda: q.get(block=True, timeout=20)) 

    Alternately, you could use Tornado's async (but thread-unsafe) queue, and use IOLoop.add_callback whenever you need to interact with the queue from another thread. 或者,您可以使用Tornado的异步(但线程不安全)队列,并在需要与另一个线程的队列进行交互时使用IOLoop.add_callback

  2. There's some magic in the AsyncHTTPClient constructor, which tries to share existing instances when possible, but this means that constructor arguments are only effective the first time. AsyncHTTPClient构造函数中有一些魔术,它尽可能地共享现有实例,但这意味着构造函数参数仅在第一次生效。 The worker_routine is picking up the default instances created by heartbeat_routine . worker_routine正在拾取heartbeat_routine创建的默认实例。 Add force_instance=True to ensure you get a fresh client in worker_routine (and call .close() on it when you're done) 添加force_instance=True以确保您在worker_routine获得了一个新客户端(并在完成后对其调用.close()

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

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