【问题标题】:Best practice for integrating CherryPy web-framework, SQLAlchemy sessions and lighttpd to serve a high-load webservice集成 CherryPy Web 框架、SQLAlchemy 会话和 lighttpd 以提供高负载 Web 服务的最佳实践
【发布时间】:2010-10-12 03:16:17
【问题描述】:

我正在使用以下设置在 lighttpd 后面开发 CherryPy FastCGI 服务器,以启用在 CherryPy 控制器中使用 ORM SQLAlchemy 会话。但是,当我对大约 500 个循环的 14 个并发请求运行压力测试时,它开始在一段时间后在open_dbsession() 中给出AttributeError: '_ThreadData' object has no attribute 'scoped_session_class' 或在close_dbsession() 中的AttributeError: 'Request' object has no attribute 'scoped_session_class' 之类的错误。错误率总共在 50% 左右。

只有当我在 lighttpd 后面运行服务器时才会发生这种情况,而不是直接通过 cherrypy.engine.start() 运行服务器时。已确认 connect() 没有引发异常。

我还尝试将scoped_session 的返回值分配给GlobalSession(就像here 一样),但随后它给出了UnboundExceptionError 之类的错误和其他SA 级错误。 (并发:10,循环:1000,错误率:16%。即使直接运行也会发生。)

有一些可能的原因,但我缺乏足够的知识来选择一个。
1、start_thread订阅在FastCGI环境下是否不可靠?似乎在connect()之前调用了open_dbsession()
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

【问题讨论】:

标签: python sqlalchemy lighttpd cherrypy


【解决方案1】:

如果您查看plugins.ThreadManager.acquire_thread,您将看到self.bus.publish('start_thread', i) 行,其中i 是所见线程的数组索引。订阅start_thread 频道的任何侦听器都需要接受i 值作为位置参数。所以重写你的连接函数来读取:def connect(i):

我猜它不知何故默默地失败了;我会看看我是否可以追踪、测试和修复它。

【讨论】:

  • 它确实不会默默地失败。检查您的错误日志;他们应该抓住这个。
  • 感谢您的回答。我检查了函数,它已经有了参数。 (我似乎把它排除在代码示例之外。我正在编辑它。抱歉。)
【解决方案2】:

我还尝试将 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()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-03-15
    • 1970-01-01
    • 2012-10-08
    • 2014-07-07
    • 1970-01-01
    • 2015-07-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多