接口性能優化怎麼做?

爲什麼要做接口性能優化

想象一下以下幾個場景:

  1. 我們在獲取一個用戶詳情接口時,刷了無數次,瀏覽器就在那轉圈,硬是刷不出來,打開控制檯,顯示接口超時
  2. 假如我們服務A有個批量發營銷短信的任務,服務A用批量的userid調服務B的用戶服務以獲取用戶的手機號,從而完成短信發送功能。奈何服務B的通過userid接口獲取用戶詳情的接口平均響應500ms以上,且接口請求量增大時,接口耗時明顯上升,最後導致服務A的部分調用超時,短信發送失敗
  3. c端用戶打開某些頁面時,譬如用戶空間或者用戶詳情頁時,發現數據加載很慢,或者加載不出,導致用戶體驗大幅降低
  4. etc…

以上只是列出了部分小的場景,在紛繁複雜的互聯網業務領域,存在接口性能問題的場景更是不勝枚舉。問題是有了,那麼我們爲什麼要做接口性能的優化呢?

  1. 用戶體驗會得到顯著提升
  2. 服務更穩定,性能更高,單位時間可處理的請求提升,響應指標有質的提升
  3. 有效防止了服務雪崩問題

怎樣做接口性能優化,思路是怎樣的

很顯然,當我們討論要做接口性能優化的時候,必然是此接口已經出現了一丟丟小毛病,確有優化的必要。譬如可能是用戶感覺購物車有點卡,頁面加載有點慢,又或者通過監控得知某些接口響應超出平均值太多…當有這些類似跡象發生時,我們就要開始着手處理,做性能優化了。從個人所經歷的職業生涯來看,主要分爲以下幾個步驟:

  1. 確定是哪個接口存在性能問題
  2. 確定這個接口的內部邏輯是怎樣的,做了哪些事情
  3. 分析接口存在性能問題的根本原因
  4. 尋找確立優化方案
  5. 迴歸驗證方案效果

其中第1、2步相對比較容易處理,而核心在於第3、4步,第5步是用於驗證效果。下面本文重點會探討第3、4步如何進行處理

分析接口存在性能的根本原因

爲什麼說這個很重要呢,因爲這個直接影響到後面選擇何種優化方案,會消耗多少成本。當然,接口存在性能問題的具體案例千千萬,筆者就從遇到過的着手,從最常見、最簡單的開始說明

業務接口存在for循環調用

舉例來看,這種場景就是A接口存在性能瓶頸,經分析發現,A接口實現裏會同步for循環調用某個正常的接口B(or 方法、耗時處理邏輯),以模擬代碼來看,典型的如同下面這種:

//性能瓶頸處
List<T> list = xxx
for (T t: list) {
    //b.biz 接口(or方法、耗時處理邏輯)
	b.biz(t);
}

上述代碼是我們在寫業務代碼是極有可能出現的,假定b.biz這塊每次同步調用耗時均值爲50ms,如果這個list.size()>10,那這個接口經過這個for循環後,耗時將達500ms以上,且size越大,耗時就越高。那麼針對此種調用,如何進行優化呢?
針對這種case,優化有多種方案:

  • 將b.biz處改成批處理,這是最佳實踐。但這樣處理也可能存在問題,譬如b.biz()接口本身不存在這個批處理接口,亦或者無法改成批處理
  • 如果本身能確定list 的size不會很大,譬如超過10,成本最低,改造速度最快的方法就是使用java8中的集合的parallelStream()方法,該方法會併發地執行。缺點就是無法控制併發的粒度
  • 在無法改造b.biz接口,且list.size()又比較大的情況下,可以使用自定義線程池,併發地調用。如果存在需要獲取b.biz()返回值的場景,併發調用時可使用countDownLatch等待獲取全部返回值。缺點就是b.biz()接口瞬時會承受比較大的壓力,極端情況下服務可能被打垮,這種不作爲推薦使用方案(b.biz()接口做了緩存且承受力較強的話可以考慮)

業務接口查db慢

這種場景還是很常見的,典型的如以下代碼:

doSmt()
//典型的查db耗時高
List<T> list = xxxDao.selectXXX(xxx)
doSmtAnother()

很顯然瓶頸在於查庫慢,但是關於這種case,如果細究起來可能得另開一章sql優化來講了。可以稍微提幾點,當發現查db慢時,可以從以下幾個方向着手:

  • 確定是否爲最後執行的sql慢
  • 如果是執行的sql慢
    • 查看該sql的執行計劃,分析sql是否存在優化的可能。典型sql優化增加有效索引,優化搜索字段順序,避免索引失效等情況。詳情可參考: sql優化
    • 如果已優化過,依然很慢,得分析是否是表數據量過大,譬如以前我們dba推薦mysql庫單錶行數量不要超過3kw,實踐中也發現,當單表數據量過大時,單純從sql優化的角度着手是無法解決性能問題的。此時可能得考慮分庫分表,或採取其他的存儲方式

複合場景,存在較多查詢

還有一種典型場景就是某個接口實現很複雜,業務邏輯多,調用其他接口或者方法的地方非常多,且接口的上下游鏈路裏存在多個重複查詢的情況(如A接口在調B接口之前查了C,然後調B接口時又查了一遍C)
接口前後存在重複調用
針對此類場景,可以從以下方法着手:

  • 確定接口的上下游是否存在重複調用的情況,若存在,可通過改造降低查詢次數
  • 接口的實現是否過於複雜,分析是否存在有效簡化的可能,針對耗時較高的部分定點優化,緩存、併發都可以採用

接口勉強複合要求,但需要更高性能

當我們到達山窮水盡的時候(緩存的銀彈暫時不作討論),倘若需要做更進一步的優化,可以着手的點有哪些呢?從個人淺薄的知識來看,我能想到的有如下這些:

  • 如果是針對的前端靜態資源類接口,可將資源前置,靠近相應的用戶側,採用cdn加速可有效改善資源類接口響應速度
  • 如果是動態服務類接口,服務端的數據中心應選擇恰當,另外對於Java語言後端服務來說,可以考慮從jvm性能調優的角度來進行優化,針對不同特性的應用,調整相應的gc算法及gc策略
  • 如果針對的是超高併發、超高性能接口的場景,可考慮使用go語言開發此類核心接口

後記

當碰到更典型的case和更好的解決方案時,此篇文章會進行追加。歡迎有不同想法的小夥伴共同探討

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章