本文最后更新于 2024-02-18,文章内容可能已经过时。

Kitex:

1.介绍:

Kitex 字节跳动内部的 Golang 微服务 RPC 框架,具有高性能、强可扩展的特点,在字节内部已广泛使用。如果对微服务性能有要求,又希望定制扩展融入自己的治理体系,Kitex 会是一个不错的选择。

框架特点:

  • 高性能:使用自研的高性能网络库 Netpoll,性能相较 go net 具有显著优势。

  • 扩展性:提供了较多的扩展接口以及默认扩展实现,使用者也可以根据需要自行定制扩展。

  • 多消息协议:RPC 消息协议默认支持 Thrift、Kitex Protobuf、gRPC。Thrift 支持 Buffered 和 Framed 二进制协议;Kitex Protobuf 是 Kitex 自定义的 Protobuf 消息协议,协议格式类似 Thrift;gRPC 是对 gRPC 消息协议的支持,可以与 gRPC 互通。除此之外,使用者也可以扩展自己的消息协议。

  • 多传输协议:传输协议封装消息协议进行 RPC 互通,传输协议可以额外透传元信息,用于服务治理,Kitex 支持的传输协议有 TTHeader、HTTP2。TTHeader 可以和 Thrift、Kitex Protobuf 结合使用;HTTP2 目前主要是结合 gRPC 协议使用,后续也会支持 Thrift。

  • 多种消息类型:支持 PingPong、Oneway、双向 Streaming。其中 Oneway 目前只对 Thrift 协议支持,双向 Streaming 只对 gRPC 支持

  • 服务治理:支持服务注册/发现、负载均衡、熔断、限流、重试、监控、链路跟踪、日志、诊断等服务治理模块,大部分均已提供默认扩展,使用者可选择集成。

  • 代码生成:Kitex 内置代码生成工具,可支持生成 Thrift、Protobuf 以及脚手架代码。

2.使用:

基于 IDL 的 KiteX 实践 在 RPC 框架中,我们知道,服务端与客户端通信的前提是远程通信,但这种通信又存在一种关联,那就是通过一套相关的协议(消息、通信、传输等)来规范,但客户端又不用关心底层的技术实现,只要定义好了这种通信方式即可。在 KiteX 中,也提供了一种生成代码的命令行工具:kitex,目前支持 thrift、protobuf 等 IDL,并且支持生成一个服务端项目的骨架。

2.1 安装:

//安装
PS F:\work\Go\golangStudy> go install github.com/cloudwego/kitex/tool/cmd/kitex\@latest
//查看安装是否成功
PS F:\work\Go\golangStudy\example> kitex
//出现这个即为成功
No IDL file found.

2.2 编写IDL文件:

给出类似的thrift文件demo.

namespace go api
​
struct RegisterRequest {
    1: string username
    2: string password
    3: string name
}
struct RegisterResponse {
    1: string code
    2: string msg
    3: i64 userid
}
​
struct LoginRequest {
    1: string username
    2: string password
​
}
struct LoginResponse {
    1: string code
    2: string msg
    3: i64 userid
}
​
struct ChangeRequest {
    1: i64 userid
    2: string token
    3: i64 type
    4: i64 number
}
struct ChangeResponse {
    1: string code
    2: string msg
}
​
struct GetInfoRequest {
    1: i64 userid
}
struct GetInfoResponse {
    1: i64 userid
    2: string name
    3: string avatar
    4: string backgroundImage
    5: string signature
}
​
service User {
    RegisterResponse register(1: RegisterRequest req)
    LoginResponse login(1: LoginRequest req)
    ChangeResponse change(1: ChangeRequest req)
    GetInfoResponse get(1:GetInfoRequest req)
}
​

2.3 代码生成:

PS F:\work\Go\golangStudy> kitex -module example -service example echo.proto
//出现这个即为成功
go: creating new go.mod: module example

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

2.4 编写代码:

生成文件后的结构如下:

image-20231121142407592

其他的文件都不用管,只需要在handlergo里面编写相关的逻辑就好.

每个方法都已经在handler.go中生成.

下面给出kitex+gorm的demo:

package main
​
import (
    "context"
    "crypto/sha256"
    "douyin/kitex_gen/api"
    "encoding/hex"
    "fmt"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "math/big"
)
​
// UserImpl implements the last service interface defined in the IDL.
type UserImpl struct{}
​
type User struct {
    Id              int64  `gorm:"column:id;"`
    UserId          int64  `gorm:"column:user_id;"`
    Username        string `gorm:"column:username;"`
    Password        string `gorm:"column:password;"`
    Name            string `gorm:"column:name;"`
    Avatar          string `gorm:"column:avatar;"`
    BackgroundImage string `gorm:"column:background_image;"`
    Signature       string `gorm:"column:signature;"`
}
​
func (u *User) TableName() string {
    return "user"
}
​
// 全局盐
var salt = "byteDanceAgain"
​
// 字符串hash,用于加密密码
func hashTo15Bits(input string) string {
    // 创建一个SHA-256哈希对象
    h := sha256.New()
    // 将字符串输入写入哈希对象
    _, err := h.Write([]byte(input))
    if err != nil {
        panic(err)
    }
    // 获取SHA-256哈希值的字节数组
    hashBytes := h.Sum(nil)
    // 取哈希值的前两个字节并转换为16进制字符串
    hashHex := hex.EncodeToString(hashBytes[:2])
    return hashHex
}
​
// 用户id生成哈希,根据账号和
func hashToI64(input string) int64 {
    // 创建一个SHA-256哈希对象
    h := sha256.New()
    // 将字符串输入写入哈希对象
    _, err := h.Write([]byte(input))
    if err != nil {
        panic(err)
    }
    // 获取SHA-256哈希值的字节数组
    hashBytes := h.Sum(nil)
    // 将哈希值的前8个字节转换为int64
    var hashInt big.Int
    hashInt.SetBytes(hashBytes[:8])
    result := hashInt.Int64()
    return result
}
​
// Register implements the UserImpl interface.
func (s *UserImpl) Register(ctx context.Context, req *api.RegisterRequest) (resp *api.RegisterResponse, err error) {
​
    ///建立数据库连接,数据库名:数据库密码
    dsn := "root:123456@tcp(127.0.0.1:3306)/dbgotest"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        return nil, err
    }
    //判断用户名是否唯一
    var user User
    db.First(&user, "username = ?", req.Username)
    if user.Id != 0 {
        resp = &api.RegisterResponse{Code: "1", Msg: "用户名已经存在!"}
        return
    }
    //对密码加盐,加密存储进入数据库
    password := hashTo15Bits(salt + req.Password)
    fmt.Print(password)
    //生成用户id
    UserId := hashToI64(req.Username + req.Password)
    fmt.Print(UserId)
    //创建一条数据,传入一个对象
    db.Create(&User{Username: req.Username, Password: password, Name: req.Name, UserId: UserId})
    resp = &api.RegisterResponse{Code: "0", Msg: "用户注册成功!", Userid: UserId}
    return
}
​
// Login implements the UserImpl interface.
func (s *UserImpl) Login(ctx context.Context, req *api.LoginRequest) (resp *api.LoginResponse, err error) {
    ///建立数据库连接,数据库名:数据库密码
    dsn := "root:123456@tcp(127.0.0.1:3306)/dbgotest"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        return nil, err
    }
​
    var user User
    db.First(&user, "username = ?", req.Username)
    if user.Id == 0 {
        resp = &api.LoginResponse{Code: "1", Msg: "用户不存在!"}
        return
    }
    //验证密码
    hashPassword := hashTo15Bits(salt + req.Password)
    if user.Password != hashPassword {
        resp = &api.LoginResponse{Code: "1", Msg: "用户密码错误!"}
        return
    }
    resp = &api.LoginResponse{Code: "0", Msg: "用户登录成功!", Userid: user.Id}
    return
}
​
// Get implements the UserImpl interface.
func (s *UserImpl) Get(ctx context.Context, req *api.GetInfoRequest) (resp *api.GetInfoResponse, err error) {
    ///建立数据库连接,数据库名:数据库密码
    dsn := "root:123456@tcp(127.0.0.1:3306)/dbgotest"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        return nil, err
    }
    //获取用户信息
    var user User
    db.First(&user, "user_id = ?", req.Userid)
    resp = &api.GetInfoResponse{Userid: user.UserId, Name: user.Name, Avatar: user.Avatar, BackgroundImage: user.BackgroundImage, Signature: user.Signature}
    return
}
​
//批量获取用户信息
​
// GetAll implements the UserImpl interface.
func (s *UserImpl) GetAll(ctx context.Context, req *api.GetAllInfoResponse) (resp *api.GetAllInfoResponse, err error) {
    dsn := "root:123456@tcp(127.0.0.1:3306)/dbgotest"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        return nil, err
    }
    var User1 []User
    list := req.UserList
    for i := 0; i < len(list); i++ {
        db.First(&User1[i], "user_id = ?", list[i])
    }
    resp = &api.GetAllInfoResponse{UserList: User1}
    return
}
​