Go 框架三件套 kitex,hertz,gorm| 青训营笔记

293 阅读7分钟

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


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

Part1 Kitex


RPC 框架 Kitex

首先让我们先聊一下什么是rpc

我们说的rpc是指角色扮演游戏(Role-playing character)- 远程过程调用(Remote Procedure Call)

所谓远程调用过程也叫远端程序呼叫 是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一个地址空间(通常为一个开放网络的一台计算机)的子程序,而程序员就像调用本地程序一样,无需额外地为这个交互作用编程(无需关注细节)。RPC是一种服务器-客户端(Client/Server)模式,经典实现是一个通过发送请求-接受回应进行信息交互的系统 常用于分布式计算

远程过程调用是一个分布式计算的客户端-服务器(Client/Server)的例子,它简单而又广受欢迎。远程过程调用总是由客户端对服务器发出一个执行若干过程请求,并用客户端提供的参数。执行结果将返回给客户端。由于存在各式各样的变体和细节差异,对应地衍生了各式远程过程调用协议,而且它们并不互相兼容。

但互不兼容可不是好事 为了允许不同的客户端均能访问服务器,许多标准化的 RPC 系统应运而生了。其中大部分采用接口描述语言(Interface Description Language,IDL),方便跨平台的远程过程调用

简单来说

rpc 是以客户端服务端的形式 通过发送请求-接受回应进行信息交互的系统
idl 是为了允许不同的客户端均能访问服务器 产生的标准化接口描述语言

我们继续聊kitex

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

Kitex 自带了一个同名的命令行工具 kitex,用来帮助大家很方便地生成代码,新项目的生成以及之后我们会学到的 server、client 代码的生成都是通过 kitex 工具进行。

按照以下命令可生成基础的文件目录

$ kitex -module mod里的包名 -service 服务名称 idl文件 生成的目录如下

image.png

    其中: 
    build.sh    是为了快速启动 
    echo.thrift 是我们的idl
    handler.go  里面封装了我们的服务逻辑
    kitex_gen   里面装的是我们的文件
    main.go     是服务端的启动文件
    script      是为了生成可执行文件

关于脚本:

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

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

运行: $ sh output/bootstrap.sh

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

客户端:

import "example/kitex_gen/api"//导入包
import "example/kitex_gen/api/echo"
import "github.com/cloudwego/kitex/client"

c, err := echo.NewClient("example", client.WithHostPorts("0.0.0.0:8888"))//创建客户端
if err != nil {
  log.Fatal(err)
}

req := &api.Request{Message: "my request"}//创建请求文件
resp, err := c.Echo(context.Background(), req, callopt.WithRPCTimeout(3*time.Second))//发送请求 并且接受回复
if err != nil {
  log.Fatal(err)
}
log.Println(resp)

NewClient: 上述代码中,echo.NewClient 用于创建 client,其第一个参数为调用的 服务名,第二个参数为 options,用于传入参数, 此处的 client.WithHostPorts 用于指定服务端的地址 上述代码中,我们首先创建了一个请求 req , 然后通过 c.Echo 发起了调用。

发送函数: 其第一个参数为 context.Context,通过通常用其传递信息或者控制本次调用的一些行为。 其第二个参数为本次调用的请求。 其第三个参数为本次调用的 options ,Kitex 提供了一种 callopt 机制,顾名思义——调用参数 ,有别于创建 client 时传入的参数,这里传入的参数仅对此次生效。

Part2 hertz


HTTP 框架 Hertz

Hertz 是一个 Golang 微服务 HTTP 框架,在设计之初参考了其他开源框架 fasthttpginecho 的优势, 并结合字节跳动内部的需求,使其具有高易用性、高性能、高扩展性等特点,目前在字节跳动内部已广泛使用。 如今越来越多的微服务选择使用 Golang,如果对微服务性能有要求,又希望框架能够充分满足内部的可定制化需求,Hertz 会是一个不错的选择

  1. 生成代码 hz new
  2. go mod tidy来整理和更新依赖
  3. go build -o hertz_demo 来构建文件
  4. 启动后就可以通过curl来进行测试

Part3 gorm


db: 数据库 Golang写的,开发人员友好的ORM库

  1. 连接数据库

     导入依赖文件。。。
     
     //配置MySQL连接参数 
     username := "root" //账号 
     password := "123456" //密码 
     host := "127.0.0.1" //数据库地址,可以是Ip或者域名 
     port := 3306 //数据库端口 
     Dbname := "tizi365" //数据库名 
     //通过前面的数据库参数,拼接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{})
    
  2. 用结构体定义你的表模型

  3.  u := User{ 
         Username:"tizi365", 
         Password:"123456", 
         CreateTime:time.Now().Unix(), 
     }
     db.create(&u)
    
  4. 利用where语句: db.Where("type = ?", 5).Delete(&Food{}) 传入空的结构体即可

  5.    food定义其中id为某一个值 之后
       db.Model(&food).Update("price", 25)
       把这个food的price更新为25
       
       利用where语句进行修改
       db.Model(&Food{}).Where("create_time > ?", "2018-11-06 20:00:00").Update("price", 25)
       
       Updatas
       //通过结构体变量设置更新字段 
       updataFood := Food{ Price:120, Title:"柠檬雪碧", } 
       //根据food模型更新数据库记录 
       //Updates会忽略掉updataFood结构体变量的零值字段, 所以生成的sql语句只有price和title字段。 
       db.Model(&food).Updates(&updataFood) 
       
       //设置Where条件,Model参数绑定一个空的模型变量 
       db.Model(&Food{}).Where("price > ?", 10).Updates(&updataFood)
       
    
  6.  type Food struct {
         Id         int
         Title      string
         Price      float32
         Stock      int
         Type       int
         //mysql datetime, date类型字段,可以和golang time.Time类型绑定, 详细说明请参考:gorm连接数据库章节。
         CreateTime time.Time
     }
    
    • Take
      查询一条记录
    例子:
    
    //定义接收查询结果的结构体变量
    food := Food{}
    
    //等价于:SELECT * FROM `foods`   LIMIT 1  
    db.Take(&food)
    
    • First
      查询一条记录,根据主键ID排序(正序),返回第一条记录
    例子:
    
    //等价于:SELECT * FROM `foods`   ORDER BY `foods`.`id` ASC LIMIT 1    
    db.First(&food)
    
    • Last
      查询一条记录, 根据主键ID排序(倒序),返回第一条记录
    //等价于:SELECT * FROM `foods`   ORDER BY `foods`.`id` DESC LIMIT 1   
    //语义上相当于返回最后一条记录
    db.Last(&food)
    
    • Find
      查询多条记录,Find函数返回的是一个数组
    //因为Find返回的是数组,所以定义一个商品数组用来接收结果
    var foods []Food
    
    //等价于:SELECT * FROM `foods`
    db.Find(&foods)
    

    where语句 db.Where("id = ?", 10).Take(&food)

    select选择返回内容 db.Select("id,title").Where("id = ?", 1).Take(&food)

    order语句 db.Where("create_time >= ?", "2018-11-06 00:00:00").Order("create_time desc").Find(&foods)

    分页和偏移 db.Order("create_time desc").Limit(10).Offset(0).Find(&foods)

    Count函数,直接返回查询匹配的行数。 db.Model(Food{}).Count(&total)

    分组

     type Result struct { 
         Type int Total int 
     } 
     var results []Result 
     //等价于: SELECT type, count(*) as total FROM `foods` GROUP BY type HAVING (total > 0) 
     db.Model(Food{}).Select("type, count(*) as total").Group("type").Having("total > 0").Scan(&results) 
     //scan类似Find都是用于执行查询语句,然后把查询结果赋值给结构体变量,区别在于scan不会从传递进来的结构体变量提取表名. 
     //这里因为我们重新定义了一个结构体用于保存结果,但是这个结构体并没有绑定foods表,所以这里只能使用scan查询函数。
    

提示:gorm库是协程安全的,gorm提供的函数可以并发的在多个协程安全的执行。

参考资料:

'https://www.tizi365.com/archives/20.html'
'https://jasperxu.github.io/gorm-zh/associations.html#bt'
'https://juejin.cn/post/7188225875211452476/#heading-16'


码风略丑 读者见谅 --2023/1/23