flask多线程下,连接泄露的bug【转载】

flask多线程下,连接泄露的bug

架构图

 

如图所示,底层使用mysql,web服务使用flask-SqlAlchemy的连接池(复用连接,减少创建销毁开销),逻辑层代码使用线程池(异步IO操作,如果要异步cpu操作,可以很方便改成进程池)。

基础知识

  1. 使用db.engine.execute(sql): 从连接池获取一个连接,执行完sql后自动commit;(commit操作的回调是: 归还连接到池里);
  2. 使用sessionorm(xxxModel.query等): 默认配置及推荐配置是autocommit=false,执行完增删查改后,处于事务未提交的状态,也就是没有归还连接。如果要归还连接,可以使用语句:
    db.session.commit()
    db.session.rollback()
    db.session.close()
    db.session.remove(): 底层会调用db.session.close()
    

小结:

db.engine.execute(sql) => 自动commit => 自动归还
session(orm) => 手动commit => 手动归还
因此db.engine.execute(sql)是绝对安全的;
orm是有条件的。接着往下看orm的安全条件。

线程与session

使用flask的SqlAlchemy插件flask-SqlAlchemy时,每个线程可以直接用db.session获得session,即使不显式获得,使用orm的model时,其实也隐式得获得了session

线程与session的关系:

每个线程有自己的threadlocalsession对象,并且随着线程销毁,会自动释放session,也就是会隐式调用session.remove,也就是会隐式释放session的连接。

多线程两种使用:

  1. t1=threading.Thread(...);
  2. 线程池: future= pool.submit(...).
    方法1的线程使用完以后自动销毁=>session自动销毁=>连接自动释放;
    方法2的线程使用完以后归还线程池=>session手动销毁=>连接释放。

小结:
不使用线程池=>连接自动释放;
使用线程池=>连接手动释放.
手动释放的方法:

db.session.remove()

空闲连接超时与连接释放bug

前面说到使用线程池时,连接没有自动释放,一直维护在线程的threadlocal存储中(tls)。那么这样似乎也没有什么关系,只要线程池大小<连接池大小,这样连接池有空闲连接,每个线程也有自己的连接可以用,一切似乎也相安无事。

然而,这里有一个之前没有提到的机制:空闲连接超时回收。

空闲连接超时回收

mysql服务端:

定期检查现存连接的空闲时间,把超出wait_timeout的连接删除,此时客户端保存的长连接引用就失效了; 这个时间的设定:

show global variables like 'wait_timeout';
set global wait_timeout=10*60; -- seconds

web服务:

flask会定期检查连接池里的连接,把空闲连接删除,重新向mysql服务端申请新的连接,这样就不会访问到失效的连接引用了。其中定期的时间是: app.config['SQLALCHEMY_POOL_RECYCLE'] =xxx(秒,应当设置为小于wait_timeout)。这就是为什么最好连接用完及时归还,否则可能就没法被flask刷新成新连接。

空闲连接超时与连接释放bug

bug发生的流程

  1. mysql服务端清除了空闲时间过长的连接;
  2. 线程池中线程一直不销毁,因此持有了活了很久的session;
  3. 活了很久的session持有了空闲很久的连接, 这个连接其实已经被服务端销毁了,因此已经不可用了,但是由于其一直没有归还到连接池中,因此一直没有得到更新。
  4. 此时web服务收到数据请求,使用该线程中的该session中的连接,就会抛异常了,因为连接已经不可用了。

一般来说,空闲时间很长以后,线程池里所有线程的所有session的所有连接都会失效,因此就会完全无法通过orm访问数据库了。

相关异常信息:

MySQL server has gone away
Can't reconnect until invalid transaction is rolled back

这里之所以说invalid transaction is rolled back,是因为老session收到数据请求后,准备要用连接了。
而连接上的事务没有自动提交,也没有rollback,因此不能直接用。
因此尝试把连接上,上一次请求的事务提交,但由于连接已经失效,所以失败了。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值