慢介面分析與最佳化

大话性能發表於2024-05-07

前言

在系統維護過程中,隨著業務增長,業務複雜度增加,業務資料量的逐步增長,可能會出現一些請求響應時間較長的情況,給使用者帶來不便甚至災難性的後果。因此,我們在系統設計期間充分考慮介面效能、在系統執行時期需要定期篩選出慢介面進行分析與最佳化,將業務系統的響應速度、併發能力及可用性維持在一個較高水位。

介面耗時的構成

Client

  • DNS Lookup

域名解析耗時,一般都應該很短,小於 20ms,內網的 DNS 伺服器應該更小,如果耗時大了就需要考慮更換 DNS 伺服器,如果無法解決,可以考慮直接做 HOST 來手動解析。

  • TCP Connect

連線握手耗時,只有在新建連線的時候會產生,否則為 0,如果相對耗時較大,且單獨訪問都經常耗時很大,那麼問題基本在於網路線路,但如果是在大量訪問下耗時較大,那麼說明伺服器或者中間網路裝置無法應對,可能連線池已用完或者無法處理。

  • Request Send

iddlerBeginRequest→ServerGotRequest

傳送資料耗時,TCP 獨有,資料傳送到對方後會有一個自動空響應,所以可以計算耗時,如果傳送內容不是太大,但耗時較大,那麼基本可以定性為網路問題,但一些特殊的協議如果可以多次不斷的傳送資料而不等待伺服器響應,那麼也有可能是 Socket 接收資料的緩衝區已滿,遭遇排隊。

  • Wait Response

ServerGotReques→ServerBeginResponse

響應等待耗時,也就是從傳送完成後,到伺服器響應第 1 個字消耗的時間,如果網路沒有問題,那麼這將是伺服器處理介面資料的精確耗時。

  • Response Receive

ServerBeginResponse→GotResponseHeaders→ServerDoneResponse

響應接收耗時,這個耗時只是接收資料傳輸和讀取的耗時,已經不是伺服器的處理耗時範圍了,資訊量越大,耗時也一定越長。

Server

  • Cache 讀寫

以 redis 為例,redis 作為記憶體資料庫,擁有非常高的效能,單個例項 QPS 能達到 10W 左右,一般場景下建議使用 cache 協助進行介面效能最佳化。

  • DB 讀寫

以 MqSql 為例,DB 讀寫往往代表著存在大量磁碟讀寫操作,複雜的查詢往往會導致 DB 寫入大量臨時表資料。同時 DB 大多為單點服務,在介面效能最佳化時往往會投入大量精力進行慢查詢最佳化。

  • 呼叫外部服務

大多數介面鏈路過程中還會同步呼叫外部服務,此時外部服務介面的效能及呼叫時的 timeout 時間、即時重試機制等會直接影響介面耗時。

  • 系統內部邏輯處理

系統程序處理的執行者為 CPU,處理延遲即代表 CPU 沒有及時運算應用程式程式碼。而導致 CPU 佔用率較高的操作有:磁碟 io、網路 io、同步鎖、jvm 頻繁 GC 等。當存在大量磁碟 io、網路 io 長時間阻塞時會導致介面耗時增加。

最佳化方式

Cache 篇

恰當使用快取

對於一些不經常更新的資料,我們往往可以放入快取提供頻繁讀取。這裡的快取包括分散式快取、本地快取。

  • 分散式快取

主體、供應商、匯率等主資料資訊往往可以使用分散式快取,透過 job 定時重新整理、過期重新整理、binlog/領域事件監聽重新整理等方式保證資料一致性。

  • 本地快取

相對分散式快取,本地快取建議存放資料量極小的不經常更新或基本不變的資料。如系統字典、系統業務配置等。

快取延遲最佳化

以 redis 為例,當我們本身使用不當或運維不合理時,可能導致 redis 訪問延遲變大。可能存在的原因:

  • 使用了複雜度過高的命令,例如 sort、sunion 等等。

  • 操作、儲存大的 bigkey。

  • 大量資料集中過期。 集中過期導致 redis 響應慢的原因與 redis 過期策略有關。

  • redis 例項記憶體使用達到了上限。

  • fork 子程序導致響應變慢。ByteCache 持久化方案:base rdb + aof。

DB 篇

慢 SQL 最佳化

若系統存在單個慢 SQL,可透過調整索引的設計/使用方式、調整分頁方式、拆解/合併等處理方式進行最佳化,複雜/大表場景透過資料歸檔、分庫分表、遷移 ES/Hive 等方式最佳化。

批次讀寫

迴圈內的查詢、insert 可能因資料量的增長導致介面效能急劇下降。將迴圈內的查詢、insert 前/後置成批次查詢、批次 insert 可以大大降低資料庫訪問次數,縮減系統與 DB 之間的連線、關閉等消耗。此外,也可結合業務實際情況分析是否可以大批次拆分成小批次處理。

避免大事務

建議單次執行影響行數不得超過 100000,總執行次數不得超過 200 次,總耗時<360s。

  • 慎用@Transactional

  • @Transactional範圍足夠小&&精確

單次執行影響行數、總執行次數較大的事務我們稱之為大事務。大事務往往執行時間較長。

  • 增刪改操作會生成 undo log,大量資料操作將生成大量的 undo log,所在表上的其他查詢及 DML 也將會受到影響,尤其是大事務發生回滾將會耗時漫長,嚴重影響業務。

  • 同時 SQL 語句影響行數過多,容易造成從庫延遲過大,甚至無法追平主庫;

  • 更新記錄時間長,鎖持有時間長,容易引發行級鎖阻塞;

呼叫外部服務篇

拒絕無效等待

當外部服務介面抖動時,若程式一直處於等待狀態,這可能會導致程式在等待過程中變得不響應。超時控制可以讓程式在一定時間內完成操作。當外部服務介面持續劣化時,則需要採取其他方案進行改造,防止影響業務使用。

系統內部處理篇

快速失敗

fail-fast。當介面收到不合法、無效的請求時,快速響應錯誤資訊。快速失敗可以節省程式處理成本,同時也能避免系統整體資料的汙染。

非同步執行

對於一些非核心業務邏輯,如果這部分邏輯執行時間長且不影響主業務流程,我們可以考慮 “非同步” 執行這些邏輯。 具體來說,可以透過以下技術方案實現:

  • 使用訊息佇列,讓主業務邏輯快速返回,將非核心邏輯作為訊息放入佇列非同步執行。

  • 設計非同步執行緒或定時任務,在主執行緒返回後,非同步執行緒負責後臺執行非核心邏輯。

  • 利用事件程式設計模型,主業務邏輯觸發事件,事件監聽者非同步響應事件執行非核心邏輯。

  • 非核心邏輯作為微服務單獨部署,主業務快速呼叫微服務,微服務後臺非同步執行邏輯。

  • 使用 reactor 模式,主執行緒接收請求觸發非核心邏輯,再透過多執行緒非同步執行非核心處理。

併發最佳化

在設計介面時,若使用序列邏輯,即一個任務完成後再執行下一個任務,則任務只能順序執行,整體吞吐量受到限制。 為提高吞吐量,可以考慮將程式的 DB 操作、外部服務訪問等遠端呼叫改為並行執行。在並行處理過程中若需序列訪問共享資源,可透過鎖或 CAS 演算法來控制併發訪問。

拒絕阻塞等待

當介面鏈路中需要呼叫一個外部系統的介面,且依賴其響應結果,但該介面耗時較長 (例如超過 10s) 時,如果一直阻塞等待,介面效能會受到極大拖累且不合理。此時可以參考 IO 多路複用模型,等待外部系統響應的事件回撥通知,再進行後續業務處理。

網路篇

流量控制

透過限流、降級等方式控制流量保護服務穩定執行。

壓縮傳輸內容

當傳輸報文較大時,可以考慮壓縮後傳輸,可減少網路頻寬佔用、提升傳輸速度。

總結

慢介面的本質是 CPU 處理不過來或者處理時間過長,在慢介面最佳化時我們要做的就是儘可能精簡系統處理的流程或者資料,讓 CPU 提效。介面耗時構成為介面鏈路的整個生命週期,所以其分析治理往往較為複雜,希望本文能提供一些最佳化思路,同時也為高效能的介面設計提供一些參考。

更多內容可以學習《測試工程師 Python 工具開發實戰》書籍《大話效能測試 JMeter 實戰》書籍

相關文章