這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
前言
由於golang提供了完善的net/http標準庫,基於該標準庫實現一個web架構的難度相比其他語言低了不少,所以go web架構簡直就是百花齊放。從老牌的revel和beego,到新出的gin,和iris等,而且還有一些類似於chi這種router。個人一般小項目,尤其是中介軟體需要暴露一些http介面的,基本就使用chi即可。
本次測試主要是gin iris echo 這三個架構。側重在於高效能,從並發和json序列化和還原序列化兩個方面來測評,畢竟後台項目側重的也就是這兩個方面。
測試
測試環境說明
為了選擇符合重IO的架構,現設定如下情境的demo,demo的具體要求如下:
- 開啟日誌功能(類比正常業務時也會記錄日誌),在請求開始和結束時分別記錄一條日誌
- 介面中用sleep暫停1秒,假設這裡的網路IO操作(同時更容易從日誌看出是否協程並發的行為)
- 用POST介面做測試,介面中不進行任何處理,把接收到的body直接序列化返回(序列化和還原序列化是架構最高頻的動作)
- 開啟架構的accesslog功能
測試載入器以及情境如下
- 測試載入器使用經典的jmeter,直接使用GUI介面測試
- 情境分為10線程並發,100線程並發,500線程並發,1000線程並發和1500線程並發
- 所有結果都只看jmeter的彙總報告,重點查看輸送量、時間和錯誤數
- 所有demo啟動的時候均啟動單線程,非同步架構不限制協程的數量,設定GOMAXPROCS=1
- 所有測試均在本地,壓測時間長度兩分鐘
- 測試時採用POST請求,資料樣本有565bytes、5KB、10KB、50KB和100KB,每個樣本都要在不同並發線程上測試
測試代碼
gin:
package mainimport ( "log" "net/http" "time" "github.com/gin-gonic/gin")// Agent ...type Agent struct { AgentID string `json:"agent_id"` QueuedAt string `json:"queued_at"` QueuedBy string `json:"queued_by"`}// Details ...type Details struct { EventID string `json:"event_id"` Endpoint string Metric string Content string Priority int Status string}// Test1 ...type Test1 struct { Agent Agent Details Details Description string EventType string `json:"event_type"` ServiceKey string `json:"service_key"`}// Test2 test2type Test2 struct { Data []*Test1}func main() { r := gin.New() // Global middleware // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. // By default gin.DefaultWriter = os.Stdout r.Use(gin.Logger()) r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.POST("/v1/test", func(c *gin.Context) { var test Test1 if err := c.BindJSON(&test); err == nil { log.Println("========================start io=====================") time.Sleep(time.Duration(1) * time.Second) log.Println("=========================end io=======================") c.JSON(http.StatusOK, test) } else { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } }) r.POST("/v2/test", func(c *gin.Context) { var test Test2 if err := c.BindJSON(&test); err == nil { log.Println("========================start io=====================") time.Sleep(time.Duration(1) * time.Second) log.Println("=========================end io=======================") c.JSON(http.StatusOK, test) } else { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } }) r.POST("/v3/test", func(c *gin.Context) { var test Test2 if err := c.BindJSON(&test); err == nil { log.Println("========================start io=====================") time.Sleep(time.Duration(1) * time.Second) log.Println("=========================end io=======================") c.JSON(http.StatusOK, test) } else { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } }) r.POST("/v4/test", func(c *gin.Context) { var test Test2 if err := c.BindJSON(&test); err == nil { log.Println("========================start io=====================") time.Sleep(time.Duration(1) * time.Second) log.Println("=========================end io=======================") c.JSON(http.StatusOK, test) } else { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } }) r.Run() // listen and serve on 0.0.0.0:8080}
iris:
package mainimport ( "time" "github.com/kataras/iris" "github.com/kataras/iris/middleware/logger")// Agent ...type Agent struct { AgentID string `json:"agent_id"` QueuedAt string `json:"queued_at"` QueuedBy string `json:"queued_by"`}// Details ...type Details struct { EventID string `json:"event_id"` Endpoint string Metric string Content string Priority int Status string}// Test1 ...type Test1 struct { Agent Agent Details Details Description string EventType string `json:"event_type"` ServiceKey string `json:"service_key"`}// Test2 test2type Test2 struct { Data []*Test1}func main() { app := iris.New() app.Use(logger.New()) app.Get("/ping", func(c iris.Context) { c.WriteString("pong") }) app.Post("/v1/test", func(c iris.Context) { var test Test1 if err := c.ReadJSON(&test); err == nil { app.Logger().Println("========================start io=====================") time.Sleep(time.Duration(1) * time.Second) app.Logger().Println("=========================end io=======================") c.JSON(test) } else { c.WriteString("failure") } }) app.Post("/v2/test", func(c iris.Context) { var test Test2 if err := c.ReadJSON(&test); err == nil { app.Logger().Println("========================start io=====================") time.Sleep(time.Duration(1) * time.Second) app.Logger().Println("=========================end io=======================") c.JSON(test) } else { c.WriteString("failure") } }) app.Post("/v3/test", func(c iris.Context) { var test Test2 if err := c.ReadJSON(&test); err == nil { app.Logger().Println("========================start io=====================") time.Sleep(time.Duration(1) * time.Second) app.Logger().Println("=========================end io=======================") c.JSON(test) } else { c.WriteString("failure") } }) app.Post("/v4/test", func(c iris.Context) { var test Test2 if err := c.ReadJSON(&test); err == nil { app.Logger().Println("========================start io=====================") time.Sleep(time.Duration(1) * time.Second) app.Logger().Println("=========================end io=======================") c.JSON(test) } else { c.WriteString("failure") } }) // Start the server using a network address. app.Run( iris.Addr(":8080"), // disables updates: iris.WithoutVersionChecker, // skip err server closed when CTRL/CMD+C pressed: iris.WithoutServerError(iris.ErrServerClosed), // enables faster json serialization and more: iris.WithOptimizations)}
echo:
package mainimport ( "log" "net/http" "time" "github.com/labstack/echo" "github.com/labstack/echo/middleware")// Agent ...type Agent struct { AgentID string `json:"agent_id"` QueuedAt string `json:"queued_at"` QueuedBy string `json:"queued_by"`}// Details ...type Details struct { EventID string `json:"event_id"` Endpoint string Metric string Content string Priority int Status string}// Test1 ...type Test1 struct { Agent Agent Details Details Description string EventType string `json:"event_type"` ServiceKey string `json:"service_key"`}// Test2 test2type Test2 struct { Data []*Test1}func main() { // Echo instance app := echo.New() // Middleware app.Use(middleware.Logger()) // Routes app.GET("/ping", func(c echo.Context) error { return c.String(200, "pong") }) app.POST("/v1/test", func(c echo.Context) error { var test Test1 if err := c.Bind(&test); err != nil { return err } log.Println("========================start io=====================") time.Sleep(time.Duration(1) * time.Second) log.Println("=========================end io=======================") return c.JSON(http.StatusOK, test) }) app.POST("/v2/test", func(c echo.Context) error { var test Test2 if err := c.Bind(&test); err != nil { return err } log.Println("========================start io=====================") time.Sleep(time.Duration(1) * time.Second) log.Println("=========================end io=======================") return c.JSON(http.StatusOK, test) }) app.POST("/v3/test", func(c echo.Context) error { var test Test2 if err := c.Bind(&test); err != nil { return err } log.Println("========================start io=====================") time.Sleep(time.Duration(1) * time.Second) log.Println("=========================end io=======================") return c.JSON(http.StatusOK, test) }) app.POST("/v4/test", func(c echo.Context) error { var test Test2 if err := c.Bind(&test); err != nil { return err } log.Println("========================start io=====================") time.Sleep(time.Duration(1) * time.Second) log.Println("=========================end io=======================") return c.JSON(http.StatusOK, test) }) // Start server app.Logger.Fatal(app.Start(":8080"))}
- 以上除了echo之外,其他三個都原生支援了jsoniter 這個效能的json序列化庫,都啟用。
- 等待1s,類比io讀寫等待
測試對比
由於要測試5種body樣本,4種情境,4個架構,因此把重點資料篩選出來(輸送量、錯誤率和99%Line,重要性依次遞減),結果都繪製了圖形,方便比對查看。
565bytes下測試結果
5KB下測試結果
10KB下測試結果
50KB下測試結果
100KB下測試結果
總結
綜合以上各個測試結果可以看出,gin以及iris都是非常優秀的架構,gin的優勢比其他稍微大點,iris次之,而echo相應差一點。
本次測試只是簡單測試了一下3個架構的並發和json相關。對比結果,不包括生態和工具的完善度等等。如果測試有什麼不完善的地方,歡迎交流。
另外歡迎大家試用和star另外一個web架構baa,為了避嫌我沒有貼出baa的資料,效能測試處於gin之後和iris之間。