源码分析peewee的异常重试

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


大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc

发表评论

邮箱地址不会被公开。 必填项已用*标注