Лучшие практики для интеграции веб-инфраструктуры CherryPy, сеансов SQLAlchemy и lighttpd для обслуживания высоконагруженного веб-сервиса - PullRequest
6 голосов
/ 09 марта 2009

Я разрабатываю сервер CherryPy FastCGI за lighttpd со следующей настройкой, чтобы включить использование сеансов ORM SQLAlchemy внутри контроллеров CherryPy. Однако, когда я запускаю стресс-тесты с 14 одновременными запросами для примерно 500 циклов, через некоторое время он начинает выдавать ошибки, такие как AttributeError: '_ThreadData' object has no attribute 'scoped_session_class' в open_dbsession() или AttributeError: 'Request' object has no attribute 'scoped_session_class' в close_dbsession(). Общая частота ошибок составляет около 50%.

Это происходит только тогда, когда я запускаю сервер за lighttpd, а не когда он запускается напрямую через cherrypy.engine.start(). Подтверждено, что connect() не вызывает исключений.

Я также пытался присвоить возвращаемое значение scoped_session для GlobalSession (как это делает здесь ), но затем он выдал ошибки, такие как UnboundExceptionError и другие ошибки уровня SA. (Параллельность: 10, циклы: 1000, частота ошибок: 16%. Происходит даже при непосредственном запуске.)

Есть несколько возможных причин, но мне не хватает знаний, чтобы выбрать одну.
1. Надежны ли подписки start_thread в среде FastCGI? Похоже, open_dbsession() вызывается раньше connect()
2. Очищается ли cherrypy.thread_data по какой-то причине?

код сервера

import sqlalchemy as sa  
from sqlalchemy.orm import session_maker, scoped_session

engine = sa.create_engine(dburi, strategy="threadlocal")  
GlobalSession = session_maker(bind=engine, transactional=False)

def connect(thread_index):  
    cherrypy.thread_data.scoped_session_class = scoped_session(GlobalSession)

def open_dbsession():  
    cherrypy.request.scoped_session_class = cherrypy.thread_data.scoped_session_class

def close_dbsession():  
    cherrypy.request.scoped_session_class.remove()


cherrypy.tools.dbsession_open = cherrypy.Tool('on_start_resource', open_dbsession)  
cherrypy.tools.dbsession_close = cherrypy.Tool('on_end_resource', close_dbsession)  
cherrypy.engine.subscribe('start_thread', connect)

lighttpd fastcgi config

...
var.server_name = "test"
var.server_root = "/path/to/root"
var.svc_env = "test"
fastcgi.server = (
  "/" => (
    "cherry.fcgi" => (
      "bin-path" => server_root + "/fcgi_" + server_name + ".fcgi",
      "bin-environment" => (
        "SVC_ENV" => svc_env
      ),
      "bin-copy-environment" => ("PATH", "LC_CTYPE"),
      "socket" => "/tmp/cherry_" + server_name + "." + svc_env + ".sock",
      "check-local" => "disable",
      "disable-time"    => 1,
      "min-procs"       => 1,
      "max-procs"       => 4,
    ),
  ),
)

редактирует

  • Восстановил отсутствующий аргумент thread_index в примере кода из исходного исходного кода (благодаря комментарию)
  • Уточнил, что ошибки не возникают сразу
  • Сузили условия до lighttpd

Ответы [ 2 ]

1 голос
/ 09 марта 2009

Если вы посмотрите на plugins.ThreadManager.acquire_thread, вы увидите строку self.bus.publish('start_thread', i), где i - индекс массива видимого потока. Любой слушатель, подписанный на канал start_thread, должен принять это значение i в качестве позиционного аргумента. Поэтому переписайте функцию соединения следующим образом: def connect(i):

Полагаю, это как-то не работает; Я посмотрю, смогу ли я отследить это, проверить и исправить.

0 голосов
/ 10 марта 2009

Я также пытался присвоить возвращаемое значение scoped_session для GlobalSession (как это делает здесь), но затем он выдал ошибки, такие как UnboundExceptionError и другие ошибки уровня SA. (Параллельность: 10, циклы: 1000, частота ошибок: 16%)

Эта ошибка не возникала, если я не создал экземпляр класса scoped_session явно.

т.е.

GlobalSession = scoped_session(session_maker(bind=engine, transactional=False))

def connect(thread_index):  
    cherrypy.thread_data.scoped_session_class = GlobalSession

def open_dbsession():  
    cherrypy.request.scoped_session_class = cherrypy.thread_data.scoped_session_class

def close_dbsession():  
    cherrypy.request.scoped_session_class.remove()
...