……源码分析peewee的异常重试 …
该文章写的有些乱,欢迎来喷 ! 另外文章后续不断更新中,请到原文地址查看更新. http://xiaorui.cc/?p=3520
Peewee 是个好用的orm,我们这边遇到复杂的业务逻辑都会使用orm来减轻我们写sql语句代码的痛苦。 几十个表,每个表都有几十个字段,时常要判断有无重复,检测缺少那些字段….. 前端时间遇到一问题,由于长时间没有对mysql读写,导致python peewee mysql连接老化,这个老化不是tcp层面的,是mysql会对client connection计时。 长时间没用,他会干掉你的连接。 你的peewee client会出现2006 Mysql server has gone away,这错误说明mysql server离你远去了,为毛,你总是不招惹她,她寂寞了就走了吧。
#xiaorui.cc
Traceback (most recent call last):
File "extract_page/main.py", line 49, in main
extract_question(task_url,doc)
File "/home/operation/zhihu/extract_page/question.py", line 45, in extract_question
q.content_text = content_text.strip()
File "/usr/local/lib/python2.7/site-packages/peewee.py", line 4512, in get_or_create
return sq.get(), False
File "/usr/local/lib/python2.7/site-packages/peewee.py", line 2885, in get
return next(clone.execute())
File "/usr/local/lib/python2.7/site-packages/peewee.py", line 2932, in execute
self._qr = ResultWrapper(model_class, self._execute(), query_meta)
File "/usr/local/lib/python2.7/site-packages/peewee.py", line 2628, in _execute
return self.database.execute_sql(sql, params, self.require_commit)
File "/usr/local/lib/python2.7/site-packages/peewee.py", line 3461, in execute_sql
self.commit()
File "/usr/local/lib/python2.7/site-packages/peewee.py", line 3285, in __exit__
reraise(new_type, new_type(*exc_args), traceback)
File "/usr/local/lib/python2.7/site-packages/peewee.py", line 3454, in execute_sql
cursor.execute(sql, params or ())
File "/usr/local/lib/python2.7/site-packages/MySQLdb/cursors.py", line 205, in execute
self.errorhandler(self, exc, value)
File "/usr/local/lib/python2.7/site-packages/MySQLdb/connections.py", line 36, in defaul
terrorhandler
raise errorclass, errorvalue
OperationalError: (2006, 'MySQL server has gone away')
一般用原生MysqlDB封装的数据接口会做try cache,出现mysql gone away 时捕获异常,然后重新建立一个连接就OK了。 但那么问题来了,你用的是orm peewee 模块。 你如果按照你以前的逻辑进行异常处理,会导致你的代码变丑。 搜了半天没找到合适的方案,就想在peewee源代码里找到一丝蛛丝马迹, 在Error异常处理里找到一个名叫RetryOperationalError的类,但在peewee doc里没找到相应的使用文档,只能自己琢磨了。 该类是python peewee 在2.7之后出的。
所有的有关mysql返回的异常,peewee都可以通过调用RetryOperationalError类解决重试,其实最主要是替换调用execute_sql(). 也就是说,你如果想做实现一个更为复杂的异常处理,也是按着这个做。
#xiaorui.cc
class RetryOperationalError(object):
def execute_sql(self, sql, params=None, require_commit=True):
try:
cursor = super(RetryOperationalError, self).execute_sql(
sql, params, require_commit)
except OperationalError:
if not self.is_closed():
self.close()
with self.exception_wrapper():
cursor = self.get_cursor()
cursor.execute(sql, params or ())
if require_commit and self.get_autocommit():
self.commit()
return cursor
再来看看peewee的异常处理的逻辑源码是怎么写的.
class PeeweeException(Exception): pass
class ImproperlyConfigured(PeeweeException): pass
class DatabaseError(PeeweeException): pass
class DataError(DatabaseError): pass
class IntegrityError(DatabaseError): pass
class InterfaceError(PeeweeException): pass
class InternalError(DatabaseError): pass
class NotSupportedError(DatabaseError): pass
class OperationalError(DatabaseError): pass
class ProgrammingError(DatabaseError): pass
class ExceptionWrapper(object):
__slots__ = ['exceptions']
def __init__(self, exceptions):
self.exceptions = exceptions
def __enter__(self): pass
def __exit__(self, exc_type, exc_value, traceback):
if exc_type is None:
return
if exc_type.__name__ in self.exceptions:
new_type = self.exceptions[exc_type.__name__]
if PY26:
exc_args = exc_value
else:
exc_args = exc_value.args
reraise(new_type, new_type(*exc_args), traceback)
class Database(object):
def exception_wrapper(self):
return ExceptionWrapper(self.exceptions)
看了peewee代码后,看看我业务里是怎么调用这些处理异常的类。
from peewee import *
from playhouse.shortcuts import RetryOperationalError
class MyRetryDB(RetryOperationalError, MySQLDatabase):
pass
db = MyRetryDB(host='xxxxx',
port=3306,
user='xiaorui.cc',
passwd='fengyun',
database='xiaorui.cc',
charset='utf8mb4'
)
解决mysql server 2006 gone away 除了这方法之外,还有比较暴力的方法是直接修改mysql server配置文件.
#xiaorui.cc mysql> show global variables like '%timeout'; +----------------------------+----------+ | Variable_name | Value | +----------------------------+----------+ | connect_timeout | 10 | | delayed_insert_timeout | 300 | | innodb_lock_wait_timeout | 50 | | innodb_rollback_on_timeout | OFF | | interactive_timeout | 36000 | | lock_wait_timeout | 31536000 | | net_read_timeout | 30 | | net_write_timeout | 60 | | slave_net_timeout | 3600 | | wait_timeout | 36000 | +----------------------------+----------+ 10 rows in set
我们可以把interactive_timeout \ wait_timeout 是一天,也就是36000秒, 即mysql链接在无操作36000秒后被自动关闭。 但是不推荐这么搞,原因你自己想想吧…
那么我是怎么搞得? 我在mysqldb上封装了一个类似peewee的orm库,在初始化连接的时候会加入一个expire过期时间,只要符合过期时间,我就要重置一个连接,这样避免了2006 gone away的发生。
