美文网首页
Golang web 框架对比

Golang web 框架对比

作者: 蔡欣圻 | 来源:发表于2022-02-16 23:15 被阅读0次

    前言

    由于golang提供了完善的net/http标准库,基于该标准库实现一个web框架的难度相比其他语言低了不少,所以go web框架简直就是百花齐放。从老牌的revel和beego,到新出的gin,和iris等,而且还有一些类似于chi这种router。个人一般小项目,尤其是中间件需要暴露一些http接口的,基本就使用chi即可。
    本次测试主要是gin iris echo 这三个框架。侧重在于高性能,从并发和json序列化和反序列化两个方面来测评,毕竟后台项目侧重的也就是这两个方面。

    测试

    测试环境说明

    为了选择符合重IO的框架,现设定如下场景的demo,demo的具体要求如下:

    1. 打开日志功能(模拟正常业务时也会记录日志),在请求开始和结束时分别记录一条日志
    2. 接口中用sleep暂停1秒,假设这里的网络IO操作(同时更容易从日志看出是否协程并发的行为)
    3. 用POST接口做测试,接口中不进行任何处理,把接收到的body直接序列化返回(序列化和反序列化是框架最高频的动作)
    4. 打开框架的accesslog功能

    测试工具以及场景如下

    1. 测试工具使用经典的jmeter,直接使用GUI界面测试
    2. 场景分为10线程并发,100线程并发,500线程并发,1000线程并发和1500线程并发
    3. 所有结果都只看jmeter的聚合报告,重点查看吞吐量、时间和错误数
    4. 所有demo启动的时候均启动单线程,异步框架不限制协程的数量,设置GOMAXPROCS=1
    5. 所有测试均在本地,压测时长两分钟
    6. 测试时采用POST请求,数据样本有565bytes、5KB、10KB、50KB和100KB,每个样本都要在不同并发线程上测试

    测试代码

    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个框架,因此把重点数据筛选出来(吞吐量、错误率和99%Line,重要性依次递减),结果都绘制了图形,方便比对查看。

    565bytes下测试结果

    图片描述
    图片描述
    图片描述

    5KB下测试结果

    图片描述
    图片描述
    图片描述

    10KB下测试结果

    图片描述
    图片描述
    图片描述

    50KB下测试结果

    图片描述
    图片描述
    图片描述

    100KB下测试结果

    图片描述
    图片描述
    图片描述

    总结

    综合以上各个测试结果可以看出,gin以及iris都是非常优秀的框架,gin的优势比其他稍微大点,iris次之,而echo相应差一点。
    本次测试只是简单测试了一下3个框架的并发和json相关。对比结果,不包括生态和工具的完善度等等。如果测试有什么不完善的地方,欢迎交流。
    另外欢迎大家试用和star另外一个web框架baa,为了避嫌我没有贴出baa的数据,性能测试处于gin之后和iris之间。

    相关文章

      网友评论

          本文标题:Golang web 框架对比

          本文链接:https://www.haomeiwen.com/subject/mtcjlrtx.html