[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 int64IPC,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的微服务的主要内容,如果未能解决你的问题,请参考以下文章
kitex 中 consistent hashing 的实现
高性能 RPC 框架 CloudWeGo-Kitex 内外统一的开源实践