flask_SQLAlchemy之Session的工作原理分析

首先要区分sqlalchemy的session(会话),Connection(连接),pool(连接池)这三者关系,session 使用连接来操作数据库,一旦任务完成 session 会将数据库 connection 交还给 pool

问题:

  1. flask_sqlalchemy如何保证session的线程安全(使用gunicorn做web服务器)
  2. 在一个请求中session在哪被释放,连接被回收的
  3. 使用gevent做gunicorn的worker的时候,和不用gevent在处理session时有啥区别

看代码

假设在这样一个工厂函数中初始化app和db

from flask_sqlalchemy import SQLAlchemy
def create_app(config=None):
	...
    app = Flask(__name__)
    db = SQLAlchemy(app)
	...
    return app

session用create_scoped_session来创建,SQLAlchemy默认用的是scoped_session(包装了一下session,用于保证session线程安全)

class SQLAlchemy(object):
	Query = None

    def __init__(self, app=None, use_native_unicode=True, session_options=None,
                 metadata=None, query_class=BaseQuery, model_class=Model,
                 engine_options=None):

        self.use_native_unicode = use_native_unicode
        self.Query = query_class
        self.session = self.create_scoped_session(session_options)
        self.Model = self.make_declarative_base(model_class, metadata)
        self._engine_lock = Lock()
        self.app = app
        self._engine_options = engine_options or {}
        _include_sqlalchemy(self, query_class)

        if app is not None:# 因为我们传了app,所以这里直接init
            self.init_app(app)

	def init_app(self, app):
		......
		# 初始化时设置flask回掉,在请求结束时清理session,可以看remove定义,会先关闭数据库连接,应该就还回pool了,然后再从ScopedRegistry的registry里删除掉session
		@app.teardown_appcontext
        def shutdown_session(response_or_exc):
            ......
            self.session.remove()
            return response_or_exc

scoped_session给每个子进程实例化了一个sqlalchemy.util._collections.ScopedRegistry,通过ScopedRegistry的registry字典给每个greenlet.greenlet线程保存了一份session,这样每个线程就有自己的session,不会有冲突,自己的线程需要session时,用自身实例作为key从registry里面取,取到就复用,取不到就创建保存

# flask_sqlalchemy/__init__.py
def create_scoped_session(self, options=None):
     if options is None:
          options = {}

      scopefunc = options.pop('scopefunc', _app_ctx_stack.__ident_func__)
      options.setdefault('query_cls', self.Query)
      return orm.scoped_session(
          self.create_session(options), scopefunc=scopefunc
      )

# sqlalchemy/orm/scoping.py
class scoped_session(object):
	def __init__(self, session_factory, scopefunc=None):
        self.session_factory = session_factory

        if scopefunc:
            self.registry = ScopedRegistry(session_factory, scopefunc)
        else:
            self.registry = ThreadLocalRegistry(session_factory)
    ...... 

# sqlalchemy/util/_collections.py
class ScopedRegistry(object):
	def __init__(self, createfunc, scopefunc):
        self.createfunc = createfunc
        self.scopefunc = scopefunc
        self.registry = {}

    def __call__(self):
        key = self.scopefunc()
        try:
            return self.registry[key]
        except KeyError:
            return self.registry.setdefault(key, self.createfunc())
    ......

如果gunicorn配置worker_class = ‘gevent’,使用协程,和上面使用线程一样,只是进程启动时会默认起一个greenlet.greenlet,这个greenlet.greenlet也会创建一个session,貌似一直没用。请求过来后,gevent生成Greenlet处理请求,使用Greenlet实例作为key去registry取session,没有则新建保存。

你可能感兴趣的:(python,flask,sqlalchemy)