作者:王亞普 來源:亞普的技術輪子

系統優化總結

之前組內一位大佬分享了一些關於系統性能優化方面的乾貨,這裏我將它整理成文並且加入自己平時常用的一些工具和技巧。由於關於系統性能優化涉及的內容非常多,我會分幾篇文章來分享。這次分享下定位系統層面問題的常用方法。

系統性能定義

  • Throughout 吞吐量 (系統每秒鐘可以處理的請求數)
  • Latency 延遲 (系統處理一個請求的延遲)
  • Usage 資源利用率

吞吐量和延遲的關係

  • 吞吐量越高,延遲會越大。因爲請求量過大,系統太繁忙,所以響應時間會降低。
  • 延遲越小,能支持的吞吐量會越高。因爲延遲短說明處理速度快,就可以處理更多的請求。
  • 異步化可以提高系統的吞吐量的靈活性,但是不會獲得更快的響應時間。

系統性能壓測的常用工具

tcpdump

1. 常用參數:

-i:指定需要的網
-s:抓取數據包時默認抓取長度爲68字節,加上-s 0後可以抓到完整的數據包
-w:監聽的數據包寫入指定的文件

2. 示例

tcpdump -i eth1 host 10.1.1.1 // 抓取所有經過eth1,目的或源地址是10.1.1.1的網絡數據包 
tcpdump -i eth1 src host 10.1.1.1 // 源地址
tcpdump -i eth1 dst host 10.1.1.1 // 目的地址

如果想使用wireshark分析tcpdump的包,需要加上是 -s 參數:

tcpdump -i eth0 tcp and port 80 -s 0 -w traffic.pcap

tcpcopy——線上引流壓測

tcpcopy是一種請求複製工具,用於實時和離線回放,它可以將線上流量拷貝到測試機器,實時模擬線上的真實環境,達到程序不上線的情況下承擔線上真實流量的測試。實戰演習的必備工具。

a. tcpdump錄製pace文件

tcpdump -i eth0 -w online.pcap tcp and port 80

b. 流量回放

tcpcopy -x 80-10.1.x.x:80 -i traffic.pcap
tcpcopy -x 80-10.1.x.x:80 -a 2 -i traffic.pcap // 離線回放加速2倍

c. 引流模式

tcpcopy -x 80-10.1.x.x:80 -r 20 // 20%引流
tcpcopy -x 80-10.1.x.x:80 -n 3 // 放大三倍引流

wrk & ApacheBench & Jmeter & webbench

個人非常推薦wrk,輕量且壓測結果準確,結合Lua腳本可以支持更復雜的測試場景。

壓測示例:4個線程來模擬1000個併發連接,整個測試持續30秒,連接超時30秒,打印出請求的延遲統計信息。

> wrk -t4 -c1000 -d30s -T30s --latency http://www.baidu.com
Running 30s test @ http://www.baidu.com
4 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.71s 3.19s 26.51s 89.38%
Req/Sec 15.83 10.59 60.00 66.32%
Latency Distribution
50% 434.52ms
75% 1.70s
90% 5.66s
99% 14.38s
1572 requests in 30.09s, 26.36MB read
Requests/sec: 52.24
Transfer/sec: 0.88MB

更多參數幫助信息:

> wrk --help
Usage: wrk
Options:
-c, --connections Connections to keep open
-d, --duration Duration of test
-t, --threads Number of threads to use
-s, --script Load Lua script file
-H, --header Add header to request
--latency Print latency statistics
--timeout Socket/request timeout
-v, --version Print version details
Numeric arguments may include a SI unit (1k, 1M, 1G)
Time arguments may include a time unit (2s, 2m, 2h)

定位性能瓶頸

可以從以下幾個方面衡量系統的性能:

  • 應用層面
  • 系統層面
  • JVM層面
  • Profiler

應用層面

應用層面的性能指標:

  • QPS
  • 響應時間,95、99線等。
  • 成功率

系統層面

系統層面指標有Cpu、內存、磁盤、網路等,推薦用一個犀利的命令查詢系統性能情況:

dstat -lcdngy

系統優化總結—系統層面

dstat非常強大,可以實時的監控cpu、磁盤、網絡、IO、內存等使用情況。

  • 安裝方法

yum install -y dstat

  • 功能說明
-c:顯示CPU系統佔用,用戶佔用,空閒,等待,中斷,軟件中斷等信息。
-C:當有多個CPU時候,此參數可按需分別顯示cpu狀態,例:-C 0,1 是顯示cpu0和cpu1的信息。
-d:顯示磁盤讀寫數據大小。 -D hda,total:include hda and total。
-n:顯示網絡狀態。 -N eth1,total:有多塊網卡時,指定要顯示的網卡。
-l:顯示系統負載情況。
-m:顯示內存使用情況。
-g:顯示頁面使用情況。
-p:顯示進程狀態。
-s:顯示交換分區使用情況。
-S:類似D/N。
-r:I/O請求情況。
-y:系統狀態。
--ipc:顯示ipc消息隊列,信號等信息。
--socket:用來顯示tcp udp端口狀態。
-a:此爲默認選項,等同於-cdngy。
-v:等同於 -pmgdsc -D total。
--output 文件:此選項也比較有用,可以把狀態信息以csv的格式重定向到指定的文件中,以便日後查看。例:dstat --output /root/dstat.csv & 此時讓程序默默的在後臺運行並把結果輸出到/root/dstat.csv文件中。

Cpu

  • 使用率:Cpu是最重要的資源,如果CPU在等待,也會導致Cpu高使用率。
  • CPU利用率 = 1 - 程序佔用cpu時間/程序總的運行時間
  • 用戶時間/內核時間:大致判斷應用是計算密集型還是IO密集型。
  • CPU花在用戶態代碼的時間稱爲用戶時間,而執行內核態代碼的時間稱爲內核時間。內核時間主要包括系統調用,內核線程和中斷的時間。當在整個系統範圍內進行測量時,用戶時間和內核時間之比揭示了運行的負載類型。計算密集型應用會把大量時間花在用戶態代碼上,用戶時間/內核時間之比接近99/1。這樣的例子有圖像處理,數據分析等。I/O密集型應用的系統調用頻率較高,通過執行內核代碼進行I/O操作。一個進行網絡I/O的Web服務器的用戶/內核時間比大約爲70/30。
  • 負載load:在特定時間間隔內運行隊列中的平均進程數。每個CPU都有一個運行隊列,隊列裏存放着已經就緒,等待被CPU執行的線程。理想狀態下,希望負載平均值小於等於Cpu核數。
系統優化總結—系統層面

Cpu使用率和load的區別:

  • 負載均值用來估量CPU利用率的發展趨勢,而不是某一時刻的狀況。
  • 負載均值包括所有CPU的需求,而不僅僅是在測量時活躍的。

磁盤

磁盤空間:沒有空間會導致程序無法啓動或者報錯。

du -sh //查看當前文件夾下所有文件大小
df -hl //以磁盤分區爲單位查看文件系統

有時候linux服務器的系統日誌文件過大導致磁盤使用率過高,推薦兩種清理方式:

sudo /dev/null > /var/log/**.log //刪除指定的較大日誌文件,速度快
sudo find /var/log/ -type f -mtime +30 -exec rm -f {} \ //刪除30天之前的日誌文件

磁盤權限:沒有權限會導致程序無法啓動或者報錯。

ll /yourdir

磁盤性能測試

dd if=/dev/zero of=output.file bs=10M count=1

io吞吐、iowait

這裏重點說下這兩個因素,大量的磁盤讀寫以及過高的iowait往往意味着磁盤可能是瓶頸。實際上iowait並不能反映磁盤成爲性能瓶頸,它實際測量的是cpu的時間:

%iowait = (cpu idle time)/(all cpu time)

所以唯一定位磁盤成爲性能瓶頸的直接方法還是看read/write時間。下面我們着重介紹下如何定位io問題。

a. 宏觀確定是否是io的問題:top命令,可以從Cpu這一行看出浪費在I/O Wait上的CPU百分比;數值越高代表越多的CPU資源在等待I/O權限。

系統優化總結—系統層面

b. 確定具體磁盤問題:iostat

系統優化總結—系統層面

%util直觀地反應可哪一塊磁盤正在被寫入,反應了設備的繁忙程度。每毫秒讀寫請求(rrqm/s wrqm/s)以及每秒讀寫(r/s w/s)對排查問題也提供了很多有用的信息。

c. 確定具體進程:簡單粗暴的iotop直觀地反映了哪些進程是導致io問題的罪魁禍首。

系統優化總結—系統層面

d. ps判斷進程是否等待IO一樣強大

衆所周知,ps命令爲我們提供了內存、cpu以及進程狀態等信息,根據進程狀態可以很容易查到正在等待IO的進程信息。

這裏簡單說下linux進程的幾種狀態:

  • R (TASK_RUNNING),可執行狀態。
  • S (TASK_INTERRUPTIBLE),可中斷的睡眠狀態。
  • D (TASK_UNINTERRUPTIBLE),不可中斷的睡眠狀態。
  • T (TASK_STOPPED or TASK_TRACED),暫停狀態或跟蹤狀態。
  • Z (TASK_DEAD – EXIT_ZOMBIE),退出狀態,進程成爲殭屍進程。
  • X (TASK_DEAD – EXIT_DEAD),退出狀態,進程即將被銷燬。

其中等待I/O的進程狀態一般是"uninterruptible sleep"即D狀態,D狀態以及R狀態進程算爲運行隊列之中,所以D狀態進程過多也會導致系統load偏高,有興趣可以看下linux load的計算原理。

查看D狀態進程:

> for x in `seq 1 1 10`; do ps -eo state,pid,cmd | grep "^D"; echo "--------"; sleep 5; done
D 13389 /usr/libexec/gcc/x86_64-redhat-linux/4.4.7/cc1 -quiet -I../../include/cat -I../ -I. -dD message_sender.c -quiet -dumpbase message_sender.c -mtune=generic -auxbase message_sender -ggdb3 -O2 -O0 -o /tmp/ccivsNPE.s

根據proc僞文件系統獲取io相關信息:

> cat /proc/pid/io
rchar: 548875497
wchar: 270446556
syscr: 452342
syscw: 143986
read_bytes: 253100032
write_bytes: 24645632
cancelled_write_bytes: 3801088

e. 確定哪個文件頻繁讀寫:lsof -p pid

系統優化總結—系統層面

網絡

1. nestat

netstat -nt 查看tcp相關連接狀態、連接數以及發送隊列和接收隊列

系統優化總結—系統層面

關於tcp的狀態需要大家熟悉三次握手和四次揮手的過程,這裏先列出tcp的全部狀態。

客戶端:SYN_SENT、FIN_WAIT1、FIN_WAIT2、CLOSING、TIME_WAIT
服務端:LISTEN、SYN_RCVD、CLOSE_WAIT、LAST_ACK
Common:ESTABLISHED、CLOSED

Tcp狀態變化圖(摘自網絡):

系統優化總結—系統層面

關於tcp狀態的幾點說明:

  • 正常的連接應該是ESTABLISHED狀態,如果存在大量的SYN_SENT的連接,則需要看下防火牆規則。
  • 如果Recv-Q或者Send-Q持續有大量包存在,意味着連接存在瓶頸或者程序存在bug。

2. 一些其他常用技巧

關於netstat還有很多有用的技巧,這裏列出平時比較常用的:

netstat -nap | grep port 顯示使用該端口的所有進程id
netstat -nat |awk '{print $6}'|sort|uniq -c|sort -rn 查詢全部狀態並排序
awk '{print $1}' access.log |sort|uniq -c|sort -nr|head -10 分析access.log獲取訪問做多的top n的ip地址
netstat -nat | grep "10.1.1.1:8080" |awk '{print $5}'|awk -F: '{print $1}'|sort|uniq -c|sort -nr|head -20 連接某服務器最多的top n的ip地址
netstat -s 如果重傳的包持續增加,那麼很大可能網卡存在問題

JVM

定位問題的殺手鐗——線程堆棧

1. 獲取線程堆棧的步驟:

ps -ef | grep java
sudo -u nobody jstack > /tmp/jstack.

小技巧:jstack信息是某個時刻的堆棧信息,有時間僅僅一個jstack並不能分析出問題所在,可以適當多幾次jstack,然後進行對比分析。

2. 如何從線程堆棧中找到本地線程對應的id

系統優化總結—系統層面

nid=native thread id,特殊的是nid使用十六進制標識,本地線程id是十進制標識,所以通過進制換算就可以講兩者對應起來。

16進制和10進制的互換:

printf %d 0x1b40
printf "0x%x" 6976

3. Cpu消耗高的分析方法

a. 找出對應的java進程pid:

ps -ef | grep java

b. 找出java進程中最消耗cpu的線程:

top -H -p 
  • 將找出的線程id轉換爲16進制
  • jstack獲取java的線程堆棧
  • 根據16進制的id從線程堆棧中找到相關的堆棧信息

說明:線程堆棧中可以看出對應線程執行的是Java代碼還是Native method

找不到對應的線程堆棧?

  • 執行的Native method是重新創建的線程。
  • 代碼bug,堆內存耗完,jvm不斷執行full gc。
  • jvm自身bug。

垃圾收集的統計信息——查看Gc原因

jstat -gccause用於查看垃圾收集的統計信息,若有發生垃圾回收,還會顯示最後一次以及當前發生垃圾回收的原因,它比-gcutil會多出最後一次垃圾回收的原因以及當前正在發生的垃圾回收的原因。

jstat -gccause pid 1234


-END-

相關文章