LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

🐱使用Go 写Thrift Rpc接口

2024/10/16 thrift

👉 简介

完整的🌰:https://github.com/D-Watson/go-thrift-sample/tree/main

🐱Thrift是用来做什么的?

Thrift 是一个远程调用(RPC)框架~类似于grpc,方便跨语言的服务之间的相互通信~

🐱Thrift的优点?

  • 跨语言支持:
    可以支持java, c++, python, go, php .etc.
  • 高性能:
    • 高效的序列化反序列化机制,使用二进制格式表示数据,解析时所需的字节数更少,传输速度更快。
    • 支持多种通讯协议:使用序列化协议TCompactProtocal,该协议使用变长编码(Variable-Length Encoding)减少数据传输的大小。
    • 字段标识符:Thrift数据结构使用字段序号来标识每个字段,而不是使用字段名,这种方法减少了传输中的开销。
    • 支持批处理:支持批量请求和流式传输,可以在一次网络中处理多个请求,减少了网络往返次数。
    • 非阻塞I/O:在异步模式下,Thrift可以在等待I/O操作时继续处理其他请求,这样可以充分利用CPU和网络资源。
  • 多种协议:
    Thrift 提供多种协议,包括二进制协议、JSON 协议等,以适应不同的需求。

1. 数据类型

1.1 基本数据类型

1. 整数类型:

  • i32: 32位带符号整数
  • i64: 64位带符号整数
  • i16:16位带符号整数
  • byte: 8位带符号整数

2. 浮点类型

  • double: 64位双精度浮点数
  • float: 32位单精度浮点数

3. 布尔类型

  • bool: 布尔值

4. 字符串类型

  • string: utf-8编码的字符串

5. 字节类型

  • binary: 原始字节流

1.2 复杂数据类型

1. 结构体

struct User{
    1: i32 id,
    2: string name,
    3: bool isActive
}

2. 枚举

enum UserRole{
    ADMIN = 1,
    USER = 2,
    GUEST = 3
}

3. 容器类型

  • 列表
    list<string> names = 1;
  • 集合
    无序集合,不包含重复元素
    set<i32> ids = 1;
  • 字典
    map<string, i32> userAge = 1;

2. 定义IDL文件

IDL(Interface Definition Language)文件里面定义了结构体,以及接口和远程调用方法:

// 文件名: user.thrift

namespace go user

// 定义用户角色的枚举类型
enum UserRole {
    ADMIN = 1,
    USER = 2,
    GUEST = 3
}

// 定义用户结构体
struct User {
    1: i32 id,              // 用户 ID
    2: string name,         // 用户名
    3: bool isActive,       // 用户是否活跃
    4: UserRole role,       // 用户角色
    5: list<string> tags     // 用户标签
}

// 定义用户服务
service UserService {
    User getUser(1: i32 userId),       // 获取用户信息
    void updateUser(1: User user),      // 更新用户信息
    list<User> getAllUsers(),           // 获取所有用户
}

执行命令:

thrift -r --gen go user.thrift

会在当前目录下生成一个gen-go文件夹,包含了生成的代码。
alt text

3. 编写server端代码

接下来我们模拟一个想要暴露api的服务它叫小a🐈,它提供了server端,运行在服务器上,供其他服务调用~
thrift编译器会为你定义的服务生成一个接口,定义如下:

type UserService interface {
    // Parameters:
    //  - UserId
    // 
    GetUser(ctx context.Context, userId int32) (_r *User, _err error)
    // Parameters:
    //  - User
    // 
    UpdateUser(ctx context.Context, user *User) (_err error)
    GetAllUsers(ctx context.Context) (_r []*User, _err error)
}

我们需要在小a🐈服务里实现上面的接口:

type UserServiceHandler struct {
}

func (h *UserServiceHandler) GetUser(ctx context.Context, userId int32) (*user.User, error) {
    fmt.Println("userId=", userId)
    return &user.User{
        ID:   userId,
        Name: "xiaoming",
    }, nil
}

func (h *UserServiceHandler) UpdateUser(ctx context.Context, user *user.User) error {
    fmt.Println("update user...")
    return nil
}

func (h *UserServiceHandler) GetAllUsers(ctx context.Context) ([]*user.User, error) {
    var userList []*user.User
    userList = append(userList, &user.User{
        ID:   0,
        Name: "xiaoming",
    })
    return userList, nil
}

//定义一个server入口方法
func InitUserService() {
    //server运行在本地的9091端口
    transport, err := thrift.NewTServerSocket("localhost:9091")
    if err != nil {
        fmt.Println("Error starting server:", err)
        return
    }
    handler := &UserServiceHandler{}
    processer := user.NewUserServiceProcessor(handler)
    server := thrift.NewTSimpleServer4(processer, transport, thrift.NewTTransportFactory(),
     thrift.NewTBinaryProtocolFactoryDefault())
    fmt.Println("Starting the server...", transport)
    if err = server.Serve(); err != nil {
        fmt.Println("Error starting server:", err)
    }
}

然后我们在小a🐈服务的main.go文件中启动server:

package main

import (
    _ "my-web-app/routers"
    "my-web-app/services"
)

func main() {
    //beego.Run()
    services.InitUserService()
}

4. 编写client端代码

现在我们有一个服务小b🐶,它作为client端想要调用小a🐈,client端也同样需要user.thrift文件,并在本地生成代码,
然后我们创建和返回一个 Thrift客户端,以便于与UserService服务通信。

func GetClient() *user.UserServiceClient {
    //填写server端地址
    addr := ":9091"
    //声明一个 thrift.TTransport 类型的变量,用于管理与 Thrift 服务的连接
    var transport thrift.TTransport
    var err error
    //使用 thrift.NewTSocket 创建一个新的套接字(socket)传输,连接到指定的地址 addr,
    //该方法返回一个 TTransport 实例和可能的错误。
    transport, err = thrift.NewTSocket(addr)
    if err != nil {
        fmt.Println("Error opening socket:", err)
    }

    //声明一个 thrift.TProtocolFactory 类型的变量,并利用 thrift.NewTBinaryProtocolFactory 
    //创建一个新的二进制协议工厂,用于序列化和反序列化数据
    var protocolFactory thrift.TProtocolFactory
    protocolFactory = thrift.NewTBinaryProtocolFactoryDefault()

    //声明一个 thrift.TTransportFactory 类型的变量,并创建一个新的传输工厂
    //这里使用的是默认的无缓冲传输工厂。
    var transportFactory thrift.TTransportFactory
    transportFactory = thrift.NewTTransportFactory()
    //使用传输工厂的 GetTransport 方法来获取一个新的传输对象,这里传入之前创建的 transport
    transport, err = transportFactory.GetTransport(transport)
    if err != nil {
        fmt.Println("error running client:", err)
    }
    //尝试打开传输连接, 若打开失败,则打印错误信息
    if err := transport.Open(); err != nil {
        fmt.Println("error running client:", err)
    }
    //iprot输入协议
    iprot := protocolFactory.GetProtocol(transport)
    //oprot输出协议
    oprot := protocolFactory.GetProtocol(transport)
    //使用输入和输出协议创建一个新的 UserServiceClient 实例
    //thrift.NewTStandardClient 用于创建一个标准的 Thrift 客户端
    client := user.NewUserServiceClient(thrift.NewTStandardClient(iprot, oprot))
    return client
}

接下来我们创建一个测试方法

func TestGetUser(t *testing.T) {
    client := GetClient()
    //执行远程调用方法GetUser
    rep, err := client.GetUser(context.Background(), 1)
    fmt.Println(rep, err)
    if err != nil {
        fmt.Println("thrift err:", err)
    }
}

执行~

5. 总结

thrift是一个比较好的微服务之间通信的框架~不仅能跨语言,还具有高可扩展性,能够支持大规模的服务调用和负载均衡~
alt text

img_show