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分成了两个部分:
两个上下文主要有两点区别:
- 生命周期不同。RequestContext 的生命周期局限于一次 http 请求之内,而 context.Context 会在 RPC Client 或者日志、Tracing 等组件间传递,其生命周期可能是链路级别的;
- 协程安全性。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++
、 Java
、Python
、PHP
、Ruby
、Erlang
、Perl
、Haskell
、C#
、Cocoa
、JavaScript
、Node.js
、Smalltalk
等多种语言,即可生成上述语言的服务器端和 客户端程序 。
对于我们经常使用的 Java
、PHP
、Python
、C++
支持良好,虽然对 iOS
环境的 Objective-C
(Cocoa
)支持稍逊,但也完全满足我们的使用要求。
(五) 稳定/广泛使用
Thrift
在很多开源项目中已经被验证是稳定和高效的,例如 Cassandra
、Hadoop
、HBase
等;国外在 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
)
}
-
文件引入
thrift支持引入另一个thrift文件:
include "User.thrift"
注意:
include 引入文件的使用,字段必须带文件名前缀:
1:required User.User user
不能直接写 User user
,这样会提示找不到 User
定义。
编译时只编译引用了其他文件的thrift文件即可:
thrift -r --gen go Service.thrift
-
定义命名空间或者包名
namespace go Sample
namespace php Sample
需要支持多个语言,则需要定义多行。
命名空间或者包名是多层级,使用 .
号隔开。例如golang对于 Sample.Model
会生成目录 Sample/Model
,包名是 Model
。
-
Field
struct User {
1:required i32 id = 0;
2:optional string name;
}
字段选项 支持 required
、optional
两种。
一旦一个参数设置为 required
,未来就一定不能删除或者改为 optional
,否则就会出现版本不兼容问题,老客户端访问新服务会出现参数错误。不确定的情况可以都使用 optional
。
-
类型定义
- 基本类型
bool:布尔值(true或false)
byte:8位有符号整数
i16:16位有符号整数
i32:32位有符号整数
i64:64位有符号整数
double:64位浮点数
string:使用UTF-8编码编码的文本字符串
- 容器类型
list<t1>:一系列t1类型的元素组成的有序列表,元素可以重复
set<t1>:一些t1类型的元素组成的无序集合,元素唯一不重复
map<t1,t2>:key/value对,key唯一
- 类型别名
typedef map<string, string> Data
- 枚举类型
enum TweetType {
TWEET,
RETWEET = 2,
DM = 0xa,
REPLY
}
默认从0开始赋值,枚举值可以赋予某个常量,允许常量是十六进制整数。末尾没有逗号。
不支持枚举类嵌套,枚举常量必须是32位正整数。
对于go,会生成 TweetType_
开头的常量。
- 常量类型
Thrift允许用户定义常量,复杂的类型和结构体可以使用JSON形式表示:
const i32 INT_CONST = 1234
const map<string,string> MAP_CONST = {"hello": "world", "goodnight": "moon"}
- 异常类型
exception BizException {
1:required i32 code
2:required string msg
}
- 结构体
结构体可以包含其他结构体,但不支持继承结构体。
struct Response {
1:required i32 errCode; //错误码
2:required string errMsg; //错误信息
3:required Data data;
}
- 服务
Thrift编译器会根据选择的目标语言为server产生服务接口代码,为client产生桩(stub)代码。
在go里是 interface
。service
里定义的方法必须由服务端实现。
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()
-
processor:thrift定义服务的处理函数
// 定义服务
type Greeter struct {
}
handler := &Greeter{}
processor := Sample.NewGreeterProcessor(handler)
-
serverTransport:在指定的端口上创建一个socket连接
var transport thrift.TServerTransport
transport, err = thrift.NewTServerSocket(*addr)
-
transportFactory
不同类型可选
//buffered
var transportFactory thrift.TTransportFactory
if *buffered {
transportFactory = thrift.NewTBufferedTransportFactory(8192)
} else {
transportFactory = thrift.NewTTransportFactory()
}
//framed
if *framed {
transportFactory = thrift.NewTFramedTransportFactory(transportFactory)
}
-
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",
})
-
定义client
iprot := protocolFactory.GetProtocol(transport)
oprot := protocolFactory.GetProtocol(transport)
client := Sample.NewGreeterClient(thrift.NewTStandardClient(iprot, oprot))
涉及到protocolFactory与transport
-
protocolFactory
protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
iprot := protocolFactory.GetProtocol(transport)
oprot := protocolFactory.GetProtocol(transport)
注意要与服务端定义的protocolFactory要一致
-
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连接从而确保安全性