Go的框架三件套 | 青训营笔记

324 阅读12分钟

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

一,GKH框架三件套

image.png

1.Gorm框架

开始我们首先要清楚Gorm框架是干什么的?一开始我也是一头雾水因为没学过哈哈----- 不过它告诉我们Gorm是ORM框架,这不就明白它大致是做什么了。ORM框架是连接数据库的桥梁,只要提供了持久化类与表的映射关系,ORM框架在运行时就能参照映射文件的信息,把对象持久化到数据库中。学过Java的同学,是不是有那味了?(偷笑-----) 说白了:Gorm 是 Go 语言中实现对象和数据库映射的框架,可以有效地提高开发数据库应用的效率。 Gorm 主要用途是把 struct类型 和 数据库表 进行映射,使用简单方便。 因此,使用 Gorm 操作数据库的时候一般不需要直接手写 SQL 语句代码。这下开始知道他大概是做什么了,接下来就来分析如何操作了。

image.png 代码示例:

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{})
}

注意事项:

image.png

  • Fist踩坑注意:使用Fist时,需要注意查询不到数据会返回ErrRecordNotFound。使用Find查询多条数据,查询不到数据不会返回错误。

  • 使用结构体作为条件查询时,GORM只会查询非零值字段。这意味着如果你的值为0,",false或其他零值,该字段不会被用于用于构建查询条件,使用Map来构建查询条件。

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

物理删除和软删除

其实说白了就是一个是真正在删除了一个没有,如图解释

image.png

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

image.png

GORM性能提高

image.png

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,底层就需要两大功能:

  1. Serialization 序列化
  2. Transport 传输

Kitex 框架及命令行工具,默认支持 ​thrift ​和 ​proto3 ​两种 IDL,对应的 Kitex 支持 ​thrift ​和 ​protobuf ​两种序列化协议。传输上 Kitex 使用扩展的 ​thrift ​作为底层的传输协议(注:thrift 既是 IDL 格式,同时也是序列化协议和传输协议)。IDL 全称是 Interface Definition Language,接口定义语言。

为什么要使用IDL定义服务与接口?

image.png

Kitex生成代码各级目录作用

image.png

怎么使用?、

安装这里我们就不说了,同学们可以自己去网上看下。

直接上代码,看看怎样建立服务器端和客户端并且开始调用:

编写 echo 服务逻辑

我们需要编写的服务端逻辑都在 ​handler.go​ 这个文件中,现在这个文件应该如下所示:

image.png 这里的 ​Echo ​函数就对应了我们之前在 IDL 中定义的 ​echo ​方法。

现在让我们修改一下服务端逻辑,让 ​Echo ​服务名副其实。

修改 ​Echo ​函数为下述代码:

image.png

编译运行

kitex 工具已经帮我们生成好了编译和运行所需的脚本:

  • 编译:

$ sh build.sh

执行上述命令后,会生成一个 ​output ​目录,里面含有我们的编译产物。

  • 运行:

$ sh output/bootstrap.sh

执行上述命令后,​Echo ​服务就开始运行啦!

编写客户端

有了服务端后,接下来就让我们编写一个客户端用于调用刚刚运行起来的服务端。

  • 首先,同样的,先创建一个目录用于存放我们的客户端代码:

$ mkdir client

  • 进入目录:

$ cd client

创建一个 main.go 文件,然后就开始编写客户端代码了。

创建 client

首先让我们创建一个调用所需的 ​client​:

image.png 上述代码中,​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基本使用

image.png

快速上手Hertz

  1. 首先,定义 IDL,这里使用 Thrift 作为 IDL 的定义(也支持使用 Protobuf 定义的 IDL),编写一个名为 Demo 的 service。这个服务有一个 API: Hello,它的请求参数是一个 query,响应是一个包含一个 RespBody 字段的 Json。

image.png

接下来我们使用 hz 生成代码,并整理和拉取依赖

image.png

填充业务逻辑,比如我们返回 hello, ${Name},那我们在 biz/handler/example/hello_service.go 中添加以下代码即可

image.png

编译并运行项目

ZWlSP2B5Zq.jpg 到现在一个简单的 Hertz 项目已经生成,下面我们来测试一下

TJOidr2yg7.jpg

补充小知识:

  • 什么是路由:路由 : 说白了就是一一对应关系的集合

专业解释就是路由是指导报文转发的路径信息,通过路由可以确认转发IP报文的路径。 路由设备是依据路由转发报文到目的网段的网络设备,最常见的路由设备:路由器。 路由设备维护着一张路由表,保存着路由信息。

路由中包含以下信息: 目的网络:标识目的网段 掩码:与目的地址共同标识一个网段 出接口:数据包被路由后离开本路由器的接口 下一跳:路由器转发到达目的网段的数据包所使用的下一跳地址 这些信息标识了目的网段、明确了转发IP报文的路径。

前端路由(单页应用程序): 一个url地址,对应哪个组件

image.png

后端路由:一个接口地址,对应哪一段接口地址

image.png

至于直连路由,静态路由动态路由那些路由获取信息方式这里不再赘述。

  • 那视频中提到的路由组又是什么:顾名思义,从字面意思理解,路由组指的就是一组路由。 那么什么样的路由可以归类到一组呢? 在实际使用中,比较常见的场景的是根据版本分组。

参考资料链接:GORM 指南 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.

RPC框架:从原理到选型,一文带你搞懂RPC (qq.com)

代码示例 | CloudWeGo

字节跳动开源 Go HTTP 框架 Hertz 设计实践 - OSCHINA - 中文开源技术交流社区

文章如果错误请在评论区指点,我会虚心请教,谢谢大家观看到这里!