前言
由于golang提供了完善的net/http標準庫捅位,基于該標準庫實現(xiàn)一個web框架的難度相比其他語言低了不少劳翰,所以go web框架簡直就是百花齊放弥喉。從老牌的revel和beego兔沃,到新出的gin,和iris等,而且還有一些類似于chi這種router颠锉。個人一般小項目法牲,尤其是中間件需要暴露一些http接口的,基本就使用chi即可琼掠。
本次測試主要是gin iris echo 這三個框架拒垃。側(cè)重在于高性能,從并發(fā)和json序列化和反序列化兩個方面來測評瓷蛙,畢竟后臺項目側(cè)重的也就是這兩個方面悼瓮。
測試
測試環(huán)境說明
為了選擇符合重IO的框架,現(xiàn)設定如下場景的demo艰猬,demo的具體要求如下:
- 打開日志功能(模擬正常業(yè)務時也會記錄日志)横堡,在請求開始和結(jié)束時分別記錄一條日志
- 接口中用sleep暫停1秒,假設這里的網(wǎng)絡IO操作(同時更容易從日志看出是否協(xié)程并發(fā)的行為)
- 用POST接口做測試冠桃,接口中不進行任何處理命贴,把接收到的body直接序列化返回(序列化和反序列化是框架最高頻的動作)
- 打開框架的accesslog功能
測試工具以及場景如下
- 測試工具使用經(jīng)典的jmeter,直接使用GUI界面測試
- 場景分為10線程并發(fā)食听,100線程并發(fā)胸蛛,500線程并發(fā),1000線程并發(fā)和1500線程并發(fā)
- 所有結(jié)果都只看jmeter的聚合報告樱报,重點查看吞吐量葬项、時間和錯誤數(shù)
- 所有demo啟動的時候均啟動單線程,異步框架不限制協(xié)程的數(shù)量迹蛤,設置GOMAXPROCS=1
- 所有測試均在本地民珍,壓測時長兩分鐘
- 測試時采用POST請求,數(shù)據(jù)樣本有565bytes笤受、5KB穷缤、10KB敌蜂、50KB和100KB箩兽,每個樣本都要在不同并發(fā)線程上測試
測試代碼
gin:
package main
import (
"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 test2
type 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 main
import (
"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 test2
type 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 main
import (
"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 test2
type 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個框架,因此把重點數(shù)據(jù)篩選出來(吞吐量摊唇、錯誤率和99%Line咐蝇,重要性依次遞減),結(jié)果都繪制了圖形巷查,方便比對查看有序。
565bytes下測試結(jié)果
5KB下測試結(jié)果
10KB下測試結(jié)果
50KB下測試結(jié)果
100KB下測試結(jié)果
總結(jié)
綜合以上各個測試結(jié)果可以看出抹腿,gin以及iris都是非常優(yōu)秀的框架,gin的優(yōu)勢比其他稍微大點旭寿,iris次之警绩,而echo相應差一點。
本次測試只是簡單測試了一下3個框架的并發(fā)和json相關(guān)盅称。對比結(jié)果肩祥,不包括生態(tài)和工具的完善度等等。如果測試有什么不完善的地方缩膝,歡迎交流混狠。
另外歡迎大家試用和star另外一個web框架baa,為了避嫌我沒有貼出baa的數(shù)據(jù)逞盆,性能測試處于gin之后和iris之間檀蹋。