……源码分析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的发生。