[kitex + gorm-gen + hertz] 快速写出一个基于go的微服务

Posted Alex抱着爆米花

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[kitex + gorm-gen + hertz] 快速写出一个基于go的微服务相关的知识,希望对你有一定的参考价值。

[kitex + gorm-gen + hertz] 快速写出一个kitex的微服务

原项目:https://github.com/cloudwego/hertz

0、目的

创建一个用户的微服务用来进行添加和查询用户

1、环境安装

Kitex 安装

Kitex 目前对 Windows 的支持并不完善,建议使用虚拟机或 WSL2 进行测试。

这里我采用Ubuntu系统,来自动生成代码然后将生成代码同步到window本地的goland开发!

要开始 Kitex 开发,首先需要安装 Kitex 代码生成工具, go install 命令可被用于安装 Go 二进制工具(在此之前,请务必检查已正确设置 GOPATH 环境变量,并将 $GOPATH/bin 添加到 PATH 环境变量中)
安装依赖

go install github.com/cloudwego/kitex@latest

go install github.com/cloudwego/thriftgo@latest

go mod edit -replace=github.com/apache/thrift=github.com/apache/thrift@v0.13.0

docker 安装 相关环境

安装文件 docker-compose.yaml
启动命令 docker-compose up -d

2、定义 用户的 IDL

namespace go demouser

enum ErrCode 
    SuccessCode                = 0
    ServiceErrCode             = 10001
    ParamErrCode               = 10002
    UserAlreadyExistErrCode    = 10003
    AuthorizationFailedErrCode = 10004


struct BaseResp 
    1: i64 status_code
    2: string status_message
    3: i64 service_time


struct User 
    1: i64 user_id
    2: string username
    3: string avatar


struct CreateUserRequest 
	// length of Message should be greater than or equal to 1
    1: string username (vt.min_size = "1")
    2: string password (vt.min_size = "1")


struct CreateUserResponse 
    1: BaseResp base_resp


struct MGetUserRequest 
    1: list<i64> user_ids (vt.min_size = "1")


struct MGetUserResponse 
    1: list<User> users
    2: BaseResp base_resp


struct CheckUserRequest 
    1: string username (vt.min_size = "1")
    2: string password (vt.min_size = "1")


struct CheckUserResponse 
    1: i64 user_id
    2: BaseResp base_resp


service UserService 
    CreateUserResponse CreateUser(1: CreateUserRequest req)
    MGetUserResponse MGetUser(1: MGetUserRequest req)
    CheckUserResponse CheckUser(1: CheckUserRequest req)

3、kitex 自动代码生成

官网
有了 IDL (可以理解为接口)以后我们便可以通过 kitex 工具生成项目代码了,执行如下命令:

$ kitex -module example -service example echo.thrift

上述命令中,-module 表示生成的该项目的 go module 名,-service 表明我们要生成一个服务端项目,后面紧跟的 example 为该服务的名字。最后一个参数则为该服务的 IDL 文件。

生成文件说明
build.sh : 构建脚本,将代码变成一个可执行的二进制文件
kitex_gen : IDL内容相关的生成代码,主要是基础的Server/Client代码,kitex的编解码的优化会在里面,这里是生成的主要代码
main.go : 程序入口
handler.go : 用户在该文件里实现IDL service 定义的方法 可以理解为api层

4、导入goland

上面的代码生成是在Linux中的,goland中使用deployment来同步到window中goland下,然后window输入

go mod tidy

来同步包

5、Demo

5.1、服务端编写handler – 假数据

package main

import (
	"context"
	"log"
	demouser "myuser/kitex_gen/demouser"
	"time"
)

// UserServiceImpl implements the last service interface defined in the IDL.
type UserServiceImpl struct

// CreateUser implements the UserServiceImpl interface.
func (s *UserServiceImpl) CreateUser(ctx context.Context, req *demouser.CreateUserRequest) (resp *demouser.CreateUserResponse, err error) 
	log.Println("姓名" + req.Username + "密码" + req.Password)
	resp = new(demouser.CreateUserResponse)
	resp.BaseResp = &demouser.BaseRespStatusCode: 200, StatusMessage: "ok", ServiceTime: time.Time.Unix()
	return resp, nil


// MGetUser implements the UserServiceImpl interface.
func (s *UserServiceImpl) MGetUser(ctx context.Context, req *demouser.MGetUserRequest) (resp *demouser.MGetUserResponse, err error) 
	resp = new(demouser.MGetUserResponse)
	users := make([]*demouser.User, 0)
	users = append(users, &demouser.User1, "test", "test")
	resp.Users = users
	resp.BaseResp = &demouser.BaseRespStatusCode: 200, StatusMessage: "ok", ServiceTime: time.Time.Unix()
	return resp, nil


// CheckUser implements the UserServiceImpl interface.
func (s *UserServiceImpl) CheckUser(ctx context.Context, req *demouser.CheckUserRequest) (resp *demouser.CheckUserResponse, err error) 
	resp = new(demouser.CheckUserResponse)
	resp.UserId = 1
	resp.BaseResp = &demouser.BaseRespStatusCode: 200, StatusMessage: "ok", ServiceTime: time.Time.Unix()
	return resp, nil


5.2、运行

package main

import (
	"log"
	"myuser/kitex_gen/demouser/userservice"
)

func main() 
	//服务端的地址 [::]:8888,注意要和demouser下的service中名字保持一致,这里userservice
	svr := userservice.NewServer(new(UserServiceImpl))

	err := svr.Run()

	if err != nil 
		log.Println(err.Error())
	


没有报错说明一起正常,下面进行客户端的编写
运行 sh build.sh 以进行编译,编译结果会被生成至 output 目录.

最后,运行 sh output/bootstrap.sh 以启动服务。服务会在默认的 8888 端口上开始运行。要想修改运行端口,可打开 main.go,为 NewServer 函数指定配置参数:

addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:9999")
svr := api.NewServer(new(EchoImpl), server.WithServiceAddr(addr))

5.3、客户端 – 测试

package main

import (
	"context"
	"github.com/cloudwego/kitex/client"
	"log"
	"myuser/kitex_gen/demouser"
	"myuser/kitex_gen/demouser/userservice"
	"time"
)

func main() 
	//第一个是服务名字,第二个是指定服务端的地址
	client, err := userservice.NewClient("userservice", client.WithHostPorts("0.0.0.0:8888"))
	if err != nil 
		log.Fatal(err)
	
	for 
		//通过client进行调用
		resp, err := client.CreateUser(context.Background(), &demouser.CreateUserRequestUsername: "wpc", Password: "123456")
		//resp, err := client.CheckUser(context.Background(), &demouser.CheckUserRequestUsername: "wpc", Password: "123456")
		//resp, err := client.MGetUser(context.Background(), &demouser.MGetUserRequestUserIds: []int641, 2)
		if err != nil 
			log.Fatal(err)
			return
		
		log.Println(resp)
		time.Sleep(time.Second)
	


到这里我们就完成了环境的搭建

5.4、使用etcd来完成注册和发现

服务端

package main

import (
	"github.com/cloudwego/kitex/pkg/rpcinfo"
	"github.com/cloudwego/kitex/server"
	etcd "github.com/kitex-contrib/registry-etcd"
	"log"
	"myuser/kitex_gen/demouser/userservice"
)

func main() 
	//服务端的地址 [::]:8888,注意要和demouser下的service中名字保持一致,这里userservice
	// 填写对应的ip地址和端口
	r, err := etcd.NewEtcdRegistry([]string"192.168.1.18:2379") // r不应重复使用。
	if err != nil 
		log.Fatal(err)
	
	svr := userservice.NewServer(new(UserServiceImpl),
		server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfoServiceName: "userservice"),
		server.WithRegistry(r))

	err = svr.Run()

	if err != nil 
		log.Println(err.Error())
	


客户端

NewClient() 后面的第一个参数要和前面的ServiceName保持一致userservice

package main

import (
	"context"
	"github.com/cloudwego/kitex/client"
	etcd "github.com/kitex-contrib/registry-etcd"
	"log"
	"myuser/kitex_gen/demouser"
	"myuser/kitex_gen/demouser/userservice"
	"time"
)

func main() 
	//第一个是服务名字,第二个是指定服务端的地址
	r, err := etcd.NewEtcdResolver([]string"192.168.1.18:2379") // r不应重复使用。
	if err != nil 
		log.Fatal(err)
	
	client, err := userservice.NewClient("userservice", client.WithResolver(r))
	....


5.5、项目地址

https://gitee.com/wangpengchengalex/go-easynote/tree/master/demo

6、user微服务

使用 gorm-gen 来快速crud 官网

6.1、创建用户表

当然也可以是SQL创建

package main

import (
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

type User struct 
	gorm.Model
	Username string `json:"username"`
	Avatar   string `json:"avatar"` //password懒得改了


func main() 
	dsn := "gorm:gorm@tcp(192.168.1.18:3306)/gorm?charset=utf8&parseTime=True&loc=Local"
	println(dsn)
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config)
	if err != nil 
		panic("failed to connect database")
	

	// 迁移 schema
	db.AutoMigrate(&User)


这将在数据库中生成

6.2、gorm-gen生成crud

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gen"
	"gorm.io/gorm"
)

func main() 
	// 连接数据库
	db, err := gorm.Open(mysql.Open("gorm:gorm@tcp(192.168.1.18:3306)/gorm?charset=utf8&parseTime=True&loc=Local"))
	if err != nil 
		panic(fmt.Errorf("cannot establish db connection: %w", err))
	
	g := gen.NewGenerator(gen.Config
		OutPath: "D:\\\\goCode\\\\myuser\\\\gorm-gen\\\\query",
		Mode:    gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface, // generate mode
	)

	g.UseDB(db)
	// Generate basic type-safe DAO API
	g.ApplyBasic(g.GenerateAllTable()...)
	g.Execute()


6.3、测试crud

package main

import (
	"context"
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"log"
	"myuser/gorm-gen/model"
	"myuser/gorm-gen/query"
)

func main() 
	// 连接数据库
	db, err := gorm.Open(mysql.Open("gorm:gorm@tcp(192.168.1.18:3306)/gorm?charset=utf8&parseTime=True&loc=Local"))
	if err != nil 
		panic(fmt.Errorf("cannot establish db connection: %w", err))
	
	query.SetDefault(db)
	// 增加数据
	u := query.User
	ctx := context.Background()
	users := []*model.UserUsername: "test1", Avatar: "test1", Username: "test2", Avatar: "test2"
	u.WithContext(ctx).Create(users...)
	// 查询数据 https://gorm.io/gen/query.html
	seach1, _ := u.WithContext(ctx).Where(u.ID.Eq(1)).First()
	log.Println("通过id查询", seach1)
	// 更新数据 https://blog.csdn.net/Jeffid/article/details/126898000
	u.WithContext(ctx).Where(u.Username.Eq("test2")).Update(u.Username, "wpc")
	seach2, _ := u.WithContext(ctx).Where(u.Username.Eq("wpc")).First()
	log.Println("更新后的查询", seach2)


6.4、添加到demo中 - dao层

规定 : 将crud的数据访问对象都放入dal文件中,下面主要三个目录 1、db 接下来定义的数据库操作 2、model
3、query (2,3都由gorm-gen生成)

1、init.go

package db

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"myuser/demo/dal/query"
)

var Q *query.Query

func Init() 
	var err error
	db, err := gorm.Open(mysql.Open("gorm:gorm@tcp(192.168.1.18:3306)/gorm?charset=utf8&parseTime=True&loc=Local"))
	if err != nil 
		panic(fmt.Errorf("cannot establish db connection: %w", err))
	
	query.SetDefault(db)
	Q = query.Q
	if err != nil 
		panic(err)
	


2、user.go

package db

import (
	"context"
	"myuser/demo/dal/model"
)

// MGetUsers multiple get list of user info
func MGetUsers(ctx context.Context, userIDs []int64) ([]*model.User, error) 
	return Q.WithContext(ctx).User.Where(Q.User.ID.In(userIDs...)).Find()


// CreateUser create user info
func CreateUser(ctx context.Context, users []*model.User) error 
	return Q.WithContext(ctx).User.Create(users...)


// QueryUser query list of user info
func QueryUser(ctx context.Context, userName string) ([]*model.User, error) 
	return Q.WithContext(ctx).User.Where(Q.User.Username.Eq(userName)).Find()


3、user_test.go

package db

import (
	"context"
	"log"
	"myuser/demo/dal/model"
	"testing"
)

func TestCreateUser(t *testing.T) 
	Init()
	users := make([]*model.User, 0)
	users = append(users, &model.UserUsername: "wpctest", Avatar: "123187")
	CreateUser(context.Background(), users)


func TestQueryUser(t *testing.T) 
	Init()
	user, err := QueryUser(context.Background(), "wpctest")
	if err != nil 
		log.Fatal(err)
	
	log.Println(user[0])


func TestMGetUsers(t *testing.T) 
	Init()
	users, err := MGetUsers(context.Background(), []int647, 9, 10)
	if err != nil 
		log.Fatal(err)
	
	for _, user := range users 
		log.Println(user)
	


相关代码地址

6.5、service层中调用dao层的方法

建一个目录service,里面是具体的实现方法,handler.go充当api层来获取参数和返回数据对象,具体的数据处理由service中的一个一个方法来处理

6.5.1、新建一个异常处理类

package errno

import (
	"errors"
	"fmt"
	"myuser/demo/kitex_gen/demouser"
)

type ErrNo struct 
	ErrCode int64
	ErrMsg  string


func (e ErrNo) Error() string 
	return fmt.Sprintf("err_code=%d, err_msg=%s", e.ErrCode, e.ErrMsg)


func NewErrNo(code int64

IPC,Hz(Hertz) and Clock Speed

   How do we measure a CPU\'s work? Whether it\'s fast or not depends on three factors: IPC, Hz, Clock speed.

IPC means instructions per cycle, Hertz[Hz] means cycles per second and Clock speed\'s definition is always the large number of Hz.

So if two CPUs have the same Clock speeds, the one that has higher IPC is more powerful.

   Furthermore, while increasing clock speed almost immediately speeds up all programs running on that computational unit,

because they are able to do more calculations per seconds(It can run more IPC), but the clock speed is unchangable

in every chip.

   From the above what the I say, it seems that chip can speed up when we add more CPUs. Nowadays, because of the

prevalence of multicore architectures, we have already created a chip including multiple CPUs within a computer. However, it does

not means that the computer can run faster if we add more CPUs. Here is chart which can explain everything:

 

以上是关于[kitex + gorm-gen + hertz] 快速写出一个基于go的微服务的主要内容,如果未能解决你的问题,请参考以下文章

字节跳动 Go RPC 框架 KiteX 性能优化实践

kitex 中 consistent hashing 的实现

IPC,Hz(Hertz) and Clock Speed

高性能 RPC 框架 CloudWeGo-Kitex 内外统一的开源实践

Mathametica_00_The Hertz' dipoles

字节跳动自研的 Go RPC 框架 KiteX 核心技术讲解