05Go框架三件套详解(Web/RPC/ORM) | 青训营笔记

167 阅读6分钟

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

课程: live.juejin.cn/4354/989924…

可以选择看juejin.cn/course/byte…

资料: juejin.cn/post/718822…

PPT: bytedance.feishu.cn/file/boxcnK…

青训营小帖士 @所有人 Hertz 新手任务地址: github.com/cloudwego/h…

Go 框架三件套详解(Web/RPC/ORM)实战环节-笔记服务项目地址:

优化版: github.com/cloudwego/b…

普通版: github.com/cloudwego/k…

Hertz 代码设计实践: www.cloudwego.io/zh/blog/202…

1 课程介绍

 将前面几节课所学到的知识应用到项目中

 掌握Hertz/Kitex/Gorm的基本用法

 通过学习实战案例, 可以使用Hertz/Kitex/Gorm完成日常后端开发任务

2 三件套的使用

简介

 Gorm 是功能强大的ORM框架, 被字节广泛使用, 拥有非常丰富的开源扩展

 Kitex 是字节内部的Golang微服务RPC框架, 具有高性能, 强可扩展性的主要特点, 支持多协议且拥有丰富的开源扩展

 Hertz 是字节内部的HTTP框架, 参考了其他开源框架的优势, 结合字节跳动内部的需求, 具有高易用性, 高性能, 高扩展性特点

使用

Gorm

gorm.cn/zh_CN/docs/…

go get -u gorm.io/gorm

(-u 表示update, 即如果包已有则更新为最新版本, 如果没用则下载)

go get -u gorm.io/driver/sqlite

这里我们要用mysql而不是sqlite

go get -u gorm.io/driver/mysql

 package main
 ​
 import (
     "gorm.io/driver/mysql"
     "gorm.io/gorm"
 )
 ​
 // Product 定义gorm model
 type Product struct {
     Code  string
     Price uint
 }
 ​
 // TableName 定义表名
 func (p Product) TableName() string {
     return "product"
 }
 ​
 func main() {
     // 连接数据库
     db, err := gorm.Open(
         // 后续会讲什么是dsn
         mysql.Open("user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"),
         &gorm.Config{})
     if err != nil {
         panic("failed to connect database")
     }
 ​
     // Create 创建数据
     // 支持创建单个或多个
     // 创建单个时传对象指针, 如&Product
     // 创建多个时传切片, 如[]Product
     db.Create(&Product{
         Code:  "042",
         Price: 100,
     })
 ​
     // Read 查询数据
     // First: 查单条, Find: 查多条
     var product Product
     // 注意传的是指针
     db.First(&product, 1)                 // 根据整型主键查找 (不懂)
     db.First(&product, "code = ?", "042") // 查找 cod 字段值为 042 的记录
     // Update - 更新单个字段
     db.Model(&product).Update("Price", 200)
     // Update - 更新多个字段
     db.Model(&product).Updates(Product{Price: 200, Code: "F42"})
     db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})
     // Delete 删除数据
     db.Delete(&product, 1)
 }
约定 (默认)

 Gorm使用名为ID的字段作为主键

 当没有定义TableName方法时, Gorm使用结构体的蛇形复数作为表面

 使用CreatedAt, UpdatedAt字段作为创建, 更新时间

数据库

 Gorm通过驱动来连接数据库

 支持MySQL, SQLServer, PostgreSQL, SQLite

 github.com/go-sql-driv…

 DSN: Data Source Name

[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]  如username:password@protocol(address)/dbname?param=value

一些经验

 //// 创建数据

 用clause.OnConfict处理数据冲突

 // 例如: create冲突时不进行处理

 db.Clauses(clause.OnConflict{DoNothing: true}).Create(&p)

 // gorm.cn/zh_CN/docs/…

 用default标签为字段定义默认值
 type User struct {
     ID int64
     Name string `gorm:"default:galeone"`
     Age int64 `gorm:"default:18"`
 }

 //// 查询数据

 First查询不到数据时, 会返回ErrRecordNotFound

 而用Find查询不到数据时, 不会返回错误

 ⭐一般都用Find, 然后自己判断是否查询到数据

 当使用结构体作为查询条件时, Gorm只会查询非零值字段
 db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users) // Age是零值字段, 会被忽略
 想查询零值字段, 要用Map来构建查询条件
 db.Where(map[string]interface{}{"Name": "jinzhu", "Age": 0}).Find(&users)

 //// 更新数据

 使用Struct更新时, 只会更新非零值, 如果需要更新零值, 可以使用Map更新或者使用Select选择字段

 //// 删除数据
 // 物理删除
 传一个值, 表示主键值, 如id=10
 db.Delete(&User{}, 10)
 传切片表示, 主键值范围, 即id IN (1, 2, 3)
 db.Delete(&User{}, []int{1, 2, 3})
 筛选后删除
 db.Where("xxx").Delete(User{})
 等价于
 db.Where(User{}, "xxx")
 // 软删除: 记录不会从数据库真正删除, 但不能通过正常的查询方法找到该记录. 使用Unscoped可以查询到被软删的数据
 要在struct里额外定义一个字段
 type User struct {
     Deleted gorm.DeleteAt // 存储的是删除时间
 }
 要用Unscoped才能查询被软删的数据, 如下
 db.Unscoped().Where("age = 20").Find(&users)
 //// 事物
 对数据一致性强的场景时, 要使用事物
 tx := db.Begin() // 开启事物, 之后应该用tx, 而不是用db
 if err = tx.Create(&User{Name: "name"}).Error; err != nil {
     tx.Rollback()
     // 遇到错误时回滚事物
     return
 }
 // 提交事务
 tx.Commit()
 // 手动Rollback和Commit可能会忘记写, 导致链接泄露
 // Gorm提供Transaction方法用于自动提交事物, 避免用户漏写Commit, Rollback
 ⭐最好用Transaction
 if err = db.Transaction(func(tx *gorm) error {
     if err = tx.Create(&User{Name: "name"}).Error; err != nil {
         return err // 返回nil自动Commit, 否则自动Rollback
     }
 })
 //// Hook
 // 在创建, 查询, 更新, 删除等操作之前/之后, 自动调用的函数
 // Hook自带默认事务, 如果任何Hook返回错误, Gorm将停止后续操作, 并回滚事务
 如
 func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
     if u.Age < 0 {
         return erros.New("can't save invalid data")
     }
     return
 }
 ​
 func (u *User) AfterCreate(tx *gorm.DB) (err error) {
     return tx.Create(&Email{ID: u.ID, Email: u.Name + "@***.com"}).Error
 }
 //// 性能优化
 mysql.Open("user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"),
         &gorm.Config{
             SkipDefaultTransaction: true, // 关闭默认事务
             PrepareStmt: true, // 缓存预编译语句
         })
 ​
 1 对于写操作(创建, 更新), 为了保证数的完整性, Gorm会将它们封装在事务内运行, 但这会降低性能, 可以使用SkipDefaultTransaction关闭默认事务
 ​
 2 使用PrepareStmt缓存预编译语句可以提高后续调用的速度, 本机测试提高大约35%左右
Gorm生态

 Gorm 代码生成工具 github.com/go-gorm/gen

 Gorm 分片库方案 github.com/go-gorm/sharding

 Gorm 手动索引 github.com/go-gorm/hints

 Gorm 乐观锁 github.com/go-gorm/optimisticlock

 Gorm 读写分离 github.com/go-gorm/dbresolver

 Gorm OpenTelemetry扩展 github.com/go-gorm/opentelemetry

Q&A

 1 db.Model(&product)的product是用来做什么的?

 可以用来做条件判断, 如db.Mode(&User{111})

 但更多的意义是传递表名

 ​  2 主键冲突时, 不处理冲突, 数据怎么进库?

 会报错, 进不了库的

 ​  3 为什么有零值的问题?

 因为Go语言零值是默认值, 不知道零值是真的零值, 还是没有值

 ​  4 用不用.Model就看后续操作是否需要表名?

 是

Kitex

示例文档 www.cloudwego.io/zh/docs/kit…

etcd.io/

opentracing.io/

了解 IDL: zh.m.wikipedia.org/zh-hans/%E6…

Thrift IDLy语法: thrift.apache.org/docs/idl

Proto3 IDL语法: developers.google.com/protocol-bu…

 RPC框架

 (RPC: Remote Produce Call)

Kitex对Windows支持不完善, 建议使用虚拟机或WSL2
正在改进
​  上手前可先稍微了解:
> 什么IDL以及IDL的语法
如果我们要进行RPC, 就需要知道对方的接口是什么, 需要传什么参数, 同时也需要知道返回值是什么样的. 这时候, 就需要通过IDL来约定双方的协议, 就像写代码的时候需要调用某个函数, 我们需要知道函数签名一样

 namespace go api
 struct Request {
     1: string message
 }
 struct Response {
     1: string message
 }
 service Echo {
     Response echo(1: Request req)
 }
 > 什么是opentracing以及etcd

go install github.com/cloudwego/kitex/tool/cmd/kitex@latest
go install github.com/cloudwego/thriftgo@latest

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

 build.sh // 构建脚本
 echo.thrift
 handler.go // 用户在该文件里实现IDL service定义的方法
 kitex_gen // IDL内容和相关生成代码, 主要是基础的Server/Client代码
     api
         echo
             client.go
             echo.go
             invoker.go
             server.go
         echo.go
         k-echo.go
     main.go // 程序入口
     script
         bootstrap.sh
         settings.py
 Kitex基本使用
     服务默认监听8888端口
     
 Kitex Client发起请求

 目前Kitex的服务注册与发现已经对接了主流了服务注册与发现中心, 如ETCD, Nacos

Kitex不是很懂, 之后仔细学了, 再看PPT

Hertz

www.cloudwego.io/zh/docs/her…

Hertx: go的http框架
Hertz提供了代码生成工具Hz, 通过定义IDL(interface description language)文件即可生成对应的基础服务代码
www.cloudwego.io/zh/docs/her…

默认使用的网络库的Netpoll, 性能优于标准库
(当使用TLS时, netpoll不支持, 配置使用标准库)
Json编解码使用Sonic
使用sync.Pool复用对象, 协议层数据解析优化

3 实战案例介绍

项目介绍

笔记项目, 一个使用Hertz, Kitex, Gorm搭建出来具备一定业务逻辑的后端API项目
demoapi API服务
demouser 用户数据管理
demonote 笔记数据管理
更多见PPT, 如项目调用关系等

优化版: github.com/cloudwego/b…

普通版: github.com/cloudwego/k… ⭐先看普通版

Hertz 代码设计实践: [www.cloudwego.io/zh/blog/202…](

4 课程总结

了解Gorm/Kitex/Hertz是什么
熟悉Gorm/Kitex/Hertz的基础用法
通过实战案例分析将三个框架的使用串联起来