写点什么

Golang 微服务框架 Kratos 实现 Thrift 服务

作者:喵个咪
  • 2023-07-27
    湖南
  • 本文字数:3833 字

    阅读完需:约 13 分钟

Golang 微服务框架 Kratos 实现 Thrift 服务

什么是 Thrift

Thrift 是 Facebook 于 2007 年开发的跨语言的 rpc 服框架,提供多语言的编译功能,并提供多种服务器工作模式;用户通过 Thrift 的 IDL(接口定义语言)来描述接口函数及数据类型,然后通过 Thrift 的编译环境生成各种语言类型的接口文件,用户可以根据自己的需要采用不同的语言开发客户端代码和服务器端代码。2007 年由 facebook 贡献到 apache 基金,是 apache 下的顶级项目,具备如下特点:


  • 支持多语言:C、C++ 、C# 、D 、Delphi 、Erlang 、Go 、Haxe 、Haskell 、Java 、JavaScript、node.js 、OCaml 、Perl 、PHP 、Python 、Ruby 、SmallTalk

  • 消息定义文件支持注释,数据结构与传输表现的分离,支持多种消息格式

  • 包含完整的客户端/服务端堆栈,可快速实现 RPC,支持同步和异步通信

Thrift 的优缺点

Thrift 的优点

  • One-stop shop,相对于 protobuf,序列化和 RPC 支持一站式解决,如果是 pb 的话,还需要考虑选择 RPC 框架,现在 Google 是开源了 gRpc,但是几年以前是没有第一方的标准解决方案的

  • 特性丰富,idl 层面支持 map,protobuf 应该是最近才支持的,map 的 key 支持任意类型,avro 只支持 string,序列化支持自定义 protocol, rpc 支持 thread pool, hsha, no blocking 多种形式,必有一款适合你,对于多语言的支持也非常丰富

  • RPC 和序列化性能都不错,这个到处都有 benchmark,并不是性能最好的,但是基本上不会成为瓶颈或者短板

  • 有很多开源项目的周边支持都是 thrift 的,hbase 提供 thrift 服务,hive,spark sql,cassandra 等一系列对外的标准服务接口都是 thrift 的以支持多语言。

  • Column Storage 的话,parquet 支持直接通过 thrift idl 转换,如果在 Hadoop 集群上存储数据,elephant-bird 支持得很好,你可以很方便地针对 thrift 的数据通过 pig 写 dsl,如果你希望在 rpc 服务外做一系列工作,可以用 finagle 包装一层。不过,这部分对于 protobuf 和 avro 支持一般也不错

Thrift 的缺点

  • 基本没有官方文档

  • RPC 在 0.6.1 升级到 0.7.0 是不兼容的!这个对于早于 0.6.1 开始使用的用户来说是个大坑

  • bug fix 和更新不积极,好在序列化和 RPC 服务都不是太复杂的问题,需要考量的设计问题不多,自己维护 patch 的成本不高,如果我没有记错的话,0.6.1 的 java 的 ThreadPool Server 是会有 Thread 死亡之后的 Thread 泄露问题的

  • 不支持双向通道,如果要支持双向通道比较麻烦

  • rpc 方法非线程安全,这就是为何很多时候服务器会被挂死,是因为客户端的并发 rpc 调用导致的,只需要客户端对 rpc 的调用进行串行化即可。统一服务器应答的时候,也需要串行化,否则有可能会把对方给挂死。特别是在多线程情况下。

什么时候应该选择 Thrift

  • 需要在非常多的语言间进行数据交换

  • 对 CPU 敏感

  • 协议层、传输层有多种控制要求

  • 需要稳定的版本

  • 不需要良好的文档和示例

Thrift 网络栈架构

TTransport 层

  • TSocket :阻塞 Socket

  • TFrameTransport :以 frame 为单位进行传输, 非阻塞式服务中使用

  • TFileTransport : 以文件形式进行传输

TProtocol 层

代表 thrift 客户端和服务端之间的传输数据的协议,指的是客户端和服务端传输数据的格式,比如 Json, thrift 中有如下格式:


  • TBinaryProtocol:二进制格式

  • TCompactProtocol:压缩格式

  • TJSONProtocol : Json 格式

  • TSimpleJsonProtocol:提供只写的 JSON 协议


Thrift 支持的 Server 模型


  • TSimpleServer :用于简单的单线程模型,常用于测试

  • TThreadPoolServer :多线程模型,使用标准的阻塞 IO

  • TNoBlockingServer: 多线程服务模型,使用非阻塞 IO,需要使用 TFramedTransport 数据传输方式。

  • THsHaServer : THsHa 引入了线程池去处理,其模型读写任务放到线程池去处理,Half-sync/Half-async 处理模式,Half-async 是在处理 IO 事件上(accept/read/write io),Half-sync 用于 handler 对 rpc 的同步处理;

Thrift 支持的数据类型以及关键字

基本数据类型

容器类型

Struct 类型

Struct:类似于 C 的 struct,是一系列相关数据的封装,在 OOP 语言中会转换为类(class),struct 的每个元素包括一个唯一的数字标识、一个数据类型、一个名称和一个可选的默认值。语法:


struct People {    1:string name;    2:i32 age;    3:string gender;}
复制代码

Union 类型

union SomeUnion {  2: string string_thing,  3: i32 i32_thing}
复制代码

Enum 类型

Enum:Thrift 枚举类型只支持单个 32 位 int 类型数据,第一个元素如果没有给值那么默认是 0,之后的元素如果没有给值,则是在前一个元素基础上加 1,语法:


enum Gender {    MALE,    FEMALE}
复制代码

别名

Typedef:Thrift 支持 C/C++风格的类型自定义,语法:


// typedef 原类型 自定义类型
typedef i32 inttypedef i64 long
复制代码

常量

Const:定义常量,Thrift 允许使用 JSON 来定义复杂类型和 struct 类型,语法:


// const 字段类型 名称标识 = 值 | 列表
const i32 MAX_RETRIES_TIME = 10;const string MY_WEBSITE = "http://facebook.com";
复制代码

Exception 类型

Exception:异常跟 struct 类似,会跟目标语言本地异常集成,语法:


exception RequestException {    1:i32 code;    2:string reason;}
复制代码

Service 类型

Service:service 是 Thrift 服务器提供的一系列功能列表接口,在客户端就是调用这些接口来完成操作,语法:


service HelloWorldService {    // service中可以定义若干个服务,相当于Java Interface中定义的方法    string doAction(1:string name, 2:i32 age);}
复制代码

命名空间

定义名称空间/包名/模块等等,可以使用编程语言名称规定某一特定语言的 namespace,用*表示所有未匹配到的语言的 namespace,语法


// namespace [语言名称] 标识符
namespace cpp apinamespace go apinamespace d apinamespace dart apinamespace java apinamespace php apinamespace perl apinamespace haxe apinamespace netstd api
// 用*表示所有未匹配到的语言的namespacenamespace * api
复制代码

注释

# shell风格注释
/* * 多行注释 */
// 单行注释
复制代码

包含引用

Thrift Include:将所有声明包含的 Thrift 文档都包含到当前 Thrift 中来,语法:


// 包含其他的thrift文件// inlucde "文件名"include "other.thrift"
复制代码


C++ Include:将用户自己的 C++头文件包含到当前 Thrift 中来,语法:


// 将用户自己的C++头文件包含到当前Thrift中来// cpp_include "头文件"cpp_include "string"
复制代码

安装编译器

Linux 安装编译器

sudo apt install thrift-compiler
复制代码

Windows 安装编译器

先去官网下载编译器:https://thrift.apache.org/download


然后把编译器放在一个全局可以运行的目录下面,比如:c:/Windows。

Mac 安装编译器

brew install thrift
复制代码

编译 Thrift 文件

# thrift --gen <language> <Thrift filename>
# 如果有thrift文件中有包含其他thrift,可以使用递归生成命令thrift -r --gen <language> <Thrift filename>
# 示例thrift -r -gen go tutorial.thrift
复制代码

开始在 Kratos 微服务框架下使用 Thrift

我封装了一个 Thrift 服务,可以在 Kratos 微服务框架下直接使用:https://github.com/tx7do/kratos-transport/tree/main/transport/thrift


实例程序的目标是从服务器获取温湿度信息,然后将温湿度信息发送给客户端。示例代码可以在单元测试里面找到。

创建 Thrift 文件

namespace go api
struct Hygrothermograph { 1: optional double Humidity, 2: optional double Temperature,}
service HygrothermographService { Hygrothermograph getHygrothermograph()}
复制代码


然后生成代码。生成的代码在 thrift 文件所在目录下的:gen-go/{namespace}之下。

开发服务器

首先,实现 Handler

type HygrothermographHandler struct {}
func NewHygrothermographHandler() *HygrothermographHandler {return &HygrothermographHandler{}}
func (p *HygrothermographHandler) GetHygrothermograph(ctx context.Context) (_r *api.Hygrothermograph, _err error) {var Humidity = float64(rand.Intn(100))var Temperature = float64(rand.Intn(100))_r = &api.Hygrothermograph{Humidity: &Humidity,Temperature: &Temperature,}fmt.Println("Humidity:", Humidity, "Temperature:", Temperature)return}
复制代码


Handler 在 Kratos 里面的使用的语义是 Service,实际应用的时候,将之实现为 Service。

实现服务端

    ctx := context.Background()
srv := NewServer(WithAddress(":7700"),WithProcessor(api.NewHygrothermographServiceProcessor(NewHygrothermographHandler())),)
if err := srv.Start(ctx); err != nil {panic(err)}
defer func () {if err := srv.Stop(ctx); err != nil {t.Errorf("expected nil got %v", err)}}()
复制代码


使用WithProcessor方法我们把之前的Handler注册成Processor到服务器。

开发客户端

conn, err := Dial(WithEndpoint("localhost:7700"),)if err != nil {t.Fatal(err)}defer conn.Close()
client := api.NewHygrothermographServiceClient(conn.Client)
reply, err := client.GetHygrothermograph(context.Background())//t.Log(err)if err != nil {t.Errorf("failed to call: %v", err)}t.Log(*reply.Humidity, *reply.Temperature)
复制代码


Thrift 客户端跟 gRpc 的客户端是一样的,使用Dial方法创建一个连接,然后直接调用 RPC 方法。

参考文档

用户头像

喵个咪

关注

还未添加个人签名 2022-06-01 加入

还未添加个人简介

评论

发布
暂无评论
Golang微服务框架Kratos实现Thrift服务_喵个咪_InfoQ写作社区