譯者 | 劉汪洋
審校 | 重樓
API 在我們的數(shù)字世界中發(fā)揮著關(guān)鍵的作用,使各種不同的應(yīng)用能夠相互通信。然而,這些 API 的可靠性是保證依賴(lài)它們的應(yīng)用程序功能正常、性能穩(wěn)定的關(guān)鍵因素。本文,我們將探討提高 API 可靠性的五種主要策略。
1.全面測(cè)試
要確保 API 的可靠性,第一步是進(jìn)行全面的測(cè)試。需要進(jìn)行的測(cè)試包括:功能測(cè)試以驗(yàn)證 API 的正確運(yùn)行,集成測(cè)試以確保 API 能與其他系統(tǒng)正常協(xié)同,以及負(fù)載測(cè)試以理解 API 在大規(guī)模使用下的表現(xiàn)。
自動(dòng)化測(cè)試能在開(kāi)發(fā)周期的早期發(fā)現(xiàn)問(wèn)題,回歸測(cè)試能保證新的修改不會(huì)對(duì)現(xiàn)有功能造成破壞。使用虛擬化或模擬技術(shù)可以模擬 API 依賴(lài),進(jìn)行更深度的測(cè)試。此外,為了確保 API 的提供者和消費(fèi)者都能滿(mǎn)足約定的接口,契約測(cè)試非常重要。
下面我們將使用 Go 的內(nèi)置 testing 包,通過(guò)一個(gè)簡(jiǎn)單的例子對(duì)一個(gè)假設(shè)的 API 端點(diǎn)(用于訪(fǎng)問(wèn) API 的 URI)進(jìn)行測(cè)試。
假設(shè)我們有一個(gè)端點(diǎn) GET /users/{id} ,用于返回用戶(hù)的詳細(xì)信息。
下面是我們可能編寫(xiě)的測(cè)試代碼:
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
// 這是一個(gè)簡(jiǎn)化的實(shí)際處理器函數(shù)示例
func UserHandler(w http.ResponseWriter, r *http.Request) {
// ... 處理器邏輯
}
func TestUserHandler(t *testing.T) {
req, err := http.NewRequest("GET", "/users/1", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(UserHandler)
handler.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
// 你還可以檢查響應(yīng)體是否符合預(yù)期的輸出
expected := `{"id": "1", "name": "John Doe"}`
if rr.Body.String() != expected {
t.Errorf("handler returned unexpected body: got %v want %v",
rr.Body.String(), expected)
}
}
這個(gè)測(cè)試創(chuàng)建了一個(gè)新的 HTTP 請(qǐng)求,模擬了對(duì)我們的 /users/{id} 端點(diǎn)的調(diào)用,然后把請(qǐng)求傳遞給了處理器函數(shù)。測(cè)試會(huì)檢查響應(yīng)狀態(tài)是否為 200 OK(即我們期望的成功請(qǐng)求應(yīng)返回的結(jié)果)以及響應(yīng)體是否與預(yù)期的輸出一致。
這個(gè)例子只是一個(gè)簡(jiǎn)單的示例,在實(shí)際應(yīng)用中,你可能會(huì)面臨更復(fù)雜的場(chǎng)景,包括測(cè)試各種邊界條件、錯(cuò)誤路徑等。此外,net/http/httptest 包提供了許多用于測(cè)試 HTTP 客戶(hù)端和服務(wù)器的工具。
總之,你可以結(jié)合單元測(cè)試、性能測(cè)試和持續(xù)的集成測(cè)試,為你的 API 構(gòu)建一個(gè)全面的測(cè)試套件。
單元測(cè)試的目的是確保你的 API 中每個(gè)組件的正確性。它通過(guò)驗(yàn)證每個(gè)部分的功能和隔離它們,使得你可以在早期發(fā)現(xiàn)并糾正問(wèn)題。單元測(cè)試通常通過(guò)模擬依賴(lài)項(xiàng)并獨(dú)立測(cè)試函數(shù)來(lái)完成。在 Go 語(yǔ)言中,可以利用諸如 testify 等包來(lái)達(dá)成這一目標(biāo)。
性能測(cè)試則是為了在高流量的情況下對(duì) API 進(jìn)行壓力測(cè)試。這種測(cè)試有助于你確定系統(tǒng)在高負(fù)載情況下的表現(xiàn),識(shí)別瓶頸,并確保 API 能處理真實(shí)世界的使用情況。性能測(cè)試可以使用 JMeter 或Gatling 等工具進(jìn)行。
最后,持續(xù)集成測(cè)試則通過(guò)模擬用戶(hù)或客戶(hù)端對(duì) API 進(jìn)行一系列連續(xù)操作,來(lái)測(cè)試系統(tǒng)的工作流程。這類(lèi)測(cè)試能夠提供對(duì)端到端工作流程、潛在的障礙或延遲,以及整體用戶(hù)體驗(yàn)的深入理解。這個(gè)過(guò)程可以自動(dòng)化并集成到你的 CI/CD 流程中,使得你可以持續(xù)監(jiān)控并及時(shí)反饋任何代碼更改的影響。
通過(guò)實(shí)施包括功能測(cè)試、單元測(cè)試、性能測(cè)試和持續(xù)合成測(cè)試在內(nèi)的全面的測(cè)試策略,你可以確保你的 API 不僅穩(wěn)定且高性能,還能為使用者提供無(wú)縫的體驗(yàn)。而在問(wèn)題出現(xiàn)時(shí),這種多元化的測(cè)試方法可以幫助你快速定位并解決問(wèn)題的根源。
2.版本控制
在維護(hù)軟件系統(tǒng)的穩(wěn)定性方面,API 版本管理扮演了核心角色。隨著時(shí)間推移,API 可能會(huì)隨著需求變化和優(yōu)化,如果沒(méi)有適當(dāng)?shù)陌姹竟芾?,可能?huì)對(duì)現(xiàn)有的客戶(hù)端應(yīng)用造成破壞。這就是 API 版本管理的關(guān)鍵所在。通過(guò)維護(hù) API 的各個(gè)版本,你可以在引入新功能和優(yōu)化的同時(shí),確保不影響使用舊版本 API 的應(yīng)用。
這種策略提升了系統(tǒng)的穩(wěn)定性,因?yàn)榧词?API 經(jīng)過(guò)改動(dòng)和優(yōu)化,客戶(hù)端應(yīng)用依然可以穩(wěn)定運(yùn)行。它讓開(kāi)發(fā)者能夠部署 API 更新,而不需要擔(dān)心這些變化會(huì)對(duì)正在運(yùn)行的應(yīng)用造成破壞,保障了系統(tǒng)的穩(wěn)定性和正常運(yùn)行。
保持向后兼容性是實(shí)現(xiàn) API 穩(wěn)定性的關(guān)鍵一環(huán),也就是說(shuō),新系統(tǒng)應(yīng)能與舊版 API 兼容。即使新的版本發(fā)布,使用舊 API 版本的應(yīng)用依然可以正常運(yùn)行。這避免破壞用戶(hù)體驗(yàn),并給了開(kāi)發(fā)者足夠的時(shí)間,讓他們可以按照自己的節(jié)奏更新應(yīng)用以適應(yīng)新的 API ,而不是為了防止應(yīng)用出錯(cuò)而被迫升級(jí)。這樣做,有助于創(chuàng)建一個(gè)整體上更穩(wěn)定、更強(qiáng)大和更具有彈性的系統(tǒng)。
示例
在 Go 語(yǔ)言中,我們可以使用多種方式來(lái)進(jìn)行 API 的版本管理。
下面這個(gè)例子展示了如何通過(guò)在 URL 中嵌入 API 版本實(shí)現(xiàn)版本管理,這種方法通常被稱(chēng)為"路徑版本控制"。
package main
import (
"fmt"
"net/http"
)
func handleRequest(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/v1/users":
fmt.Fprintf(w, "You've hit the version 1 of the users API!")
case "/v2/users":
fmt.Fprintf(w, "You've hit the version 2 of the users API!")
default:
http.NotFound(w, r)
}
}
func main() {
http.HandleFunc("/", handleRequest)
http.ListenAndServe(":8080", nil)
}
在這個(gè)例子中,我們定義了一個(gè)處理函數(shù),它根據(jù)請(qǐng)求的 URL 路徑來(lái)匹配響應(yīng)的代碼。當(dāng)訪(fǎng)問(wèn) "/v1/users" 路徑時(shí),我們認(rèn)為這是對(duì)我們 API 第一版本的請(qǐng)求。同樣地,"/v2/users" 則對(duì)應(yīng)我們 API 的第二個(gè)版本。通過(guò)添加更多的分支,你可以輕松地?cái)U(kuò)展這種模式以適應(yīng)更多版本和端點(diǎn)。
此外,你也可以通過(guò)自定義頭部或媒體類(lèi)型版本管理(也稱(chēng)為"內(nèi)容協(xié)商")來(lái)實(shí)現(xiàn)版本管理。
不論你選擇何種版本管理策略,都應(yīng)為 API 的每個(gè)版本維護(hù)清晰且最新的文檔,這是一種良好的實(shí)踐。
但是,我們也需要謹(jǐn)慎使用版本管理。我們應(yīng)盡可能地保持向后兼容性,并提供清晰的文檔。文檔應(yīng)詳細(xì)說(shuō)明每個(gè)新版本中的變化,并提供廢棄舊版本的合理時(shí)間表。
3. 面向失敗設(shè)計(jì)
在理想情況下,API 始終能夠正確運(yùn)行。然而在實(shí)際操作中,出現(xiàn)失敗的情況并不罕見(jiàn)。在設(shè)計(jì) API 的過(guò)程中,我們需要考慮其容錯(cuò)能力,這可能涉及到諸如優(yōu)雅降級(jí)(即系統(tǒng)繼續(xù)運(yùn)行但是功能有所縮減)和故障轉(zhuǎn)移機(jī)制(即出現(xiàn)故障時(shí),系統(tǒng)自動(dòng)切換到備份系統(tǒng))等策略。
將明確的錯(cuò)誤消息和代碼納入 API,能有助于應(yīng)用程序更好地理解問(wèn)題所在以及采取應(yīng)對(duì)策略。我們可以通過(guò)重試邏輯、速率限制和斷路器,讓系統(tǒng)從臨時(shí)性問(wèn)題中恢復(fù),避免故障級(jí)聯(lián)。
下圖顯示了應(yīng)對(duì)各種故障類(lèi)型的操作方法:
示例:斷路器模式
在斷路器模式中,Go 語(yǔ)言有一個(gè)叫 go-hystrix 的熱門(mén)庫(kù),該庫(kù)專(zhuān)注于延遲和容錯(cuò)處理。它主要是在服務(wù)停止時(shí),通過(guò)快速失敗阻止故障級(jí)聯(lián)。以下是一個(gè)基本示例:
package main
import (
"github.com/afex/hystrix-go/hystrix"
"log"
"net/http"
"errors"
)
func main() {
hystrix.ConfigureCommand("my_command", hystrix.CommandConfig{
Timeout: 1000,
MaxConcurrentRequests: 100,
ErrorPercentThreshold: 25,
})
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
err := hystrix.Do("my_command", func() error {
// 調(diào)用其他服務(wù)
return nil
}, nil)
if err != nil {
log.Printf("Failed to talk to other services: %v", err)
http.Error(w, "Failed to talk to other services", http.StatusInternalServerError)
}
})
log.Fatal(http.ListenAndServe(":1234", nil))
}
在上述示例中,我們將一個(gè)命令封裝在 hystrix.Do() 中。如果基于我們?cè)O(shè)置的參數(shù),傳入 Do() 的函數(shù)失敗或超時(shí),斷路器就會(huì)被觸發(fā),后續(xù)的調(diào)用將會(huì)立即失敗,而不再調(diào)用該函數(shù)。
請(qǐng)注意,這只是一個(gè)基本的示例,實(shí)際應(yīng)用場(chǎng)景將涉及更多復(fù)雜的用法,需要針對(duì)這個(gè)庫(kù)以及其他的彈性實(shí)用程序庫(kù)涉及的各種參數(shù)進(jìn)行細(xì)致調(diào)整。請(qǐng)務(wù)必閱讀各種庫(kù)的文檔,深入理解如何在你的代碼中有效地使用它們。
4. 監(jiān)控與分析
實(shí)時(shí)監(jiān)控與及時(shí)分析對(duì)于保證 API 的穩(wěn)定性至關(guān)重要。執(zhí)行一套全面的 API 監(jiān)控策略可以包括對(duì)運(yùn)行時(shí)間、性能以及錯(cuò)誤的檢測(cè),這有助于我們?cè)趩?wèn)題擴(kuò)散影響用戶(hù)前,及時(shí)發(fā)現(xiàn)并處理。
同時(shí),深入分析 API 的使用模式可以讓我們得到重要的洞察。了解到高峰負(fù)載時(shí)段、最常使用的端點(diǎn)以及其他使用詳情后,您就可以主動(dòng)地找出可能存在的弱點(diǎn),并據(jù)此進(jìn)行 API 優(yōu)化。
選擇正確的指標(biāo)去追蹤,對(duì)于了解你的 API 的健康狀態(tài)和性能至關(guān)重要。以下是一些需要考慮的關(guān)鍵指標(biāo):
1.吞吐量:您的 API 單位時(shí)間內(nèi)處理的請(qǐng)求數(shù)量,可以進(jìn)一步分為端點(diǎn)、HTTP 方法(如 GET、POST、PUT、DELETE 等)或響應(yīng)狀態(tài)碼。
2.錯(cuò)誤率:?jiǎn)挝粫r(shí)間內(nèi)的錯(cuò)誤響應(yīng)數(shù)量,通常是指含有 4xx 或 5xx 狀態(tài)碼的響應(yīng)。同吞吐量一樣,這個(gè)指標(biāo)也可以按端點(diǎn)、HTTP 方法或具體的狀態(tài)碼進(jìn)行細(xì)分。
3.延遲:處理一個(gè)請(qǐng)求所需的時(shí)間,通常以一系列百分位數(shù)(如第 50、95 和 99 百分位)來(lái)追蹤,這可以幫助您了解典型和極端情況下的性能表現(xiàn)。您可能需要針對(duì)不同的端點(diǎn)或 HTTP 方法單獨(dú)追蹤此項(xiàng)。
4.流量:發(fā)送和接收的數(shù)據(jù)量,可以按端點(diǎn)、HTTP 方法或響應(yīng)狀態(tài)碼進(jìn)行細(xì)分。
5.可用性:您的 API 正常運(yùn)行并能夠處理請(qǐng)求的時(shí)間占比,可以作為一個(gè)整體進(jìn)行測(cè)量,或者針對(duì)每一個(gè)單獨(dú)的端點(diǎn)進(jìn)行測(cè)量。
6.飽和度:系統(tǒng)達(dá)到最大容量的程度,這可以通過(guò)測(cè)量 CPU 使用率、內(nèi)存使用率、磁盤(pán) I/O 或其他可能限制系統(tǒng)處理更多負(fù)載的資源來(lái)了解。
7.斷路器觸發(fā):如果您使用斷路器模式處理故障,您可能需要追蹤斷路器被觸發(fā)的頻率,這可以幫助您了解 API 或其依賴(lài)項(xiàng)失敗的頻率。
請(qǐng)記住,根據(jù)你的 API 特性和應(yīng)用需求,選擇追蹤的具體指標(biāo)可能會(huì)有所不同。關(guān)鍵是要選擇那些能為你提供有意義的 API 健康狀況和性能洞察力的指標(biāo)。
以 Prometheus 為例:
Prometheus 是一款內(nèi)建客戶(hù)端庫(kù)的開(kāi)源系統(tǒng)監(jiān)控和警告工具包,它支持用各種語(yǔ)言度量您的服務(wù)。下面就是一個(gè)示例,說(shuō)明如何使用 Go 客戶(hù)端庫(kù)在 HTTP 端點(diǎn)上展示指標(biāo)。
我們將使用 Prometheus 的 Go 客戶(hù)端 來(lái)展示和創(chuàng)建這些指標(biāo)。
package main
import (
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
httpRequestsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Number of HTTP requests",
},
[]string{"path"},
)
httpRequestDuration = prometheus.NewSummaryVec(
prometheus.SummaryOpts{
Name: "http_request_duration_seconds",
Help: "Duration of HTTP requests in seconds",
},
[]string{"path"},
)
)
func init() {
// Register the metrics.
prometheus.MustRegister(httpRequestsTotal)
prometheus.MustRegister(httpRequestDuration)
}
func handler(w http.ResponseWriter, r *http.Request) {
// Increment the counter for the received requests.
httpRequestsTotal.WithLabelValues(r.URL.Path).Inc()
// Measure the time it took to serve the request.
timer := prometheus.NewTimer(httpRequestDuration.WithLabelValues(r.URL.Path))
defer timer.ObserveDuration()
// Handle the request.
w.Write([]byte("Hello, world!"))
}
func main() {
http.HandleFunc("/", handler)
// Expose the registered metrics via HTTP.
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":8080", nil)
}
在這個(gè)例子中,我們創(chuàng)建并注冊(cè)了兩個(gè)指標(biāo):http_requests_total 和 http_request_duration_seconds。前者是一個(gè)計(jì)數(shù)器,每接收到一個(gè)請(qǐng)求就增加一次計(jì)數(shù),后者是一個(gè)匯總指標(biāo),用于記錄處理每個(gè)請(qǐng)求所花費(fèi)的時(shí)間。
然后,我們創(chuàng)建了一個(gè) HTTP 處理器,每處理一個(gè)請(qǐng)求,就會(huì)增加計(jì)數(shù)器并測(cè)量請(qǐng)求的執(zhí)行時(shí)長(zhǎng)。我們利用 promhttp.Handler() 在 /metrics 端點(diǎn)上展示這些指標(biāo)。
現(xiàn)在,只要你啟動(dòng)了服務(wù)器并向其發(fā)送請(qǐng)求,就可以通過(guò)訪(fǎng)問(wèn) http://localhost:8080/metrics 或者使用工具如 curl 來(lái)查看這些指標(biāo)。
這只是一個(gè)基礎(chǔ)的示例,在實(shí)際應(yīng)用中,你可能會(huì)希望追蹤更多的指標(biāo),并基于其他維度(如 HTTP 方法、響應(yīng)狀態(tài)碼等)對(duì)它們進(jìn)行細(xì)分。
5. 利用 API 網(wǎng)關(guān)
API 網(wǎng)關(guān)是一種強(qiáng)大的工具,能有效提升 API 的健壯性。作為系統(tǒng)的統(tǒng)一入口,API 網(wǎng)關(guān)能夠處理諸如路由、負(fù)載均衡、認(rèn)證、限流等多項(xiàng)功能。通過(guò)將這些問(wèn)題從 API 本體中抽離,你能更專(zhuān)注于業(yè)務(wù)邏輯,而非基礎(chǔ)設(shè)施。
另外,API 網(wǎng)關(guān)還可提供額外的彈性特性,如自動(dòng)故障轉(zhuǎn)移、為提高性能而對(duì)響應(yīng)進(jìn)行緩存,以及在高負(fù)載時(shí)對(duì)請(qǐng)求進(jìn)行緩沖或排隊(duì)。
下面列出了 API 網(wǎng)關(guān)能提供的部分功能,幫助你為技術(shù)棧選擇適合的 API 網(wǎng)關(guān):
- 請(qǐng)求路由: API 網(wǎng)關(guān)可以依據(jù)請(qǐng)求中的路由信息將客戶(hù)端請(qǐng)求路由到合適的后端服務(wù)。
- API 版本管理: API 網(wǎng)關(guān)能管理多版本的 API,允許客戶(hù)端并行使用不同版本。
- 限流: 為了避免請(qǐng)求過(guò)量淹沒(méi)后端服務(wù),API 網(wǎng)關(guān)能夠限制某個(gè)或某組客戶(hù)端的請(qǐng)求速率。
- 身份驗(yàn)證和授權(quán): API 網(wǎng)關(guān)通常處理客戶(hù)端請(qǐng)求的身份驗(yàn)證和授權(quán),確保只有經(jīng)過(guò)驗(yàn)證并授權(quán)的請(qǐng)求才能到達(dá)后端服務(wù)。
- API 密鑰管理: API 網(wǎng)關(guān)通常管理 API 密鑰,這些密鑰用于跟蹤和控制 API 的使用方式。
- 緩存: 為了提升性能并降低后端服務(wù)的負(fù)載,API 網(wǎng)關(guān)可以緩存后端服務(wù)的響應(yīng),并在收到相同的請(qǐng)求時(shí)返回緩存的響應(yīng)。
- 請(qǐng)求和響應(yīng)轉(zhuǎn)換: API 網(wǎng)關(guān)可以將請(qǐng)求和響應(yīng)轉(zhuǎn)換為客戶(hù)端或后端服務(wù)所需的格式。
- 熔斷器功能: 當(dāng)服務(wù)出現(xiàn)故障,API 網(wǎng)關(guān)可以通過(guò)將請(qǐng)求路由到正常運(yùn)行的服務(wù)來(lái)防止應(yīng)用程序崩潰。
- 監(jiān)控和分析: API 網(wǎng)關(guān)能收集 API 的使用和性能數(shù)據(jù),用于分析、監(jiān)控和警報(bào)。
- 安全策略: API 網(wǎng)關(guān)可以執(zhí)行安全策略,如 IP 白名單,同時(shí)防止 SQL 注入、跨站腳本攻擊(XSS)等安全威脅。
以下是一些知名的開(kāi)源 API 網(wǎng)關(guān):
- Kong:Kong 是一個(gè)云原生、快速、可擴(kuò)展、分布式的微服務(wù)管理層(也稱(chēng)為 API 網(wǎng)關(guān)或 API 中間件)。自 2015 年以來(lái),它以開(kāi)源項(xiàng)目的形式存在,其核心功能是用 Lua 編寫(xiě)的,并運(yùn)行在 Nginx 網(wǎng)絡(luò)服務(wù)器上。
- Tyk:Tyk 是一個(gè)開(kāi)源的 API 網(wǎng)關(guān),運(yùn)行速度快且可擴(kuò)展性強(qiáng),既可以運(yùn)行在獨(dú)立服務(wù)器上,也可與已有的 Nginx 安裝進(jìn)行協(xié)同工作。
- Express Gateway:Express Gateway 是一個(gè)基于 Express.js 構(gòu)建的微服務(wù) API 網(wǎng)關(guān)。該網(wǎng)關(guān)完全可擴(kuò)展,不依賴(lài)任何特定框架,能夠在短時(shí)間內(nèi)提供強(qiáng)大且可擴(kuò)展的解決方案。
- KrakenD:KrakenD 是一個(gè)高性能的開(kāi)源 API 網(wǎng)關(guān)。KrakenD 消除了所有 SOA 架構(gòu)的復(fù)雜性,以支持應(yīng)用程序開(kāi)發(fā)者快速發(fā)布新功能,同時(shí)保持出色的性能。
總的來(lái)說(shuō),提升 API 的可靠性不是一項(xiàng)一次性任務(wù),而是需要持續(xù)投入的工作。這包括嚴(yán)格的測(cè)試、精確的版本控制、遵循好的設(shè)計(jì)原則,智能地使用如 API 網(wǎng)關(guān)這樣的工具,以及持續(xù)的監(jiān)控和分析。有了這些策略,你就能構(gòu)建出能經(jīng)受住時(shí)間考驗(yàn)并為你的應(yīng)用程序提供可靠基礎(chǔ)的 API。
譯者介紹
劉汪洋,51CTO社區(qū)編輯,昵稱(chēng):明明如月,一個(gè)擁有 5 年開(kāi)發(fā)經(jīng)驗(yàn)的某大廠(chǎng)高級(jí) Java 工程師,擁有多個(gè)主流技術(shù)博客平臺(tái)博客專(zhuān)家稱(chēng)號(hào)。
原文標(biāo)題:5 Ways to Improve Your API Reliability,作者:CodeReliant 社區(qū)