MySQL 异步驱动浅析 (一):性能分析

本文由 Jilen 发表在 ScalaCool 团队博客。mysql

Mysql Async 是一个 Scala 编写的,基于 Netty 实现的非阻塞异步数据库驱动。在本系列文章中咱们将逐步分析:git

  • 与传统的 JDBC 驱动相比有何优点
  • Mysql Async 异步驱动存在什么问题,该如何优化

项目设计目标

项目官网设计目标以下github

  • 快、快、更快
  • 低内存开销
  • 尽可能避免内存拷贝(也是为了更快,更节约内存)
  • 易于使用,调用方法,返回 Future
  • 从不阻塞
  • 全部功能都被测试覆盖
  • 很小的依赖

能够看出做者是但愿经过异步非阻塞能让驱动更快(注意此处咱们不讨论是真异步或者伪异步)。
接下来本文将具体分析与传统的 mysql-connector/j 相比到底是不是更快,快在哪里。sql

网络 IO

MysqlAsync 的 IO

  • 项目使用 Netty 的 NIO 来实现,在网络 IO 这一点上确实是非阻塞的。
  • 协议实现过程也没用使用 synchronizedLock
  • Netty 默认状况下线程数为 CPU 核数2倍

Mysql JDBC 驱动 的 IO

mysql-connector/j 使用的仍是 Blocking IO ,这要求处理请求时必需有足够多的线程,不然吞吐量将受很大限制。数据库

例如一样基于 Blocking IO 的 Tomcat7 默认就配置了 200 线程。编程

链接池

MysqlAsync 的连接池

Mysql Async Pool

项目还提供一个链接池,采用分区设计,一个 PartitionedAsyncObjectPool 包含多个 SingleThreadedAsyncObjectPool网络

PartitionedAsyncObjectPool

流程十分简单,根据线程的 id 选择 SingleThreadedAsyncObjectPool,而后从中获取数据库连接。不存在阻塞的可能并发

SingleThreadedAsyncObjectPool

顾名思义,这是一个单线程的对象池。当请求获取连接时,若是有多余连接则直接返回,若是没有则加入队列,等待有连接经过 giveBack 方法释放时返回给队列里的某个请求。
这里用了 Scala 的 FuturePromise 实现,也不存在阻塞的状况。框架

分析源代码后发现此处使用只有一个线程的 ThreadPoolExecutor 来确保同一时间只有一个线程请求连接。异步

// Worker.scala
  def action(f: => Unit) {
    this.executionContext.execute(new Runnable {
      def run() {
        ...
      }
    })
  }复制代码

上述代码中this.executionContext.execute 最终会执行 TreadPoolExecutor.execute
TreadPoolExecutor.execute 并非彻底非阻塞的。

这带来了一个问题:当多个线程同时要获取连接时,只有一个线程能够得到连接,其余线程所有处于 blocked 状态。

因为是分区设计,而且 Play 这样的全异步框架主线程数默认很是少,因此这个问题在某些场合下并不严重。

Hikaricp

HikariCP 也许是目前优化得最好 JDBC 链接池。
该项目 Wiki 中的几篇文章也值得一看。

咱们没法从理论上直接得出何者性能更优的答案,后续将经过具体测试来估计何者更优。

性能测试

为了验证上述观点,我进行了简单的性能测试,主要测试了简单查询和事务两个方面。

简单查询

SELECT 1复制代码

事务

update user set remain = remain + ? where id = ?
update user set remain = remain - ? where id = ?复制代码

简单查询(1000qps)

MysqlAsync (64连接,默认16线程)

MysqlAsync-select

JDBC (64连接,64线程)

Hikaricp-select

事务(1000tps,针对100条 user 记录)

MysqlAsync (64连接,默认16线程)

MysqlAsync-trans

JDBC (64连接,64线程)

MysqlAsync-trans

结论

  • 在查询很是简单,速度很快的状况下二者性能至关,Mysql Async 有微弱的优点。
  • 在并发竞争更新,而且存在事务状况下(数据库存在大量锁):
    • 基于 Hikaricp 链接池的程序在一段时间后直接失去响应,大量请求超时。
    • 基于 MysqlAsync 的程序仍旧在执行,大部分失败是由于事务中存在死锁或者系统繁忙。
  • 经过调整链接数和线程数,hikaricp + mysql-connector/j 方案也许能够提高性能,但这套方案的问题是你永远不知道多少线程和连接数才是合适的。

下表是结合上述测试和定性分析得出的结果

项目 MysqlAsync HikariCP + mysql-connector/j
编程模型 异步 同步
网络IO NIO BIO
连接池 异步实现 同步实现
过载防御 经过调节队列长度实现 须要额外实现 (例如指定线程池任务队列长度)
可伸缩性 只须要设置合理链接数(例如几十个) 须要测试最佳线程数和连接数
线程数

总得来讲 MysqlAsync 经过减小了线程数确实达到了如下效果

  • 更少内存占用
  • 减小没必要要等待,从而减小线程上下文切换
  • 与 Play 这样的全异步框架更契合,不用反复调试线程数量和连接数量
相关文章
相关标签/搜索