我有一个由cherry提供支持的网站。对于某些页面,我需要相当长的处理时间(数百万行数据库上的多连接SQL请求)。处理过程有时需要20秒或更长时间,浏览器会因为太长而崩溃。
这里的一切都取决于网站的容量。CherryPy是一个线程服务器,一旦每个线程都在等待数据库,新的请求将不会被处理。也有请求队列的方面,但总的来说是这样的。
穷人的解决方案
如果你知道你有小流量,你可以试着解决。如果需要,增加response.timeout
(默认为300秒)。增加server.thread_pool
(默认为10)。如果您使用备用代理,如nginx,在CherryPy应用程序前,也增加代理超时时间。
下面的解决方案需要你重新设计你的网站。特别是使其异步,其中客户端代码发送任务,然后使用拉或推来获得其结果。它将需要在线的两端进行更改。
CherryPy BackgroundTask
您可以在服务器端使用cherrypy.process.plugins.BackgroundTask
和一些中间存储(例如数据库中的新表)。用于拉取的XmlHttpRequest或用于推送到客户端的WebSockets。CherryPy可以同时处理。
请注意,因为CherryPy是在单个Python进程中运行的,所以后台任务的线程也将在其中运行。如果执行一些SQL结果集的后处理,就会受到GIL的影响。因此,您可能需要将其重写为使用进程,这有点复杂。
工业解决方案如果你的网站运行或被认为是大规模运行,你最好考虑像Rq或芹菜这样的分布式任务队列。它使服务器端有所不同。客户端是相同的拉或推。
下面是一个带有XHR轮询的BackgroundTags
的玩具实现。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
import uuid
import cherrypy
from cherrypy.process.plugins import BackgroundTask
config = {
'global' : {
'server.socket_host' : '127.0.0.1',
'server.socket_port' : 8080,
'server.thread_pool' : 8,
}
}
class App:
_taskResultMap = None
def __init__(self):
self._taskResultMap = {}
def _target(self, task, id, arg):
time.sleep(10) # long one, right?
try:
self._taskResultMap[id] = 42 + arg
finally:
task.cancel()
@cherrypy.expose
@cherrypy.tools.json_out()
def schedule(self, arg):
id = str(uuid.uuid1())
self._taskResultMap[id] = None
task = BackgroundTask(
interval = 0, function = self._target, args = [id, int(arg)],
bus = cherrypy.engine)
task.args.insert(0, task)
task.start()
return str(id)
@cherrypy.expose
@cherrypy.tools.json_out()
def poll(self, id):
if self._taskResultMap[id] is None:
return {'id': id, 'status': 'wait', 'result': None}
else:
return {
'id' : id,
'status' : 'ready',
'result' : self._taskResultMap.pop(id)
}
@cherrypy.expose
def index(self):
return '''<!DOCTYPE html>
<html>
<head>
<title>CherryPy BackgroundTask demo</title>
<script type='text/javascript'
src='http://cdnjs.cloudflare.com/ajax/libs/qooxdoo/3.5.1/q.min.js'>
</script>
<script type='text/javascript'>
// Do not structure you real JavaScript application this way.
// This callback spaghetti is only for brevity.
function sendSchedule(arg, callback)
{
var xhr = q.io.xhr('/schedule?arg=' + arg);
xhr.on('loadend', function(xhr)
{
if(xhr.status == 200)
{
callback(JSON.parse(xhr.responseText))
}
});
xhr.send();
};
function sendPoll(id, callback)
{
var xhr = q.io.xhr('/poll?id=' + id);
xhr.on('loadend', function(xhr)
{
if(xhr.status == 200)
{
callback(JSON.parse(xhr.responseText))
}
});
xhr.send();
}
function start(event)
{
event.preventDefault();
// example argument to pass to the task
var arg = Math.round(Math.random() * 100);
sendSchedule(arg, function(id)
{
console.log('scheduled (', arg, ') as', id);
q.create('<li/>')
.setAttribute('id', id)
.append('<span>' + id + ': 42 + ' + arg +
' = <img src="http://sstatic.net/Img/progress-dots.gif" />' +
'</span>')
.appendTo('#result-list');
var poll = function()
{
console.log('polling', id);
sendPoll(id, function(response)
{
console.log('polled', id, '(', response, ')');
if(response.status == 'wait')
{
setTimeout(poll, 2500);
}
else if(response.status == 'ready')
{
q('#' + id)
.empty()
.append('<span>' + id + ': 42 + ' + arg + ' = ' +
response.result + '</span>');
}
});
};
setTimeout(poll, 2500);
});
}
q.ready(function()
{
q('#run').on('click', start);
});
</script>
</head>
<body>
<p>
<a href='#' id='run'>Run a long task</a>, look in browser console.
</p>
<ul id='result-list'></ul>
</body>
</html>
'''
if __name__ == '__main__':
cherrypy.quickstart(App(), '/', config)