使用Singleflight優(yōu)化Go代碼
介紹
有許多方法可以?xún)?yōu)化代碼以提高效率,減少運(yùn)行進(jìn)程就是其中之一。在本文中,我們將看到如何通過(guò)使用一個(gè)Go包Singleflight來(lái)減少重復(fù)進(jìn)程,從而優(yōu)化Go代碼。
問(wèn)題
假設(shè)你有一個(gè)web應(yīng)用,它每秒有10個(gè)請(qǐng)求(RPS)。根據(jù)您所知道的數(shù)據(jù),其中一些請(qǐng)求具有相同的模式,實(shí)際上可以生成相同的結(jié)果,這意味著實(shí)際上存在冗余流程。
從上面的插圖中,我們知道用戶(hù)1和用戶(hù)2想要相同的東西,但最終,我們(大多數(shù)情況下)分別處理這兩個(gè)請(qǐng)求。
解決方案
Singleflight是可以解決這類(lèi)問(wèn)題的Go包之一,如文檔中所述,它提供了重復(fù)函數(shù)調(diào)用抑制機(jī)制。
很酷,如果我們知道我們要調(diào)用的函數(shù)是重復(fù)的,我們就可以減少處理的函數(shù)的數(shù)量,讓我們看看在現(xiàn)實(shí)世界中如何使用它。
實(shí)現(xiàn)
我們將創(chuàng)建兩個(gè)程序,server.go 和 client.go。
server.go — 將作為web服務(wù),可以接收 /api/v1/get_something 的請(qǐng)求,參數(shù)名為name
// server.go
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/api/v1/get_something", func(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
response := processingRequest(name)
_, _ = fmt.Fprint(w, response)
})
err := http.ListenAndServe(":15001", nil)
if err != nil {
fmt.Println(err)
}
}
func processingRequest(name string) string {
fmt.Println("[DEBUG] processing request..")
return "Hi there! You requested " + name
}
client.go — 將作為一個(gè)客戶(hù)端,向web服務(wù)發(fā)出5個(gè)并發(fā)請(qǐng)求(你可以在變量totalRequests中設(shè)置這個(gè)數(shù)字)。
// client.go
package main
import (
"io"
"log"
"net/http"
"sync"
)
func main() {
var wg sync.WaitGroup
endpoint := "http://localhost:15001/api/v1/get_something?name=something"
totalRequests := 5
for i := 0; i < totalRequests; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
makeAPICall(endpoint)
}(i)
}
wg.Wait()
}
func makeAPICall(endpoint string) {
resp, err := http.Get(endpoint)
if err != nil {
log.Fatalln(err)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalln(err)
}
result := string(body)
log.Printf(result)
}
首先,我們可以運(yùn)行 server.go,然后繼續(xù)執(zhí)行 client.go。我們將在服務(wù)器腳本的終端中看到如下內(nèi)容:
[DEBUG] processing request..
[DEBUG] processing request..
[DEBUG] processing request..
[DEBUG] processing request..
[DEBUG] processing request..
客戶(hù)端的輸出是這樣的:
2023/09/05 10:29:34 Hi there! You requested something
2023/09/05 10:29:34 Hi there! You requested something
2023/09/05 10:29:34 Hi there! You requested something
2023/09/05 10:29:34 Hi there! You requested something
2023/09/05 10:29:34 Hi there! You requested something
這是正確的,因?yàn)槲覀儚目蛻?hù)端發(fā)送了五個(gè)請(qǐng)求,并在服務(wù)器中處理了這五個(gè)請(qǐng)求。
現(xiàn)在讓我們?cè)诖a中實(shí)現(xiàn)Singleflight,這樣它會(huì)更有效率。
// server.go
package main
import (
"fmt"
"net/http"
"golang.org/x/sync/singleflight"
)
var g = singleflight.Group{}
func main() {
http.HandleFunc("/api/v1/get_something", func(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
response, _, _ := g.Do(name, func() (interface{}, error) {
result := processingRequest(name)
return result, nil
})
_, _ = fmt.Fprint(w, response)
})
err := http.ListenAndServe(":15001", nil)
if err != nil {
fmt.Println(err)
}
}
func processingRequest(name string) string {
fmt.Println("[DEBUG] processing request..")
return "Hi there! You requested " + name
}
重新啟動(dòng)服務(wù)器并再次運(yùn)行客戶(hù)端程序后,服務(wù)器的終端顯示如下:
[DEBUG] processing request..
[DEBUG] processing request..
客戶(hù)端的輸出還是沒(méi)有變化:
2023/09/05 10:32:49 Hi there! You requested something
2023/09/05 10:32:49 Hi there! You requested something
2023/09/05 10:32:49 Hi there! You requested something
2023/09/05 10:32:49 Hi there! You requested something
2023/09/05 10:32:49 Hi there! You requested something
太好了!所有客戶(hù)端都得到了預(yù)期的響應(yīng),但是現(xiàn)在我們的服務(wù)器只處理了兩個(gè)請(qǐng)求。想象一下,如果您處理數(shù)千個(gè)類(lèi)似的請(qǐng)求,您將帶來(lái)多大的效率,這是驚人的!
結(jié)論
在本文中,我們了解了Singleflight在優(yōu)化代碼方面的強(qiáng)大功能。不僅僅是處理一個(gè)web請(qǐng)求,你還可以將它的用例擴(kuò)展到其他事情上,比如從數(shù)據(jù)庫(kù)中獲取數(shù)據(jù)等等。
還有一些我在本文中沒(méi)有涉及的內(nèi)容,例如Singleflight的過(guò)程失敗會(huì)怎樣,以及我們?nèi)绾尉彺嫠?/p>