1. 開篇閑談
閑暇之余看到一篇博文: 如何正確設(shè)置數(shù)據(jù)庫連接池的大小?我的天,原來之前都設(shè)置錯(cuò)了!
基本上來說,大部分項(xiàng)目都需要跟數(shù)據(jù)庫做交互,那么,數(shù)據(jù)庫連接池的大小設(shè)置成多大合適呢?
一些開發(fā)老鳥可能還會告訴你:沒關(guān)系,盡量設(shè)置的大些,比如設(shè)置成 200,這樣數(shù)據(jù)庫性能會高些,吞吐量也會大些!
的確如此,每次看到application.properties配置數(shù)據(jù)庫連接池的時(shí)候,總是想要不再搞大一點(diǎn)連接數(shù)。
#整它個(gè)2000試試
spring.datasource.hikari.minimum-idle=2000
spring.datasource.hikari.maximum-pool-size=2000
然后發(fā)現(xiàn),業(yè)務(wù)頁面點(diǎn)點(diǎn) 也沒啥區(qū)別,看看數(shù)據(jù)庫的連接數(shù)的確上去了,但是發(fā)現(xiàn)大部分都在sleep:

2. 試驗(yàn)
探索真理的捷徑莫過于動(dòng)手,搞個(gè)demo,ab壓測下看看,到底線程池連接數(shù)對請求響應(yīng)會不會產(chǎn)生大影響。
2.1. 環(huán)境準(zhǔn)備
一般mysql最大連接數(shù)比較少,提前設(shè)置下最大連接數(shù)
SET GLOBAL max_connections=2100
構(gòu)建項(xiàng)目,新建Spring-boot web項(xiàng)目,添加如下Pom依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
<version>4.1.0</version>
</dependency>
排除tomcat內(nèi)嵌服務(wù)器,改用undertow,避免內(nèi)嵌服務(wù)器性能影響。
配置數(shù)據(jù)庫和連接池,采用牛逼的一匹的號稱世界最快連接池:hikariCP
# 數(shù)據(jù)庫連接配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/demo?serverTimezone=GMT%2b8&useUnicode=true&characterEncoding=utf8&useSSL=true
spring.datasource.username=root
spring.datasource.password=123456
# Hikari 連接池配置
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=5
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=DatebookHikariCP
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=3000000
spring.datasource.hikari.connection-test-query=SELECT 1
當(dāng)然提前得建一個(gè)庫demo,和一張表up_user,且手工添加5000行數(shù)據(jù)
CREATE TABLE `up_user` (
`id` INT (11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
`code` VARCHAR (36) NOT NULL,
`name` VARCHAR (60) NOT NULL,
`card_no` VARCHAR (100),
`email` VARCHAR (135),
`phone` VARCHAR (135),
`birthday` DATETIME ,
`gender` SMALLINT (6) DEFAULT 0,
`create_user` VARCHAR (36),
`create_time` DATETIME ,
`update_user` VARCHAR (36),
`update_time` DATETIME ,
`status` INT (2) DEFAULT 0,
`is_deleted` INT (2) DEFAULT 0,
`remark` VARCHAR(500) NULL
);
2.2. 測試服務(wù)編寫
- 內(nèi)置指標(biāo)配置類
@Configuration
public class MetricsConfig {
@Bean
public MetricRegistry metrics() {
return new MetricRegistry();
}
@Bean
public Meter requestMeter(MetricRegistry metrics) {
return metrics.meter("request");
}
@Bean
public Timer responses(MetricRegistry metrics) {
return metrics.timer("executeTime");
}
@Bean
public ConsoleReporter consoleReporter(MetricRegistry metrics) {
return ConsoleReporter.forRegistry(metrics)
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build();
}
}
- 測試服務(wù)
@RestController
public class SqlTestController {
@Autowired
private HikariDataSource dataSource;
@Autowired
private Timer responses;
@RequestMapping("/one")
public String getSomeUser() throws SQLException, InterruptedException {
Connection connection = null;
PreparedStatement preparedStatement = null;
final Timer.Context context = responses.time();
String sql = "select * from up_user where id>"+new Random().nextInt(5000)+" limit 0,100";
try {
connection = dataSource.getConnection();
preparedStatement = connection.prepareStatement(sql);
preparedStatement.executeQuery();
} catch (SQLException e) {
e.printStackTrace();
} finally {
if(connection !=null){
connection.close();
}
if(preparedStatement !=null) {
preparedStatement.close();
}
TimeUnit.MILLISECONDS.sleep(200);
context.stop();
}
return "ok";
}
}
- 啟動(dòng)類
@SpringBootApplication
public class MetricsDemoApplication {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(MetricsDemoApplication.class, args);
ConsoleReporter reporter = ctx.getBean(ConsoleReporter.class);
reporter.start(1, TimeUnit.SECONDS);
}
}
- 工具apache bench (安裝略)
搞一臺其他機(jī)器,進(jìn)行壓測,200線程并發(fā),一共訪問10000次
ab -c 200 -n 10000 http://10.30.21.8:9527/one?id=3
測試方案
線程池連接數(shù)5、100、1000服務(wù)器機(jī)器 2核四線程
2.3. 測試結(jié)果
5個(gè)連接數(shù):

100個(gè)連接數(shù):

1000個(gè)連接數(shù):

3. 總結(jié)
對比發(fā)現(xiàn)三種情況下每秒請求個(gè)數(shù),每個(gè)請求平均處理時(shí)間,整體1000個(gè)請求響應(yīng)時(shí)間,其實(shí)3個(gè)差別不大。細(xì)微的變化:
- 3次測試請求的方差(stddev),0.98 ->2.32->3.08
- 3次試驗(yàn)80%請求的響應(yīng)時(shí)間,1299->1327->1357
由此得出:連接池的確不是越大越好,某些情況下差別不大,那么為什么這次實(shí)驗(yàn)性能差這么不明顯呢,估計(jì)影響有兩個(gè):
- 筆者電腦為2核4線程PC,cpu對連接的處理切換,在多連接情況下不會太明顯,沒有8核16核那么大
- 壓測量不夠大,如果增大估計(jì)效果會更明顯
所以,一般的服務(wù)器配置,其實(shí)10~20個(gè)連接數(shù),其實(shí)就可以了,沒有必要浪費(fèi)了MySql的連接資源。
4. 后記
既然很多實(shí)戰(zhàn)經(jīng)驗(yàn)豐富的項(xiàng)目都推薦 連接池連接數(shù):2*CUP+硬盤數(shù),那么筆者心血來潮試了一把,一個(gè)連接數(shù)結(jié)果會怎么樣:

方差(stddev):1.27 ,80%請求的響應(yīng)時(shí)間:1318
顯然和5個(gè)連接數(shù)比,的確性能有所下降。