Golang web 框架對比

前言

由于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的具體要求如下:

  1. 打開日志功能(模擬正常業(yè)務時也會記錄日志)横堡,在請求開始和結(jié)束時分別記錄一條日志
  2. 接口中用sleep暫停1秒,假設這里的網(wǎng)絡IO操作(同時更容易從日志看出是否協(xié)程并發(fā)的行為)
  3. 用POST接口做測試冠桃,接口中不進行任何處理命贴,把接收到的body直接序列化返回(序列化和反序列化是框架最高頻的動作)
  4. 打開框架的accesslog功能

測試工具以及場景如下

  1. 測試工具使用經(jīng)典的jmeter,直接使用GUI界面測試
  2. 場景分為10線程并發(fā)食听,100線程并發(fā)胸蛛,500線程并發(fā),1000線程并發(fā)和1500線程并發(fā)
  3. 所有結(jié)果都只看jmeter的聚合報告樱报,重點查看吞吐量葬项、時間和錯誤數(shù)
  4. 所有demo啟動的時候均啟動單線程,異步框架不限制協(xié)程的數(shù)量迹蛤,設置GOMAXPROCS=1
  5. 所有測試均在本地民珍,壓測時長兩分鐘
  6. 測試時采用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"))}
  1. 以上除了echo之外,其他三個都原生支持了jsoniter 這個性能的json序列化庫章喉,都啟用汗贫。
  2. 等待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之間檀蹋。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市云芦,隨后出現(xiàn)的幾起案子俯逾,更是在濱河造成了極大的恐慌,老刑警劉巖舅逸,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桌肴,死亡現(xiàn)場離奇詭異,居然都是意外死亡琉历,警方通過查閱死者的電腦和手機坠七,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旗笔,“玉大人彪置,你說我怎么就攤上這事∮瘢” “怎么了拳魁?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長撮弧。 經(jīng)常有香客問我潘懊,道長,這世上最難降的妖魔是什么贿衍? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任授舟,我火速辦了婚禮,結(jié)果婚禮上贸辈,老公的妹妹穿的比我還像新娘释树。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布奢啥。 她就那樣靜靜地躺著署浩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扫尺。 梳的紋絲不亂的頭發(fā)上筋栋,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音正驻,去河邊找鬼弊攘。 笑死,一個胖子當著我的面吹牛姑曙,可吹牛的內(nèi)容都是我干的襟交。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼伤靠,長吁一口氣:“原來是場噩夢啊……” “哼捣域!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起宴合,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤焕梅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后卦洽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贞言,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年阀蒂,在試婚紗的時候發(fā)現(xiàn)自己被綠了该窗。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蚤霞,死狀恐怖酗失,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情昧绣,我是刑警寧澤规肴,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站滞乙,受9級特大地震影響奏纪,放射性物質(zhì)發(fā)生泄漏鉴嗤。R本人自食惡果不足惜斩启,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望醉锅。 院中可真熱鬧兔簇,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至狸窘,卻和暖如春墩朦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背翻擒。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工氓涣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人陋气。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓劳吠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親巩趁。 傳聞我的和親對象是個殘疾皇子痒玩,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容