Tornado AsyncHTTPClient性能下降
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tornado AsyncHTTPClient性能下降相关的知识,希望对你有一定的参考价值。
设置:Python 2.7.15,Tornado 5.1
我有一台网络服务器机器,每秒处理约40个/recommend
请求。平均响应时间为25毫秒,但存在很大差异(一些请求可能需要超过500毫秒)。
每个请求在内部生成1-8个Elasticsearch查询(HTTP请求)。每个Elasticsearch查询可能需要1-150毫秒。
Elasticsearch请求通过elasticsearch-dsl库同步处理。
目标是减少I / O等待时间(查询到Elasticsearch)并每秒处理更多请求,这样我就可以减少机器数量。有一件事是不可接受的 - 我不想增加平均处理时间(25毫秒)。
我在网上发现了一些龙卷风弹性搜索实现,但由于我只需要使用一个端点到Elasticsearch(/_search
),我试图单独做到这一点。
下面是我的网络服务器的退化实现。使用相同的负载(每秒约40个请求),平均请求响应时间增加到200ms!
深入研究,我发现内部异步处理时间(对Elasticsearch的查询)不稳定,每个fetch
调用所花费的时间可能不同,并且总平均值(在ab
负载测试中)很高。
我正在使用ab
来模拟负载并通过打印当前的fetch
处理时间,平均fetch
处理时间和最大处理时间来内部测量它。当一次做一个请求(并发1)时:ab -p es-query-rcom.txt -T application/json -n 1000 -c 1 -k 'http://localhost:5002/recommend'
我的版画看起来像:[avg req_time: 3, dur: 3] [current req_time: 2, dur: 3] [max req_time: 125, dur: 125] reqs: 8000
但是当我尝试增加并发性(最多8个)时:ab -p es-query-rcom.txt -T application/json -n 1000 -c 8 -k 'http://localhost:5002/recommend'
现在我的版画看起来像:[avg req_time: 6, dur: 13] [current req_time: 4, dur: 4] [max req_time: 73, dur: 84] reqs: 8000
平均req现在慢了x2(或者我测量的x4)!我在这里想念什么?为什么我看到这种退化?
Web_server.朋友:
import tornado
from tornado.httpclient import AsyncHTTPClient
from tornado.options import define, options
from tornado.httpserver import HTTPServer
from web_handler import WebHandler
SERVICE_NAME = 'web_server'
NUM_OF_PROCESSES = 1
class Statistics(object):
def __init__(self):
self.total_requests = 0
self.total_requests_time = 0
self.total_duration = 0
self.max_time = 0
self.max_duration = 0
class RcomService(object):
def __init__(self):
print 'initializing RcomService...'
AsyncHTTPClient.configure("tornado.curl_httpclient.CurlAsyncHTTPClient", max_clients=3)
self.stats = Statistics()
def start(self, port):
define("port", default=port, type=int)
db = self.get_db(self.stats)
routes = self.generate_routes(db)
app = tornado.web.Application(routes)
http_server = HTTPServer(app, xheaders=True)
http_server.bind(options.port)
http_server.start(NUM_OF_PROCESSES)
tornado.ioloop.IOLoop.current().start()
@staticmethod
def generate_routes(db):
return [
(r"/recommend", WebHandler, dict(db=db))
]
@staticmethod
def get_db(stats):
return {
'stats': stats
}
def main():
port = 5002
print('starting %s on port %s', SERVICE_NAME, port)
rcom_service = RcomService()
rcom_service.start(port)
if __name__ == '__main__':
main()
Web_handler.朋友:
import time
import ujson
from tornado import gen
from tornado.gen import coroutine
from tornado.httpclient import AsyncHTTPClient
from tornado.web import RequestHandler
class WebHandler(RequestHandler):
def initialize(self, db):
self.stats = db['stats']
@coroutine
def post(self, *args, **kwargs):
result = yield self.wrapper_innear_loop([{}, {}, {}, {}, {}, {}, {}, {}]) # dummy queries (empty)
self.write({
'res': result
})
@coroutine
def wrapper_innear_loop(self, queries):
result = []
for q in queries: # queries are performed serially
res = yield self.async_fetch_gen(q)
result.append(res)
raise gen.Return(result)
@coroutine
def async_fetch_gen(self, query):
url = 'http://localhost:9200/my_index/_search'
headers = {
'Content-Type': 'application/json',
'Connection': 'keep-alive'
}
http_client = AsyncHTTPClient()
start_time = int(round(time.time() * 1000))
response = yield http_client.fetch(url, method='POST', body=ujson.dumps(query), headers=headers)
end_time = int(round(time.time() * 1000))
duration = end_time - start_time
body = ujson.loads(response.body)
request_time = int(round(response.request_time * 1000))
self.stats.total_requests += 1
self.stats.total_requests_time += request_time
self.stats.total_duration += duration
if self.stats.max_time < request_time:
self.stats.max_time = request_time
if self.stats.max_duration < duration:
self.stats.max_duration = duration
duration_avg = self.stats.total_duration / self.stats.total_requests
time_avg = self.stats.total_requests_time / self.stats.total_requests
print "[avg req_time: " + str(time_avg) + ", dur: " + str(duration_avg) +
"] [current req_time: " + str(request_time) + ", dur: " + str(duration) + "] [max req_time: " +
str(self.stats.max_time) + ", dur: " + str(self.stats.max_duration) + "] reqs: " +
str(self.stats.total_requests)
raise gen.Return(body)
我尝试使用异步类(Simple
vs curl
),max_clients
大小,但我不明白在我的情况下什么是最好的调整。但
增加的时间可能是因为并发== 1,CPU未被充分利用且c == 8,它被100%+利用并且无法赶上所有请求。例如,抽象CPU可以处理1000次操作/秒,发送请求需要50个CPU操作,并且读取请求结果也需要50个CPU操作。当你有5 RPS时,你的CPU利用率为50%,平均请求时间为50 ms(发送请求)+请求时间+ 50 ms(读取请求)。但是当你有40 RPS(比5 RPS多8倍)时,你的CPU将被过度使用400%,一些完成的请求将等待解析,所以平均请求时间现在是50 ms +请求时间+ CPU等待时间+ 50 ms。
总而言之,我的建议是检查两个负载的CPU利用率,并确保分析发送请求和解析响应所需的时间,CPU可能是您的瓶颈。
以上是关于Tornado AsyncHTTPClient性能下降的主要内容,如果未能解决你的问题,请参考以下文章