MySQL 异步驱动浅析 (三):连接池改进方案

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

上一篇文章分析了 Mysql 异步驱动的一些缺点,大部分已经在我们内部版本中修复了。

其中分区设计的链接池在实际使用过程中会产生一些非常严重的问题。

连接池中的锁阻塞

Mysql Async Pool

前文中曾经提到 SingleThreadedAsyncObjectPool 这个单线程的连接池实现并不是完全非阻塞的,再多个线程请求链接情况下仍旧会产生锁阻塞。
同时文章中也提到 Play!Framework 这样的框架主线程数可以非常少,所以不用过分担忧。

事实证明这是错误的,因为 PartitionedAsyncObjectPool 默认使用了 Executors.newCachedThreadPool, 这就导致不论主线程数多少,高并发情况下会创建大量线程同时去获取链接。
SingleThreadedAsyncObjectPool 使用了 Executors.newFixedThreadPool,显然这意味着每次入队都会产生一个锁阻塞,在系统并发非常高的情况下,这会极大加剧锁竞争,一旦获得锁线程被中断,则所有的线程都会处于

频繁的线程切换

驱动中默认情况下,存在多个 ExecutionContext,凭空增加了内存消耗和上下文切换

难以定位的内存泄漏

在实际使用过程中,我们经历了运行一段时间后 JVM 疯狂 FGC 的情况。
经分析发现存在链接泄漏,连接池存在大量未被回收的 MySQLConnection 对象,并且非常诡异的是我们无法定位到底是谁持有了这些未释放的 Connection。

考虑到上述问题,我开始着手设计一个全新的链接池,名字就叫 NewPool

设计一个完全无锁无阻塞的连接池

这种全新连接池实现主要依赖以下设计

  • 使用两个 ConcurrentLinkedQueue 保存等待列表和空闲链接,全程不存在锁
  • 使用 Semaphore 保证连接数和队列长度不超过限制

主要代码如下(部分)

val conns: ConcurrentLinkedQueue[Future[Connection]] = ...
val queue: ConcurrentLinkedQueue[Promise[Connection]] = ...
val createSemaphore: Semaphore = ...
val queueSemaphore: Semaphore = ...

def withConnection[A](f: Connection => Future[A]): Future[A] = {
    val c = acquire()
    c.flatMap { cc =>
      f(cc).andThen { //此处可能需要 try catch 处理不按套路抛出异常的情况
        case _ => release(c)
      }
    }
  }

  private def acquire(): Future[Connection] = {
    val conn = conns.poll()
    if (conn != null) { //有空余链接,则返回这个链接
      reconnectIfDead(conn)
    } else if (createSemaphore.tryAcquire()) { //连接数少于最大链接数,创建一个
      createNew()
    } else if (queueSemaphore.tryAcquire()) { //队列未满,入队
      val p = Promise[Connection]
      enqueueTask(p)
      p.future
    } else { //返回队列已满
      Future.failed(QueueIsFull)
    }
  }


  private def release(c: Future[Connection]) = {
    val wait = queue.poll()
    if (wait == null) {
      conns.offer(c)
    } else {
      wait.completeWith(c)
      queueSemaphore.release()
    }
  }复制代码

Semaphore 的 trypAcquire 操作和 ConcurrentLinkedQueue 都不会产生锁,确实做到了 Lock-Free。

性能测试

为了验证上述猜测,我基于 scalameter 做了简单的性能测试。结果如下

简单查询(SELECT 1)

新的方案(图中蓝色线条)对非常简单的查询,仍旧有 100% 左右的性能提升

select performance

简单事务(SELECT + UPDATE)

执行 SQL 如下

for {
  u <- c.sendQuery(s"SELECT * FROM user WHERE id = ${id}")
  r <- c.sendQuery(s"UPDATE user SET remain = remain + 100 WHERE id = ${id}")
} yield r复制代码

可以看到新方案(图中绿色线条)有非常大幅度提升

transaction performance
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值