这是我参与「第五届青训营 」笔记创作活动的第5天。
前言
本次青训营中我觉得最为重要,最为基础的一节课了,大项目的实现全靠这哥仨。由于不熟悉Go语言,因此要以Java中Spring生态的框架在Go中模拟开发大项目,Gorm相当于MyBatis,Kitex差不多是SpingClond,Hertz是WEB框架,对应SpringMVC。因此该节课非常重要,认真做好笔记!
三件套介绍
Gorm
简介
Gorm 是一个已经迭代了10年+的功能强大的 ORM框架,在字节内部被广泛使用并且拥有非常丰富的开源扩展 文档介绍
基本使用
- 定义数据库bean结构体
- 为结构体实现一个名为TableName的返回表名字符串的函数来绑定表名
- 连接数据库:db = gorm.Open(mysql.Open(dsn),&gorm.Config{自定义参数})
- 添加行:db.Create(&结构体或者切片)
- 查询单行记录:db.First(&结构体)默认查询第一条数据或者db.First(&放结果的结构体,int主键)或者db.First(&放结果的结构体,条件"code = ?",参数值),会存在问题
- 更新数据单个字段:db.Model(&结构体)。Update(字段名,值)
- 更新数据多个字段:
- db.Model(&结构体)。Update(结构体)//仅更新非零值怎么办
- db.Model(&结构体)。Update(map[string]interface{})//可以更新非零值
- 删除行:db.Delete(&结构体,int主键)或者db.Delete(&结构体,条件"code = ?",参数值)类似查询
gorm约定
- Gorm 使用名为 ID 的字段 作为主键
- 没有实现TableName函数的时候使用结构体的蛇形负数作为表名
- 字段名的蛇形作为列名
- 使用 CreatedAt、UpdatedAt 字段作为创建更新时间
gorm支持的数据库
- GORM 目前支持 MySQL、SQLServer、PostgreSQL、SQLite.GORM 通过驱动来连接数据库,如果需要连接其它类型的数据库,可以复用/自行开发驱动。连接到数据库的DSN
- 连接SQLServer等使用适合的DSN
创建数据
- 定义数据库bean结构体(没有实现TableName使用结构体名的蛇形负数)
- go tag: gorm
- "colomn:列名" 可以让结构体成员属性名与字段名不一致
- "default:value"
- gorm是链式调用,会返回结果res
- res.Errot获取是否有err
- p.ID获取回填自增主键
常见case
- 出现唯一索引冲突:使用 clause.OnConflict 处理数据冲突
p := &Product{
ID: 0,
Code: "042",
}
db.Clauses(clause.OnConflict{DoNothing: true}).Create(&p)
- 设置默认值:通过使用 default 标签为字段定义默认值
查询详细
- 查询数据的注意点查询文档
- First默认查询第一条,查不到返回ErrRecordNotFound
- 建议使用 Find 查询多条数据,查询不到数据不会返回错误
- Find查询:result := db.Where(query条件).Find(&切片地址)
- result.RowsAffected查询找到的记录数,相当于len(切片)
- result.Error获取是否有错误
- Find复杂查询:IN/LIKE/AND/传结构体地址体条件查询(非零值)/map
- 当使用结构作为条件查询时,GORM 只会查询非零值字段。这意味着如果您的字段值为 0、"、false 或其他 零值,该字段不会被用于构建查询条件,使用Map 来构建查询条件。
更新数据
- 更新数据时的注意点更新文档
- 如果Updates()不是传递结构体的话就需要使用db.Model()传递表名,有也可以用Model传递表名
- 没有where就会直接使用Model中结构体的非零值作为条件
- Select去选定要更新的字段名
- 使用 Struct 更新时,只会更新非零值,如果需要更新零值可以使用 Map 更新或使用Select 选择字段
删除数据
- 物理删除与软删除删除文档
- 物理删除:执行的话数据库中是真的删除了
- db.Delete(&结构体,主键)
- db.Delete(&结构体,切片)来批量删除IN
- db.Where("name like ?",值).Delete(&结构体获取表名)
- 实际中建议使用软删除
- GORM 提供了 gorm.DeletedAt 用于帮助用户实现软删
- 拥有软删除能力的 Model 调用 Delete 时,记录不会被从数据库中真正删除。但 GORM 会将 DeletedAt 置为当前时间,并且你不能再通过正常的查询方法找到该记录。
- 使用 Unscoped 可以查询到被软删的数据
GORM事务
- Gorm 提供了 Begin、Commit、Rollback 方法用于使用事务事务文档
- tx:=db.Begin()开启事务,返回db的事务对象,固化连接,不会去复用其他连接池中的连接了,因此要用这个db对象
- tx.RollBack()
- tx.Commit()
- 但是好像都需要手动调用方法,可能会忘记导致连接泄露
- Gorm 提供了 Tansaction 方法用于自动提交事务,避免用户漏写 Commit、Rollbcak。
- db.Transaction(func(tx) error),当放回error时会自动回滚,nil则提交
- db.Transaction(func(tx) error),当放回error时会自动回滚,nil则提交
Gorm Hook
- GORM 在 提供了 CURD 的 Hook 能力。Hook 是在创建、查询、更新、删除等操作之前、之后自动调用的函数。hook文档
- 如果任何 Hook 返回错误,GORM 将停止后续的操作并回滚事务。(gorm默认开启事务,能保证一致性,但是性能会降低,可以使用下面性能提高中的方法关闭)
性能提高
- 对于写操作(创建、更新、删除》 ,为了确保数据的完整性,(GORM 会将它们封装在事务内运行。但这会降低性能,你可以使用SkipDefaultTransaction 关闭默认事务。
- 使用 PrepareStmt 缓存预编译语句可以提高后续调用的速度,李老师机器测试提高大约 35 %左右。
dsn := "username:password@tcp(localhost:3306)/database?charset=utf-8"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
SkipDefaultTransaction: true,
PrepareStmt: true,
})
GORM生态
GORM 拥有非常丰富的扩展生态,以下列举一部分常用扩展
| GORM扩展名 | 地址 | 作用 |
|---|---|---|
| GORM 代码生成工具 | github.com/go-gorm/gen | 快速生成CRUD代码确保数据安全 |
| GORM 分片库方案 | github.com/go-gorm/sha… | 使用分片库 |
| GORM 手动索引 | github.com/go-gorm/hin… | 提高性能手动选择索引 |
| GORM 乐观锁 | github.com/go-gorm/opt… | 使用数据库实现乐观锁 |
| GORM 读写分离 | github.com/go-gorm/dbr… | 读写分离提高性能 |
| GORM OpenTelemetry 扩展 | github.com/go-gorm/ope… | 链路追踪和监控 |
关于更多的 GORM 用法可以查看 Gorm 的文档(gorm.cn)
Kitex
Kitex 是字节内部的Golang 微服务 RPC 架具有高性能、强可扩展的主要特点,支持多协议并且拥有丰富的开源扩展
Kitex服务注册与发现
目前 Kitex 的服务注册与发现已经对接了主流了服务注册与发现中心,如 ETCD,Nacos 等。
Kitex拓展
Kitex 拥有非常丰富的扩展生态,以下列举一部分常用扩展
| 拓展 | 地址 |
|---|---|
| XDS 扩展 | github.com/kitex-contr… |
| opentelemetry 扩展 | https://githubcom/kitex-contrib/obs-opentelemetry |
| ETCD 服务注册与发现扩展 | github.com/kitex-contr… |
| Nacos 服务注册与发现扩展 | github.com/kitex-contr… |
| Zookeeper 服务注册与发现扩展 | github.com/kitex-contr… |
| polaris 扩展 | github.com/kitex-contr… |
| 丰富的示例代码与业务Demo | github.com/cloudwego/k… |
Hertz
Hertz 是字节内部的 HTTP框架,参考了其他开源框架的优势,结合字节跳动内部的需求,具有高易用性、高性能、高扩展性特点。Hertz快速开始
基本使用
func main() {
h := server.Default()
h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
})
h.Spin()
}
- 为什么使用两个上下文:根据实际中出现的问题,分为两个,一个专注于传递元信息,一个专注于做请求和处理
Hertz路由
- Hertz 提供了 GET、POSTPUT、DELETE、ANY 等方法用于注册路由,Handler自定义HTTP方法。h.GET / POST / PUT /ANY
- Hertz 提供了路由组( Group)的能力,用于支持路由分组的功能。v1=h.Group("/prefix");v1.POST()
- Hertz 提供了参数路由和通配路由,路由的优先级为: 静态路由 > 命名路由 > 通配路由
- 参数路由:h.GET("/path/:id",func);c.Param("id")获取参数
- 通配路由:h.GET("/path/:id/*action ",func);c.Param("action")。匹配的范围更大,action后可以有子路径
参数绑定
Hertz 提供了 Bind、Validate、BindAndValidate 数用于进行参数绑定和校验。将HTTP中的请求参数转到结构体中
- 定义参数结构体时使用tag来标记这个成员要从哪里获取参数,如query/path/header/form/json见代码
- 参数校验的话使用vd tag来进行校验(得具体了解语法)
- BindAndValidate()需要回写,传指针
Hertz中间件
Hertz 的中间件主要分为客户端中间件与服务端中间件,如下展示一个服务端中间件
- 通用的操作:如日志记录、计算接口耗时、元信息的设置和传递(例如springMVC中的拦截器)
- 实现的话自定义一个无参数函数,返回app.HandleFunc并实现函数即可
- RequestContext.Next()可以下一步执行,并以此分为前置处理和后置处理
- 终止中间件调用链的执行:Abort()AbortWithMsg()AbortWlthStats()
- 在server中注册调用:
- 全局注册:h.Use(函数名())
- 局部注册:为路由v1或者为路径绑定,v1.Use(函数名())
func main() {
h := server.Default()
//全局绑定中间件
h.Use(MyMiddleware())
h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
})
//局部路由中间件绑定
group1 := h.Group("/route")
group1.Use(MyMiddleware2())
{
group1.GET("/ping")
}
h.Use(MyMiddleware())
h.Spin()
}
func MyMiddleware() app.HandlerFunc {
return func(c context.Context, ctx *app.RequestContext) {
//pre-handle
fmt.Println("pre-handle")
ctx.Next(c)
//post-handle
fmt.Println("post-handle")
}
}
func MyMiddleware2() app.HandlerFunc {
return func(c context.Context, ctx *app.RequestContext) {
//pre-handle
fmt.Println("route pre-handle")
ctx.Next(c)
//post-handle
fmt.Println("route post-handle")
}
}
Hertz Client
- Hertz 提供了 HTTP Client 用于帮助用户发送 HTTP 请求
- 新建一个Client对象c
- 添加请求参数(若有)
- 发送http请求并接收返回:status,body, _ := c.Get(context.Background(),dst,url)
- 具体可以通过文档中的示例代码深入了解Hertz
c, err := client.NewClient()
if err != nil {
fmt.Println(err)
return
}
var postArgs protocol.Args
postArgs.Set("arg", "a")
status, body, err := c.Post(context.Background(), nil/*使用复用的结构体提升性能*/, "http://example.com", &postArgs)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("status = %v body = %v \n", status, string(body))
Hertz代码生成
- 可以生成对应的IDL和具体的代码
Hertz性能
- 网络库 NetPoll:使用字节的内部网络库,在小包场景下的性能优于标准库,但不支持TLS,需要配置切换到标准库
- Json 编解码 Sonic:高性能的编解码的JSON库做性能优化,默认使用Sonic
- 使用 sync.Pool 复用对象协议层数据解析优化
Hertz生态
Hertz 拥有非常丰富的扩展生态,以下列举一部分常用扩展
| 拓展 | 地址 |
|---|---|
| HTTP2 扩展 | github.com/hertz-contr… |
| opentelemetry 扩展 | aithub.com/hertz-contr… |
| 国际化扩展 | github.com/hertz-contr… |
| 反向代理扩展 | github.com/hertz-contr… |
| JWT 鉴权扩展 | qithub.com/hertz-contr… |
| Websocket 扩展 | github.com/hertz-contr… |
| 丰富的示例代码与业务Demo | github.com/cloudwego/h… |
| 所有扩展可以参考github.com/cloudwego/h… |
项目实战
项目介绍
笔记项目是一个使用 Hertz 、 Kitex、Gorm 搭建出来的具备一定业务逻辑的后端 API 项目
项目功能
项目调用关系
总结
本次课程介绍了Go框架的三件套,web框架hertz,ORM框架Gorm以及RPC框架Kitex。在本次课程熟悉了 Gorm/Kitex/Hertz的基础用法以及常见的问题。最后通过实战案例分析将三个框架的使用串联起来,将框架与实际的一个小需求结合起来,做了一个项目架构的设计以及设计,重点讲解了重点代码。本次课程的项目实战在后续其他时间需要去熟悉具体的代码。