我正在使用以下设置在lighttpd后面开发CherryPy FastCGI服务器,以启用在CherryPy Controller 中使用ORM SQLAlchemy session 。但是,当我用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(就像here一样),但是随后它给出了UnboundExceptionError之类的错误和其他SA级错误。 (并发:10,循环:1000,错误率:16%。即使直接运行,也会发生。)

有一些可能的原因,但我缺乏足够的知识来选择一个。
1.在FastCGI环境下start_thread订阅是否不可靠?似乎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配置
...
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
  • 最佳答案

    如果查看plugins.ThreadManager.acquire_thread,您将看到self.bus.publish('start_thread', i)行,其中i是可见线程的数组索引。订阅start_thread channel 的所有监听器都需要接受i值作为位置参数。因此,将您的connect函数重写为:def connect(i):
    我的猜测是某种程度上的默默失败。我将查看是否可以跟踪,测试和修复它。

    关于python - 集成CherryPy Web框架,SQLAlchemy session 和lighttpd以提供高负载Web服务的最佳实践,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/625288/

    10-11 16:36