头一次用python写正规web(超过4,5个文件),虽然继续烂尾,但踩了不少坑,也抄了不少的代码,总结下。。
我主要参考了这个项目。
数据库部分
首先关于数据库部分,xichuangzhu用了 falsk-sqlalchemy 也就是 flask 封装的 SQLAlchemy。我一开始没明白为什么要依赖这个东西。
于是参考这篇文章直接裸用了SQLAlchemy。
一开始,我看 xichuangzhu 的项目对所有对数据库的操作都没有封装,全都是取session,execute xx, session.commit, session.close 这样的流程,实在是不能忍,于是学了上篇的文章对一些公用的操作进行写封装,就类似 ibatis 自动能生成 crud 模版一样。然后代码实际开始使用后发现了我封装的是操作都只适用于单线程,一多线程就2b了。
原来的封装如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| DB_CONNECT_STRING = 'mysql+mysqldb://root:111111@localhost/test?charset=utf8' engine = create_engine(DB_CONNECT_STRING, echo=True, pool_size=10, max_overflow=0) DB_Session = sessionmaker(bind=engine)
session = scoped_session(DB_Session)
class BaseModel(BaseModel): __abstract__ = True
@classmethod def set_attrs(cls, id, attrs): if hasattr(cls, 'id'): session.query(cls).filter(cls.id == id).update(attrs) session.commit()
|
看起来很和谐,以为多线程下第一个线程做 query 的同时,第二个线程可以用同一个 session 继续query,其实不然,这个 session 在未 close 之前无法做别的查询,于是就2了。
于是再去看 flask-sqlalchemy 的源码知道这个东西存在的意义了。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| class SQLAlchemy(object):
def __init__(self, app=None, use_native_unicode=True, session_options=None): self.use_native_unicode = use_native_unicode
if session_options is None: session_options = {}
session_options.setdefault( 'scopefunc', connection_stack.__ident_func__ )
self.session = self.create_scoped_session(session_options)
def create_scoped_session(self, options=None): """Helper factory method that creates a scoped session.""" if options is None: options = {} scopefunc=options.pop('scopefunc', None) return orm.scoped_session( partial(_SignallingSession, self, **options), scopefunc=scopefunc )
def init_app(self, app): ... teardown = app.teardown_appcontext ...
@teardown def shutdown_session(response_or_exc): if app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']: if response_or_exc is None: self.session.commit() self.session.remove() return response_or_exc
|
它利用了flask的框架,为他的每个 request 都增加了 shutdown 的 hook 用于在一个请求完成后最后帮助 close 掉session。
可是也有个缺点,不是很通用,比较针对web,就是如果你的在一个请求中如果要请求多次数据库,那么这个过程中的 session 的 close 操作还是得自己 close,(因为你只有一个session) 这尼玛还是好蛋疼。
而且我业务代码都写的差不多了,该动起来也很烦,最最关键的是我用到多线程的地方只是个后台多线程处理xxx,和 flask 没有半毛钱关系,想用也用不了,于是只能自己写一个。
多线程操作db的原理就是每个线程拥有属于自己的 session,并且多线程的话必定是搭配session池来使用的,所以 session 在用完后不能 close,而是 remove 即将线程放回池。
session 的定义是 scoped_session, 然后在用完后将session close掉即可。
于是在网上找到个2种方式可以用来做一层代理。代理的作用是
create scoped_session
拿 session 做xxx业务
session.remove
关于实现方法网上找到2种。 一个是利用yield,stackoverflow 的具体例子 还有个是利用 with
由于不想以来其他的第三方包,于是用 with 实现了stackoverflow的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class SessionProxy: def __enter__(self): self.session = scoped_session(DB_Session) return self.session
def __exit__(self, type, value, trace): self.session.remove() logger.error('session error %s', trace) if trace is not None else trace
def get_session(): return SessionProxy()
class BaseModel(BaseModel): __abstract__ = True
@classmethod def set_attr(cls, session, id, attr, value): if hasattr(cls, 'id'): session.query(cls).filter(cls.id == id).update({ attr: value }) session.commit()
def xxx(): with get_session() as session: Video.set_attrs(session, v.id, {Video.status: 3, Video.result_status: 3}) upload_video(v) Video.set_attrs(session, v.id, {Video.status: 4, Video.result_status: 4})
|
虽然这样也有点蛋疼,我的业务代码中所有操作db的代码需要用with包起来。但好处是session用完后可以不需要close。而且完美支持多线程,并发最多支持到同时get_session() 池的大小
个(有点拗口。。。)