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))
把上面代碼存到一個叫pool.py的文件裏。然後把Django源碼裏的db/backend/mysql這個package拷貝出來,單獨存到我們project目錄裏一個mysql_pool的目錄裏。然後修改其中的base.py,在頂上import的部分,找到 import MySQLdb as Database 這句,用下面代碼替換之
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)
這樣我們就用自己的模塊替換了MySQLdb的,當要connect的時候判斷到有連接的時候就不重新創建連接了。
把站點跑起來看,結果如何?刷新幾次後報錯了。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))
我們增加了一個_ConnectionWrapper類來代理Connection對象,然後屏蔽掉close函數。把站點跑起來後發現不會出現之前的問題了,跑起來也順暢不少。但是過了幾個小時後問題又來了。因爲MySQLdb的Connection有個很蛋痛的問題,就是連接閒置8小時後會自己斷掉。不過要解決這個問題很簡單,我們發現連接如果閒置了快8小時就close掉重新建立一個連接不就行了麼?所以最後解決方案的代碼如下:
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))
就此問題解決,世界終於清淨了