Django web请求是怎么建立数据库连接的以及CONN_MAX_AGE的作用

通过CONN_MAX_AGE优化Django的数据库连接:
上周对我们用Django+Django-rest-framework提供的一套接口进行了压力测试。压测的过程中,收到DBA通知——数据库连接数过多,希望我们优化下程序。
具体症状就是,如果设置mysql的最大连接数为1000,压测过程中,很快连接数就会达到上限,调整上限到2000,依然如此。

Django的数据库连接:
Django对数据库的链接处理是这样的,Django程序接受到请求之后,在第一访问数据库的时候会创建一个数据库连接,直到请求结束,关闭连接。下次请求也是如此。因此,这种情况下,随着访问的并发数越来越高,就会产生大量的数据库连接。也就是我们在压测时出现的情况。
关于Django每次接受到请求和处理完请求时对数据库连接的操作,最后会从源码上来看看。

使用CONN_MAX_AGE减少数据库请求:
上面说了,每次请求都会创建新的数据库连接,这对于高访问量的应用来说完全是不可接受的。
因此在Django1.6时,提供了持久的数据库连接, 通过DATABASE配置上添加CONN_MAX_AGE来控制每个连接的最大存活时间。具体使用可以参考最后的链接。
这个参数的原理就是在每次创建完数据库连接之后,把连接放到一个Theard.local的实例中。
在request请求开始结束的时候,打算关闭连接时会判断是否超过CONN_MAX_AGE设置这个有效期。每次进行数据库请求的时候其实只是判断local中有没有已存在的连接,有则复用。

基于上述原因,Django中对于CONN_MAX_AGE的使用是有些限制的,使用不当,会适得其反。
因为保存的连接是基于线程局部变量的,因此如果你部署方式采用多线程,必须要注意保证你的最大线程数不会多余数据库能支持的最大连接数。
另外,如果使用开发模式运行程序(直接runserver的方式),建议不要设置CONN_MAX_AGE,因为这种情况下,每次请求都会创建一个Thread。
同时如果你设置了CONN_MAX_AGE,将会导致你创建大量的不可复用的持久的连接。

CONN_MAX_AGE设置多久:
CONN_MAX_AGE的时间怎么设置主要取决于数据库对空闲连接的管理,
比如你的MySQL设置了空闲1分钟就关闭连接,那你的CONN_MAX_AGE就不能大于一分钟,不过DBA已经习惯了程序中的线程池的概念,会在数据库中设置一个较大的值。

优化结果
了解了上述过程之后,配置了CONN_MAX_AGE参数,再次测试,终于没有接到DBA通知,查看数据库连接数,最大700多。

最好的文档是代码
Django的文档上只是简单得介绍了原理和使用方式,对于好奇的同学来说,这个显然是不够的。于是我也好奇的看了下代码,把相关的片段贴到这里。

https://docs.djangoproject.com/en/1.6/ref/databases/#persistent-database-connections 
https://github.com/django/django/blob/master/django/core/handlers/wsgi.py#L164 
https://github.com/django/django/blob/master/django/http/response.py#L310 
https://github.com/django/django/blob/master/django/db/init.py#L62
https://github.com/django/django/blob/master/django/db/utils.py#L252 
https://github.com/django/django/blob/master/django/db/backends/init.py#L383

具体源码流程请参考:Django请求生命周期的源码分析:中间件的process_request 、 process_view 、process_response的执行顺序和请求信号的发送

基于上述理解,做下面的验证:
Case1:在开发模式 runserver 下的 CONN_MAX_AGE 为 0 或者小于测试程序中 ORM 操作之后slepp的时间 的条件下:

  1. 一进来视图函数且 ORM 操作之前就 sleep(100),可以看到这个时候数据库连接还没有存在,(说明数据库的连接是在 ORM 操作执行过程里创建的)
    2. ORM操作之后且在 return Response 之前再 sleep(100) 可以看到数据库连接还在呢,
    3. 而 return Response 之后发现数据库连接没有了,这是和看到的源码一致的。

Case2:在开发模式 runserver 下的 CONN_MAX_AGE 为 0:

  1. 一进来视图函数就进行 ORM操作,然后 sleep(100),最后是 Response
  2. 然后在这 100s 内并发请求这个接口,可以看到在这 100s 之间有多少并发,数据库上 show processlist 就有多少连接。 这是因为这种启动方式对每一个请求都会创建一个线程来处理, 而数据库连接是保存在线程局部变量里的。注意当响应请求的线程 Response 后,线程也就退出了 (通过 ps -eLf 可以验证),所以存在线程局部变量里的连接也就要断开了,所以就需要休眠 100s 然后赶紧并发请求,再查看数据库连接。
    参考. https://www.liaoxuefeng.com/wiki/897692888725344/923057354442720
    ThreadLocal 最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。
数据库连接:休眠 100 s 内,并发访问该接口,然后同时查看数据库连接

    mysql> show processlist;
	+-----+------+----------------------+----------+---------+------+----------+------------------+
	| Id  | User | Host                 | db       | Command | Time | State    | Info             |
	+-----+------+----------------------+----------+---------+------+----------+------------------+
	| 369 | root | 192.168.56.101:40252 | NULL     | Query   |    0 | starting | show processlist |
	| 381 | root | 192.168.56.101:40276 | robertdb | Sleep   |   36 |          | NULL             |
	| 383 | root | 192.168.56.101:40280 | robertdb | Sleep   |   27 |          | NULL             |
	| 385 | root | 192.168.56.101:40284 | robertdb | Sleep   |   25 |          | NULL             |
	| 387 | root | 192.168.56.101:40288 | robertdb | Sleep   |   22 |          | NULL             |
	| 389 | root | 192.168.56.101:40292 | robertdb | Sleep   |   18 |          | NULL             |
	| 391 | root | 192.168.56.101:40296 | robertdb | Sleep   |   15 |          | NULL             |
	| 393 | root | 192.168.56.101:40300 | robertdb | Sleep   |   12 |          | NULL             |
	| 395 | root | 192.168.56.101:40304 | robertdb | Sleep   |    9 |          | NULL             |
	| 397 | root | 192.168.56.101:40308 | robertdb | Sleep   |    6 |          | NULL             |
	| 399 | root | 192.168.56.101:40312 | robertdb | Sleep   |    3 |          | NULL             |
	+-----+------+----------------------+----------+---------+------+----------+------------------+
	11 rows in set (0.00 sec)
	
	mysql> 

你可能感兴趣的:(Django,数据库,#,处理请求,django,数据库,前端)