第五届字节跳动青训营Class5笔记 | 青训营笔记

259 阅读6分钟

这是我参与「第五届青训营 」笔记创作活动的第5天

本次课程讲解了Go框架三件套(Gorm、Kitex、Hertz)的安装配置及使用

三件套介绍

  • Gorm:
    • Gorm 是一个已经迭代了10年+的功能强大的 ORM 框架,在字节内部被广泛使用并且拥有非常丰富的开源扩展。
  • Kitex
    • Kitex 是字节内部的 Golang 微服务 RPC 框架,具有高性能、强可扩展的主要特点,支持多协议并且拥有丰富的开源扩展。
  • Hertz
    • Hertz是是字节内部的 Golang 微服务 HTTP 框架,Hertz参考了其他开源框架的优势,结合字节跳动内部的需求,具有高易用性、高性能、高扩展性特点。

Gorm

Gorm作为中继数据的存在,协调内部定义的结构体数据和Mysql等数据库中获取的数据。

Gorm基本操作

Gorm的数据库操作方法都要使用链式调用的方式:db.func1().func2().<...>.ExecuteQuery()

//定义gorm model
type Product struct{
    Code string
    Price uint
}
//TableName()为gorm框架规定的接口
func(p Product) TableName() string{
    return "product"
}
//连接到mysql
db,err := gorm.Open(mysql.Open(url),&gorm.Config{})
//增
db.Create(&Product{Code:"D42",Price:100})
//删
var product Product
db.Delete(&product,1)
//改
var product Product
db.Model(&product).Update("Price",200) //单字段更改
db.Model(&product).Updates(Product{Price:200,Code:"F42"} //仅更新非零值字段
db.Model(&product).Updates(map[string]interface{}{"Price":200,"Code":"F42"})
//查
var product Product
db.First(&porduct,1) //根据整型主键查找
db.First(&product,"code=?","D42") //查询Code字段为D42的记录

Gorm目前支持MySQL、SQLServer、PostgreSQL、SQLite等多种类型的数据库

Gorm通过驱动来连接数据库,需要连接其他类型数据库时可以复用/自行开发驱动

//Gorm创建数据
type Product struct{
    ID    uint    `gorm:"primarykey"`
    Code  string  `gorm:"column:code"`
    Price uint    `gorm:"column:user_id"`
//使用go tag标注了结构体
func main(){
    db,err := gorm(mysql.Open(url,&gorm.Config{})
    if err != nil {
        panic("failed to connect database)
    }
    products := []*Prodcut{{Code:"D41"},{Code:"D42"},{Code:"D43"}}
    res=db.Create(products) //当用primarykey显式定义主键时,创建表操作默认会返回主键
    fmt.Println(res.Error) //错误信息包含在res的Error成员中
    for _,p:=range products {
        fmt.Println(p.ID)
    }
}

也可以使用clause.OnConflict来处理冲突,在go tag中使用default关键字在定义默认值。

当使用Create()First()等方法时,程序已经执行了查询命令,此时再使用Where()方法是不生效的,因此限定范围时要先使用Where()方法

//查询多条数据
users := make([]*User, 0)
users := db.Where("age>10").Find(&users)

查询中的Where()方法包含两个参数,前者为类似于DML语法的字符串,后者为?中的内容,如

  • "name IN ?" , []string{"jinzhu:,:jinzhu 2"}
  • "name LIKE ?", "%jin%"
  • "name = ? AND age >= ?", "jinzhu", "22"

使用Find查找相较于使用First查找是更为安全的,因为使用First如果没有查找到数据会返回错误,而使用Find查找时查找错误只会返回空数组。

使用Struct作为参数时,gorm只会处理非零值

在增删改查时如果对于Find(),Create(),Update(),Updates()等方法没有传递定义为model的结构体指针的话,需要先使用.Model()方法去传递表名

Update()操作也可以更新表达式,使用gorm.Expr()方法即可

gorm的软删方案
type User struct{
    ID      int64
    Name    string
    Age     int64
    Deleted gorm.DeleteAt
}

在model结构体的定义时如果添加了类型为gorm.DeleteAt类型的数据,在删除时就不会将数据从数据库中真正删除,但是GORM会将定义的gorm.DeleteAt类型的数据设置为当前时间,并且不能再通过正常的查询获取当前的数据。使用Unscoped可以查询到被软删的数据。

GORM事务

GORM提供了Begin、Commit、Rollback方法用于使用事务

事务可以用来处理对数据一致性要求较强的情况,执行一系列操作后,保证操作要么全部成功,要么全部失败,失败后可以使用回滚机制进行管理

db, err := gorm.Open(mysql(url),&gorm.Config{})
if err != nil{
    panic("failed to connect database”)
}
tx := db.begin()
//使用Transaction可以自动提交事务
if err = db.Transaction(func(tx *gorm.DB) error{
    if err=tx.Create(&User{Name:"name"}).Error:err!=nil{
        tx.Rollback()
        return
    }
    if err=tx.Create(&User{Name:"name1"}).Error:err!=nil{
        tx.Rollback()
        return
    }
    return nil
});err !=nil{
    return
}

db.begin()操作既开启了一个事务,又固化了对于数据库的链接(因为Go中底层设计了一个链接池用于连接数据库,固化之后可以保证都使用同一个连接),因此必须使用tx作为之后的中继数据变量

GORM性能提高

  • SkipDefaultTransaction可以用于关闭写操作中的默认事务(Gorm会给基本操作添加一个默认事务)
  • 使用PrepareStmt缓存预编译语句可以提高后续调用的速度

Kitex

RPC(Remote Procedure Call):远程过程调用,指像调用本服务中的函数一样去调用别的服务的函数。

使用IDL定义服务与接口:如果我们要进行 RPC,就需要知道对方的接口是什么,需要传什么参数,同时也需要知道返回值是什么样的。这时候,就需要通过 IDL 来约定双方的协议,就像在写代码的时候需要调用某个函数,我们需要知道函数签名一样

使用IDL来调节接口:

namespace go api
struct Request{
    1: string message
}
struct Response{
    1:string message
}
service Echo{
    Response echo(1:Request req)
}

使用kitex -module example -service example echo.thrift命令生成代码

image.png

image.png 使用client.WithHostPorts()来指定服务端的ip

Hertz

func mian(){
    h := server.Default(server.WithHostPorts(url))
    h,GET(relative path,func(c context.Context, ctx *app.RequestContext){
        ctx.JSON(consts.StatusOK, utils.H{"ping":"pong"})
    })
    //正常运行时代码会停止在这里
    h.Spin()
}

使用Hertz实现,服务监听8080端口并注册了一个GET方法的路由参数

image.png

Hertz与Gin等http服务框架不同的是传递了两个上下文,分别用于处理请求(RequestContext)和源信息

Hertz路由

Hertz提供了参数路由和通配路由,参数路由形如:/a/:b,通配路由形如:a/*b,通配路由可以获取之后多段内容,而参数路由只能获取一段内容,其中b值可以被获取如下:

h.GET("hertz/:version",func(ctx context.Context, c*app.RequestContext){
    version := c.Param("version")
    c.String(consts.StatusOK,"Hello %s", version)

Hertz参数绑定

image.png

Hertz中间件

中间件主要用于处理一些统一业务逻辑,用于将业务逻辑和服务端代码解耦。

func MyMiddleware() app.HandlerFunc{
    return func(ctx context.Context, c * app.RequestContext){
        fmt.Println("pre-handle")
        c.Next(ctx) //用于向下执行
        fmt.Println("post-handle")
    }
}

注册方式:h.use(MyMiddleware())

总结

本节课讲解了实际开发中使用的Go语言三件套:提供对象关系映射的Gorm,提供远程调用作为微服务的Kitex,对外提供api服务的Hertz,总结了框架文档中的要点,并通过实战案例分析将三个框架的使用串联了起来。