python - Cherrypy: qué soluciones para páginas con un gran tiempo de procesamiento
design long-running-processes (1)
Tengo un sitio web impulsado por cherrypy. Para algunas páginas, necesito un tiempo de procesamiento bastante largo (una solicitud SQL de combinación múltiple en un DB de varios millones de filas). El procesamiento necesita a veces 20 segundos o más, y el navegador se cuelga porque es demasiado largo.
Me pregunto cuál sería una buena solución aquí.
Todo aquí depende de un volumen del sitio web. CherryPy es un servidor de subprocesos y una vez que cada subproceso está esperando la base de datos, las nuevas solicitudes no se procesarán. También hay un aspecto de la cola de solicitudes, pero en general es así.
La solución del pobre
Si sabe que tiene poco tráfico, puede intentar solucionarlo. Aumente la response.timeout
Timeout si es necesario (el valor predeterminado es 300 segundos). Aumente server.thread_pool
(por defecto es 10). Si usa proxy de reserva, como nginx , frente a la aplicación CherryPy , aumente el tiempo de espera de proxy allí también.
Las siguientes soluciones requerirán que rediseñes tu sitio web. Específicamente para que sea asincrónico, donde el código del cliente envía una tarea, y luego usa pull o push para obtener su resultado. Se requerirán cambios en ambos lados del cable.
CherryPy BackgroundTask
Puede utilizar cherrypy.process.plugins.BackgroundTask
y algún almacenamiento intermedio (por ejemplo, nueva tabla en su base de datos) en el servidor. XmlHttpRequest for pull o WebSockets para enviar al lado del cliente. CherryPy puede manejar ambos.
Tenga en cuenta que debido a que CherryPy se ejecuta en un único proceso de Python, el subproceso de la tarea en segundo plano también se ejecutará dentro de él. Si realiza algún resultado de conjunto de resultados de SQL, se verá afectado por GIL . Por lo tanto, es posible que desee reescribirlo para usar procesos en su lugar, lo cual es un poco más complicado.
Solución industrial
Si su sitio web opera o se considera que funciona a escala, es mejor considerar una cola de tareas distribuidas como Rq o Apio . Hace la diferencia del lado del servidor. El lado del cliente es el mismo tirar o empujar.
Ejemplo
Aquí sigue una implementación de juguetes para BackgroundTags
con XHR sondeo.
#!/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)