1、thrift的基本介绍

1.1 thrift的定义

Thrift是一个轻量级、跨语言的RPC框架,主要用于各个服务之间的RPC通信,最初由Facebook于2007年开发,2008年进入Apache开源项目。它通过自身的IDL中间语言, 并借助代码生成引擎生成各种主流语言的RPC服务端/客户端模板代码。Thrift支持多种不同的编程语言,包括C++, Java, Python,PHP,Ruby, Erlang, Haskell, C#, Cocoa, Javascript, Node.js, Smalltalk, OCaml, Golang等,本系列主要讲述基于Java语言的Thrift的配置方式和具体使用。

1.2 thrift架构

Thrift技术栈分层从下向上分别为:传输层(Transport Layer)、协议层(Protocol Layer)、处理(Processor Layer)和服务层(Server Layer)。

传输层(Transport Layer):传输层负责直接从网络中读取和写入数据,它定义了具体的网络传输协议;比如说TCP/IP传输等。协议层(Protocol Layer):协议层定义了数据传输格式,负责网络传输数据的序列化和反序列化;比如说JSON、XML、二进制数据等。处理层(Processor Layer):处理层是由具体的IDL(接口描述语言)生成的,封装了具体的底层网络传输和序列化方式,并委托给用户实现的Handler进行处理。服务层(Server Layer):整合上述组件,提供具体的网络IO模型(单线程/多线程/事件驱动),形成最终的服务。 采用TCP/IP作为更底层的通信协议图:

1.3 thrift的基本特性

1.3.1 开发速度快

通过编写RPC接口Thrift IDL文件,利用编译生成器自动生成服务端骨架(Skeletons)和客户端桩(Stubs)。从而省去开发者自定义和维护接口编解码、消息传输、服务器多线程模型等基础工作。服务端:只需要按照服务骨架即接口,编写好具体的业务处理程序(Handler)即实现类即可。客户端:只需要拷贝IDL定义好的客户端桩和服务对象,然后就像调用本地对象的方法一样调用远端服务。

1.3.2 接口维护简单

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

1.3.3 学习成本低

因为其来自Google Protobuf开发团队,所以其IDL文件风格类似Google Protobuf,且更加易读易懂;特别是RPC服务接口的风格就像写一个面向对象的Class一样简单。初学者只需参照:http://thrift.apache.org/,一个多小时就可以理解Thrift IDL文件的语法使用。

1.3.4 多语言/跨语言支持

Thrift支持C++、 Java、Python、PHP、Ruby、Erlang、Perl、Haskell、C#、Cocoa、JavaScript、Node.js、Smalltalk等多种语言,即可生成上述语言的服务器端和客户端程序。

1.3.5 稳定/广泛使用

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

2、thrift的基本语法

2.1 thrift的IDL介绍

Thrift是一个典型的CS(客户端/服务端)结构,客户端和服务端可以使用不同的语言开发。既然客户端和服务端能使用不同的语言开发,那么一定就要有一种中间语言来关联客户端和服务端的语言,这种语言就是IDL(InterfaceDescription Language)

Thrift 采用IDL(Interface Definition Language)来定义通用的服务接口,然后通过Thrift提供的编译器,可以将服务接口编译成不同语言编写的代码,通过这个方式来实现跨语言的功能

2.2 IDL的基本语法——基础类型

2.3 特殊类型、集合容器

binary: 未编码的字节序列,是string的一种特殊形式;这种类型主要是方便某些场景下JAVA调用。JAVA中对应的是java.nio.ByteBuffer类型,GO中是[]byte

目前有三种容器类型:

在使用容器类型时必须指定泛型,否则无法编译idl文件。其次,泛型中的基本类型,JAVA语言中会被替换为对应的包装类型。

2.4 常量及类型别名(Const&&Typedef)

//常量定义

const i32 MALE_INT = 1

const map GENDER_MAP = {1: "male", 2: "female"}

//某些数据类型比较长可以用别名简化

typedef map gmp

2.5 struct类型

在面向对象语言中,表现为“类定义”;在弱类型语言、动态语言中,表现为“结构/结构体”。定义格式如下

struct <结构体名称> {

<序号>:[字段性质] <字段类型> <字段名称> [= <默认值>] [;|,]

}

例如:

struct User{

1: required string name, //该字段必须填写

2: optional i32 age = 0; //默认值

3: bool gender //默认字段类型为optional

}

struct bean{

1: i32 number=10,

2: i64 bigNumber,

3: double decimals,

4: string name="thrifty"

}

struct有以下一些约束:

struct不能继承,但是可以嵌套,不能嵌套自己其成员都是有明确类型成员是被正整数编号过的,其中的编号使不能重复的,这个是为了在传输过程中编码使用成员分割符可以是逗号(,)或是分号(;),而且可以混用字段会有optional和required之分和protobuf一样,但是如果不指定则为无类型–可以不填充该值,但是在序列化传输的时候也会序列化进去,optional是不填充则不序列化,required是必须填充也必须序列化。每个字段可以设置默认值同一文件可以定义多个struct,也可以定义在不同的文件,进行include引入

2.6 枚举、异常

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

enum HttpStatus {

OK = 200,

NOTFOUND=404

}

异常在语法和功能上类似于结构体,差别是异常使用关键字exception,而且异常是继承每种语言的基础异常类

exception MyException {

1: i32 errorCode

2: string message

}

service ExampleService {

string GetName() throws (1: MyException e)

}

2.7 Service (服务定义类型)

service UserService {

User getById(1:i32 id)

bool isExist(1:string name)

}

编译后的内容

2.8 Namespace (名字空间)

Thrift中的命名空间类似于C++中的namespace和java中的package,它们提供了一种组织(隔离)代码的简便方式。名字空间也可以用于解决类型定义中的名字冲突。由于每种语言均有自己的命名空间定义方式(如python中有module), thrift允许开发者针对特定语言

定义namespace

namespace java com.yogurt.test

转化后

package com.yogurt.test

3、thrift的快速使用

3.1 环境待建

thrift编译器的安装 参考文档:https://thrift.apache.org/docs/install/

windows 安装 下载地址:https://thrift.apache.org/download

centos 安装 参考文档:https://thrift.apache.org/docs/install/centos.html

本人是Windows系统,所以这里搭建Windows环境

3.1.1 下载windows的文件,重命名文件为thrift.exe

3.1.2 环境变量配置

配置PATH路径,我的路径为:E:\installation\thrift

3.1.3 测试

安装成功!

3.2 快速入门尝试下

3.2.1 编写Thrift文件

namespace java com.yogurt

struct User{

1:i32 id

2:string name

3:i32 age=0

}

service UserService {

User getById(1:i32 id)

bool isExist(1:string name)

}

Thrift的相关命令:

# 生成java

thrift -gen java user.thrift

# 生成c++

thrift -gen cpp user.thrift

# 生成php

thrift -gen php user.thrift

# 生成node.js

thrift -gen js:node user.thrift

#可以通过以下命令查看生成命令的格式

thrift -help

//指定输出目录

thrift --gen java -o target user.thrift

进行编译:

默认会放在当前路径下,我当前的路径是/User/asus

3.2.2 编写代码

客户端代码

package com.yogurt;

import com.yogurt.Service.User;

import com.yogurt.Service.UserService;

import org.apache.thrift.protocol.TBinaryProtocol;

import org.apache.thrift.transport.TSocket;

/**

* @author yogurt

* @Date 2023/2/26 - 18:36 - 2023

*/

public class client {

public static void main(String[] args) {

try{

TSocket socket = new TSocket("localhost", 9000);

// 指定二进制编码

TBinaryProtocol protocol = new TBinaryProtocol(socket);

UserService.Client client = new UserService.Client(protocol);

socket.open();

// RPC 调用

User byId = client.getById(9000);

System.out.println("byId = " + byId);

}catch (Exception e){

System.out.println(e);

}

}

}

服务端代码

package com.yogurt;

import com.yogurt.Service.Impl.UserServiceImpl;

import com.yogurt.Service.UserService;

import org.apache.thrift.protocol.TBinaryProtocol;

import org.apache.thrift.server.TServer;

import org.apache.thrift.server.TSimpleServer;

import org.apache.thrift.transport.TServerSocket;

/**

* @author yogurt

* @Date 2023/2/26 - 18:27 - 2023

*/

public class SimpleService {

public static void main(String[] args) {

try{

TServerSocket socket = new TServerSocket(9000);

// 获取process

UserService.Processor processor = new UserService.Processor(new UserServiceImpl());

// 指定TBinaryProtocol

TBinaryProtocol.Factory factory = new TBinaryProtocol.Factory();

TServer.Args args1 = new TSimpleServer.Args(socket);

args1.processor(processor);

args1.protocolFactory(factory);

TSimpleServer tSimpleServer = new TSimpleServer(args1);

tSimpleServer.serve();

}catch (Exception e){

System.out.println(e);

}

}

}

结果

这是在单线程场景下的,后续将详细介绍多线程下的RPC调用

参考链接

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。