系統的效能瓶頸,排查該從哪些方面入手,如何定位?
如何排查系統的效能瓶頸點?
梳理系統的效能瓶頸點這件事應該不是一件簡單的事情,需要針對不同設計的系統來進行單獨分析。
首先一套完整可用的系統應該是有ui介面的(這裡強調的是一套完整的,可用的系統,而並不是指單獨的一箇中臺系統),系統分為了前端模組和後端模組。
這裡由於我個人的擅長領域更多是處於後端模組,所以對於系統的瓶頸點梳理我會從後端進行分析。
這裡我結合常用的nginx+tomcat+redis+mysql這類常見架構進行分析:
請求入口 所有的請求打入到後臺的服務當中,首先需要考慮的一個點就是:
頻寬因素:
假設有200m的流量同時請求進入伺服器,但是頻寬只有1m,這麼來算光是接收這批資料量資訊也要消耗大約200s的時間。頻寬可以理解為在指定時間內從一端請求到另一端的流量總量。而且區域網和廣域網的頻寬計算其實也是不一樣的,
伺服器的ulimit
通常我們使用的線上伺服器都是centos系列,這裡我列舉centos7相關的系統配置:ulimit配置 檢視伺服器允許的最大開啟檔案數目(linux系統中設計概念為一切皆檔案) 通常如果我們的java程式需要增大一些socket的連結數目,可以通過調整ulimit 裡面的open引數進行配置。
[root@izwz9ic9ggky8kub9x1ptuz ~]# ulimit -a | grep open open files (-n) 1000
檢視使用者的最大程序數目
[root@izwz9ic9ggky8kub9x1ptuz ~]# ulimit -a | grep user max user processes (-u) 7284
相關的配置存放在了/etc/security/limits.conf檔案中。
系統的一些核心引數配置
如果是在一些壓力測試場景中,我們通常會預見到這種報錯:
apr_socket_recv: Connection reset by peer (54)
通常這種情況是因為系統內部的一些防範引數設定導致的,需要調整/etc/sysctl.conf 檔案中的相關引數:
net.ipv4.tcp_syncookies = 0 #當併發請求數目超過了1000之後,伺服器自身可能會認為是收到了syn泛洪攻擊,但對於高併發系統,要禁用此設定 net.ipv4.tcp_max_syn_backlog #引數決定了SYN_RECV狀態佇列的數量,一般預設值為512或者1024,即超過這個數量,系統將不再接受新的TCP連線請求,一定程度上可以防止系統資源耗盡。可根據情況增加該值以接受更多的連線請求。 net.ipv4.tcp_tw_recycle #引數決定是否加速TIME_WAIT的sockets的回收,預設為0。 net.ipv4.tcp_tw_reuse #引數決定是否可將TIME_WAIT狀態的sockets用於新的TCP連線,預設為0。 net.ipv4.tcp_max_tw_buckets #引數決定TIME_WAIT狀態的sockets總數量,可根據連線數和系統資源需要進行設定。
對於防範引數還可以如下修改檢視:
cd /proc/sys/net/ipv4 echo "0" > tcp_syncookies
通常企業中使用的都是nginx進行接收請求,然後進行負載均衡轉發。在nginx層裡面會有幾個核心引數配置:最大連線數,最大併發訪問數
#指定同一個ip的每次請求數量都限制為10次 limit_conn_zone $binary_remote_addr zone=perip:10m; limit_conn perip 10;
Tomcat部分分析
Tomcat支援三種接收請求的處理方式:BIO、NIO、APR 。
1、Bio方式,阻塞式I/O操作即使用的是傳統Java I/O操作,Tomcat7以下版本預設情況下是以bio模式執行的,由於每個請求都要建立一個執行緒來處理,執行緒開銷較大,不能處理高併發的場景,在三種模式中效能也最低
2、Nio方式,是Java SE 1.4及後續版本提供的一種新的I/O操作方式(即java.nio包及其子包),是一個基於緩衝區、並能提供非阻塞I/O操作的Java API,它擁有比傳統I/O操作(bio)更好的併發執行效能。tomcat 8版本及以上預設nio模式
3、apr模式,簡單理解,就是從作業系統級別解決非同步IO問題,大幅度的提高伺服器的處理和響應效能, 也是Tomcat執行高併發應用的首選模式。啟用這種模式稍微麻煩一些,需要安裝一些依賴庫, 而apr的本質就是使用jni技術呼叫作業系統底層的IO介面,所以需要提前安裝所需要的依賴,首先是需要安裝openssl和apr
tomcat連線引數調整
在tomcat中有這麼一段經典的配置引數:
<Connector port="80" maxHttpHeaderSize="8192" maxThreads="4000" minSpareThreads="1000" maxSpareThreads="2000" enableLookups="false" redirectPort="8443" acceptCount="2000" connectionTimeout="20000" disableUploadTimeout="true" />
maxThreads表示tomcat最多可以建立多少個執行緒來處理請求。
minSpareThread表示tomcat一開始啟動的時候會建立多少個執行緒,即使是閒著也會建立。
maxSpareThread表示tomcat建立的最大閒置執行緒數目。一旦tomcat建立的執行緒數目達到這個瓶頸,那麼就需要進行執行緒的回收了。
connectionTimeout表示連線的超時時長。
假設我們同時有1000個請求併發訪問,但是一臺tomcat的maxThreads只設置為了500,那麼此時就會出現請求擁塞的情況,也就是瓶頸點之一。
Redis部分效能瓶頸分析
一些大key的查詢,導致網路出現擁塞情況
例如說往一個list集合中儲存了50m的資料,一旦發生list全量查詢,同時又有其他指令在進行訪問的時候,就容易會導致網路堵塞。因為redis的設計為單執行緒處理請求,所以其他指令傳送到redis服務端的時候,都需要等待redis將之前的任務處理完畢之後才能繼續執行。
線上環境出現了一些”違規操作“
比較常見的違規操作:批量執行keys指令
在redis處於高qps的狀態下,隨意一個keys指令都可能是致命的。keys指令的時間複雜度是O(n)級別,容易導致一時間系統的卡頓。
記憶體空間不足
當redis處於記憶體空間不足的時候,基本就是整個系統處於癱瘓作用。因此我們在對每個儲存在redis中的數值都需要設定一個合理的過期時間,以及需要思考儲存資料的體積大小。
MySQL部分效能瓶頸分析
通常我們在分析sql查詢方面都容易出現一個誤區,就是上來直接進行explian分析,但是卻忽略了系統的運作上下文環境。
假設有一張t_user表,已經儲存了幾千萬的資料,並且也對使用者的id進行了索引建立,但是sql執行速度依舊是超過1s時長,這個時候就需要換一種思路進行分析了。
例如從表的拆分方面進行思考,是否該對錶進行橫向拆分,拆解為t_user_01,t_user_02......
以下是我總結的一些對於資料庫層面可能出現效能瓶頸的幾點總結:
1.鎖
排查是否會存在鎖表的情況導致資料庫響應緩慢。
2.sql查詢還有優化空間,有待完善
通常我們對於sql的執行分析都會使用explain命令進行檢視:
這裡我貼出了一張關於explain的常用引數含義表供大家參考:
id | SELECT識別符。這是SELECT的查詢序列號 |
---|---|
select_type | SIMPLE:簡單SELECT(不使用UNION或子查詢) PRIMARY:最外面的SELECT UNION:UNION中的第二個或後面的SELECT語句 DEPENDENT UNION:UNION中的第二個或後面的SELECT語句,取決於外面的查詢 UNION RESULT:UNION 的結果 SUBQUERY:子查詢中的第一個SELECT DEPENDENT SUBQUERY:子查詢中的第一個SELECT,取決於外面的查詢 DERIVED:匯出表的SELECT(FROM子句的子查詢) |
table | 查詢sql過程中關聯的表名稱 |
type | 連線型別。下面給出各種聯接型別,按照從最佳型別到最壞型別進行排序: system:表僅有一行(=系統表)。這是const聯接型別的一個特例。 const:表最多有一個匹配行,它將在查詢開始時被讀取。因為僅有一行,在這行的列值可被優化器剩餘部分認為是常數。const表很快,因為它們只讀取一次! eq_ref:對於每個來自於前面的表的行組合,從該表中讀取一行,效能僅次於const。 ref:對於每個來自於前面的表的行組合,所有有匹配索引值的行將從這張表中讀取。 ref_or_null:該聯接型別如同ref,但是添加了MySQL可以專門搜尋包含NULL值的行。 index_merge:該聯接型別表示使用了索引合併優化方法。 unique_subquery:該型別替換了下面形式的IN子查詢的ref: value IN (SELECT primary_key FROM single_table WHERE some_expr) unique_subquery是一個索引查詢函式,可以完全替換子查詢,效率更高。 index_subquery:該聯接型別類似於unique_subquery。可以替換IN子查詢,但只適合下列形式的子查詢中的非唯一索引: value IN (SELECT key_column FROM single_table WHERE some_expr) range:只檢索給定範圍的行,使用一個索引來選擇行。 index:該聯接型別與ALL相同,除了只有索引樹被掃描。這通常比ALL快,因為索引檔案通常比資料檔案小。 ALL:對於每個來自於先前的表的行組合,進行完整的表掃描。 |
possible_keys | 這個引數更像是mysql的一次預測,預測指定sql可能執行過程中哪些索引會生效 |
key | sql在執行過程中實際生效的索引列 |
key_len | 顯示MySQL決定使用的鍵長度。 |
ref | 顯示使用哪個列或常數與key一起從表中選擇行。 |
rows | 顯示MySQL認為它執行查詢時必須檢查的行數。多行之間的資料相乘可以估算要處理的行數。 |
filtered | 顯示了通過條件過濾出的行數的百分比估計值。 |
Extra | Distinct:MySQL發現第1個匹配行後,停止為當前的行組合搜尋更多的行。 Not exists:MySQL能夠對查詢進行LEFT JOIN優化,發現1個匹配LEFT JOIN標準的行後,不再為前面的的行組合在該表內檢查更多的行。 range checked for each record (index map: #):MySQL沒有發現好的可以使用的索引,但發現如果來自前面的表的列值已知,可能部分索引可以使用。 Using filesort:MySQL需要額外的一次傳遞,以找出如何按排序順序檢索行。 Using index:從只使用索引樹中的資訊而不需要進一步搜尋讀取實際的行來檢索表中的列資訊。 Using temporary:為了解決查詢,MySQL需要建立一個臨時表來容納結果。 Using where:WHERE 子句用於限制哪一個行匹配下一個表或傳送到客戶。 Using sort_union(...), Using union(...), Using intersect(...):這些函式說明如何為index_merge聯接型別合併索引掃描。 Using index for group-by:類似於訪問表的Using index方式,Using index for group-by表示MySQL發現了一個索引,可以用來查 詢GROUP BY或DISTINCT查詢的所有列,而不要額外搜尋硬碟訪問實際的表。 |
3.查詢出的資料量過大
例如說一條sql直接查詢了全表的資料資訊量,直接佔滿了網路頻寬,因此訪問時候出現了網路擁塞。
4.硬體裝置不足
例如在面對一些高qps的查詢時候,資料庫本身的機器硬體配置較低,自然處理速度會比較慢。
5.自適應hash出現鎖衝突
AHI是innodb儲存引擎特有的屬性,innodb儲存引擎會針對索引資料的查詢結果做自適應的優化,當某些特定的索引查詢頻率特別高的時候會自動為其建立hash索引,從而提升查詢的效率。相比於B+Tree索引來說,hash索引能夠大大減少對於io的訪問次數,“一擊命中” 查詢資料,具備更加高效的效能,而且hash索引是由mysql內部自動適配的,無需dba在外部做過多的干預。
早期版本的hash索引是採用了單鎖模式來防範併發訪問問題,這對於程式自身的一個運作高效性有一定的”折扣“,後期通過對hash索引進行了分割槽,不同頁的資料用不同的hashtable,每個分割槽有對應的鎖來做併發訪問的預防。
如果某天你發現了有很多執行緒都被堵塞在了RW-latches的時候,有可能就是因為hash索引的併發訪問負載過高導致的堵塞,這個時候可以通過增大hash索引的分割槽引數,或者關閉自適應hash索引特性來進行處理。
作者:Java知音-idea