实践记录:go-zero从访问请求到数据库的过程| 青训营

159 阅读7分钟

0.使用go-zero整体概述

这篇实践笔记主要详细记录了使用go-zero框架如何完成从请求到数据库访问的流程。

接下来会以点赞功能为例子。在青训营的大项目中,我们将点赞服务放在归为内容服务content下,目录结构如下图所示:

image.png

概述流程:我们的项目采用的是微服务的框架,因此对于每一个服务(比如点赞所在的service/content)会将整个业务逻辑分为API和RPC两部分。当用户发起请求时,通过网关到API中的对应handler中,进一步的调用API中的logic业务;而对于数据库的访问和操作则完全交给微服务也就是RPC来做。API会在自己的逻辑中调用需要用到的RPC服务,最后完成业务逻辑。

1. go-zero中目录结构的建立

go-zero是一款基于grpc的微服务框架。在使用其开发程序的时候,可以利用框架自带的功能为我们搭建好整个框架的目录结构,以及一些固定的文件之间调用关系和配置。首先我们就来介绍一下对于我们项目中的content服务,如何生成API和RPC这两部分的文件目录。

(1)编写content.api文件,生成API文件目录。

  • 以点赞操作为例子,首先我们需要自己写API文件,在其中按照如下的type语法格式写出与点赞相关的请求和返回结构体。
type (
    FavoriteActionReq {
        Token string `form:"token"`           // 用户鉴权token
        VideoId int64 `form:"video_id"`       // 视频id
        ActionType int32 `form:"action_type"` // 1-点赞,2-取消点赞
    }
​
    FavoriteActionResp {
        StatusCode int32 `json:"status_code"`         // 状态码,0-成功,其他值-失败
        StatusMsg string `json:"status_msg,optional"` // 返回状态描述
    }
)
​
type (
    FavoriteListReq {
        Token string `form:"token"`    // 用户鉴权token
        UserId int64 `form:"user_id"`  // 用户id
    }
    FavoriteListResp {
        StatusCode int32 `json:"status_code"`         // 状态码,0-成功,其他值-失败
        StatusMsg string `json:"status_msg,optional"` // 返回状态描述
        VideoList []Video `json:"video_list"`        // 视频列表
    }
)
  • 接下来建立service content { }在其中声明其对应的handler名称;
  • 指定我们在content服务的请求格式(get/post/put等等)和路由前缀,并将上面写的结构体作为请求的传入和返回参数写在里面;
  • 最后在service content上面使用@server()
    • 在其中使用middleware:可以一起指定加在刚才content中所有功能的中间件;
    • 使用prefix:可以为刚才content中所有功能指定相同的路由前缀;
    • 使用group:可以将content中写的这些服务一会儿生成api时放在同一个自定义名字的文件夹下,便于管理;
@server(
  middleware: JwtAuthMiddleware
  prefix: douyin/favorite
  group : favorite
)
service content {
  @handler favoriteAction
  post /action (FavoriteActionReq) returns (FavoriteActionResp)
​
  @handler favoriteList
  get /list (FavoriteListReq) returns (FavoriteListResp)
}
  • 之后使用下方的命令,在*.api处填上刚才api文件的名字content.api,就可以完成API目录的生成。
goctl api go -api *.api -dir ../  --style=goZero
  • 目录的结构如下图所示,其中的第三方依赖加载、路由等文件及逻辑都已经由go-zero帮我们写好了。我们后面只需要在生成的 internal/logic/favorite 生成好的xxxLogic.go中,编写API的业务逻辑就可以了。 image.png (2)编写content.proto文件,生成RPC文件目录。

  • 生成RPC文件目录的过程和生成API的过程基本相同,区别之处在于RPC中需要进行数据库操作,因此我们要额外生成与数据库进行交互的model文件。

  • 对于一个微服务框架,.proto文件用来传递微服务的数据,因此同样类似API需要些结构体以及xxxReq和xxxResp。

  • 仍然继续点赞操作的例子,我们需要编写 message Favorite {}结构,并编写与favorite表项修改相关的 message AddFavoriteReq {}、message DelFavoriteResp {} 等请求结构。当编写好后可以由content.proto生成RPC文件目录。由于编写proto文件相对来讲比较繁琐,所以可以使用其他人开发的脚本来通过已有的数据库一键生成这样的.proto文件,也避免了自己写的错误。数据库生成proto文件的命令如下(具体的sql2pb脚本可以直接github找到并安装到本地环境):

sql2pb -go_package ./pb -host 43.136.243.177 -package pb -password liujun -port 3306 -schema liujun_user -service_name content -content root > content.proto
message Favorite {
  int64 id = 1;
  int64 videoId = 2;
  int64 userId = 3;
  int64 createTime = 4;
  int64 updateTime = 5;
}
​
message AddFavoriteReq {
  int64 videoId = 1;
  int64 userId = 2;
  int64 isDelete = 3;
}
​
message AddFavoriteResp {
}
​
message UpdateFavoriteReq {
  int64 id = 1;
  int64 isDelete = 2;
}
  • 之后使用下方的命令,在*.content处填上刚才proto文件的名字content.proto,就可以完成RPC目录的生成。
goctl rpc protoc *.proto --go_out=../ --go-grpc_out=../  --zrpc_out=../ --style=goZero
  • 目录的结构如下图所示,其中的第三方依赖加载和微服务之间的通信配置等都已经由go-zero帮我们写好了。我们后面只需要在生成的 internal/logic 生成好的xxxLogic.go中,编写RPC的业务逻辑并操作数据库就可以了。

image.png

  • 虽然有了RPC目录,但是还有接下来RPC生成中的最后一步,就是生成与数据库交互的框架代码。go-zero框架本身就提供了orm的数据库管理功能。使用命令可以自动为我们创建model文件,在model文件中就由go-zero自动生成了与数据库交互的增删改查。
goctl model mysql datasource -url="${username}:${passwd}@tcp(${host}:${port})/${dbname}" -table="${tables}"  -dir="${modeldir}" -cache=true --style=goZero
  • 生成的model代码如下所示,这样我们只需要在前面的xxxLogic.go中调用model里面操作数据库的方法就可以实现微服务的业务。(图片中的comment相关的文件忽略,这里只描述点赞功能的逻辑,其余功能同理)

2. go-zero中从请求到数据库的完整流程。

接下来将会以点赞操作为例子,说明go-zero中是如何完成一个从请求到数据库操作的逻辑全过程的。

(1)首先启动项目。运行API文件下的content.go启动当前的 content 服务,首先展示一下我们在其中操作的文件目录以及启动的main.go文件的样子,如下图:

image.png

image.png

  • 在这个过程中会做三件事情
    • 第一件事情是读取api/etc/content.yaml文件,将其中的配置加载到api/internal/config/config.go文件中,这个配置文件中配置的是我们要用到的如redis等中间件,以及如何和RPC服务通信。对于这个过程,在点赞中,我们选择使用 etcd 与cotent 中的rpc服务通信,因此要在这里配置一下RPC中的服务以便API通过etcd通信并调用RPC中的服务: image.png 之后对应的在config.go中命名配置中的对象(因为只是举例点赞操作,除了ContentRpcConf,其余的都可以忽略): image.png

    • 第二件事情我们根据前面的config来起一个server;

    • 第三件事情是在这个server中加入svc/svcContext中的第三方依赖,并注册我们的handler。以点赞为例,同上面config一样我们也要把ContentRpcConf在这里引入: image.png

  • 从前端根据前面定义的路径发起一个点赞请求: image.png

(2)接下来将会描述请求在API中依次经过的路径,文件目录如下所示: image.png

  • 请求会首先到达API中internal/handler中的route.go 文件根据请求格式和路径寻找到对应handler的路由
//route.go
server.AddRoutes(
    rest.WithMiddlewares(
      []rest.Middleware{serverCtx.JwtAuthMiddleware},
      []rest.Route{
        {
          Method:  http.MethodPost,
          Path:    "/action",
          Handler: favorite.FavoriteActionHandler(serverCtx),
        },
        {
          Method:  http.MethodGet,
          Path:    "/list",
          Handler: favorite.FavoriteListHandler(serverCtx),
        },
      }...,
    ),
    rest.WithPrefix("/douyin/favorite"),
  )
  • 之后请求会到达internal/handler/favoriteAcitonHandler.go的文件下,在其中会做两件事情: image.png

    • 如图,第一件事情是到svc/serviceContext.go文件下取出对应的中间件等依赖;
    • 第二件事情是调用 internal/logic/favoriteActionLogic.go中的逻辑,在这个逻辑中我们会通过在config中注册的ContentRpcConf来通过etcd调用RPC中对应logic的服务。到目前为止就完成了点赞在API中的全部流程,接下来进入到在RPC中的调用。
  • 到rpc中的流程与API中基本相同就不再赘述,只描述多出来的访问数据库的部分。此时API已经调用了RPC/logic中的某个或某几个服务。以点赞服务为例此时API将会需要调用其中的addFavorite、delFavorite逻辑,我们继续在该逻辑中向下直到访问数据库。 image.png

  • 对于favoriteAction中的数据库的调用语句,都生成并存储到了model中的favoriteModel_gen.go中。我们将会从logic中调用这里的语句,以点赞中的新增点赞为例子,我们将会调用其中的insert语句对数据库执行插入操作。其中sql语句已经在insert方法中写好,至此就完成了从请求到数据库的插入过程。 image.png