Cherrypy:它解决了处理时间长的页面



我有一个由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)

相关内容

  • 没有找到相关文章

最新更新