歡迎光臨
每天分享高質量文章

一篇超實用的服務異常處理指南

1. 服務異常的處理流程

2. 負載

2.1 檢視機器 cpu 的負載

top -b -n 1 |grep java|awk '{print "VIRT:"$5,"RES:"$6,"cpu:"$9"%","mem:"$10"%"}'

 

2.2 查詢 cpu 佔用率高的執行緒

top -p 25603 -H
printf 0x%x 25842
jstack 25603 | grep 0x64f2

cat /proc/interrupts

(1)CPU
(2)Memory
(3)IO
(4)Network

可以從以下幾個方面監控CPU的資訊:
(1)中斷;
(2)背景關係切換;
(3)可執行佇列;
(4)CPU 利用率。

3. 記憶體

3.1 系統記憶體

free 命令

[root@server ~]# free
 total used free shared buffers cached
Mem: 3266180 3250000 10000 0 201000 3002000
-/+ buffers/cache: 47000 3213000
Swap: 2048276 80160 1968116

這裡的預設顯示單位是 kb。

各項指標解釋

  • total:總計物理記憶體的大小。

  • used:已使用多大。

  • free:可用有多少。

  • Shared:多個行程共享的記憶體總額。

  • buffers: 磁碟快取的大小。

  • cache:磁碟快取的大小。

  • -/+ buffers/cached): used:已使用多大,free:可用有多少。

  • 已用記憶體 = 系統used memory – buffers – cached
    (47000 = 3250000-201000-3002000)

  • 可用記憶體 = 系統free memory + buffers + cached
    (3213000 = 10000+201000+3002000)

什麼是buffer/cache?

  • buffer 指 Linux 記憶體的:Buffer cache,緩衝區緩

  • cache 指 Linux記憶體中的:Page cache,頁面快取

     

page cache

page cache 主要用來作為檔案系統上的檔案資料的快取來用,尤其是針對當行程對檔案有 read/write 操作的時候。

如果你仔細想想的話,作為可以對映檔案到記憶體的系統呼叫:mmap是不是很自然的也應該用到 page cache?在當前的系統實現裡,page cache 也被作為其它檔案型別的快取裝置來用,所以事實上 page cache 也負責了大部分的塊裝置檔案的快取工作。

buffer cache

buffer cache 主要用來在系統對塊裝置進行讀寫的時候,對塊進行資料快取的系統來使用。這意味著某些對塊的操作會使用 buffer cache 進行快取,比如我們在格式化檔案系統的時候。

一般情況下兩個快取系統是一起配合使用的,比如當我們對一個檔案進行寫操作的時候,page cache 的內容會被改變,而 buffer cache 則可以用來將 page 標記為不同的緩衝區,並記錄是哪一個緩衝區被修改了。這樣,核心在後續執行臟資料的回寫(writeback)時,就不用將整個 page 寫回,而只需要寫回修改的部分即可。

在當前的核心中,page cache 是針對記憶體頁的快取,說白了就是,如果有記憶體是以page進行分配管理的,都可以使用page cache作為其快取來管理使用。

當然,不是所有的記憶體都是以頁(page)進行管理的,也有很多是針對塊(block)進行管理的,這部分記憶體使用如果要用到 cache 功能,則都集中到 buffer cache中來使用。(從這個角度出發,是不是buffer cache改名叫做block cache更好?)然而,也不是所有塊(block)都有固定長度,系統上塊的長度主要是根據所使用的塊裝置決定的,而頁長度在X86 上無論是 32位還是 64位都是 4k。

3.2 行程記憶體

3.2.1 行程記憶體統計

/proc/[pid]/status
透過/proc//status可以檢視行程的記憶體使用情況,包括虛擬記憶體大小(VmSize),物理記憶體大小(VmRSS),資料段大小(VmData),棧的大小(VmStk),程式碼段的大小(VmExe),共享庫的程式碼段大小(VmLib)等等。

Name: gedit /*行程的程式名*/
State: S (sleeping) /*行程的狀態資訊,具體參見http://blog.chinaunix.net/u2/73528/showart_1106510.html*/
Tgid: 9744 /*執行緒組號*/
Pid: 9744 /*行程pid*/
PPid: 7672 /*父行程的pid*/
TracerPid: 0 /*跟蹤行程的pid*/
VmPeak: 60184 kB /*行程地址空間的大小*/
VmSize: 60180 kB /*行程虛擬地址空間的大小reserved_vm:行程在預留或特殊的記憶體間的物理頁*/
VmLck: 0 kB /*行程已經鎖住的物理記憶體的大小.鎖住的物理記憶體不能交換到硬碟*/
VmHWM: 18020 kB /*檔案記憶體對映和匿名記憶體對映的大小*/
VmRSS: 18020 kB /*應用程式正在使用的物理記憶體的大小,就是用ps命令的引數rss的值 (rss)*/
VmData: 12240 kB /*程式資料段的大小(所佔虛擬記憶體的大小),存放初始化了的資料*/
VmStk: 84 kB /*行程在使用者態的棧的大小*/
VmExe: 576 kB /*程式所擁有的可執行虛擬記憶體的大小,程式碼段,不包括任務使用的庫 */
VmLib: 21072 kB /*被映像到任務的虛擬記憶體空間的庫的大小*/
VmPTE: 56 kB /*該行程的所有頁表的大小*/
Threads: 1 /*共享使用該訊號描述符的任務的個數*/

3.2.2 JVM 記憶體分配

java記憶體組成介紹:堆(Heap)和非堆(Non-heap)記憶體

按照官方的說法:“Java 虛擬機器具有一個堆,堆是執行時資料區域,所有類實體和陣列的記憶體均從此處分配。堆是在 Java 虛擬機器啟動時建立的。” “在JVM中堆之外的記憶體稱為非堆記憶體(Non-heap memory)”。

可以看出JVM主要管理兩種型別的記憶體:堆和非堆。

簡單來說堆就是Java程式碼可及的記憶體,是留給開發人員使用的;非堆就是JVM留給自己用的。

所以方法區、JVM內部處理或最佳化所需的記憶體(如JIT編譯後的程式碼快取)、每個類結構(如執行時常數池、欄位和方法資料)以及方法和構造方法 的程式碼都在非堆記憶體中。

  1. JVM 本身需要的記憶體,包括其載入的第三方庫以及這些庫分配的記憶體

  2. NIO 的 DirectBuffer 是分配的 native memory

  3. 記憶體對映檔案,包括 JVM 載入的一些 JAR 和第三方庫,以及程式內部用到的。上面 pmap 輸出的內容裡,有一些靜態檔案所佔用的大小不在 Java 的 heap 裡,因此作為一個Web伺服器,趕緊把靜態檔案從這個Web伺服器中人移開吧,放到nginx或者CDN裡去吧。

  4. JIT, JVM會將Class編譯成native程式碼,這些記憶體也不會少,如果使用了Spring的AOP,CGLIB會生成更多的類,JIT的記憶體開銷也會隨之變大,而且Class本身JVM的GC會將其放到Perm Generation裡去,很難被回收掉,面對這種情況,應該讓JVM使用ConcurrentMarkSweep GC,並啟用這個GC的相關引數允許將不使用的class從Perm  Generation中移除, 引數配置:

    -XX:+UseConcMarkSweepGC -X:+CMSPermGenSweepingEnabled -X:+CMSClassUnloadingEnabled,如果不需要移除而Perm Generation空間不夠,可以加大一點:-X:PermSize=256M -X:MaxPermSize=512M

  5. JNI,一些JNI介面呼叫的native庫也會分配一些記憶體,如果遇到JNI庫的記憶體洩露,可以使用valgrind等記憶體洩露工具來檢測

  6. 執行緒棧,每個執行緒都會有自己的棧空間,如果執行緒一多,這個的開銷就很明顯了

  7. jmap/jstack 取樣,頻繁的取樣也會增加記憶體佔用,如果你有伺服器健康監控,記得這個頻率別太高,否則健康監控變成致病監控了。

1.方法區

也稱”永久代” 、“非堆”,它用於儲存虛擬機器載入的類資訊、常量、靜態變數、是各個執行緒共享的記憶體區域。預設最小值為 16 MB,最大值為 64 MB,可以透過-XX: PermSize 和 -XX: MaxPermSize 引數限制方法區的大小。

執行時常量池:是方法區的一部分,Class檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量池,用於存放編譯器生成的各種符號取用,這部分內容將在類載入後放到方法區的執行時常量池中。

2.虛擬機器棧
描述的是java 方法執行的記憶體模型:每個方法被執行的時候 都會建立一個“棧幀”用於儲存區域性變數表(包括引數)、操作棧、方法出口等資訊。

每個方法被呼叫到執行完的過程,就對應著一個棧幀在虛擬機器棧中從入棧到出棧的過程。宣告週期與執行緒相同,是執行緒私有的。

區域性變數表存放了編譯器可知的各種基本資料型別(boolean、byte、char、short、int、float、long、double)、物件取用(取用指標,並非物件本身),其中64位長度的long和double型別的資料會佔用2個區域性變數的空間,其餘資料型別只佔1個。

區域性變數表所需的記憶體空間在編譯期間完成分配,當進入一個方法時,這個方法需要在棧幀中分配多大的區域性變數是完全確定的,在執行期間棧幀不會改變區域性變數表的大小空間。

3.本地方法棧
與虛擬機器棧基本類似,區別在於虛擬機器棧為虛擬機器執行的java方法服務,而本地方法棧則是為Native方法服務。

4.堆
也叫做java 堆、GC堆是java虛擬機器所管理的記憶體中最大的一塊記憶體區域,也是被各個執行緒共享的記憶體區域,在JVM啟動時建立。

該記憶體區域存放了物件實體及陣列(所有 new 的物件)。其大小透過 -Xms (最小值) 和 -Xmx (最大值) 引數設定,-Xms為 JVM 啟動時申請的最小記憶體,預設為作業系統物理記憶體的 1/64 但小於 1G;

-Xmx 為 JVM 可申請的最大記憶體,預設為物理記憶體的1/4但小於 1G,預設當空餘堆記憶體小於 40% 時,JVM 會增大 Heap 到 -Xmx 指定的大小,可透過 -XX:MinHeapFreeRation= 來指定這個比列;

當空餘堆記憶體大於70%時,JVM 會減小 heap 的大小到 -Xms 指定的大小,可透過XX:MaxHeapFreeRation= 來指定這個比列,對於執行系統,為避免在執行時頻繁調整 Heap 的大小,通常 -Xms 與 -Xmx 的值設成一樣。

由於現在收集器都是採用分代收集演演算法,堆被劃分為新生代和老年代。新生代主要儲存新建立的物件和尚未進入老年代的物件。老年代儲存經過多次新生代GC(Minor GC)任然存活的物件。

5.程式計數器
是最小的一塊記憶體區域,它的作用是當前執行緒所執行的位元組碼的行號指示器,在虛擬機器的模型裡,位元組碼直譯器工作時就是透過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、迴圈、異常處理、執行緒恢復等基礎功能都需要依賴計數器完成。

3.2.3 直接記憶體

直接記憶體並不是虛擬機器記憶體的一部分,也不是Java虛擬機器規範中定義的記憶體區域。jdk1.4中新加入的NIO,引入了通道與緩衝區的IO方式,它可以呼叫Native方法直接分配堆外記憶體,這個堆外記憶體就是本機記憶體,不會影響到堆記憶體的大小。

3.2.4 JVM 記憶體分析

檢視 JVM 堆記憶體情況
jmap -heap [pid]

[root@server ~]$ jmap -heap 837
Attaching to process ID 837, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 24.71-b01
using thread-local object allocation.
Parallel GC with 4 thread(s)//GC 方式
Heap Configuration: //堆記憶體初始化配置
 MinHeapFreeRatio = 0 //對應jvm啟動引數-XX:MinHeapFreeRatio設定JVM堆最小空閑比率(default 40)
 MaxHeapFreeRatio = 100 //對應jvm啟動引數 -XX:MaxHeapFreeRatio設定JVM堆最大空閑比率(default 70)
 MaxHeapSize = 2082471936 (1986.0MB) //對應jvm啟動引數-XX:MaxHeapSize=設定JVM堆的最大大小
 NewSize = 1310720 (1.25MB)//對應jvm啟動引數-XX:NewSize=設定JVM堆的‘新生代’的預設大小
 MaxNewSize = 17592186044415 MB//對應jvm啟動引數-XX:MaxNewSize=設定JVM堆的‘新生代’的最大大小
 OldSize = 5439488 (5.1875MB)//對應jvm啟動引數-XX:OldSize=:設定JVM堆的‘老生代’的大小
 NewRatio = 2 //對應jvm啟動引數-XX:NewRatio=:‘新生代’和‘老生代’的大小比率
 SurvivorRatio = 8 //對應jvm啟動引數-XX:SurvivorRatio=設定年輕代中Eden區與Survivor區的大小比值
 PermSize = 21757952 (20.75MB) //對應jvm啟動引數-XX:PermSize=:設定JVM堆的‘永生代’的初始大小
 MaxPermSize = 85983232 (82.0MB)//對應jvm啟動引數-XX:MaxPermSize=:設定JVM堆的‘永生代’的最大大小
 G1HeapRegionSize = 0 (0.0MB)
Heap Usage://堆記憶體使用情況
PS Young Generation
Eden Space://Eden區記憶體分佈
 capacity = 33030144 (31.5MB)//Eden區總容量
 used = 1524040 (1.4534378051757812MB) //Eden區已使用
 free = 31506104 (30.04656219482422MB) //Eden區剩餘容量
 4.614088270399305% used //Eden區使用比率
From Space: //其中一個Survivor區的記憶體分佈
 capacity = 5242880 (5.0MB)
 used = 0 (0.0MB)
 free = 5242880 (5.0MB)
 0.0% used
To Space: //另一個Survivor區的記憶體分佈
 capacity = 5242880 (5.0MB)
 used = 0 (0.0MB)
 free = 5242880 (5.0MB)
 0.0% used
PS Old Generation //當前的Old區記憶體分佈
 capacity = 86507520 (82.5MB)
 used = 0 (0.0MB)
 free = 86507520 (82.5MB)
 0.0% used
PS Perm Generation//當前的 “永生代” 記憶體分佈
 capacity = 22020096 (21.0MB)
 used = 2496528 (2.3808746337890625MB)
 free = 19523568 (18.619125366210938MB)
 11.337498256138392% used
670 interned Strings occupying 43720 bytes.

關於這裡的幾個generation網上資料一大把就不細說了,這裡算一下求和可以得知前者總共給Java環境分配了644M的記憶體,而ps輸出的VSZ和RSS分別是7.4G和2.9G,這到底是怎麼回事呢?

前面jmap輸出的內容裡,MaxHeapSize 是在命令列上配的,-Xmx4096m,這個java程式可以用到的最大堆記憶體。

VSZ是指已分配的線性空間大小,這個大小通常並不等於程式實際用到的記憶體大小,產生這個的可能性很多,比如記憶體對映,共享的動態庫,或者向系統申請了更多的堆,都會擴充套件線性空間大小,要檢視一個行程有哪些記憶體對映,可以使用 pmap 命令來檢視:

pmap -x [pid]

[root@server ~]$ pmap -x 837
837: java
Address Kbytes RSS Dirty Mode Mapping
0000000040000000 36 4 0 r-x-- java
0000000040108000 8 8 8 rwx-- java
00000000418c9000 13676 13676 13676 rwx-- [ anon ]
00000006fae00000 83968 83968 83968 rwx-- [ anon ]
0000000700000000 527168 451636 451636 rwx-- [ anon ]
00000007202d0000 127040 0 0 ----- [ anon ]
...
...
00007f55ee124000 4 4 0 r-xs- az.png
00007fff017ff000 4 4 0 r-x-- [ anon ]
ffffffffff600000 4 0 0 r-x-- [ anon ]
---------------- ------ ------ ------
total kB 7796020 3037264 3023928

這裡可以看到很多anon,這些表示這塊記憶體是由mmap分配的。

RSZ是Resident Set Size,常駐記憶體大小,即行程實際佔用的物理記憶體大小, 在現在這個例子當中,RSZ和實際堆記憶體佔用差了2.3G,這2.3G的記憶體組成分別為:

檢視 JVM 堆各個分割槽的記憶體情況

jstat -gcutil [pid]

[root@server ~]$ jstat -gcutil 837 1000 20
 S0 S1 E O P YGC YGCT FGC FGCT GCT
 0.00 80.43 24.62 87.44 98.29 7101 119.652 40 19.719 139.371
 0.00 80.43 33.14 87.44 98.29 7101 119.652 40 19.719 139.371

分析 JVM 堆記憶體中的物件
檢視存活的物件統計
jmap -histo:live [pid]

dump 記憶體
jmap -dump:format=b,file=heapDump [pid]

然後用jhat命令可以參看
jhat -port 5000 heapDump
在瀏覽器中訪問:http://localhost:5000/ 檢視詳細資訊

4. 服務指標

4.1 響應時間(RT)

響應時間是指系統對請求作出響應的時間。直觀上看,這個指標與人對軟體效能的主觀感受是非常一致的,因為它完整地記錄了整個計算機系統處理請求的時間。

由於一個系統通常會提供許多功能,而不同功能的處理邏輯也千差萬別,因而不同功能的響應時間也不盡相同,甚至同一功能在不同輸入資料的情況下響應時間也不相同。

所以,在討論一個系統的響應時間時,人們通常是指該系統所有功能的平均時間或者所有功能的最大響應時間。

當然,往往也需要對每個或每組功能討論其平均響應時間和最大響應時間。

對於單機的沒有併發操作的應用系統而言,人們普遍認為響應時間是一個合理且準確的效能指標。需要指出的是,響應時間的絕對值並不能直接反映軟體的效能的高低,軟體效能的高低實際上取決於使用者對該響應時間的接受程度。

對於一個遊戲軟體來說,響應時間小於100毫秒應該是不錯的,響應時間在1秒左右可能屬於勉強可以接受,如果響應時間達到3秒就完全難以接受了。

而對於編譯系統來說,完整編譯一個較大規模軟體的原始碼可能需要幾十分鐘甚至更長時間,但這些響應時間對於使用者來說都是可以接受的。

4.2 吞吐量(Throughput)

吞吐量是指系統在單位時間內處理請求的數量。對於無併發的應用系統而言,吞吐量與響應時間成嚴格的反比關係,實際上此時吞吐量就是響應時間的倒數。

前面已經說過,對於單使用者的系統,響應時間(或者系統響應時間和應用延遲時間)可以很好地度量系統的效能,但對於併發系統,通常需要用吞吐量作為效能指標。

對於一個多使用者的系統,如果只有一個使用者使用時系統的平均響應時間是t,當有你n個使用者使用時,每個使用者看到的響應時間通常並不是n×t,而往往比n×t小很多(當然,在某些特殊情況下也可能比n×t大,甚至大很多)。

這是因為處理每個請求需要用到很多資源,由於每個請求的處理過程中有許多不走難以併發執行,這導致在具體的一個時間點,所佔資源往往並不多。也就是說在處理單個請求時,在每個時間點都可能有許多資源被閑置,當處理多個請求時,如果資源配置合理,每個使用者看到的平均響應時間並不隨使用者數的增加而線性增加。

實際上,不同系統的平均響應時間隨使用者數增加而增長的速度也不大相同,這也是採用吞吐量來度量併發系統的效能的主要原因。

一般而言,吞吐量是一個比較通用的指標,兩個具有不同使用者數和使用者使用樣式的系統,如果其最大吞吐量基本一致,則可以判斷兩個系統的處理能力基本一致。

4.3 併發使用者數

併發使用者數是指系統可以同時承載的正常使用系統功能的使用者的數量。與吞吐量相比,併發使用者數是一個更直觀但也更籠統的效能指標。

實際上,併發使用者數是一個非常不準確的指標,因為使用者不同的使用樣式會導致不同使用者在單位時間發出不同數量的請求。

一網站系統為例,假設使用者只有註冊後才能使用,但註冊使用者並不是每時每刻都在使用該網站,因此具體一個時刻只有部分註冊使用者同時線上,線上使用者就在瀏覽網站時會花很多時間閱讀網站上的資訊,因而具體一個時刻只有部分線上使用者同時向系統發出請求。

這樣,對於網站系統我們會有三個關於使用者數的統計數字:註冊使用者數、線上使用者數和同時發請求使用者數。由於註冊使用者可能長時間不登陸網站,使用註冊使用者數作為效能指標會造成很大的誤差。而線上使用者數和同事發請求使用者數都可以作為效能指標。

相比而言,以線上使用者作為效能指標更直觀些,而以同時發請求使用者數作為效能指標更準確些。

4.4 QPS每秒查詢率(Query Per Second)

每秒查詢率QPS是對一個特定的查詢伺服器在規定時間內所處理流量多少的衡量標準,在因特網上,作為域名系統伺服器的機器的效能經常用每秒查詢率來衡量。對應fetches/sec,即每秒的響應請求數,也即是最大吞吐能力。

從以上概念來看吞吐量和響應時間是衡量系統效能的重要指標,QPS雖然和吞吐量的計量單位不同,但應該是成正比的,任何一個指標都可以含量伺服器的並行處理能力。當然Throughput更關心資料量,QPS更關心處理筆數。

4.5 CPU利用率

CPU Load Average < CPU個數 核數 0.7

Context Switch Rate
就是Process(Thread)的切換,如果切換過多,會讓CPU忙於切換,也會導致影響吞吐量。

《高效能伺服器架構 》這篇文章的第2節就是說的是這個問題的。

究竟多少算合適?google 了一大圈,沒有一個確切的解釋。

Context Switch大體上由兩個部分組成:中斷和行程(包括執行緒)切換,一次中斷(Interrupt)會引起一次切換,行程(執行緒)的建立、啟用之類的也會引起一次切換。CS的值也和TPS(Transaction Per Second)相關的,假設每次呼叫會引起N次CS,那麼就可以得出

Context Switch Rate = Interrupt Rate + TPS* N

CSR減掉IR,就是行程/執行緒的切換,假如主行程收到請求交給執行緒處理,執行緒處理完畢歸還給主行程,這裡就是2次切換。

也可以用CSR、IR、TPS的值代入公式中,得出每次事物導致的切換數。因此,要降低CSR,就必須在每個TPS引起的切換上下功夫,只有N這個值降下去,CSR就能降低,理想情況下N=0,但是無論如何如果N >= 4,則要好好檢查檢查。另外網上說的CSR<5000,我認為標準不該如此單一。

這三個指標在 LoadRunner 中可以監控到;另外,在 linux 中,也可以用 vmstat 檢視r(Load Arerage),in(Interrupt)和cs(Context Switch)

5. 工具

uptime

dmesg

top
檢視行程活動狀態以及一些系統狀況

vmstat
檢視系統狀態、硬體和系統資訊等

iostat
檢視CPU 負載,硬碟狀況

sar
綜合工具,檢視系統狀況

mpstat
檢視多處理器狀況

netstat
檢視網路狀況

iptraf
實時網路狀況監測

tcpdump
抓取網路資料包,詳細分析

mpstat
檢視多處理器狀況

tcptrace
資料包分析工具

netperf
網路頻寬工具

dstat
綜合工具,綜合了 vmstat, iostat, ifstat, netstat 等多個資訊

Reference

  • http://tmq.qq.com/2016/07/it-is-necessary-to-know-the-background-performance-test/
    https://www.ibm.com/developerworks/java/library/j-nativememory-linux/
    http://www.oracle.com/technetwork/java/javase/index-137495.html
    http://www.hollischuang.com/archives/303

轉載自 Zane Blog,原文為《服務調優》

已同步到看一看
贊(0)

分享創造快樂