一种简单的架构:前端一台 Web 服务器运行业务代码,后端一台数据库服务器存储业务数据。
系统架构
系统上线时,用户量不大,运行平稳,但是随着流量的提升,系统访问速度会变慢。
分析日志后,发现系统慢的原因是出现在系统库的交互上,因为数据调用的方式是首先获取数据库的连接,然后依靠这个连接,查询数据库的数据,最后释放数据库资源。
整个 MySQL 创建连接过程主要分成两个部分:
第一个包是服务端给客户端要认证的报文,第二个和第三个包是客户端加密后发送给服务端的包,最后两个包是服务端给客户端 OK 的报文。
数据校验过程大概占整个查询过程的80% 时间,比如 数据校验占 4ms ,查询过程使用 1ms 这样频繁创建连接,创建连接严重影响了性能。
如果数据库不再频繁创建连接,如果实现就使用连接池,将连接先建立好,这样不频繁创建连接,那么查询性能会大大提升。
用连接池预先建立数据库连接
开发过程中往往会用到很多连接池,比如数据库连接池, HTTP 连接池,Redis 连接池等。
看个实例:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="xxxx"/>
<property name="username" value="xxxx"/>
<property name="password" value="xxxx"/>
<!-- 连接池最大数量 -->
<property name="maxActive" value="60"/>
<!-- 初始化连接大小 -->
<property name="initialSize" value="5"/>
<!-- 连接池最小空闲 -->
<property name="minIdle" value="5"/>
<!-- 连接池最大空闲 -->
<property name="maxIdle" value="10"/>
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="60000"/>
<property name="testOnBorrow" value="true"/>
<!-- 是否对sql进行自动提交-->
<property name="defaultAutoCommit" value="true"/>
<!-- 读写1分钟超时 -->
<property name="connectionProperties" value="oracle.jdbc.ReadTimeout=60000"/>
<property name="validationQuery" value="SELECT 1 FROM dual"/>
</bean>
"select 1" 的命令用来检查连接是否可用。
testOnBorrow 配置项 是先校验连接是否可用,如果可用采用执行 SQL 语句。这种连接方式会引入多余的开销,线上可用尽量不要使用,在测试服务上可用。
假设有一个接口,需要三次访问数据库,但是根据经验判断,觉得这个可能成为系统瓶颈,为避免频繁创建线程导致的开销,可以使用线程池来管理多个线程与数据库的交互。
JDK 1.5 中引入的 ThreadPoolExecutor 就是一种线程池的实现,有几个重要的参数:coreThreadPool 和 maxThreadCount,这两个参数控制整个线程池的执行过程。
image
实际项目中曾经有这么个问题,任务丢给线程池后,长时间都没有被执行,后来排查发现是 coreThreadCount 和 maxThreadCount 设置的比较小。
池化技术的思想: 核心思想是空间换时间,期望使用预先创建好的对象来减少频繁创建对象的性能开销,同时还可以对对象进行统一管理,减少对象使用成本。