Django的数据库可以通过配置使用专门定制的Backend,我们就从这里入手。
首先我们看看Django自带的Backend是如何实现的。在Django官网上可以看到自带MySql的Package结构,可以点击 此处 前往瞻仰。
通观源码我们可以发现,Django基本上是封装了MySQLdb的Connection和Cursor这两个对象。而且重头实现整个Backend既不实际而且也不能从根本上解决问题。所以我们可以换一个思路。所有的数据库操作都是从获取Connection对象开始的,而获取Connection对象只有一个入口,就是MySQLdb.connect这个函数。所以我们只需要包装MySQLdb这个模块,用我们自己的connect方法替代原本的,这样就从根源上解决了问题。我们在包装器内部维护MySQLdb的Connection对象,使其保持长连接,每次connect被调用的时候判断一下,如果连接存在就返回现有连接,不就完美了吗?所以我们可以分分钟写下第一个解决方案:
proxies = {} class _DbWrapper(): def __init__(self,module): self.connection=None #这个就是维护的长连接对象 self.db=module #这个是被包装的原生MySQLdb的module def __getattr__(self, key): return getattr(self.db, key) #代理所有不关心的函数 def connect(self,*argv,**kwargv): if not self.connection: self.connection=self.db.connect(*argv,**kwargv) return _ConnectionWrapper(self.connection) def manage(module,keepalive=7*3600): try: return proxies[module] except KeyError: return proxies.setdefault(module,_DbWrapper(module))
try: import MySQLdb as Database Database = pool.manage(Database) except ImportError, e: from django.core.exceptions import ImproperlyConfigured raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e)
把站点跑起来看,结果如何?刷新几次后报错了。Why?看看日志可以看到如下的错误:
Traceback (most recent call last):
File “/home/www/.virtualenvs/django13/lib/python2.7/site-packages/gevent/wsgi.py”, line 114, in handle
result = self.server.application(env, self.start_response)
File “/home/www/.virtualenvs/django13/lib/python2.7/site-packages/django/core/handlers/wsgi.py”, line 275, in __call__
signals.request_finished.send(sender=self.__class__)
File “/home/www/.virtualenvs/django13/lib/python2.7/site-packages/django/dispatch/dispatcher.py”, line 172, in send
response = receiver(signal=self, sender=sender, **named)
File “/home/www/.virtualenvs/django13/lib/python2.7/site-packages/django/db/__init__.py”, line 85, in close_connection
conn.close()
File “/home/www/.virtualenvs/django13/lib/python2.7/site-packages/django/db/backends/__init__.py”, line 244, in close
self.connection.close()
看来我们光是包装了MySQLdb本身还不行,在connect后Django获取了Connection的对象,之后就能为所欲为,他用完后很自觉的关掉了,因为他直觉的以为每次connect都拿到了新的Connection对象。所以我们必须把Connection对象也包装了了。所以升级后的解决方案代码如下:
proxies = {} class _ConnectionWrapper(object): """ 用来包装Connection的类 """ def __init__(self,conn): self.conn=conn def close(self): """ 屏蔽掉关闭连接的行为 """ pass def __getattr__(self,key): """ 把其他属性都原封不动的代理出去 """ return getattr(self.conn, key) class _DbWrapper(): """ 代理MySQLdb模块的对象 """ def __init__(self,module): self.connection=None #HOLD住的长连接 self.db=module #原始的MySQLdb模块 def __getattr__(self, key): """ 代理除connect外的所有属性 """ return getattr(self.db, key) def connect(self,*argv,**kwargv): if not self.connection: self.connection=self.db.connect(*argv,**kwargv) return _ConnectionWrapper(self.connection) def manage(module): try: return proxies[module] except KeyError: return proxies.setdefault(module,_DbWrapper(module))
import time proxies = {} class _ConnectionWrapper(object): def __init__(self,conn): self.conn=conn def close(self): pass def __getattr__(self,key): return getattr(self.conn, key) class _DbWrapper(): def __init__(self,module,max_idle): self.connection=None self.db=module self.max_idle=max_idle self.connected=0 def __getattr__(self, key): return getattr(self.db, key) def connect(self,*argv,**kwargv): if not self.connection or time.time()-self.connected>=self.max_idle: try: if self.connection: self.connection.close() except: pass self.connection=self.db.connect(*argv,**kwargv) self.connected=time.time() return _ConnectionWrapper(self.connection) def manage(module,keepalive=7*3600): try: return proxies[module] except KeyError: return proxies.setdefault(module,_DbWrapper(module,keepalive))