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 這樣的全異步框架更契合,不用反覆調試線程數量和連接數量
相關文章
相關標籤/搜索