从压力测试看数据库连接池配置

什么是数据库连接池

  写过后台的都知道,在操作数据库之前,先要获得数据库连接,然后用数据库连接去进行数据库的增删改查等操作。建立连接和销毁连接都是一笔很大的性能开销,因此产生了连接池的技术。连接池与线程池一样,都是一种资源池,专门用于处理共享资源的。它可以维护一定数量的连接不销毁,当有连接申请时,从池中取出供客户使用;使用完毕则释放归还池中;当池中连接的数量不足时,还可以创建一部分连接。

使用连接池的优点

  1. 减少建立连接与销毁连接的性能开销,避免频繁进行连接的建立与销毁,提升系统性能。
  2. 限制客户端向数据库发起的数据库访问,避免系统宕机,也避免过多的连接被直接拒绝。

数据库连接池的配置

  目前有很多连接池框架,使用这些框架非常简单,最主要就是对一些参数进行配置。这些参数包括最大连接数,最小连接数等,参数取值不同,对应用的性能与稳定性会造成不同的影响。下面我就以Tomcat自带的DHCP数据库连接池为例,通过对一个微博Web系统的同一条微博进行并发的疯狂评论操作作压力测试,去看看数据库连接池的参数配置对系统会产生什么样的影响。相信通过下面的压力测试分析,读者们可以体会到上述使用连接池的优点,同时也可以结合上一讲对压力测试的介绍,加深对压力测试的理解,了解到压力测试在系统优化中起到什么样的作用。

测试前的配置与代码准备

Tomcat的DHCP配置文件

  如果Web应用是部署在Tomcat上的,其参数配置全在context.xml文件里。

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
<?xml version='1.0' encoding='utf-8'?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- The contents of this file will be loaded for each web application -->
<Context>
<Resource
name="jdbc/drp"
type="javax.sql.DataSource"
driverClassName="org.postgresql.Driver"
maxIdle="100"
minIdle="100"
maxWait="-1"
username="postgres"
password="gdzqzxwjs95"
url="jdbc:postgresql://localhost:5432/weibo"
maxActive="100"/>
<!-- Default set of monitored resources -->
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<!-- Uncomment this to disable session persistence across Tomcat restarts -->
<!--
<Manager pathname="" />
-->
<!-- Uncomment this to enable Comet connection tacking (provides events
on session expiration as well as webapp lifecycle) -->
<!--
<Valve className="org.apache.catalina.valves.CometConnectionManagerValve" />
-->
</Context>
  • name是连接池的名称,后台调用数据库连接是需要引用这个名称。
  • type数据源类型,因为后台用java搭建,这里用java的数据源类。
  • driverClassName是数据库的驱动,这里用的是postgresql数据库。
  • maxIdle代表最大空闲连接数,0代表没有限制。
  • minIdle代表最小空闲连接数。
  • maxWait代表最大等待时间,-1代表没有限制。
  • username是数据库的用户名。
  • password是数据库的密码。
  • url用于定位数据库。
  • maxActive代表最大连接数,0代表没有限制。

  下面的测试,我们主要围绕maxIdle,minIdle,maxWait和maxAcitve展开。

  minIdle是数据库能维持的最小的空闲连接,当Web应用启动时,会自动创建连接到minIdle这个数目,此后minIdle便没有意义。

  maxIdle代表数据库能维持的最大空闲连接,当minIdle数目的连接不够用时,连接池会继续创建连接。当总连接没有超过maxIdle时,新创建的连接如果被释放不会被销毁,仍然归还池中;当总连接数超过maxIdle时,如果有连接被释放那么这些连接会被销毁,直到使连接池的连接数量达到maxIdle。此后,数据库的总连接数不会少于maxIdle。

  maxActive代表数据库能维持的最大连接数。当maxIdle数目的数据库连接仍然不够用时,连接池会继续创建连接,直到连接数达到maxActive。这些新创建的连接被释放时直接销毁,不归还池中。如果数据库连接池的总连接数达到maxAcitve,但仍然有连接申请,则这些连接会等待。一般maxIdle与maxAcitve的值应尽量接近,可以取maxIdle=maxActive。这样可以避免高负荷系统频繁创建与销毁连接。

  maxWait是最大等待时间。前面说过,当数据库连接数达到maxActive,其余的连接申请会等待,而等待的最大时间就是maxWait。当等待时间超过maxWait,但仍然没有连接被释放时,则这些等待中的连接会被拒绝。

后台获取和释放数据库连接代码

1
2
3
Context ctx=new InitialContext();
DataSource ds=(DataSource)ctx.lookup("java:comp/env/jdbc/drp");
Connection conn = ds.getConnection();
1
2
3
4
5
6
7
try {
if (conn != null) {
conn.close();
}
}catch (Exception err1){
err1.printStackTrace();
}

  代码很简单,连接释放还是直接用close方法,底层会由连接池实现连接归还而不销毁。

压力测试

测试任务

  利用JMeter作测试工具,对同一条微博作高并发的疯狂评论,对数据库而言就是对评论表作高并发的插入操作,同时并发更新微博表同一条记录的评论数属性。

不用连接池

1秒内启100个线程

压力测试1秒100

1秒内启200个线程

压力测试1秒200

1秒内启250个线程

压力测试1秒250

1秒内启300个线程

压力测试1秒300

错误及分析

连接拒绝异常

  以上测试从1秒启100个线程开始,不断增加测试的线程数,直到1秒内启动250个线程的时候开始出错。且启动线程的密集程度越高,错误率越高。此时后台抛出连接拒绝异常。

  产生此异常,是因为数据库允许的连接数往往是有限的。以postgresql数据库为例,默认的数据库最大允许连接数为100,当同时占用的连接数超过100个时,便会把超过的连接直接拒绝掉,同时抛出连接拒绝异常。之所以1秒产生250个线程才出错,是因为如果机器运行足够快,前面启动的一部分线程已经运行完了可以被释放,以供后续产生的线程使用。总之,只要同时占用的连接数不超过100个,就不会拒绝连接;反之多余的连接会被拒绝。

使用连接池,且maxActive超过100

配置maxActive = maxIdle = 200,maxWait = -1,1秒内启200个线程。

压力测试1秒200_too_many

错误及分析

too_many_clients

  postgresql里默认的最大连接数是100,如果连接池数目超过数据库的连接数限制,则会抛出上述异常。因此maxActive参数不能超过数据库的连接数限制。特别注意的是,即便maxActive设为100,但如果除了此程序还有其他程序访问同一数据库,那么其他程序也会占用数据库连接数,使数据库可用连接小于100,从而使此程序抛出上述异常!因此,准确来说,连接池的maxActive参数应该是不能大于数据库最大的可用连接数!

使用连接池,maxActive=100,maxWait设为5秒

设置maxIdle = maxActive = 100,maxWait = 5000,1秒内启250个线程

压力测试1秒250_pool

设置maxIdle = maxActive = 100,maxWait = 5000,1秒内启300个线程

压力测试1秒300_pool

设置maxIdle = maxActive = 100,maxWait = 5000,1秒内启400个线程

压力测试1秒400_pool

设置maxIdle = maxActive = 100,maxWait = 5000,1秒内启450个线程

压力测试1秒450_pool

错误及分析

timeout_wating_for_idle

  当1秒启400个线程以上时,后台会抛出上述异常。

  因为上面的测试maxWait设为5秒,当请求的连接数大于maxActive时,连接池不会再创建新的连接,而是等待已有的连接变为空闲。当等待的时间超过maxWait设定的时间,则会抛出上述异常。

maxWait无限制

设置maxIdle = maxActive = 100,maxWait = -1,1秒内启400个线程

1秒启400

设置maxIdle = maxActive = 100,maxWait = -1,1秒内启500个线程

1秒启500

设置maxIdle = maxActive = 100,maxWait = -1,1秒内启700个线程

1秒启700

设置maxIdle = maxActive = 100,maxWait = -1,1秒内启1000个线程

1秒启1000

分析

  只要maxWait不加限制,且连接池设置的最大连接数不超过数据库允许的最大连接数,无论有多少的连接请求,都不会抛出异常,不会有错误。但是由于内存的限制,过多的请求必然导致响应时间变长,用户体验变差。为了测试方便,上面的压力测试我是没有用Duration Assertion的,只通过Response Assertion对http response的返回内容作断言判断是否正确返回。下面,我就对压力测试加上Duration Assertion,对登录响应限制为5秒,对评论微博的响应限制为10秒,继续进行测试。

设置Duration Assertion

设置maxIdle = maxActive = 100,maxWait = -1,1秒内启150个线程

1秒150_time

设置maxIdle = maxActive = 100,maxWait = -1,1秒内启200个线程

1秒200_time

设置maxIdle = maxActive = 100,maxWait = -1,1秒内启250个线程

1秒250_time

错误及分析

  综上,只要将连接池的最大等待时间不加限制,无论有多大的并发访问都不会因为异常而使数据库操作失败,从而导致错误。主要的性能瓶颈来自于机器内存,过多的请求会使响应时间急剧恶化,严重降低用户体验。这也是根据压力测试的结果能作出的软件上的最大优化。

总结

  建议所有后台的数据库连接都采用连接池技术。上面的压力测试暴露的问题全是出自数据库连接上,其实像疯狂评论这样的并发操作,特别容易产生数据库死锁或者序列化并发更新异常等更难解决的错误。后面我还会再对数据库的锁机制做一些总结,分析一下为什么上面的压力测试不会出现死锁等更严重的错误,以及这些错误是怎么产生的。