Hertz和Thrift简单示例

Hertz和Thrift简单示例

Hertz

Hertz 是字节跳动服务框架团队研发的超大规模的企业级微服务 HTTP 框架,具有高易用性、易扩展、低时延等特点。

官方文档:https://www.cloudwego.io/zh/docs/hertz/

基本使用:

定义路由:

func main() {
	h := server.Default(server.WithHostPorts("127.0.0.1:50000"))
	h.GET("/ping", router.Deal)
	h.Spin()
}

路由的Handler:

其中与其他框架最大的不同点是将Context分成了两个部分:

两个上下文主要有两点区别:

  1. 生命周期不同。RequestContext 的生命周期局限于一次 http 请求之内,而 context.Context 会在 RPC Client 或者日志、Tracing 等组件间传递,其生命周期可能是链路级别的;
  2. 协程安全性。RequestContext 协程不安全,不适合异步传递,但可以通过 Copy()方法获取一个协程安全的副本,而 context.Context 本身就是协程安全的。
func Deal(c context.Context, ctx *app.RequestContext) {
	ctx.JSON(consts.StatusOK, utils.H{"message": res})
}

Thrift

Thrift是一个 轻量级跨语言远程服务调用框架,最初由 Facebook开发,后面进入 Apache开源项目。它通过自身的 IDL 中间语言 , 并借助代码生成引擎生成各种主流语言的 RPC 服务端 /客户端模板代码。

官方安装:https://thrift.apache.org/docs/BuildingFromSource.html

网上资料:

注意安装的thrift的版本与go的插件版本一定要相同!

安装go插件:

go get github.com/apache/thrift/lib/go/thrift

首先安装依赖:

apt install libboost-dev libboost-test-dev libboost-program-options-dev libboost-filesystem-dev libboost-thread-dev libevent-dev automake libtool flex bison pkg-config g++ libssl-dev

安装Thrift:

git clone https://github.com/apache/thrift
cd thrift
./bootstrap.sh
./configure --without-qt4 --wihout-qt5
make
make install

编译使用:

thrift -r --gen go compute.thrift

Thrift文件定义

namespace go compute

service MulRange {
    string BigRange(1:i64 max)
}

客户端

func Deal(c context.Context, ctx *app.RequestContext) {

	transportFactory := thrift.NewTTransportFactory()

	protocolFactory := thrift.NewTBinaryProtocolFactoryConf(nil)
	addr := "127.0.0.1:9999"

	cfg := &thrift.TConfiguration{}

	// 建立和服务器的连接socket,通过socket建立Transport
	var transport thrift.TTransport
	transport = thrift.NewTSocketConf(addr, cfg)
	transport, _ = transportFactory.GetTransport(transport)
	defer transport.Close()

	// 打开Transport,与服务器进行连接
	transport.Open()

	iprot := protocolFactory.GetProtocol(transport)
	oprot := protocolFactory.GetProtocol(transport)

	client := compute.NewMulRangeClient(thrift.NewTStandardClient(iprot, oprot))

	num, _ := client.BigRange(context.Background(), 10)
	fmt.Println(num)

	ctx.JSON(consts.StatusOK, utils.H{"message": num})
}

服务端

// 尽量一个struct对应一个service
type mulrangeThrift struct {
}

func (m *mulrangeThrift) BigRange(_ context.Context, max int64) (string, error) {

	result := max + 1253
	return strconv.FormatInt(result, 10), nil
}

func main() {
	// 创建服务器
	serverTransport, _ := thrift.NewTServerSocket(net.JoinHostPort("127.0.0.1", "9999"))

	// 创建二进制协议
	transportFactory := thrift.NewTTransportFactory()
	protocolFactory := thrift.NewTBinaryProtocolFactoryConf(nil)

	mulrangeProcessor := compute.NewMulRangeProcessor(new(mulrangeThrift))

	// 启动服务器
	server := thrift.NewTSimpleServer4(mulrangeProcessor, serverTransport, transportFactory, protocolFactory)
	server.Serve()

	// 退出时停止服务器
	defer server.Stop()
}

Thrift深入学习

参考资料:https://juejin.cn/post/6844903622380093447

Thrift是一个 轻量级跨语言远程服务调用框架,最初由 Facebook开发,后面进入 Apache开源项目。它通过自身的 IDL 中间语言 , 并借助代码生成引擎生成各种主流语言的 RPC 服务端 /客户端模板代码。

Thrift的特性

(一) 开发速度快

通过编写 RPC接口 Thrift IDL文件,利用编译生成器自动生成 服务端骨架 (Skeletons)和 客户端桩 (Stubs)。从而省去开发者自定义维护接口编解码消息传输服务器多线程模型等基础工作。

  • 服务端:只需要按照服务骨架接口 ,编写好具体的 业务处理程序 (Handler)即实现类即可。
  • 客户端:只需要拷贝 IDL定义好的客户端桩服务对象 ,然后就像调用本地对象的方法一样调用远端服务。

(二) 接口维护简单

通过维护 Thrift格式的IDL( 接口描述语言 )文件(注意写好注释),即可作为给 Client使用的接口文档使用,也自动生成接口代码,始终保持代码和文档的一致性。且 Thrift协议可灵活支持接口可扩展性

(三) 学习成本低

因为其来自 Google Protobuf开发团队,所以其 IDL文件风格类似 Google Protobuf,且更加 易读易懂 ;特别是 RPC服务接口的风格就像写一个面向对象Class一样简单。

初学者只需参照:thrift.apache.org/,一个多小时就可以理解 Thrift IDL文件的语法使用。

(四) 多语言/跨语言支持

Thrift支持 C++JavaPythonPHPRubyErlangPerlHaskellC#CocoaJavaScriptNode.jsSmalltalk等多种语言,即可生成上述语言的服务器端客户端程序

对于我们经常使用的 JavaPHPPythonC++支持良好,虽然对 iOS环境的 Objective-C(Cocoa)支持稍逊,但也完全满足我们的使用要求。

(五) 稳定/广泛使用

Thrift在很多开源项目中已经被验证是稳定高效的,例如 CassandraHadoopHBase等;国外在 Facebook中有广泛使用,国内包括百度、美团小米、和饿了么等公司。

数据类型

  • 基本类型
    • bool : 布尔值
    • byte : 8位有符号整数
    • i16 : 16位有符号整数
    • i32 : 32位有符号整数
    • i64 : 64位有符号整数
    • double : 64位浮点数
    • string : UTF-8编码的字符串
    • binary : 二进制串
  • 结构体类型
    • struct : 定义的结构体对象
  • 容器类型
    • list : 有序元素列表
    • set : 无序无重复元素集合
    • map : 有序的key/value集合
  • 异常类型
    • exception : 异常类型
  • 服务类型
    • service : 具体对应服务的类

Thrift协议

Thrift可以让用户选择客户端服务端之间传输通信协议的类别,在传输协议上总体划分为 文本 (text)和 二进制 (binary)传输协议。为 节约带宽提高传输效率 ,一般情况下使用二进制类型的传输协议为多数,有时还会使用基于文本类型的协议,这需要根据项目/产品中的实际需求。常用协议有以下几种:

  • TBinaryProtocol:二进制编码格式进行数据传输
  • TCompactProtocol:高效率的、密集二进制编码格式进行数据传输
  • TJSONProtocol: 使用 JSON文本的数据编码协议进行数据传输
  • TSimpleJSONProtocol:只提供 JSON只写的协议,适用于通过脚本语言解析

Thrift与Protobuf的区别

Thrift和Protobuf的最大不同,在于Thrift提供了完整的RPC支持,包含了Server/Client,而Protobuf只包括了stub的生成器和格式定义。

Thrift示例

thrift语法

User.thrift

namespace go Sample

struct User {
    1:required i32 id;
    2:required string name;
    3:required string avatar;
    4:required string address;
    5:required string mobile;
}

struct UserList {
    1:required list<User> userList;
    2:required i32 page;
    3:required i32 limit;
}

Service.thrift

include "User.thrift"

namespace go Sample

typedef map<string, string> Data

struct Response {
    1:required i32 errCode; //错误码
    2:required string errMsg; //错误信息
    3:required Data data;
}

//定义服务
service Greeter {
    Response SayHello(
        1:required User.User user
    )

    Response GetUser(
        1:required i32 uid
    )
}
  1. 文件引入

thrift支持引入另一个thrift文件:

include "User.thrift"

注意:

include 引入文件的使用,字段必须带文件名前缀:

1:required User.User user

不能直接写 User user,这样会提示找不到 User定义。

编译时只编译引用了其他文件的thrift文件即可:

thrift -r --gen go Service.thrift
  1. 定义命名空间或者包名
namespace go Sample
namespace php Sample

需要支持多个语言,则需要定义多行。

命名空间或者包名是多层级,使用 .号隔开。例如golang对于 Sample.Model会生成目录 Sample/Model,包名是 Model

  1. Field
struct User {
    1:required i32 id = 0;
    2:optional string name;
}

字段选项 支持 requiredoptional两种。

一旦一个参数设置为 required,未来就一定不能删除或者改为 optional,否则就会出现版本不兼容问题,老客户端访问新服务会出现参数错误。不确定的情况可以都使用 optional

  1. 类型定义
  2. 基本类型
bool:布尔值(truefalsebyte8位有符号整数
i1616位有符号整数
i3232位有符号整数
i6464位有符号整数
double64位浮点数
string:使用UTF-8编码编码的文本字符串
  1. 容器类型
list<t1>:一系列t1类型的元素组成的有序列表,元素可以重复
set<t1>:一些t1类型的元素组成的无序集合,元素唯一不重复
map<t1,t2>:key/value对,key唯一
  1. 类型别名
typedef map<string, string> Data
  1. 枚举类型
enum TweetType {
    TWEET,
    RETWEET = 2,
    DM = 0xa,
    REPLY
}

默认从0开始赋值,枚举值可以赋予某个常量,允许常量是十六进制整数。末尾没有逗号。

不支持枚举类嵌套,枚举常量必须是32位正整数。

对于go,会生成 TweetType_开头的常量。

  1. 常量类型

Thrift允许用户定义常量,复杂的类型和结构体可以使用JSON形式表示:

const i32 INT_CONST = 1234
const map<string,string> MAP_CONST = {"hello": "world", "goodnight": "moon"}
  1. 异常类型
exception BizException {
    1:required i32 code
    2:required string msg
}
  1. 结构体

结构体可以包含其他结构体,但不支持继承结构体。

struct Response {
    1:required i32 errCode; //错误码
    2:required string errMsg; //错误信息
    3:required Data data;
}
  1. 服务

Thrift编译器会根据选择的目标语言为server产生服务接口代码,为client产生桩(stub)代码。

在go里是 interfaceservice里定义的方法必须由服务端实现。

service Greeter {
    Response SayHello(
        1:required User.User user
    )
}

参数是user,返回值是Response类型

服务端代码

服务端主要完成4个部分的工作:

  • Create a transport
  • Create input/output protocols for the transport
  • Create a processor based on the input/output protocols
  • Wait for incoming connections and hand them off to the processor

服务端最终要创建这样的一个server

func NewTSimpleServerFactory6(processorFactory TProcessorFactory, serverTransport TServerTransport, inputTransportFactory TTransportFactory, outputTransportFactory TTransportFactory, inputProtocolFactory TProtocolFactory, outputProtocolFactory TProtocolFactory) *TSimpleServer {
    return &TSimpleServer{
        processorFactory:       processorFactory,
        serverTransport:        serverTransport,
        inputTransportFactory:  inputTransportFactory,
        outputTransportFactory: outputTransportFactory,
        inputProtocolFactory:   inputProtocolFactory,
        outputProtocolFactory:  outputProtocolFactory,
    }
}

说明:

  • 需要至少指定2个字段(processorFactory和serverTransport)
  • 常用是指定4个字段(包括TransportFactory和ProtocolFactory),默认input与output使用的协议相同
server := thrift.NewTSimpleServer4(processor, transport, transportFactory, protocolFactory)
err = server.Serve()
  1. processor:thrift定义服务的处理函数
// 定义服务
type Greeter struct {
}
handler := &Greeter{}
processor := Sample.NewGreeterProcessor(handler)
  1. serverTransport:在指定的端口上创建一个socket连接
var transport thrift.TServerTransport
transport, err = thrift.NewTServerSocket(*addr)
  1. transportFactory

不同类型可选

//buffered
var transportFactory thrift.TTransportFactory
if *buffered {
    transportFactory = thrift.NewTBufferedTransportFactory(8192)
} else {
    transportFactory = thrift.NewTTransportFactory()
}

//framed
if *framed {
    transportFactory = thrift.NewTFramedTransportFactory(transportFactory)
}
  1. ProtocolFactory

不同类型可选

var protocolFactory thrift.TProtocolFactory
switch *protocol {
case "compact":
    protocolFactory = thrift.NewTCompactProtocolFactory()
case "simplejson":
    protocolFactory = thrift.NewTSimpleJSONProtocolFactory()
case "json":
    protocolFactory = thrift.NewTJSONProtocolFactory()
case "binary", "":
    protocolFactory = thrift.NewTBinaryProtocolFactoryDefault()

客户端代码

客户端定义好client后直接调用方法即可,如下所示:

client := GetClient()
rep, err := client.GetUser(ctx, 100)
rep, err := client.SayHello(ctx, &Sample.User{
     Name:    "thrift",
     Address: "address",
 })
  1. 定义client
iprot := protocolFactory.GetProtocol(transport)
oprot := protocolFactory.GetProtocol(transport)
client := Sample.NewGreeterClient(thrift.NewTStandardClient(iprot, oprot))

涉及到protocolFactory与transport

  1. protocolFactory
protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
iprot := protocolFactory.GetProtocol(transport)
oprot := protocolFactory.GetProtocol(transport)

注意要与服务端定义的protocolFactory要一致

  1. transport

创建socket连接:

var transport thrift.TTransport
var err error
transport, err = thrift.NewTSocket(addr)

注意要提前进行类型定义,否则后面类型不匹配

定义transportFactory:

transportFactory := thrift.NewTTransportFactory()
transport, err = transportFactory.GetTransport(transport)
transport.Open()

注意transportFactory的类型要与服务端相同

其他

可以添加key同时使用SSL进行Socket连接从而确保安全性


Hertz和Thrift简单示例
https://zhangzhao219.github.io/2023/02/24/Backend/HertzAndThrift/
作者
Zhang Zhao
发布于
2023年2月24日
许可协议