天天看點

RDS MySQL Java 開發實戰 ——義泊

内容簡要:

一、深入淺出ORM架構MyBatis

二、連接配接池架構剖析和最佳實踐

三、Java應用性能問題診斷技巧

(一)為什麼要選MyBatis

RDS MySQL Java 開發實戰 ——義泊

在以前是直接用JDBC進行資料庫查詢,優點是簡單直接,缺點是開發效率低。用JDBC寫程式,需要大量手工寫代碼,代碼重複率較高,後來逐漸演化出ORM架構。

ORM架構最早期有Hibernate以及JPA規範,Hibernate能夠屏蔽底層資料庫差異,自動根據SQL語言生成對應底層不同資料庫的方言,缺點是對關聯查詢支援與動态SQL能力不太友好,很難寫出高效SQL。

國内目前流行的是輕量級MyBatis,對動态SQL以及關聯查詢的支援性較高,缺點是因為它綁定一個DB,手寫SQL還要動态拼接,很難從一個DB自由的切換到另外一個DB,但由于平時很少切換DB,是以問題不是很大。

(二)MyBatis基本概念介紹

RDS MySQL Java 開發實戰 ——義泊

MyBatis主要分為三層:接口層,核心層與基礎層。

l  接口層

是通過提供的API作為資料庫進行增/删/改/查,都是MyBatis的API。

l  核心層

Ø  是SQL預處理、SQL執行、結果映射。

1)  SQL預處理:是對代碼裡的變量進行綁定,以及動态SQL生成;

2)  SQL執行:是把生成好的SQL,通過JDBC驅動,傳到對應的DB裡執行,而且要負責網絡通信的部分;

3)  結果映射:是把資料庫傳回的結果從關系型資料轉換成Java對象資料。

l  基礎層

Ø  包括日志、事務管理、緩存、連接配接池、動态代理、配置解析。

1)  日志:是做架構裡面的日志輸出以及SQL語句輸出;

2)  事務管理:是對 JDBC事物、資料庫事物做管理;

3)  緩存:能夠把結果集緩存在JVM的記憶體内部。優點是比較快,缺點是會占用堆記憶體。有條件的情況下,建議使用者多使用分布式緩存;

4)  連接配接池:能夠加速查詢,提高性能;

5)  動态代理:在用MyBatis程式設計時,核心是通過接口執行資料庫查詢。而Mapper接口本身是沒有實作的,實作是在或注解來配置SQL語句。動态代理會在運作時生成代理,當調用Mapper接口時,轉換成實際的SQL語句;

6)  配置解析:因為MyBatis裡面有存在大量配置,需要配置新子產品,讀取XML配置,并把它映射為配置屬性。

(三)MyBatis從0開始搭建工程

工程的搭建主要包括三部分:技術選型,項目依賴和工程結構。

1.技術選型

RDS MySQL Java 開發實戰 ——義泊

從0開始搭建MyBatis工程,使用MyBatis沒有意義。

如上圖所示,現在主流是用Spring架構做結合,還有底層有連接配接池也要去做結合。最新的選型是Spring-boot2,需要開發Web工程,選擇Spring-Webmvc架構,持久層或IRM映射層用MyBatis架構,連接配接池選擇HikariCP,HikariCP是Spring-boot2的連接配接池,資料庫使用RDS MySQL,也是國内比較流行的資料庫。

有了以上選型之後,可以通過Spring官方網站上面的網站連接配接:“

https://spring.io/quickstart

”填入相關資訊,就可以生成框項目模闆。模闆下載下傳之後在ID裡面打開導入項目,就有開始工作的項目模闆。

2.項目依賴:

RDS MySQL Java 開發實戰 ——義泊

第一部分依賴:“spring-boot-starter”,依賴的作用是自動管理開始spring-boot項目的預設依賴,已經內建在Starter裡面了。

第二部分依賴:用到了Spring-webmvc,是最新的2.4.1版本。

第三部分依賴:因為用到MyBatis,是以還需要依賴MyBatis starter。需要注意的是MyBatis并沒有Spring官方的Starter,而是MyBatis社群提供的 MyBatis–spring-boot-starter。

第四部分依賴:JDBC實作,“mysql-connector-java”因為JDBC是一套規範API,具體實作由各個資料庫的廠商實作。

第五部分依賴:Spring-boot-starter架構測試部分。

第六部分依賴:為了減少代碼量,還用到了Lombok。

3.工程結構

RDS MySQL Java 開發實戰 ——義泊

上圖為工程結構圖,從上往下看:

l  第1層:控制器層Controller,這裡有一個自己的控制器。

l  第2層:Mapper,主要是資料庫增/删/改/查的接口。

l  第3層:Model層,主要是在Java裡面的對象定義。

l  第4層:Service層,包括 API層和Impl層。

API層主要是接口的定義; Impl層主要是對接口的實作。

在接口裡面調用Impl層,或者調用業務層實作業務邏輯組裝和編排。

l  第5層:是Java應用的啟動入口。選擇用XML方式配置MyBatis,是以在Resource目錄裡面,需要增加Mapper目錄,mapper裡面放入StaffMapper檔案。

l  第6層:是Spring的配置檔案,還有Log4j配置檔案,還有Mybatis配置檔案。Mybatis配置的也可以通過程式設計的方式實作。

l  第7層:有Pom檔案。

通過以上操作,從0開始搭建的MyBatis工程就完成了。當工程啟動後,在浏覽器裡面輸入“http://localhost:8001/query?name=yanglong”連結,這裡的端口還有路徑都是自己定義的,可以驗證工程是否正常運作。

(一)為什麼要用連接配接池?

RDS MySQL Java 開發實戰 ——義泊

不用連接配接池會存在以下問題:

1)如果不用連接配接池,每一次查詢都需要建立連接配接,有兩層握手。第一層是TCP層的握手,第二層是 MySQL協定握手。兩層協定大概需要有多個TCP資料包,這些都需要時間,在資料庫内部還需要處理,建立連接配接是費時間的操作;

2)對于當代的應用來說,應用伺服器是很多台資料庫,伺服器相比之下可能少一些。大量應用伺服器會存在一個問題,在業務流量高峰期存在對DB的連接配接,而DB能夠承載的連接配接數有限。是以說如果不用連接配接池,那麼這個連接配接的數量就不受控制,導緻DB性能嚴重降低;

3)如果不用連接配接池,意味着每次執行SQL語句時,都需要建立TCL連結和關閉TCL連結,而關閉動作是在應用端完成,導緻應用伺服器上存在較多攤位狀态的TCP連接配接。目前狀态的連接配接數達到一定數量之後會引起應用問題,例如端口不夠用。

l  用連接配接池的優點:

1)不用每次都建立連接配接,而是直接從連接配接池裡取連接配接,性能更佳;

2)連接配接池可以控制連接配接數量,以及當連接配接出現問題時,連接配接池能夠去自動探測連接配接是否存活,如果連接配接中斷,連接配接池會自動重建;

比如應用使用RBS的MySQL,需要對MySQL的執行個體進行配置變更。如升規格,提高磁盤空間,遇到問題者壓力大時,希望能夠重新開機MySQL,有了連接配接池就能夠自動處理好這些問題。

3)連接配接池能夠對連接配接進行靈活的管理,對連接配接池配置與連接配接池狀态監控,看到連接配接池裡面的各種連接配接數量和性能名額。

(二)連接配接池架構

RDS MySQL Java 開發實戰 ——義泊

連接配接池架構分為:接口層、核心層、基礎層。

l  接口層:對于MyBatis是從連接配接池裡擷取連接配接,連接配接用完之後關閉,調用連接配接的Close,歸還連接配接。

l  核心層:負責并發控制、連接配接控制、異常處理。

1) 并發控制:連接配接池裡的連接配接數量有限,應用裡面的線程數量多于連接配接池的連接配接數量。

第一種情況,當連接配接池裡的連接配接都處于活躍狀态時,下一個請求,想要繼續得到連接配接需要等待,因為數量有限,需要排隊。

第二種情況,同一個連結,不能配置設定給多個線程,否則可能會開啟事物。對同一個連接配接進行多次開啟事務,會引起事故的混亂。

2) 連接配接控制:需要能夠動态調整連接配接池大小,同時連接配接池保證連接配接池裡面的連接配接數量在期望範圍内。

3) 異常處理:出現異常時,比如底層資料庫重新開機,網絡中斷,或者連接配接裡面發生了協定層引擎層面錯誤,連接配接已經不能再使用,這個時候連接配接池自動處理這些問題,将連接配接關閉。

l  基礎層:包括配置管理、監控、定時任務、日志、位元組碼操縱。

1) 配置管理:是連接配接池裡面有很多的配置項,雖然常用的不多,但是可配置的點很多,需要進行解析管理。

2) 監控:連接配接運作時需要統計和監控,最好能夠提供檢視的頁面。

3) 定時任務:連接配接池裡連接配接數量超過一定的程度,釋放空閑連接配接,是通過定時任務完成。

4) 位元組碼操縱:在Java架構裡面會存在大量的位元組碼操縱,動态生成代理,增加業務邏輯。

(三)Druid最佳實踐

1.參數配置

RDS MySQL Java 開發實戰 ——義泊

如上圖所示,常用配置包括:

1)  Max-active:指的是連接配接池裡允許的最大活躍連接配接數,這個值根據應用實際情況調整。

2)  Min-idle:關掉多餘連接配接,保留有效連接配接,節省資料庫的資源,這個值根據應用實際情況調整。

3)  Max-wait,指應用線程等待逾時。可以配幾秒範圍,根據業務應用實際情況進行判定。

4)  Validation-query,指的是連接配接池探測目前連接配接是否是健康的SQL語句。Druid最新版GDP驅動,調用Ping指令,如果GDP比較新,不會發SQL語句過去,而是發Ping指令。

5)  Validation-query-timeout,指的就是探測逾時的時間。

6)  Test-on-borrow指連接配接從連接配接池裡取出時,連接配接池是否需要對連接配接進行健康探測。建議關閉False。

7)  Test-on-return,建議關閉False。

8)  Test-while-idle,指的是控制,當連接配接處于空閑狀态時,是否需檢測連接配接的健康狀态。建議打開True。

9)  Time-between-eviction-runs-millis指的是觸發空閑連接配接健康探測門檻值,需要跟上面的Test-while結合起來。

10)Remove-abandoned,洩露連接配接強制回收,預設是False關閉。

11)Remove-abandoned-timeout,指的是強制回收的觸發時間門檻值。配置時間不要太短,因為業務長時間使用連接配接,是以逾時時間要比業務實際合理時間要高。配置參數機關是“秒”。

12)Log-abandoned,指的是關閉被洩露連接配接時輸出堆棧。當一個連接配接被探測為連接配接線路并且強制關閉的時候,是否要在日志裡面輸出連接配接擷取連接配接的現場的對賬,跟Remove結合使用。

RDS MySQL Java 開發實戰 ——義泊

如上圖所示,當使用者開啟了空閑連接配接的健康探測時,在Druid的源碼内部會出發怎麼樣的邏輯?

入口是MyBatis連接配接Druid時,getConnectionDirect是一個循環,在循環裡從連接配接池裡取一個空閑連接配接,當探測空閑連接配接健康開關開啟時,Druid會去檢測連接配接的空閑時間是否超過下面配置的門檻值。

如果超過了,循環會發起一個連接配接的健康探測。如果探測出來連接配接有問題,Druid會直接把連接配接關閉。在循環裡面,會回到循環開頭,從連接配接池裡再拿一個連接配接,直到拿到一個可用的連接配接,如果連接配接池裡所有的連接配接都有問題,會重新建立連接配接.

2.監控

Druid的最佳實踐做監控可以開啟Spring,在配置中開啟Delivery的監控選項,就會有一個内部維護狀态。

RDS MySQL Java 開發實戰 ——義泊

同時基于Web的頁面管理頁面可以去監控連接配接詞的運作狀态。如上圖所示,可以檢視連接配接時的配置參數、版本、内路徑,也可以檢視連接配接實名的連接配接數、 SQL監控、C線監控、結成API,通過API可以拿到這些監控資料。

除此以外,通過JMX直接連到應用内部,檢視登入到内部一些JMX對象的一些屬性,如圖内有些參數是可以修改的,有些隻能看的還有一些操作,一些API可以去調用,是以JMX是較為強大的。

3.連接配接洩露診斷

l  現象

1)  正常請求拿不到連接配接報錯;

2)  響應時間增大;

3)  應用不可用;

l  原因

1)連接配接被取出後沒有正常歸還,導緻連接配接長時間被占有但沒有使用;

2)一般都是代碼問題;

l  解決辦法

1)開啟連接配接池洩露診斷;

2)修改代碼;

RDS MySQL Java 開發實戰 ——義泊

如上圖兩行日志,連接配接詞裡的連接配接全部被應用取走之後,新的應用與新的請求去取連接配接的時候就會得不到連接配接造成逾時報錯。一旦連接配接式的連接配接全部洩露,後續所有的請求都會因拿不到連接配接而報錯。

可以通過在Spring的配置檔案裡面增加Druid的連接配接線路的參數。開啟之後,當出現連接配接線路時,Druid會在日志裡面列印出被洩露出去的線程,在取得連接配接的對賬,可以直接看到是什麼的業務代碼,在什麼情況下去拿了連結而沒有歸還,有助于定位問題。

應用性能問題的診斷主要從三個方面:記憶體、CPU、網絡

(一)記憶體

1.  記憶體

1)OutOfMemoryError: Java heap space;

2)頻繁FULL GC;

1)記憶體洩露;

2)堆大小配置不合理;

l  解決方法

1)jvisualvm;

2)jstat;

3)jmap;

4)mat。

RDS MySQL Java 開發實戰 ——義泊

如上圖所示,這段日志是當出現記憶體耗盡的時候,結果會報出來一些錯誤,應用會出現頻繁的FULL GC。

2.記憶體-JMX

RDS MySQL Java 開發實戰 ——義泊

診斷結記憶體的問題的方法:

可以打開JMX通過啟動參數以-D開頭這4個參數進行遠端連接配接,連接配接之後可以看到最大堆的大小以及實際已經使用的情況。

3.記憶體-Jmap

同時通過JMX對堆進行一個Dump,檔案會在Jvm運作所在主機的對應的目錄上。

RDS MySQL Java 開發實戰 ——義泊
RDS MySQL Java 開發實戰 ——義泊

第二種是 Jmap-heap2780程序号,可以看到Jmap的堆的最大大小是512兆,同時看到老年代的使用情況是78%。通過指令把Jmap的堆記憶體Dump到檔案中做後續分析。通過指令提示正在進行Dump,完成後會有一個Heap Dump File Created日志。

用MAT打開堆的Dump檔案,然後用記憶體線路分析模式,通過結果可以看到 Controller裡面有大量的對象,MAT的下載下傳位址https:/www.eclipse.org/mat/。

4.記憶體-結合代碼确認問題

RDS MySQL Java 開發實戰 ——義泊

确認最終的問題,打開 MAT打開堆的Dump檔案之後,通過記憶體線路的分析,可以找到MyController下的曆史對象,裡面存在大量字元串,每個字元串的大小約為2M,大概有100個,占用了268兆,可以看到具體的字元串的内容。

結合代碼,看到在代碼中第63行申請了一個1M長度,2M位元組大小的記憶體,并且把記憶體放到一個全局的靜态變量中進行應用,是以JC無法回收,因為是一個強應用無法回收,否則将導緻記憶體洩漏。

通過Jmap把堆Dump出來,再通過MVP工具對記憶體進行分析,找到占用記憶體的對象,再通過對象裡的一些引用關系,就能夠找到代碼裡面建立對象,找到出現問題的代碼,完成記憶體的定位。

(二)CPU

CPU問題定位:

1)應用響應緩慢;

2)Java程序CPU占用高;

1)存在大量消耗CPU的邏輯;

2)循環;

3)複雜計算;

1)Top;

2)Jvisualvm ;

3)Async-profiler;

1.CPU-JMX或Jstack

RDS MySQL Java 開發實戰 ——義泊

如上圖所示案例,一個一核的主機,CPU使用率接近100%,主機的負載達到了6點多,Java程序的CPU使用率可以到99%,通過選擇JMX或者Jstack。

JMX連上去之後去檢查每個線程是否在執行,這裡通過JMX可以看到線程池1裡的線程号1~6在長期的運作,是以這可能就是問題線程。

同時可以通過Jstack檢視Jvm裡面每個現場的堆棧,但是通過Jstack有一個缺點就是當應用裡面線程非常多的時候,Jstack的結果會非常大。

2.CPU-Async-Profiler

通過使用Async-profiler下載下傳對應平台已編譯好的代碼,解壓後找到對應的Java程序,通過這一串指令,對Java程序做一個系統剖析,剖析完之後會生成一個火焰圖,通過火焰圖能夠準确的看到應用的熱點代碼。

l  這裡對上列參數做一個說明:

增加了-e itimer後,會不依賴perf_events,隻剖析JVM内一般就足夠;

-d 10 表示剖析持續10秒,可以根據實際情況調整;

最後的3141是Java程序的pid。

RDS MySQL Java 開發實戰 ——義泊

通過生成的火焰圖之後,用浏覽器打開可以看到裡面有6個線程長期霸占CPU。火焰圖從上往下看,下面的方法是處于棧底的,上面的方法是屬于棧頂的,棧頂的方法就是正在執行的方法,而棧頂上面的最長的方法就是占用CPU最多的方法,是以可以看到這裡有6個線程,裡面有6個棧,每個棧上都正在執行方法在消耗CPU。

RDS MySQL Java 開發實戰 ——義泊

結合上圖代碼,通過堆棧來對應到源碼。可以看到,通過JMX可以找到可疑的線程,通過Async-profiler可以生成火焰圖,幫助使用者直接定位到存在性能問題的線程以及它的堆棧,通過堆棧就用源碼找到真正有問題的代碼。

(三)網絡

1.常用指令

l  網絡問題包含以下常用指令:

1)檢視目前主機IP:ip a

2)檢視目前主機名:hostname

3)檢查目标IP是否可達:ping

4)檢查目标端口是否可達:telnet

5)檢視網卡:ifconfig

6)檢視路由表:route –n

7)檢視從目前主機發往目标主機中間會經過哪些路由:traceroute –i

8)檢視目前主機的網卡流量:iptraf-ng

9)檢視以IP為機關的網絡流量排名:iftop –n

10)    檢視目前主機上監聽的端口:netstat –tpnl

11)    檢視目前主機上的TCP連接配接:netstat –tpn

2.TCP狀态機

RDS MySQL Java 開發實戰 ——義泊

TCP狀态機是TCP連接配接的核心部分,隻有深入了解TCP狀态機,才能靈活運用TCP的指令與工具,以及了解輸出的結果與意義。

3.TCP狀态說明:

CLOSED:表示目前連接配接已經關閉。

LISTEN:表示目前正監聽中,随時準備接受連接配接請求。

SYN_SENT:表示已經發送出建立TCP連接配接的資料包,等待對方回應。

SYN_RECVD:表示接受到了建立TCP連接配接的資料包,準備給對方發送SYN + ACK。

ESTABLISHED:表示已經建立TCP連接配接。

FIN WAIT-1:表示主動關閉連接配接的一方已經發出了FIN包。

CLOSE WAIT:表示被動關閉的一方收到了FIN包。

FIN WAIT-2:表示主動關閉的一方收到了FIN的ACK包,等待對方發出的 FIN包。

LAST ACK:表示被動關閉的一方發出了FIN包,開始等待對方發出ACK。

TIMED WAIT:表示主動關閉的一方已經發出了ACK,此時主動關閉的一方要等待2倍Maximum segment lifetime,在此期間,任何因為網絡延遲或者擁堵而未及時到達的包将會被丢棄,以防止下一個連接配接收到了上一個連接配接的包。

2.實戰的場景

a.配置問題引起的應用阻塞

RDS MySQL Java 開發實戰 ——義泊

現象是一段Python(其它語言相同)程式會阻塞,應用僵死。

診斷:

可以看到Python程式在等待主機192.168.1.1的ACK,而這個主機根本就不存在是以無法通路。再結合目标端口是80,定位到是程式HTTP請求的目标主機錯誤。

b.某個資料庫的連接配接數暴漲,想查到連接配接來源

可以看到,來自10.0.3.15的連接配接數達到了999個,結合代碼就可找到問題。

c.線上應用沒有列印SQL到日志中,DB也沒有開審計日志,如何能看到SQL資訊?

RDS MySQL Java 開發實戰 ——義泊

可以在應用伺服器執行tcpdump,-i any表示監聽任意網卡,port 3310表示去dump3310端口,-tttt表示在每個包前面增加一個時間戳,-nn表示不對端口和IP進行反向域名解析,-A表示以應用層的方式輸出日志,-s0表示Buffer的控制。

通過這個指令,從網絡上可以清晰的看到真實的SQL語句,這對于排查問題非常有幫助。

d.傳說中的TCP3次握手和4次揮手,到底是什麼樣

指令:tcpdump -i any port 3310 -tttt -nn -X -s0

RDS MySQL Java 開發實戰 ——義泊

如上圖所示,使用者還可以通過Tcpdump來檢視3次握手和4次揮手的網絡資料。上圖最上方三個包就是我們說的三次握手,它能夠去建立TCP連接配接,建立成後MySQL會向用戶端發送提示輸入密碼的文字,同時用戶端會向MySQL用戶端發送一個響應,表示收到資料包。

如果這個時候關閉連接配接就會産生4次揮手,分别是使用者用戶端向MySQL端發送一個關閉連接配接封包,MySQL會馬上響應一個ACK,同時發出一個關閉連接配接封包,使用者用戶端也向MySQL響應一個ACK,這就是傳說中TCP的3次握手和4次揮手。