让Django支持数据库长连接(可以提高不少性能哦)
书接上回
上回我们说到:《在生产系统使用Tornado WebServer来代替FastCGI加速你的Django应用》
那么现在很流行用一些高性能的nonblock的app server来host Django的应用,这些Server可以看做是一个单进程单线程的程序,然后用nginx在前端反向代理并且负载均衡到N多个后端工作进城来充分利用多CPU的性能,当然这部分的配置工作在上回已经说得很清楚了。但是对于Django来说有一个问题。因为Django的数据库连接是在查询的时候实时创建的,用完就会关掉,这样就会频繁的开闭连接。但是对于Tornado这种Server来说这种方式是低效的。这种Server最高效的工作模式是每个进程开启一个连接,并长期保持不关闭。本文的目的就是尝试使Django改变一贯的作风,采用这种高效的工作模式。本文基于Django1.3的版本,如果是低版本可以稍加更改一样可以使用。
Django的数据库可以通过配置使用专门定制的Backend,我们就从这里入手。
首先我们看看Django自带的Backend是如何实现的。在Django官网上可以看到自带MySql的Package结构,可以点击 此处前往瞻仰。
通观源码我们可以发现,Django基本上是封装了MySQLdb的Connection和Cursor这两个对象。而且重头实现整个Backend既不实际而且也不能从根本上解决问题。所以我们可以换一个思路。所有的数据库操作都是从获取Connection对象开始的,而获取Connection对象只有一个入口,就是MySQLdb.connect这个函数。所以我们只需要包装MySQLdb这个模块,用我们自己的connect方法替代原本的,这样就从根源上解决了问题。我们在包装器内部维护MySQLdb的Connection对象,使其保持长连接,每次connect被调用的时候判断一下,如果连接存在就返回现有连接,不就完美了吗?所以我们可以分分钟写下第一个解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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): “”“ 替换原有的connection对象 ”“” if not self .connection: self .connection = self .db.connect( * argv, * * kwargv) return _ConnectionWrapper( self .connection) def manage(module,keepalive = 7 * 3600 ): “”“ 返回代替原生MySQLdb模块的对象 ”“” 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 这句,用下面代码替换之
1
2
3
4
5
6
|
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对象也包装了了。所以升级后的解决方案代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
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掉重新建立一个连接不就行了么?所以最后解决方案的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
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)) |
就此问题解决,世界终于清净了
让Django支持数据库长连接(可以提高不少性能哦)的更多相关文章
- Erlang C1500K长连接推送服务-性能
Whatsapp已经使用Erlang在生产环境跑到96GB内存单机 3M长连接,参加:WhatsApp的Erlang世界.毕竟业务级别能达到Whatsapp那样极少,现在只有千万级,单机太多挂一台影响 ...
- ORM,Django对数据库的连接和使用
ORM,Django对数据库连接和使用数据 ORM(对象关系映射) 很多语言中的web框架都有这个概念 为什么要有ORM? 写程序离不开数据 新的语法,不需要我们自己写SQL语句 我们按照新的语法写代 ...
- 使用mysql的长连接
有个资料看得我云里雾里的.现在用自己的言语来总结一下,写文字,能够加深自己的理解.也会在写的过程中帮助自己发现理解方面瑕疵,继续查资料求证. 短链接的缺点:创建一个连接,程序执行完毕后,就会自动断掉与 ...
- 使django与数据库保持长连接
最近遇到一个很蛋疼的问题,写了一个后台管理系统, 由于是后台管理系统,所以使用频率不是很高,当django程序在闲置一段时间后,再次打开后台系统,就变得很慢,然后又好了.查了很多方面,从模板引擎到请求 ...
- Comet:基于 HTTP 长连接的“服务器推”技术
“服务器推”技术的应用 请访问 Ajax 技术资源中心,这是有关 Ajax 编程模型信息的一站式中心,包括很多文档.教程.论坛.blog.wiki 和新闻.任何 Ajax 的新信息都能在这里找到. c ...
- 转载:Comet:基于 HTTP 长连接的“服务器推”技术
转自:http://www.ibm.com/developerworks/cn/web/wa-lo-comet/ 很多应用譬如监控.即时通信.即时报价系统都需要将后台发生的变化实时传送到客户端而无须客 ...
- [转载] Comet:基于 HTTP 长连接的“服务器推”技术
转载自http://www.ibm.com/developerworks/cn/web/wa-lo-comet/ “服务器推”技术的应用 传统模式的 Web 系统以客户端发出请求.服务器端响应的方式工 ...
- Comet:基于 HTTP 长连接的“服务器推”技术(转载)
“服务器推”技术的应用 传统模式的 Web 系统以客户端发出请求.服务器端响应的方式工作.这种方式并不能满足很多现实应用的需求,譬如: 监控系统:后台硬件热插拔.LED.温度.电压发生变化: 即时通信 ...
- Loadrunner_http长连接设置
最近协助同事解决了几个问题,也对loadrunner的一些设置加深了理解,关键是更加知其所以然. ljonathan http://www.51testing.com/html/48/202848-2 ...
随机推荐
- fiddler 清除证书+重新添加证书
1. 清除证书 ①任意浏览器,打开Internet属性弹窗,点击内容页签下<证书> ②删除个人页签下,颁发者为DO_NOT_TRUST_Fiddler**的数据 2. 重新认证证书 ①打开 ...
- Python小练习(持续更新....)
最近一直在学习python,这些小练习有些是书上的,有些是别人博客上的! # 1.题目1# 给一个字符串,统计其中的数字.字母和其他类型字符的个数:# 比如输入“124mid-=”,输出:数字=3,字 ...
- DNN 数据的表示
- Swift来了,是不是能够入手IOS开发了?
在今天的WWDC2014上,苹果公布了一种全新的Swift.在苹果高管 Craig Federighi 的描写叙述中,Swift 在各个方面优于 Objective-C,也不会有那么多复杂的符号和表达 ...
- windows下php升级到7.2
1: 官网下载:https://windows.php.net/download#php-7.2
- bug-5——(js)indexOf()
indexOf()方法可返回某个指定的字符串值在字符串中首次出现的位置. ①对大小写敏感 ②如果要检索的字符串值没有出现,则该方法返回-1. ③位置时从0开始的 $j(this).html().ind ...
- java基础入门之九九乘法表
/* 自学java 九九乘法表 Power by Stuart Date: 2015.4.23 */public class Math { public static void main (Strin ...
- (转)关于Http协议,一片就够了
HTTP简介 HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送 ...
- 内存写越界导致破环堆结构引起的崩溃问题定位经验[如报错malloc(): memory corruption或free(): invalid next size]
前段时间开发的一个后端C模块上线后,线上出core,初始时,因为訪问压力不大,所以崩溃是上线3天左右出现的.当时用gdb跟进调用堆栈并检查源代码,发现出core位置的代码沒有啥问题.因为当时开发任务较 ...
- 第13条:合理利用try/expect/else/finally结构中的每个代码块
核心知识点: (1)无论try块是否发生异常,都可以使用try/finally复合语句中地finally块来执行清理工作. (2)顺利运行try块后,若想使某些操作能在finally块地清理代码之前执 ...