原文地址: https://www.tony-yin.site/201...
本文分享一次数据库链接调优的经历,并对pgpool
一系列生命周期配置参数进行解读。html
测试发现数据库读写有时候会短期卡顿,通过定位,发现数据库VIP
未发生漂移,服务端口正常,数据库(pgpool
)链接数已到达上限,因此判断是数据库链接数过多致使以后的短期访问阻塞。python
因为创建数据库链接最多的客户端用的是Django
框架,因此先分析一下Django
对数据库链接的处理流程。web
Django
程序接受到请求以后,在第一次访问数据库的时候会建立一个链接,在请求结束时,关闭链接。Django1.6
后的版本,提供了数据库持久链接的配置,经过DATABASE
配置上添加CONN_MAX_AGE
来控制每一个链接的最大存活时间。参数的原理就是在每次建立完数据库链接以后,把链接放到一个Theard.local
的实例中。在request
请求结束时会判断是否超过CONN_MAX_AGE
设置这个有效期。每次进行数据库请求的时候其实只是判断local
中有没有已存在的链接,有则复用。数据库
class BaseDatabaseWrapper(object): def connect(self): """Connects to the database. Assumes that the connection is closed.""" # Reset parameters defining when to close the connection max_age = self.settings_dict['CONN_MAX_AGE'] self.close_at = None if max_age is None else time.time() + max_age self.connection = self.get_new_connection(conn_params) ...... ...... self.run_on_commit = [] def close_if_unusable_or_obsolete(self): """ Closes the current connection if unrecoverable errors have occurred, or if it outlived its maximum age. """ if self.connection is not None: # If the application didn't restore the original autocommit setting, # don't take chances, drop the connection. if self.get_autocommit() != self.settings_dict['AUTOCOMMIT']: self.close() return # If an exception other than DataError or IntegrityError occurred # since the last commit / rollback, check if the connection works. if self.errors_occurred: if self.is_usable(): self.errors_occurred = False else: self.close() return if self.close_at is not None and time.time() >= self.close_at: self.close() return
Django
是经过HttpResponse
来做为请求结束的标准,关闭链接的代码也是在HttpResponseBase
这个class
中。django
class HttpResponseBase(six.Iterator): def close(self): for closable in self._closable_objects: try: closable.close() except Exception: pass self.closed = True # 请求结束时触发request_finished这个触发器 signals.request_finished.send(sender=self._handler_class)
经过上面能够大体了解Django
对于数据库链接的处理流程,客户端中并无设置长链接时间,因此理论上来讲每一个请求都会在结束时关闭数据库链接,那么为何数据库端(pgpool
)还显示有不少没有关闭的链接呢?后端
那无非两种场景:缓存
Response
;Response
,可是没能关闭掉链接;restful
接口最终是返回Response
,可是中间代码出错或者抛异常会致使最后没能运行到Response
。DB
的操做,Django
是不会帮助程序关闭链接的。再谈正常走Response
的程序,什么缘由会致使链接没能被正常关闭?restful
经过上面Django
数据库链接处理流程分析,能够知道Django
里的数据库链接是放在线程的local()
实例中,当本线程须要一个数据库链接,判断local
中是否存在已有链接,有则复用;可是若是采用多线程的方式访问数据库,Django
则会建立一条属于当前线程的数据库链接,即每一个线程都会建立属于本身的数据库链接,每一个线程也只能关闭当前线程的数据库链接。多线程
因此若是一段程序中,采用了多线程的方式访问DB
,最后经过主线程返回Response
,主线程是不会关闭其余线程中的数据库链接。并发
Response
的状况,须要添加try-catch
,在最终的finally
加上返回Response
的代码;web
接口,即最后不走Response
的状况,须要在程序最后额外添加关闭数据库链接的代码;from django.db import connections # 每个线程都有专属的connections,把本线程名下的全部链接关闭。 connections.close_all()
Pgpool
配置文件中配置客户端链接空闲最大时间为300
秒:
client_idle_limit = 300 # Client is disconnected after being idle for that many seconds # (even inside an explicit transactions!) # 0 means no disconnection
该参数表示当一个客户端在执行最后一条查询后若是空闲到了client_idle_limit
秒数, 到这个客户端的链接将被断开。这里配置为300
秒,防止客户端存在长时间的空闲链接占用链接数。
查看pgpool
链接数和状态:
[root@host1 ~]# systemctl status pgpool ● pgpool.service - Pgpool-II Loaded: loaded (/usr/lib/systemd/system/pgpool.service; enabled; vendor preset: disabled) Active: active (running) since Wed 2019-10-23 16:14:42 CST; 5 days ago Main PID: 3195664 (pgpool) Memory: 163.8M CGroup: /system.slice/pgpool.service ├─ 2500232 pgpool: test_user test_db 192.168.1.1(39436) idle ├─ 2561154 pgpool: wait for connection request ├─ 794851 pgpool: test_user test_db 192.168.225.1(17831) idle in transaction ......
链接通常分为如下几种状态:
客户端位于192.168.1.1
上,经过test_user
用户链接的数据库test_db
,而且该链接当前处于idle
状态。
2500232 pgpool: test_user test_db 192.168.1.1(39436) idle
客户端位于192.168.1.1
上,经过test_user
用户链接的数据库test_db
,而且该链接当前处于transaction
状态。
794851 pgpool: test_user test_db 192.168.1.1(17831) idle in transaction
当客户端断开链接后,原子进程便会切换成wait
状态,等待下一次客户端链接,若是child_life_time
配置的时间范围内,没有客户端向这个子进程创建链接,该子进程便会kill
掉并从新生成新的子进程。
该参数表示最后一次客户端断开链接后,子进程空闲持续的最大时间,避免长时间不退出致使内存泄露。客户端断开数据库链接后,子进程切换成wait request
状态,若是child_life_time
配置的时间范围内,没有客户端与该子进程创建链接,该子进程便会被kill
掉并从新生成新的子进程。0
表示永远不会退出子进程。
该参数表示最后一次客户在链接中的数据库操做后,该链接持续的最大时间。当一个客户端在执行最后一条查询后若是空闲到了client_idle_limit
秒数,这个客户端的链接将被pgpool
断开。该参数能够防止客户端长时间的空闲链接占用链接数,本文也是经过该参数进行数据库优化。0
表示永远不会断开链接。
该参数表示pgpool
容许的最大链接数,当pgpool
创建的链接数到达child_max_connections
配置的数目后,则会优先退出最先的子进程。该参数主要用于并发量大的场景,短期内触发不到child_life_time
和client_idle_limit
参数,0
表示永远不会退出子进程。
该参数表示pgpool
与数据库后端创建链接的最大时间,主要用于缓存,提升数据库性能。0
表示永远不会关闭链接。
Django
在大部分状况下会帮助关闭数据库链接,但部分场景,如无Response
、程序抛异常、多线程等场景会致使数据库链接没法自动关闭。客户端须要在以上场景下采起对应的解决方案,防止数据库链接堆积。此外,数据库端也须要配置客户端空闲链接最大时间,保证在客户端不关闭链接的状况下可以关闭长时间空闲的数据库链接。