这是我参与「第五届青训营 」伴学笔记创作活动的第 5天
一,GKH框架三件套
1.Gorm框架
开始我们首先要清楚Gorm框架是干什么的?一开始我也是一头雾水因为没学过哈哈----- 不过它告诉我们Gorm是ORM框架,这不就明白它大致是做什么了。ORM框架是连接数据库的桥梁,只要提供了持久化类与表的映射关系,ORM框架在运行时就能参照映射文件的信息,把对象持久化到数据库中。学过Java的同学,是不是有那味了?(偷笑-----) 说白了:Gorm 是 Go 语言中实现对象和数据库映射的框架,可以有效地提高开发数据库应用的效率。 Gorm 主要用途是把 struct类型 和 数据库表 进行映射,使用简单方便。 因此,使用 Gorm 操作数据库的时候一般不需要直接手写 SQL 语句代码。这下开始知道他大概是做什么了,接下来就来分析如何操作了。
代码示例:
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"fmt"
"time"
"errors"
)
// 定义 User 数据模型,绑定数据库中 users 表
// Gorm 操作数据库,需要定义一个 struct 类型和 Mysql 表进行绑定或者叫映射
// struct 中的字段 和 Mysql 表的字段一一对应
// 在这里 User 类型可以代表 Mysql user表
type User struct {
ID int64 // 主键
// 通过在字段后面的标签说明,定义golang字段和表字段的关系
// 例如 `gorm:"column:username"` 标签说明含义是: Mysql 表的列名为 username
// 这里 golang 定义的 Username 变量和 Mysql 表字段 username 一样,他们的名字可以不一样。
Username string `gorm:"column:username"`
Password string `gorm:"column:password"`
CreateTime int64 `gorm:"column:createtime"`
}
// 设置表名,可以通过给 struct 类型定义 TableName 函数,返回当前 struct 绑定的 Mysql 表名是什么
func (u User) TableName() string {
// 绑定 Mysql 表名为 users
return "users"
}
func main() {
// 配置 MySQL 连接参数
username := "root" //账号
password := "123456" //密码
host := "127.0.0.1" //数据库地址,可以是Ip或者域名
port := 3306 //数据库端口
Dbname := "codebaoku" //数据库名
// 通过前面的数据库参数,拼接 Mysql DSN,其实就是数据库连接串(数据源名称)
// Mysql dsn格式: {username}:{password}@tcp({host}:{port})/{Dbname}?charset=utf8&parseTime=True&loc=Local
// 类似{username}使用花括号包着的名字都是需要替换的参数
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local", username, password, host, port, Dbname)
// 连接 Mysql
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("连接数据库失败, error=" + err.Error())
}
// 定义一个用户,并初始化数据
u := User{
Username:"codebaoku",
Password:"123456",
CreateTime:time.Now().Unix(),
}
// 插入一条用户数据
// 下面代码会自动生成SQL语句:INSERT INTO `users` (`username`,`password`,`createtime`) VALUES ('codebaoku','123456','1540824823')
if err := db.Create(&u).Error; err != nil {
fmt.Println("插入失败", err)
return
}
// 查询并返回第一条数据
// 定义需要保存数据的struct变量
u = User{}
// 自动生成sql: SELECT * FROM `users` WHERE (username = 'codebaoku') LIMIT 1
result := db.Where("username = ?", "codebaoku").First(&u)
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
fmt.Println("找不到记录")
return
}
// 打印查询到的数据
fmt.Println(u.Username,u.Password)
// 更新数据库记录
// 自动生成Sql语句: UPDATE `users` SET `password` = '654321' WHERE (username = 'codebaoku')
db.Model(&User{}).Where("username = ?", "codebaoku").Update("password", "654321")
// 删除数据库记录
// 自动生成Sql: DELETE FROM `users` WHERE (username = 'codebaoku')
db.Where("username = ?", "codebaoku").Delete(&User{})
}
注意事项:
-
Fist踩坑注意:使用Fist时,需要注意查询不到数据会返回ErrRecordNotFound。使用Find查询多条数据,查询不到数据不会返回错误。
-
使用结构体作为条件查询时,GORM只会查询非零值字段。这意味着如果你的值为0,",false或其他零值,该字段不会被用于用于构建查询条件,使用Map来构建查询条件。
-
使用Struct更新时,只会更新非0值,如果需要更新0值可以使用Map更新或者使用Select选择字段。
物理删除和软删除
其实说白了就是一个是真正在删除了一个没有,如图解释
GORM事务处理
1.自动事务处理
通过 db.Transaction 函数实现事务,如果闭包函数返回错误,则回滚事务。 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db') 返回任何错误都会回滚事务
db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
return err
}
if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
return err
}
// 返回 nil 提交事务
return nil
})
2.手动事务处理
这个有点麻烦,有想法的同学们可以去上网看下详细的。
GORM HOOK
GORM性能提高
2.Kitex框架
老规矩我们先来了解什么是RPC,毕竟这是RPC的框架。 RPC(Remote Procedure Call Protocol)远程过程调用协议。一个通俗的描述是:客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个对象,就像调用本地应用程序中的对象一样。 比较正式的描述是:一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。 所以说RPC是一种协议一套规范,用来远程过程调用的。也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。 至于怎么连接的细节,想了解的同学可以去找资料看下,这里不深究。
那我们为什么要用RPC呢? 其实这是应用开发到一定的阶段的强烈需求驱动的。如果我们开发简单的单一应用,逻辑简单、用户不多、流量不大,那我们用不着。当我们的系统访问量增大、业务增多时,我们会发现一台单机运行此系统已经无法承受。此时,我们可以将业务拆分成几个互不关联的应用,分别部署在各自机器上,以划清逻辑并减小压力。此时,我们也可以不需要RPC,因为应用之间是互不关联的。
当我们的业务越来越多、应用也越来越多时,自然的,我们会发现有些功能已经不能简单划分开来或者划分不出来。此时,可以将公共业务逻辑抽离出来,将之组成独立的服务Service应用 。而原有的、新增的应用都可以与那些独立的Service应用 交互,以此来完成完整的业务功能。 所以此时,我们急需一种高效的应用程序之间的通讯手段来完成这种需求,所以你看,RPC大显身手的时候来了!
其实描述的场景也是服务化 、微服务和分布式系统架构的基础场景。即RPC框架就是实现以上结构的有力方式。
常用的的RPC框架有很多,比如我们现在学的Kitex框架,gRPC,Spring cloud等等。
Kitex介绍
Kitex 是一个 RPC 框架,既然是 RPC,底层就需要两大功能:
- Serialization 序列化
- Transport 传输
Kitex 框架及命令行工具,默认支持 thrift 和 proto3 两种 IDL,对应的 Kitex 支持 thrift 和 protobuf 两种序列化协议。传输上 Kitex 使用扩展的 thrift 作为底层的传输协议(注:thrift 既是 IDL 格式,同时也是序列化协议和传输协议)。IDL 全称是 Interface Definition Language,接口定义语言。
为什么要使用IDL定义服务与接口?
Kitex生成代码各级目录作用
怎么使用?、
安装这里我们就不说了,同学们可以自己去网上看下。
直接上代码,看看怎样建立服务器端和客户端并且开始调用:
编写 echo 服务逻辑
我们需要编写的服务端逻辑都在 handler.go 这个文件中,现在这个文件应该如下所示:
这里的
Echo 函数就对应了我们之前在 IDL 中定义的 echo 方法。
现在让我们修改一下服务端逻辑,让 Echo 服务名副其实。
修改 Echo 函数为下述代码:
编译运行
kitex 工具已经帮我们生成好了编译和运行所需的脚本:
- 编译:
$ sh build.sh
执行上述命令后,会生成一个 output 目录,里面含有我们的编译产物。
- 运行:
$ sh output/bootstrap.sh
执行上述命令后,Echo 服务就开始运行啦!
编写客户端
有了服务端后,接下来就让我们编写一个客户端用于调用刚刚运行起来的服务端。
- 首先,同样的,先创建一个目录用于存放我们的客户端代码:
$ mkdir client
- 进入目录:
$ cd client
创建一个 main.go 文件,然后就开始编写客户端代码了。
创建 client
首先让我们创建一个调用所需的 client:
上述代码中,
echo.NewClient 用于创建 client,其第一个参数为调用的 服务名,第二个参数为 options,用于传入参数,此处的 client.WithHostPorts 用于指定服务端的地址。
发起调用
接下来让我们编写用于发起调用的代码:
上述代码中,我们首先创建了一个请求 req , 然后通过 c.Echo 发起了调用。
其第一个参数为 context.Context,通过通常用其传递信息或者控制本次调用的一些行为,你可以在后续章节中找到如何使用它。
其第二个参数为本次调用的请求。
其第三个参数为本次调用的 options ,Kitex 提供了一种 callopt 机制,顾名思义——调用参数 ,有别于创建 client 时传入的参数,这里传入的参数仅对此次生效。 此处的 callopt.WithRPCTimeout 用于指定此次调用的超时(通常不需要指定,此处仅作演示之用)同样的,你可以在 *** 基础特性*** 一节中找到更多的参数。
发起调用
在编写完一个简单的客户端后,我们终于可以发起调用了。
你可以通过下述命令来完成这一步骤:
$ go run main.go
如果不出意外,你可以看到类似如下输出:
2021/05/20 16:51:35 Response({Message:my request})
恭喜你!至此你成功编写了一个 Kitex 的服务端和客户端,并完成了一次调用!## 发起调用
在编写完一个简单的客户端后,我们终于可以发起调用了。
你可以通过下述命令来完成这一步骤:
$ go run main.go
如果不出意外,你可以看到类似如下输出:
2021/05/20 16:51:35 Response({Message:my request})
恭喜你!至此你成功编写了一个 Kitex 的服务端和客户端,并完成了一次调用!
有了这次小小试手,相信你对Kitex框架已经有了初步的了解,接下来深入掌握就要靠你自己了。
补充小知识:
- 什么是链式调用:说白了就是对象调用任何方法(除了节点关系方法)执行完后返回的就是对象自己。
3.Hertz框架
众所周知,HTTP 协议是当今使用最为广泛的协议之一,HTTP 是前(客户)端与服务端通信的基础协议。HTTP 框架负责的就是对 HTTP 请求的解析、根据对应的路由选择对应的后端逻辑了,HTTP 在企业实际业务场景中使用广泛。 而Hertz是一个用于 Go的高性能、高可用性、可扩展的HTTP 框架。它旨在为开发人员简化构建微服务。
Hertz基本使用
快速上手Hertz
- 首先,定义 IDL,这里使用 Thrift 作为 IDL 的定义(也支持使用 Protobuf 定义的 IDL),编写一个名为 Demo 的 service。这个服务有一个 API: Hello,它的请求参数是一个 query,响应是一个包含一个 RespBody 字段的 Json。
接下来我们使用 hz 生成代码,并整理和拉取依赖
填充业务逻辑,比如我们返回 hello, ${Name},那我们在 biz/handler/example/hello_service.go 中添加以下代码即可
编译并运行项目
到现在一个简单的 Hertz 项目已经生成,下面我们来测试一下
补充小知识:
- 什么是路由:路由 : 说白了就是一一对应关系的集合
专业解释就是路由是指导报文转发的路径信息,通过路由可以确认转发IP报文的路径。 路由设备是依据路由转发报文到目的网段的网络设备,最常见的路由设备:路由器。 路由设备维护着一张路由表,保存着路由信息。
路由中包含以下信息: 目的网络:标识目的网段 掩码:与目的地址共同标识一个网段 出接口:数据包被路由后离开本路由器的接口 下一跳:路由器转发到达目的网段的数据包所使用的下一跳地址 这些信息标识了目的网段、明确了转发IP报文的路径。
前端路由(单页应用程序): 一个url地址,对应哪个组件
后端路由:一个接口地址,对应哪一段接口地址
至于直连路由,静态路由动态路由那些路由获取信息方式这里不再赘述。
- 那视频中提到的路由组又是什么:顾名思义,从字面意思理解,路由组指的就是一组路由。 那么什么样的路由可以归类到一组呢? 在实际使用中,比较常见的场景的是根据版本分组。
参考资料链接:GORM 指南 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.
RPC框架:从原理到选型,一文带你搞懂RPC (qq.com)
字节跳动开源 Go HTTP 框架 Hertz 设计实践 - OSCHINA - 中文开源技术交流社区
文章如果错误请在评论区指点,我会虚心请教,谢谢大家观看到这里!