提起API,作為程序員來說并不陌生,很多程序員的大部分工作都是圍繞著它, 然而,
被大家忽略,API的性能會直接影響產品的用戶體驗,比如,一個視頻軟件,播放1s后需要加載5s,還有人會用它嗎?API背后隱藏了很多復雜的業務邏輯,如何保證API的性能,直接體現了一個程序員的綜合能力。今天我們就來聊聊8種提升API性能的常用方法。
一、什么是API?
在講解方法之前,先對API做個簡單的介紹。API,Application Programming Interface,翻譯為:應用程序接口,它是一種允許兩個軟件組件使用一組定義和協議相互通信的機制。比如,手機上的天氣預報軟件,它通過 API 與遠程氣象系統“交互”,獲取天氣相關數據,最后再將數據展示在手機上。如下圖:軟件A通過API與軟件B進行交互
二、API性能提升方法
1. 緩存
緩存,應該是最容易被大家使用,提升API性能的方法,如下圖:
在日常的業務開發中,通常會包含對數據庫的CRUD,但是數據庫的讀寫性能是有限的,比如在一些場景中,需要對某些數據進行頻繁的讀取,這時候,可以考慮將這些數據緩存起來,下次讀取時,直接從緩存中讀取,減少對數據庫的訪問,提升API性能。舉個例子:假如訪問 DB的耗時是 100ms,訪問緩存的耗時是10ms,那么整個API的性能就提升 10倍。常用的緩存工具有 redis 和 google Guava cache(本地緩存)。
可能有些小伙伴會問:一個 API的響應數據,100ms 和 10ms 對于用戶來說,似乎沒有很大的差異?
假如把時間放大 10倍,100倍,就能發現,這個差異非常明顯,比如,一個 API的響應時間是 10s, 如果能夠通過緩存將響應時間降低到 1ms,那么整個系統的吞吐量就提升了 10倍,性能提升相當可觀,對于用戶的體驗來說也是天壤之別。
2. 連接池
連接池,是一種數據庫連接管理技術,它可以在系統初始化時,創建一定數量的數據庫連接,當有請求時,直接從連接池中獲取連接,使用完畢后 ,再將連接放回連接池中,這樣就可以避免頻繁的創建和銷毀數據庫連接,提升API的性能。如下圖:
服務器和數據庫建立連接是基于 TCP協議,而TCP 需要3次握手,這個過程比較耗時,如果每次請求都需要創建一個連接,那么就會頻繁的進行3次握手,從而影響 API的性能。所以在日常開發中,和數據庫連接時,通常都會使用一些三方的池化工具,以達到復用連接的目的。常用的池化工具有:JDBC,HikariCP,Druid,C3P0,DBCP,BoneCP等。
3. 異步
異步,是一種編程模型,它可以在一個線程中執行多個任務,如下圖:
在日常的業務開發中,通常包含核心鏈路和非核心鏈路,比如:訂單支付中,支付是核心鏈路,支付后郵件通知是非核心鏈路,因此,可以把這些非核心鏈路的操作,改成異步實現,這樣就可以提升API的性能。常用的異步方式有:線程池,消息隊列,事件總線等。比如:上面的郵件發送,當用戶支付完之后,可以使用線程池去實現郵件發送,也可以往消息隊列中發送一條消息,由消費服務去消費,實現郵件發送。
4. N + 1問題
“N+1 問題” 是一個在數據庫查詢性能優化領域常見的概念,指的是在進行關聯查詢時,當你需要獲取主表中的 N 條記錄以及每條記錄關聯的另一個表中的相關信息時,會導致在獲取相關信息時產生額外的查詢操作,從而造成額外的負擔和性能問題。如下圖:
舉個例子:假如有兩張表,文章 “post”表 和文章評論”comment”表,現在要統計每篇文章的評論數,通常SQL語句寫法如下:
1 2 3 4 5 |
SELECT id FROM post; // 1 //for each post SELECT count(*) FROM comment WHERE post_id = ? // N |
如上文的例子,查詢 1次 post表,假如 post中有 N條數據,這樣就需要額外查詢 N次 comment表,因此,產生了 N + 1次查詢。
解決”N+1 問題”的通常方法為:使用 JOIN 進行關聯查詢,如下SQL:
1 2 |
SELECT post.id, count(comment.id) FROM post LEFT JOIN comment ON post.id = comment.post_id GROUP BY post.id; |
但是,在一些分庫分表的情況下,無法進行 JOIN查詢,該如何解決這種 N+1問題?
通常的做法有:數據冗余,說白了就是空間換時間。比如:在 post表中,增加一個 comment_count字段,用于存儲評論數,這樣就可以避免 N+1問題,但是會造成數據冗余,增加了存儲空間。
因此,在程序員的世界,很多時候都是在時間和空間上的權衡(trade off)。
5. 分頁
分頁(Pagination),是一種常見的數據查詢方式,它可以將大量的數據,分成多個頁面進行展示,如下圖:
分頁也是業務開發中比較常見的一種方式,當數據量比較大時,通常會使用分頁的方式進行查詢,這樣可以避免一次性查詢大量的數據,造成內存溢出的問題。
6. JSON 序列化
JSON(JAVAScript Object Notation)序列化是將數據結構或對象轉換為JSON格式的字符串的過程,以便在網絡傳輸、存儲或與其他程序交互時進行數據交換。JSON是一種輕量級的數據交換格式,易于人類閱讀和編寫,同時也易于解析和生成。在各種編程語言中,可以使用庫或內置函數來進行JSON序列化和反序列化操作。如下圖:
7. 壓縮 payload
在API開發中有個默認的約定:參數要盡量的少。因為參數越多,API的復雜度就越高,維護成本也就越高。因此,通常我們會對參數進行壓縮。如下圖:
比如:上傳文件,通常會對文件進行壓縮,以減少文件的大小,提升上傳速度。
8. 精簡Log或者異步log
在業務流程中,通常會增加 log來標記一些核心的流程,以及記錄錯誤信息,方便排查問題。但是,log通常是磁盤操作,如果log過多,就會影響API的性能。因此,通常會對log進行精簡,或者異步log。如下圖:
異步日志(Asynchronous Logging)是一種在計算機程序中進行日志記錄的技術。與傳統的同步日志記錄不同,異步日志記錄允許程序在記錄日志時不必等待日志寫入磁盤或其他存儲介質完成,而是將日志數據放入隊列或緩沖區中,然后由另一個線程或進程負責將日志異步地寫入存儲介質。異步日志記錄通常會涉及以下一些組件:
-
日志緩沖區或隊列:程序將要記錄的日志信息放入緩沖區或隊列中,然后可以繼續執行其他任務。
-
日志寫入線程:另一個線程負責從緩沖區或隊列中獲取日志數據,并將其寫入實際的存儲介質(如磁盤)中。這個過程是異步的,不會阻塞主程序的執行。
-
同步機制:由于異步操作涉及多線程,可能需要適當的同步機制來確保線程之間的安全性,避免競態條件等問題。
異步日志的優點可以減少主程序的延遲和性能損失的同時,提升性能。需要注意的是,在實現異步日志時,要小心處理緩沖區溢出、數據丟失以及確保正確的日志順序等問題。
三、總結
本文分別了介紹了API的性能優化方案,包括:
-
緩存
-
連接池
-
異步
-
N+1問題
-
分頁
-
JSON序列化
-
壓縮payload
-
精簡log等
當然,這些方案并不是一定要使用,而是根據實際的業務場景,具體問題具體分析,選擇合適的方案。
另外,在API的開發中我們通常需要關注三個最常見的問題:
-
性能
-
安全性
-
健壯性