2010年2月13日星期六

异步数据库driver试用

我们知道,目前的JDBC driver是同步工作方式,也就是发送请求后需要block等待响应。而且connection上的操作都是串行化的,在一个请求处理没有完成之前,后续的请求都必须等待。因此,在处理大并发量访问数据库时,都会应用到连接池技术,实现连接复用,避免连接频繁打开关闭这种不必要的开销,而且复用后不会创建太多的连接。但即便是用了连接池,在客户端越来越多的情况下,对于数据库来说,连接还是太多了。
实际上这种同步串行化的操作风格是对连接的浪费,每个物理连接并没有得到充分的利用,特别是在大并发访问数据库的情况下。面对这种场景很自然的想到,如果有异步的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 listener = new DbListener() {
            public void onCompletion(DbFuture resultSetDbFuture) throws Exception {
                latch.countDown();
                resultSetDbFuture.get();
            }
        };
        for (int i = 0; i < count; i++) {
            connection.executeQuery(query).addListener(listener);
        }

通过feature拿到结果:
            DbSessionFuture resultFuture = session.executeUpdate(sql);
            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标识当前请求,请求并行处理程度并没有想象中那么高。

没有评论: