首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> MySQL 資料庫 >> MySQL 綜合教學

MySQL + Tomcat + C3P0 系統效能調優實戰:從 60 QPS 到 250 QPS 的全鏈路優化之路

     編輯:MySQL 綜合教學

MySQL + Tomcat + C3P0 系統效能調優實戰:從 60 QPS 到 250 QPS 的全鏈路優化之路

系統背景

  • 邏輯:HTTP 請求 → C3P0 連接池 → MySQL 查詢 → 返回 JSON
  • 壓測工具:JMeter
  • 初始數據:1000 條視頻,後擴至 1000+ 視頻

優化前基線

併發原始 QPS1000 數據 QPSCPU備註
50025063~70100%連接池 15、慢 SQL 拖垮
100025557~65100%

第一階段:SQL 慢查詢剖析與重寫

問題 SQL

SELECT * FROM infoobject_table 
WHERE category = 'advertisement' 
  AND deleteflag = false 
  AND id IN (SELECT info_object_fk FROM timespan_table 
             WHERE vod_id = ? AND deleteflag = false)
ORDER BY RAND() LIMIT 1;

現象: 小表 100ms,1000 視頻數據量下 > 1s。IN + 子查詢 + ORDER BY RAND() 導致全表掃描 + 文件排序。

優化方案

  1. 子查詢改 JOIN:INNER JOIN timespan_table t ON i.id = t.info_object_fk
  2. ORDER BY RAND() 改應用層隨機:先 SELECT id FROM ... 取 ID 列表,Java 隨機挑一個再查詳情
  3. 加組合索引:idx_cat_del (category, deleteflag)、idx_vod_del (vod_id, deleteflag)

效果

併發優化後 QPS瓶頸轉移
500150CPU 100% → Tomcat 線程阻塞
1000160QPS 卡在 160,CPU 僅 10%,內存 1G/4G

第二階段:連接池與中間件配置拉滿

MySQL (my.cnf)

max_connections = 1000

Tomcat (server.xml)

<Connector port="8080" protocol="HTTP/1.1"
    minSpareThreads="25" maxSpareThreads="75"
    maxThreads="800" minThreads="600"
    acceptCount="200"
    connectionTimeout="20000"
    redirectPort="8443" />

JVM (catalina.sh / setenv.sh)

JAVA_OPTS="-server -Xms4400M -Xmx4400M -Xss512k
-XX:+AggressiveOpts -XX:+UseBiasedLocking
-XX:PermSize=128M -XX:MaxPermSize=256M
-XX:+DisableExplicitGC
-XX:MaxTenuringThreshold=31
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+CMSParallelRemarkEnabled
-XX:+UseCMSCompactAtFullCollection
-XX:LargePageSizeInBytes=128m
-XX:+UseFastAccessorMethods
-XX:+UseCMSInitiatingOccupancyOnly
-Djava.awt.headless=true"

C3P0 (Spring XML)

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    <property name="driverClass" value="com.mysql.jdbc.Driver"/>
    <property name="jdbcUrl" value="jdbc:mysql://192.168.4.112:3306/iod1000?useUnicode=true&characterEncoding=utf-8"/>
    <property name="checkoutTimeout" value="60000"/>
    <property name="idleConnectionTestPeriod" value="30"/>
    <property name="initialPoolSize" value="50"/>
    <property name="maxPoolSize" value="800"/>
    <property name="minPoolSize" value="50"/>
    <property name="maxStatements" value="100"/>
    <property name="properties">
        <props>
            <prop key="preferredTestQuery">SELECT 1</prop>
            <prop key="c3p0.maxIdleTime">25000</prop>
            <prop key="c3p0.testConnectionOnCheckout">true</prop>
            <prop key="user">root</prop>
            <prop key="password">iptv4Um8</prop>
        </props>
    </property>
</bean>

效果

  • QPS 仍卡在 120,CPU 12%、內存 900M/2.4G
  • 連接池監控顯示:getNumBusyConnections 長期滿載

第三階段:代碼層級瓶頸—— 日誌輸出阻塞

發現

JConsole 線程堆棧:大量線程 BLOCKED on java.util.logging.ConsoleHandler.publish()。

根因

同步控制台日誌輸出成為串行瓶頸,所有工作線程排隊寫日誌。

解決

  1. 註釋所有 logger.info() 輸出 SQL 的代碼
  2. 或改用異步日誌框架 (Logback + AsyncAppender / Log4j2 AsyncLogger)

最終效果

QPS 突破 250,CPU 穩定 30-40%,線程無阻塞。

全鏈路調優檢查清單

層級檢查項關鍵指標
資料庫慢 SQL、索引、連接數、緩衝池QPS、延遲、Threads_connected
連接池min/max/idle/busy、檢查週期、語句緩存getNumBusyConnections < maxPoolSize
中間件Tomcat 線程池、Accept 队列、JVM GCCPU、GC 頻率/停頓、線程狀態
應用代碼同步阻塞點(日誌/鎖/遠程調用)、N+1 查詢線程堆棧分析
作業系統文件描述符、網路隊列、磁碟 IOulimit -n、netstat -s

核心心法

「調優不是疊參數,是找到當前瓶頸、解除它、再找下一個。日誌輸出阻塞線程池是典型的『偽 CPU 瓶頸』—— CPU 閒著,線程卻在排隊寫字符串。」

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved