Go语言 基于gin框架从0开始构建一个bbs server(七)- 接口文档,限流,单元测试,压测

源码地址

今天这篇文章涉及到的编码内容很少,主要是介绍一个项目上线之前 需要做哪些工作,新手可以看看这篇文章以后心里有个底,有需要的话 这文章里面每一个方向 都值得花时间 深入搞一下

生成接口文档

github.com/swaggo/swag

使用gin框架的 swagger gin swagger

具体写注释 可以参照 注释api

首先要 安装

go install github.com/swaggo/swag/cmd/swag@latest
复制代码

其次 我们就可以参照文档 来写注释了

就以我们的登录接口为例

可以定义一组专门的struct 来代表参数 当然你也可以直接用项目里的那些成熟的struct 都可以

package controllers

//c.JSON(http.StatusOK, &Response{
//Code: CodeSuccess,
//Msg:  CodeSuccess.Msg(),
//Data: data,
//})

type _ResponseLogin struct {
   Code int64 `json:"code"` // 业务状态响应码
   Message string `json:"message"` //提示信息
   Data  string `json:"data"` // token
}

type _RequestLogin struct {
   // 用户名
   Username string `json:"username"`
   //密码
   Password string `json:"password"`
}
复制代码
// LoginHandler 用户登录接口
// @Router /api/v1/login [post]
// @Summary 登录接口
// @Accept application/json
// @Produce application/json
// @Param login body _RequestLogin true "需要上传的json"
// @Success 200 {object} _ResponseLogin
func LoginHandler(c *gin.Context) {
   p := new(models.ParamLogin)

   if err := c.ShouldBindJSON(p); err != nil {
      zap.L().Error("LoginHandler with invalid param", zap.Error(err))
      // 因为有的错误 比如json格式不对的错误 是不属于validator错误的 自然无法翻译,所以这里要做类型判断
      errs, ok := err.(validator.ValidationErrors)
      if !ok {
         ResponseError(c, CodeInvalidParam)
      } else {
         ResponseErrorWithMsg(c, CodeInvalidParam, removeTopStruct(errs.Translate(trans)))
      }
      return
   }

   // 业务处理
   token, err := logic.Login(p)
   if err != nil {
      // 可以在日志中 看出 到底是哪些用户不存在
      zap.L().Error("login failed", zap.String("username", p.UserName), zap.Error(err))
      if errors.Is(err, mysql.WrongPassword) {
         ResponseError(c, CodeInvalidPassword)
      } else {
         ResponseError(c, CodeServerBusy)
      }
      return
   }
   ResponseSuccess(c, token)
}
复制代码

在main函数 这里 不要忘记加一些基本的说明

image.png

写好了以后 就可以用swag init 来生成文档了

默认是生成在这个路径下:

image.png

然后去我们生成路由的文件哪里 先import

gs "github.com/swaggo/gin-swagger"
 "github.com/swaggo/gin-swagger/swaggerFiles"
_ "go_web_app/docs" // 千万不要忘了导入把你上一步生成的docs

复制代码

然后添加一下对应的路由


//swagger 接口文档
// http://localhost:你的端口号/swagger/index.html 可以看到接口文档
r.GET("/swagger/*any", gs.WrapHandler(swaggerFiles.Handler))

复制代码

然后跑起来看一下:

image.png

还是挺方便的

另外一定要注意对于swagger里面使用的 struct来说 首字母 一定要大写,否则 swag展示不出来正确的参数

最后就是我们还可以修改一下air的配置 让他支持 文档修改以后 自动重启web服务,很方便,要注意的是命令的变化

image.png

单元测试

其实就是新建一个 xxx_test.go 的文件 然后直接跑就行了

package controllers

import (
   "bytes"
   "encoding/json"
   "github.com/gin-gonic/gin"
   "github.com/stretchr/testify/assert"
   "net/http"
   "net/http/httptest"
   "testing"
)

func TestCreatePostHandler(t *testing.T) {
   gin.SetMode(gin.TestMode)
   url := "/api/v1/post"
   r := gin.Default()
   r.POST(url, CreatePostHandler)
   w := httptest.NewRecorder()
   body := `{
    "title":"新增呢2",
    "content":"vivo比华1231为好多了",
    "community_id":2
         }`
   req, _ := http.NewRequest(http.MethodPost, url, bytes.NewReader([]byte(body)))
   r.ServeHTTP(w, req)

   assert.Equal(t, 200, w.Code)
   // 判断响应中 是不是包含了 未登录的 错误信息
   assert.Contains(t, w.Body.String(), "未登录")
   // 也可以继续用下面的方法来判定
   res := new(Response)
   err := json.Unmarshal(w.Body.Bytes(), res)
   if err != nil {
      t.Fatalf("json unmarshal failed: %v", err)
   }
   assert.Equal(t, res.Code, CodeNoLogin)
}
复制代码

这里唯一要注意的是,如果你是命令行跑,最好是直接跑某个包下面的,否则会很麻烦,因为你的test代码里面 引用不到其他文件里的代码。

压力测试

压力测试 我们需要 知道下面几个概念

响应时间RT 吞吐量 qps tps 并发连接数

这5个指标 大家可以自行百度查询对应的含义

Apache Bench

apache 出的 压测工具

wrk

开源的压测工具,很好用,通过lua脚本来编写复杂的场景

可以验证下我们的本机服务

image.png

wrk -t8 -c100 -d30s --latency http://127.0.0.1:8016/api/v1/postlist?pageSize=10

限流

uber的这个很好理解,其实就是 不管你输入有多大,但是我输出始终就是那么多,很像以前小时候打酱油的时候,瓶子口那里插个漏斗,然后师傅往漏斗里面灌就行了。

但是这个有一个缺点就是 如果突发性的场景 可能不太适合,比如12点来个秒杀活动? 访问量大增,结果这个时候漏斗把大部分请求 都暂时挡在外面了,等过会轮到执行的时候 秒杀都结束了。

uber的漏斗限流算法

令牌桶限流算法

这个很好理解,就是你一开始设计一个令牌桶,比如里面做1000个牌,每个请求来的时候 如果有牌子 就直接去处理请求,没有牌子 就等一下。

这种就比之前的漏斗模型 要好很多,不会像漏斗算法那样 那么粗暴

令牌桶算法实现

在gin框架中 使用限流中间件

package middleware

import (
   "github.com/gin-gonic/gin"
   "github.com/juju/ratelimit"
   "net/http"
   "time"
)

// RateLimitMiddleware 每fillInterval 秒 自动添加 cap 个数的令牌 注意参数 要用Time.Second 否则就是2ns了
func RateLimitMiddleware(fillInterval time.Duration, cap int64) func(c *gin.Context) {
   bucket := ratelimit.NewBucket(fillInterval, cap)
   return func(c *gin.Context) {
      // 如果取不到令牌 就直接返回吧
      if bucket.TakeAvailable(1) == 0 {
         c.String(http.StatusOK, "rate limit")
         c.Abort()
         return
      }
      c.Next()
   }
}
复制代码

性能统计

其实go语言已经自带了很不错的性能统计工具了,这里我们因为使用gin框架,为了方便 就直接用gin下的性能统计工具 大同小异 都是一样的

"github.com/gin-contrib/pprof"
复制代码

然后在我们注册路由的地方 加上一行代码就可以了:

image.png

然后开启我们的web服务

访问 如下地址 即可(端口号就是你的server)

http://localhost:8016/debug/pprof/

image.png

这个就是告诉你 提供了哪些分析

然后我们就可以在命令行中 分析了

终端输入:

go tool pprof http://127.0.0.1:8016/debug/pprof/profile

然后等待30s(你可以在这30s内访问你的server 试试看)

30s结束以后:

image.png

top3 就可以查看 这段时间内 耗时最长的函数 image.png

当然你也可以用图形化的方式 来查看

brew install graphviz

然后 再在输入框内 输入web 就可以直接看到svg图片了很直观

也可以利用 go torch 火焰图来看。

猜你喜欢

转载自juejin.im/post/7038188270806630436