隨著應(yīng)用的數(shù)據(jù)量不斷的增加,系統(tǒng)的反應(yīng)一般會(huì)越來越慢,這個(gè)時(shí)候我們就需要性能調(diào)優(yōu)。性能調(diào)優(yōu)的步驟如下:
成都創(chuàng)新互聯(lián)是專業(yè)的青浦網(wǎng)站建設(shè)公司,青浦接單;提供網(wǎng)站設(shè)計(jì)制作、做網(wǎng)站,網(wǎng)頁設(shè)計(jì),網(wǎng)站設(shè)計(jì),建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進(jìn)行青浦網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴(kuò)展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團(tuán)隊(duì),希望更多企業(yè)前來合作!Java 應(yīng)用性能優(yōu)化是一個(gè)老生常談的話題,典型的性能問題如頁面響應(yīng)慢、接口超時(shí),服務(wù)器負(fù)載高、并發(fā)數(shù)低,數(shù)據(jù)庫頻繁死鎖等。尤其是在“糙快猛”的互聯(lián)網(wǎng)開發(fā)模式大行其道的今天,隨著系統(tǒng)訪問量的日益增加和代碼的臃腫,各種性能問題開始紛至沓來。
Java 應(yīng)用性能的瓶頸點(diǎn)非常多,比如磁盤、內(nèi)存、網(wǎng)絡(luò) I/O 等系統(tǒng)因素,Java 應(yīng)用代碼,JVM GC,數(shù)據(jù)庫,緩存等。筆者根據(jù)個(gè)人經(jīng)驗(yàn),將 Java 性能優(yōu)化分為 4 個(gè)層級(jí):應(yīng)用層、數(shù)據(jù)庫層、框架層、JVM 層,如圖 1 所示。
圖 1.Java 性能優(yōu)化分層模型
每層優(yōu)化難度逐級(jí)增加,涉及的知識(shí)和解決的問題也會(huì)不同。比如應(yīng)用層需要理解代碼邏輯,通過 Java 線程棧定位有問題代碼行等;數(shù)據(jù)庫層面需要分析 SQL、定位死鎖等;框架層需要懂源代碼,理解框架機(jī)制;JVM 層需要對(duì) GC 的類型和工作機(jī)制有深入了解,對(duì)各種 JVM 參數(shù)作用了然于胸。
圍繞 Java 性能優(yōu)化,有兩種最基本的分析方法:現(xiàn)場(chǎng)分析法和事后分析法。
現(xiàn)場(chǎng)分析法通過保留現(xiàn)場(chǎng),再采用診斷工具分析定位。現(xiàn)場(chǎng)分析對(duì)線上影響較大,部分場(chǎng)景(特別是涉及到用戶關(guān)鍵的在線業(yè)務(wù)時(shí))不太合適。
事后分析法需要盡可能多收集現(xiàn)場(chǎng)數(shù)據(jù),然后立即恢復(fù)服務(wù),同時(shí)針對(duì)收集的現(xiàn)場(chǎng)數(shù)據(jù)進(jìn)行事后分析和復(fù)現(xiàn)。下面我們從性能診斷工具出發(fā),分享一些案例與實(shí)踐。
性能診斷一種是針對(duì)已經(jīng)確定有性能問題的系統(tǒng)和代碼進(jìn)行診斷,還有一種是對(duì)預(yù)上線系統(tǒng)提前性能測(cè)試,確定性能是否符合上線要求。
本文主要針對(duì)前者,后者可以用各種性能壓測(cè)工具(例如 JMeter)進(jìn)行測(cè)試,不在本文討論范圍內(nèi)。
針對(duì) Java 應(yīng)用,性能診斷工具主要分為兩層:OS 層面和 Java 應(yīng)用層面(包括應(yīng)用代碼診斷和 GC 診斷)。
OS 診斷
OS 的診斷主要關(guān)注的是 CPU、Memory、I/O 三個(gè)方面。
對(duì)于 CPU 主要關(guān)注平均負(fù)載(Load Average),CPU 使用率,上下文切換次數(shù)(Context Switch)。
通過 top 命令可以查看系統(tǒng)平均負(fù)載和 CPU 使用率,圖 2 為通過 top 命令查看某系統(tǒng)的狀態(tài)。
圖 2.top 命令示例
平均負(fù)載有三個(gè)數(shù)字:63.66,58.39,57.18,分別表示過去 1 分鐘、5 分鐘、15 分鐘機(jī)器的負(fù)載。按照經(jīng)驗(yàn),若數(shù)值小于 0.7*CPU 個(gè)數(shù),則系統(tǒng)工作正常;若超過這個(gè)值,甚至達(dá)到 CPU 核數(shù)的四五倍,則系統(tǒng)的負(fù)載就明顯偏高。
圖 2 中 15 分鐘負(fù)載已經(jīng)高達(dá) 57.18,1 分鐘負(fù)載是 63.66(系統(tǒng)為 16 核),說明系統(tǒng)出現(xiàn)負(fù)載問題,且存在進(jìn)一步升高趨勢(shì),需要定位具體原因了。
通過 vmstat 命令可以查看 CPU 的上下文切換次數(shù),如圖 3 所示:
圖 3.vmstat 命令示例
上下文切換次數(shù)發(fā)生的場(chǎng)景主要有如下幾種:
1)時(shí)間片用完,CPU 正常調(diào)度下一個(gè)任務(wù);
2)被其它優(yōu)先級(jí)更高的任務(wù)搶占;
3)執(zhí)行任務(wù)碰到 I/O 阻塞,掛起當(dāng)前任務(wù),切換到下一個(gè)任務(wù);
4)用戶代碼主動(dòng)掛起當(dāng)前任務(wù)讓出 CPU;
5)多任務(wù)搶占資源,由于沒有搶到被掛起;
6)硬件中斷。
Java 線程上下文切換主要來自共享資源的競(jìng)爭(zhēng)。一般單個(gè)對(duì)象加鎖很少成為系統(tǒng)瓶頸,除非鎖粒度過大。但在一個(gè)訪問頻度高,對(duì)多個(gè)對(duì)象連續(xù)加鎖的代碼塊中就可能出現(xiàn)大量上下文切換,成為系統(tǒng)瓶頸。
比如在我們系統(tǒng)中就曾出現(xiàn) log4j 1.x 在較大并發(fā)下大量打印日志,出現(xiàn)頻繁上下文切換,大量線程阻塞,導(dǎo)致系統(tǒng)吞吐量大降的情況,其相關(guān)代碼如清單 1 所示,升級(jí)到 log4j 2.x 才解決這個(gè)問題。
for(Category c = this; c != null; c=c.parent) {
// Protected against simultaneous call to addAppender, removeAppender,…
synchronized(c) {
if (c.aai != null) {
write += c.aai.appendLoopAppenders(event);
}
…
}
}
從操作系統(tǒng)角度,內(nèi)存關(guān)注應(yīng)用進(jìn)程是否足夠,可以使用 free –m 命令查看內(nèi)存的使用情況。
通過 top 命令可以查看進(jìn)程使用的虛擬內(nèi)存 VIRT 和物理內(nèi)存 RES,根據(jù)公式 VIRT = SWAP + RES 可以推算出具體應(yīng)用使用的交換分區(qū)(Swap)情況,使用交換分區(qū)過大會(huì)影響 Java 應(yīng)用性能,可以將 swappiness 值調(diào)到盡可能小。
因?yàn)閷?duì)于 Java 應(yīng)用來說,占用太多交換分區(qū)可能會(huì)影響性能,畢竟磁盤性能比內(nèi)存慢太多
I/O 包括磁盤 I/O 和網(wǎng)絡(luò) I/O,一般情況下磁盤更容易出現(xiàn) I/O 瓶頸。通過 iostat 可以查看磁盤的讀寫情況,通過 CPU 的 I/O wait 可以看出磁盤 I/O 是否正常。
如果磁盤 I/O 一直處于很高的狀態(tài),說明磁盤太慢或故障,成為了性能瓶頸,需要進(jìn)行應(yīng)用優(yōu)化或者磁盤更換。
除了常用的 top、 ps、vmstat、iostat 等命令,還有其他 Linux 工具可以診斷系統(tǒng)問題,如 mpstat、tcpdump、netstat、pidstat、sar 等。Brendan 總結(jié)列出了 Linux 不同設(shè)備類型的性能診斷工具,如圖 4 所示,可供參考。
圖 4.Linux 性能觀測(cè)工具
應(yīng)用代碼性能問題是相對(duì)好解決的一類性能問題。通過一些應(yīng)用層面監(jiān)控報(bào)警,如果確定有問題的功能和代碼,直接通過代碼就可以定位;或者通過 top+jstack,找出有問題的線程棧,定位到問題線程的代碼上,也可以發(fā)現(xiàn)問題。對(duì)于更復(fù)雜,邏輯更多的代碼段,通過 Stopwatch 打印性能日志往往也可以定位大多數(shù)應(yīng)用代碼性能問題。
常用的 Java 應(yīng)用診斷包括線程、堆棧、GC 等方面的診斷。
jstack
jstack 命令通常配合 top 使用,通過 top -H -p pid 定位 Java 進(jìn)程和線程,再利用 jstack -l pid 導(dǎo)出線程棧。由于線程棧是瞬態(tài)的,因此需要多次 dump,一般 3 次 dump,一般每次隔 5s 就行。將 top 定位的 Java 線程 pid 轉(zhuǎn)成 16 進(jìn)制,得到 Java 線程棧中的 nid,可以找到對(duì)應(yīng)的問題線程棧。
圖 5. 通過 top –H -p 查看運(yùn)行時(shí)間較長(zhǎng) Java 線程
如圖 5 所示,其中的線程 24985 運(yùn)行時(shí)間較長(zhǎng),可能存在問題,轉(zhuǎn)成 16 進(jìn)制后,通過 Java 線程棧找到對(duì)應(yīng)線程 0x6199 的棧如下,從而定位問題點(diǎn),如圖 6 所示。
圖 6.jstack 查看線程堆棧
JProfiler
JProfiler 可對(duì) CPU、堆、內(nèi)存進(jìn)行分析,功能強(qiáng)大,如圖 7 所示。同時(shí)結(jié)合壓測(cè)工具,可以對(duì)代碼耗時(shí)采樣統(tǒng)計(jì)。
圖 7. 通過 JProfiler 進(jìn)行內(nèi)存分析
Java GC 解決了程序員管理內(nèi)存的風(fēng)險(xiǎn),但 GC 引起的應(yīng)用暫停成了另一個(gè)需要解決的問題。JDK 提供了一系列工具來定位 GC 問題,比較常用的有 jstat、jmap,還有第三方工具 MAT 等。
jstat
jstat 命令可打印 GC 詳細(xì)信息,Young GC 和 Full GC 次數(shù),堆信息等。其命令格式為
jstat –gcxxx -t pid <interval> <count>,如圖 8 所示。
圖 8.jstat 命令示例
jmap
jmap 打印 Java 進(jìn)程堆信息 jmap –heap pid。通過 jmap –dump:file=xxx pid 可 dump 堆到文件,然后通過其它工具進(jìn)一步分析其堆使用情況
MAT
MAT 是 Java 堆的分析利器,提供了直觀的診斷報(bào)告,內(nèi)置的 OQL 允許對(duì)堆進(jìn)行類 SQL 查詢,功能強(qiáng)大,outgoing reference 和 incoming reference 可以對(duì)對(duì)象引用追根溯源。
圖 9.MAT 示例
圖 9 是 MAT 使用示例,MAT 有兩列顯示對(duì)象大小,分別是 Shallow size 和 Retained size,前者表示對(duì)象本身占用內(nèi)存的大小,不包含其引用的對(duì)象,后者是對(duì)象自己及其直接或間接引用的對(duì)象的 Shallow size 之和,即該對(duì)象被回收后 GC 釋放的內(nèi)存大小,一般說來關(guān)注后者大小即可。
對(duì)于有些大堆 (幾十 G) 的 Java 應(yīng)用,需要較大內(nèi)存才能打開 MAT。
通常本地開發(fā)機(jī)內(nèi)存過小,是無法打開的,建議在線下服務(wù)器端安裝圖形環(huán)境和 MAT,遠(yuǎn)程打開查看?;蛘邎?zhí)行 mat 命令生成堆索引,拷貝索引到本地,不過這種方式看到的堆信息有限。
為了診斷 GC 問題,建議在 JVM 參數(shù)中加上-XX:+PrintGCDateStamps。常用的 GC 參數(shù)如圖 10 所示。
圖 10. 常用 GC 參數(shù)
對(duì)于 Java 應(yīng)用,通過 top+jstack+jmap+MAT 可以定位大多數(shù)應(yīng)用和內(nèi)存問題,可謂必備工具。有些時(shí)候,Java 應(yīng)用診斷需要參考 OS 相關(guān)信息,可使用一些更全面的診斷工具,比如 Zabbix(整合了 OS 和 JVM 監(jiān)控)等。在分布式環(huán)境中,分布式跟蹤系統(tǒng)等基礎(chǔ)設(shè)施也對(duì)應(yīng)用性能診斷提供了有力支持。
在介紹了一些常用的性能診斷工具后,下面將結(jié)合我們?cè)?Java 應(yīng)用調(diào)優(yōu)中的一些實(shí)踐,從 JVM 層、應(yīng)用代碼層以及數(shù)據(jù)庫層進(jìn)行案例分享。
JVM 調(diào)優(yōu):GC 之痛
XX商業(yè)平臺(tái)某系統(tǒng)重構(gòu)時(shí)選擇 RMI 作為內(nèi)部遠(yuǎn)程調(diào)用協(xié)議,系統(tǒng)上線后開始出現(xiàn)周期性的服務(wù)停止響應(yīng),暫停時(shí)間由數(shù)秒到數(shù)十秒不等。通過觀察 GC 日志,發(fā)現(xiàn)服務(wù)自啟動(dòng)后每小時(shí)會(huì)出現(xiàn)一次 Full GC。由于系統(tǒng)堆設(shè)置較大,F(xiàn)ull GC 一次暫停應(yīng)用時(shí)間會(huì)較長(zhǎng),這對(duì)線上實(shí)時(shí)服務(wù)影響較大。
經(jīng)過分析,在重構(gòu)前系統(tǒng)沒有出現(xiàn)定期 Full GC 的情況,因此懷疑是 RMI 框架層面的問題。通過公開資料,發(fā)現(xiàn) RMI 的 GDC(Distributed Garbage Collection,分布式垃圾收集)會(huì)啟動(dòng)守護(hù)線程定期執(zhí)行 Full GC 來回收遠(yuǎn)程對(duì)象,清單 2 中展示了其守護(hù)線程代碼。
清單 2.DGC 守護(hù)線程源代碼
private static class Daemon extends Thread {
public void run() {
for (;;) {
//…
long d = maxObjectInspectionAge();
if (d >= l) {
System.gc();
d = 0;
}
//…
}
}
}
定位問題后解決起來就比較容易了。一種是通過增加-XX:+DisableExplicitGC 參數(shù),直接禁用系統(tǒng) GC 的顯示調(diào)用,但對(duì)使用 NIO 的系統(tǒng),會(huì)有堆外內(nèi)存溢出的風(fēng)險(xiǎn)。
另一種方式是通過調(diào)大 -Dsun.rmi.dgc.server.gcInterval 和-Dsun.rmi.dgc.client.gcInterval 參數(shù),增加 Full GC 間隔,同時(shí)增加參數(shù)-XX:+ExplicitGCInvokesConcurrent,將一次完全 Stop-The-World 的 Full GC 調(diào)整為一次并發(fā) GC 周期,減少應(yīng)用暫停時(shí)間,同時(shí)對(duì) NIO 應(yīng)用也不會(huì)造成影響。
從圖 11 可知,調(diào)整之后的 Full GC 次數(shù) 在 3 月之后明顯減少。
圖 11.Full GC 監(jiān)控統(tǒng)計(jì)
GC 調(diào)優(yōu)對(duì)高并發(fā)大數(shù)據(jù)量交互的應(yīng)用還是很有必要的,尤其是默認(rèn) JVM 參數(shù)通常不滿足業(yè)務(wù)需求,需要進(jìn)行專門調(diào)優(yōu)。GC 日志的解讀有很多公開的資料,本文不再贅述。
GC 調(diào)優(yōu)目標(biāo)基本有三個(gè)思路:降低 GC 頻率,可以通過增大堆空間,減少不必要對(duì)象生成;降低 GC 暫停時(shí)間,可以通過減少堆空間,使用 CMS GC 算法實(shí)現(xiàn);避免 Full GC,調(diào)整 CMS 觸發(fā)比例,避免 Promotion Failure 和 Concurrent mode failure(老年代分配更多空間,增加 GC 線程數(shù)加快回收速度),減少大對(duì)象生成等。
應(yīng)用層調(diào)優(yōu):嗅到代碼的壞味道
從應(yīng)用層代碼調(diào)優(yōu)入手,剖析代碼效率下降的根源,無疑是提高 Java 應(yīng)用性能的很好的手段之一。
某商業(yè)廣告系統(tǒng)(采用 Nginx 進(jìn)行負(fù)載均衡)某次日常上線后,其中有幾臺(tái)機(jī)器負(fù)載急劇升高,CPU 使用率迅速打滿。我們對(duì)線上進(jìn)行了緊急回滾,并通過 jmap 和 jstack 對(duì)其中某臺(tái)服務(wù)器的現(xiàn)場(chǎng)進(jìn)行保存。
圖 12. 通過 MAT 分析堆棧現(xiàn)場(chǎng)
堆?,F(xiàn)場(chǎng)如圖 12 所示,根據(jù) MAT 對(duì) dump 數(shù)據(jù)的分析,發(fā)現(xiàn)最多的內(nèi)存對(duì)象為 byte[] 和 java.util.HashMap $Entry,且 java.util.HashMap $Entry 對(duì)象存在循環(huán)引用。初步定位在該 HashMap 的 put 過程中有可能出現(xiàn)了死循環(huán)問題(圖中 java.util.HashMap $Entry 0x2add6d992cb8 和 0x2add6d992ce8 的 next 引用形成循環(huán))。
查閱相關(guān)文檔定位這屬于典型的并發(fā)使用的場(chǎng)景錯(cuò)誤 (http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6423457) ,簡(jiǎn)要的說就是 HashMap 本身并不具備多線程并發(fā)的特性,在多個(gè)線程同時(shí) put 操作的情況下,內(nèi)部數(shù)組進(jìn)行擴(kuò)容時(shí)會(huì)導(dǎo)致 HashMap 的內(nèi)部鏈表形成環(huán)形結(jié)構(gòu),從而出現(xiàn)死循環(huán)。
針對(duì)此次上線,大的改動(dòng)在于通過內(nèi)存緩存網(wǎng)站數(shù)據(jù)來提升系統(tǒng)性能,同時(shí)使用了懶加載機(jī)制,如清單 3 所示。
清單 3. 網(wǎng)站數(shù)據(jù)懶加載代碼
可以看到此處的 domainMap 為靜態(tài)共享資源,它是 HashMap 類型,在多線程情況下會(huì)導(dǎo)致其內(nèi)部鏈表形成環(huán)形結(jié)構(gòu),出現(xiàn)死循環(huán)。
通過對(duì)前端 Nginx 的連接和訪問日志可以看到,由于在系統(tǒng)重啟后 Nginx 積攢了大量的用戶請(qǐng)求,在 Resin 容器啟動(dòng),大量用戶請(qǐng)求涌入應(yīng)用系統(tǒng),多個(gè)用戶同時(shí)進(jìn)行網(wǎng)站數(shù)據(jù)的請(qǐng)求和初始化工作,導(dǎo)致 HashMap 出現(xiàn)并發(fā)問題。在定位故障原因后解決方法則比較簡(jiǎn)單,主要的解決方法有:
(1)采用 ConcurrentHashMap 或者同步塊的方式解決上述并發(fā)問題;
(2)在系統(tǒng)啟動(dòng)前完成網(wǎng)站緩存加載,去除懶加載等;
(3)采用分布式緩存替換本地緩存等。
對(duì)于壞代碼的定位,除了常規(guī)意義上的代碼審查外,借助諸如 MAT 之類的工具也可以在一定程度對(duì)系統(tǒng)性能瓶頸點(diǎn)進(jìn)行快速定位。但是一些與特定場(chǎng)景綁定或者業(yè)務(wù)數(shù)據(jù)綁定的情況,卻需要輔助代碼走查、性能檢測(cè)工具、數(shù)據(jù)模擬甚至線上引流等方式才能最終確認(rèn)性能問題的出處。以下是我們總結(jié)的一些壞代碼可能的一些特征,供大家參考:
(1)代碼可讀性差,無基本編程規(guī)范;
(2)對(duì)象生成過多或生成大對(duì)象,內(nèi)存泄露等;
(3)IO 流操作過多,或者忘記關(guān)閉;
(4)數(shù)據(jù)庫操作過多,事務(wù)過長(zhǎng);
(5)同步使用的場(chǎng)景錯(cuò)誤;
(6)循環(huán)迭代耗時(shí)操作等。
數(shù)據(jù)庫層調(diào)優(yōu):死鎖噩夢(mèng)
對(duì)于大部分 Java 應(yīng)用來說,與數(shù)據(jù)庫進(jìn)行交互的場(chǎng)景非常普遍,尤其是 OLTP 這種對(duì)于數(shù)據(jù)一致性要求較高的應(yīng)用,數(shù)據(jù)庫的性能會(huì)直接影響到整個(gè)應(yīng)用的性能。搜狗商業(yè)平臺(tái)系統(tǒng)作為廣告主的廣告發(fā)布和投放平臺(tái),對(duì)其物料的實(shí)時(shí)性和一致性都有極高的要求,我們?cè)陉P(guān)系型數(shù)據(jù)庫優(yōu)化方面也積累了一定的經(jīng)驗(yàn)。
對(duì)于廣告物料庫來說,較高的操作頻繁度(特別是通過批量物料工具操作)很極易造成數(shù)據(jù)庫的死鎖情況發(fā)生,其中一個(gè)比較典型的場(chǎng)景是廣告物料調(diào)價(jià)。客戶往往會(huì)頻繁的對(duì)物料的出價(jià)進(jìn)行調(diào)整,從而間接給數(shù)據(jù)庫系統(tǒng)造成較大的負(fù)載壓力,也加劇了死鎖發(fā)生的可能性。下面以搜狗商業(yè)平臺(tái)某廣告系統(tǒng)廣告物料調(diào)價(jià)的案例進(jìn)行說明。
某商業(yè)廣告系統(tǒng)某天訪問量突增,造成系統(tǒng)負(fù)載升高以及數(shù)據(jù)庫頻繁死鎖,死鎖語句如圖 13 所示。
圖 13. 死鎖語句
其中,groupdomain 表上索引為 idx_groupdomain_accountid (accountid),idx_groupdomain_groupid(groupid),primary(groupdomainid) 三個(gè)單索引結(jié)構(gòu),采用 Mysql innodb 引擎。
此場(chǎng)景發(fā)生在更新組出價(jià)時(shí),場(chǎng)景中存在著組、組行業(yè)(groupindus 表)和組網(wǎng)站(groupdomain 表)。
當(dāng)更新組出價(jià)時(shí),若組行業(yè)出價(jià)使用組出價(jià)(通過 isusegroupprice 標(biāo)示,若為 1 則使用組出價(jià))。同時(shí)若組網(wǎng)站出價(jià)使用組行業(yè)出價(jià)(通過 isuseindusprice 標(biāo)示,若為 1 則使用組行業(yè)出價(jià))時(shí),也需要同時(shí)更新其組網(wǎng)站出價(jià)。由于每個(gè)組下面大可以有 3000 個(gè)網(wǎng)站,因此在更新組出價(jià)時(shí)會(huì)長(zhǎng)時(shí)間的對(duì)相關(guān)記錄進(jìn)行鎖定。
從上面發(fā)生死鎖的問題可以看到,事務(wù) 1 和事務(wù) 2 均選擇了 idx_groupdomain_accountid 的單列索引。根據(jù) Mysql innodb 引擎加鎖的特點(diǎn),在一次事務(wù)中只會(huì)選擇一個(gè)索引使用,而且如果一旦使用二級(jí)索引進(jìn)行加鎖后,會(huì)嘗試將主鍵索引進(jìn)行加鎖。進(jìn)一步分析可知事務(wù) 1 在請(qǐng)求事務(wù) 2 持有的idx_groupdomain_accountid
二級(jí)索引加鎖(加鎖范圍“space id 5726 page no 8658 n bits 824 index”),但是事務(wù) 2 已獲得該二級(jí)索引 (“space id 5726 page no 8658 n bits 824 index”) 上所加的鎖,在等待請(qǐng)求鎖定主鍵索引 PRIMARY 索引上的鎖。由于事務(wù) 2 等待執(zhí)行時(shí)間過長(zhǎng)或長(zhǎng)時(shí)間不釋放鎖,導(dǎo)致事務(wù) 1 最終發(fā)生回滾。
通過對(duì)當(dāng)天訪問日志跟蹤可以看到,當(dāng)天有客戶通過腳本方式發(fā)起大量的修改推廣組出價(jià)的操作,導(dǎo)致有大量事務(wù)在循環(huán)等待前一個(gè)事務(wù)釋放鎖定的主鍵 PRIMARY 索引。該問題的根源實(shí)際上在于 Mysql innodb 引擎對(duì)于索引利用有限,在 Oracle 數(shù)據(jù)庫中此問題并不突出。
解決的方式自然是希望單個(gè)事務(wù)鎖定的記錄數(shù)越少越好,這樣產(chǎn)生死鎖的概率也會(huì)大大降低。最終使用了(accountid, groupid)的復(fù)合索引,縮小了單個(gè)事務(wù)鎖定的記錄條數(shù),也實(shí)現(xiàn)了不同計(jì)劃下的推廣組數(shù)據(jù)記錄的隔離,從而減少該類死鎖的發(fā)生幾率。
通常來說,對(duì)于數(shù)據(jù)庫層的調(diào)優(yōu)我們基本上會(huì)從以下幾個(gè)方面出發(fā):
(1)在 SQL 語句層面進(jìn)行優(yōu)化:慢 SQL 分析、索引分析和調(diào)優(yōu)、事務(wù)拆分等;
(2)在數(shù)據(jù)庫配置層面進(jìn)行優(yōu)化:比如字段設(shè)計(jì)、調(diào)整緩存大小、磁盤 I/O 等數(shù)據(jù)庫參數(shù)優(yōu)化、數(shù)據(jù)碎片整理等;
(3)從數(shù)據(jù)庫結(jié)構(gòu)層面進(jìn)行優(yōu)化:考慮數(shù)據(jù)庫的垂直拆分和水平拆分等;
(4)選擇合適的數(shù)據(jù)庫引擎或者類型適應(yīng)不同場(chǎng)景,比如考慮引入 NoSQL 等。
性能調(diào)優(yōu)同樣遵循 2-8 原則,80%的性能問題是由 20%的代碼產(chǎn)生的,因此優(yōu)化關(guān)鍵代碼事半功倍。同時(shí),對(duì)性能的優(yōu)化要做到按需優(yōu)化,過度優(yōu)化可能引入更多問題。對(duì)于 Java 性能優(yōu)化,不僅要理解系統(tǒng)架構(gòu)、應(yīng)用代碼,同樣需要關(guān)注 JVM 層甚至操作系統(tǒng)底層??偨Y(jié)起來主要可以從以下幾點(diǎn)進(jìn)行考慮:
1)基礎(chǔ)性能的調(diào)優(yōu)
這里的基礎(chǔ)性能指的是硬件層級(jí)或者操作系統(tǒng)層級(jí)的升級(jí)優(yōu)化,比如網(wǎng)絡(luò)調(diào)優(yōu),操作系統(tǒng)版本升級(jí),硬件設(shè)備優(yōu)化等。比如 F5 的使用和 SDD 硬盤的引入,包括新版本 Linux 在 NIO 方面的升級(jí),都可以極大的促進(jìn)應(yīng)用的性能提升;
2)數(shù)據(jù)庫性能優(yōu)化
包括常見的事務(wù)拆分,索引調(diào)優(yōu),SQL 優(yōu)化,NoSQL 引入等,比如在事務(wù)拆分時(shí)引入異步化處理,最終達(dá)到一致性等做法的引入,包括在針對(duì)具體場(chǎng)景引入的各類 NoSQL 數(shù)據(jù)庫,都可以大大緩解傳統(tǒng)數(shù)據(jù)庫在高并發(fā)下的不足;
3)應(yīng)用架構(gòu)優(yōu)化
引入一些新的計(jì)算或者存儲(chǔ)框架,利用新特性解決原有集群計(jì)算性能瓶頸等;或者引入分布式策略,在計(jì)算和存儲(chǔ)進(jìn)行水平化,包括提前計(jì)算預(yù)處理等,利用典型的空間換時(shí)間的做法等;都可以在一定程度上降低系統(tǒng)負(fù)載;
4)業(yè)務(wù)層面的優(yōu)化
技術(shù)并不是提升系統(tǒng)性能的唯一手段,在很多出現(xiàn)性能問題的場(chǎng)景中,其實(shí)可以看到很大一部分都是因?yàn)樘厥獾臉I(yè)務(wù)場(chǎng)景引起的,如果能在業(yè)務(wù)上進(jìn)行規(guī)避或者調(diào)整,其實(shí)往往是最有效的。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。
本文名稱:Java性能調(diào)優(yōu)的7個(gè)實(shí)用技巧分享-創(chuàng)新互聯(lián)
當(dāng)前URL:http://aaarwkj.com/article18/ccohdp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供企業(yè)建站、網(wǎng)頁設(shè)計(jì)公司、網(wǎng)站營(yíng)銷、靜態(tài)網(wǎng)站、域名注冊(cè)、標(biāo)簽優(yōu)化
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容