最近有個用戶量 5W-10W 的 web 應用,頻繁導致 weblogic 崩潰,讓運維組很難受。
通過幾天跟蹤系統日志和 weblogic 運行狀況,發現報錯的姿勢有很多,其中對定位問題比較關鍵的報錯:
ExecuteThread: '496' for queue: 'weblogic.kernel.Default (self-tuning)' has beenbusy for "712" seconds working on the request "XXXX", which is more than the configured time (StuckThreadMaxTime) of "600" seconds.
weblogic 分配給 web 應用使用的線程響應返回周期最大為10分鐘,線程遲遲無法返回結果導致阻塞,並且這樣的刺頭線程越來越多。
運行一段時間後達到 weblogic 阻塞線程的閥值,weblogic 自然就崩潰了。
剛開始也試著調大 weblogic 響應周期/阻塞線程的閥值,但是阻塞線程還是會存在並且很快達到閥值。
仔細比對奔潰前後日志,查看 weblogic 阻塞線程詳情,導致阻塞開始罪魁禍首是數據庫查詢需要很長時間。
該系統與內外圍很多廠商系統有進行數據交互,數據庫裡面旁根錯雜的 db_link/synonyms/view/procedure。
而且是老舊項目,代碼經過很多人修改,已經風燭殘年搖搖欲墜,俺想重造它不是一天兩天了,因為很多原因無法進行,很無奈。
規范數據庫中的交互流程?然後動代碼?oh,no! 限定解決的期限將至,不能拖。
所有最後將目光放到數據庫連接池這部分,也使我不得不重新審視這塊對於 web 項目的重要性。好了,言歸正傳。
DataSource:數據源是在 JDBC2.0 中引入的一個概念;
在 JDBC 擴展包中定義了Java.sql.DataSource 接口,它負責建立與數據庫的連接;
在應用程序訪問數據庫是不必編寫連接數據庫的代碼,可直接從數據源獲得數據庫連接。
ConnectionPool :在數據源中初始化建立了多個數據庫連接,這些數據庫連接保存在連接池(ConnectionPool)中。
Java程序訪問數據庫時,只需從連接池中取出空閒狀態的數據庫連接,當訪問結束時,將數據庫連接返回給連接池。
JNDI : (Java Naming and Directory Interface)Java命名與目錄接口;
為開發人員提供了查找和訪問各種命名和目錄服務的通用、統一的接口。
其實可以將 JNDI 理解為一種對象和名字綁定的技術,即指定一個資源名稱,將該名稱與某一資源或服務相關聯。
結合圖和上面的簡述,數據層關鍵性對象都已展露無遺,同樣也很容易理解。
JNDI 避免了程序與數據庫之間的緊耦合,使應用更加易於配置、易於部署。
weblogic 上配置 JNDI 為圖形界面,操作起來很方便,而且那是運維組的事情,術業有專攻。
這裡我拿 Tomcat 為例(開發時你也不可能在本機裝個 weblogic 調試吧),簡述下配置 JDNI 的幾種方式:
a. 全局使用:Tomcat 的 conf 文件夾下的 context.xml 配置文件中添加:
<Resource name="jndi/db_test" auth="Container" type="javax.sql.DataSource" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/db_test" username="root" password="123456" maxActive="20" maxIdle="10" maxWait="10000"/>
b.局部使用:Tomcat 的 conf 文件夾下 server.xml 的 <host> 標簽內添加:
Context path="/demo_jndi" docBase="/demo_jndi"> <Resource name="jndi/db_test" type="javax.sql.DataSource" driverClassName="com.mysql.jdbc.Driver" maxIdle="2" maxWait="5000" username="root" password="123456" url="jdbc:mysql://localhost:3306/db_test" maxActive="4"/> </Context>
c.局部使用:應用 META-INFO 下新建 context.xml 添加:
<?xml version="1.0" encoding="UTF-8"?> <Context> <Resource name="jndi/db_test" auth="Container" type="javax.sql.DataSource" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/db_test" username="root" password="123456" maxActive="20" maxIdle="10" maxWait="10000"/> </Context>
上述幾種配置使用的數據源都為 javax.sql.DataSource,當然你也可以引入其他開源的數據源。
也就是 web 容器使用其他的開源數據庫連接池,比如像下面這集中姿勢(記得將依賴的 jar 添加到容器的 lib 中):
<Resource name="jndi/db_test" auth="Container" //DBCP type="javax.sql.DataSource" factory="org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory" //C3P0 type="com.mchange.v2.c3p0.ComboPooledDataSource" factory="org.apache.naming.factory.BeanFactory" //Druid type="com.alibaba.druid.pool.DruidDataSource" factory="com.alibaba.druid.pool.DruidDataSourceFactory" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/db_test" username="root" password="123456" maxActive="20" maxIdle="10" maxWait="10000"/>
配置好之後,使用起來也是相當的簡單,核心如下2行代碼即可:
Context ctx = new InitialContext(); DataSource ds = (DataSource) ctx.lookup("java:comp/env/jndi/db_test");
如果項目中引入了 Spring 上述兩行代碼都可以省了,變動注入數據源配置,如下:
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName" value = "java:comp/env/jndi/db_test"/> </bean>
應用起初使用的是自帶 C3P0 ConnectionPool,出現崩潰問題後,輾轉嘗試很多方法。
將應用的數據庫連接池改動為服務器 weblogic JNDI ConnectionPool ,使數據庫連接的壓力從應用轉移到 web 容器。
崩潰問題得到了緩解,經過後續的 weblogic 連接池參數的調整,卡死崩潰問題得到妥善解決。
騷年?是不是感覺 web 容器配置 JNDI 提供給應用使用後效率遠遠大於應用自帶的連接池。
同樣 JNDI 避免了程序與數據庫之間的緊耦合,使應用更加易於配置、易於部署。
試想發布在 weblogic 上面的 10個自帶連接池的應用,當數據庫信息變動時,你是不是想哭?
而且這樣幾乎無代碼改動,只需變動下 DataSource 的獲取。