实际上这种同步串行化的操作风格是对连接的浪费,每个物理连接并没有得到充分的利用,特别是在大并发访问数据库的情况下。面对这种场景很自然的想到,如果有异步的JDBC driver,所有的请求都可以并行发送处理,这样就可以充分利用单个物理连接,避免建立过多连接的情况。
adbcj(http://code.google.com/p/adbcj/)定义了一套异步访问数据库的API,并用两种方式实现了这个API。
1. 使用threadpool包装现有的MySQL connector
2. 基于MINA实现的native driver
第一种方式使用的是现有的MySQL connector,虽然用threadpool包装后达到异步性,但底层的connection对操作还是串行化处理,性能并没有本质的改变,只是用来展示如何实现ADBCJ。
第二种实现方式不仅对调用者表现为异步性,底层的connection对请求也是并行发送,最大限度的利用了物理连接资源。
目前该项目还处在试验阶段,native driver只支持int和varchar类型。
作者的blog列出了他测试对比的结果 http://swamp.homelinux.net/blog/index.php/2009/11/25/performance-comparison-of-apache-mina-and-jboss-netty-revisited/,可以看出adbcj的性能优势。但作者只测试了query,ResultSet是通过callback通知。我比较关心的是insert,而且希望通过feature在调用线程直接得到操作的结果。
callback方式:
final CountDownLatch latch = new CountDownLatch(count);
final DbListener
public void onCompletion(DbFuture
latch.countDown();
resultSetDbFuture.get();
}
};
for (int i = 0; i < count; i++) {
connection.executeQuery(query).addListener(listener);
}
通过feature拿到结果:
DbSessionFuture
Result result = resultFuture.get();
long affected = result.getAffectedRows();
if (affected != 1) {
System.out.println("插入数据库失败");
}
我也做了一个简单的测试,对比adbcj和使用线程池的情况。首先创建一个表,只包含int和varchar:
CREATE TABLE `adbcjtest` (
`id` bigint(64) unsigned NOT NULL auto_increment,
`topic` varchar(32) NOT NULL,
`body` varchar(4000) NOT NULL,
PRIMARY KEY (`id`)
)
测试的场景有:
1. 10个线程,单个adbcj连接 VS. 10个线程,JDBC连接池大小为10。插入1000万条记录
2. 50个线程,单个adbcj连接 VS. 50个线程,JDBC连接池大小为10。插入1000万条记录
3. 100个线程,单个adbcj连接 VS. 100个线程,JDBC连接池大小为10。插入1000万条记录
客户端为Linux ,4核,4G内存,MySQL Server为Linux,8核,16G内存。
测试的对比结果,纵轴为TPS:
可以看出,在并发小的场景下,adbcj并没有优势。并发线程增加后,adbcj的性能就超过了使用JDBC连接池。同时注意到,所有的测试场景adbcj只使用了单个连接。
我们还可以对比测试过程中客户端的load情况:
所有的测试场景,adbcj的load都低于JDBC连接池。
所以,异步的数据库访问方式值得关注,它能够很大程度上的提高连接的利用率。目前MySQL官方没有提供Java异步方式的驱动,而adbcj发展有些缓慢,也许有必要自己实现一个。
另外,我浏览了一下MySQL的通信协议:http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol,发现MySQL的请求命令都没有sequence number,因此请求并不是完全并行处理。从adbcj的代码也可以看出来,需要用一个activeRequest标识当前请求,请求并行处理程度并没有想象中那么高。
没有评论:
发表评论