Thrift简介

该文章部分内容来源于一下文章:

http://www.micmiu.com/soa/rpc/thrift-sample/

https://blog.csdn.net/kongliand/article/details/106990798

一:概述

RPC(Remote Procedure Call,远程过程调用)是一个计算机通信协议,此协议允许进程间通信。简单来说,当机器 A 上的进程调用机器 B 上的进程时,A 上的调用进程被挂起,而 B 上的被调用进程开始执行。调用方可以通过参数将信息传送给被调用方,然后可以通过被调用方传回的结果得到返回。RPC 框架屏蔽了底层传输方式(TCP/UDP)、序列化和反序列化(XML/JSON/二进制)等内容,使用框架只需要知道被调用者的地址和接口就可以了,无须额外地为这些底层内部编程。
目前主流的 RPC 框架有如下几种。

  • Thrift:Facebook 开源的跨语言框架
  • gRPC:Google 基于 HTTP/2 和 Protobuf 的能用框架
  • Avro:Hadoop 的子项目

本文讲述 RPC 的基本原理并以 Thrift 框架为例说明 RPC 的使用。
  

二:Thrift的原理

图中,TProtocol(协议层),定义数据传输格式,例如:

  • TBinaryProtocol:二进制格式(serveserverr代码中使用的);
  • TCompactProtocol:压缩格式;
  • TJSONProtocol:JSON格式;
  • TSimpleJSONProtocol:提供JSON只写协议, 生成的文件很容易通过脚本语言解析;
  • TDebugProtocol:使用易懂的可读的文本格式,以便于debug

TTransport(传输层),定义数据传输方式,可以为TCP/IP传输,内存共享或者文件共享等)被用作运行时库。

  • TSocket:阻塞式socker;
  • TFramedTransport:以frame为单位进行传输,非阻塞式服务中使用;
  • TFileTransport:以文件形式进行传输;
  • TMemoryTransport:将内存用于I/O,java实现时内部实际使用了简单的ByteArrayOutputStream;
  • TZlibTransport:使用zlib进行压缩, 与其他传输方式联合使用,当前无java实现;

Thrift支持的服务模型

  • TSimpleServer:简单的单线程服务模型,常用于测试;
  • TThreadPoolServer:多线程服务模型,使用标准的阻塞式IO;
  • TNonblockingServer:多线程服务模型,使用非阻塞式IO(需使用TFramedTransport数据传输方式);

Thrift实际上是实现了C/S模式,通过代码生成工具将thrift文生成服务器端和客户端代码(可以为不同语言),从而实现服务端和客户端跨语言的支持。用户在Thirft文件中声明自己的服务,这些服务经过编译后会生成相应语言的代码文件,然后客户端调用服务,服务器端提服务便可以了。

三:数据类型

1.基本类型:

  • bool:布尔值,true 或 false,对应 Java 的 boolean
  • byte:8 位有符号整数,对应 Java 的 byte
  • i16:16 位有符号整数,对应 Java 的 short
  • i32:32 位有符号整数,对应 Java 的 int
  • i64:64 位有符号整数,对应 Java 的 long
  • double:64 位浮点数,对应 Java 的 double
  • string:utf-8编码的字符串,对应 Java 的 String

2.结构体类型:

     struct:定义公共的对象,类似于 C 语言中的结构体定义,在 Java 中是一个 JavaBean.但是       struct有以下一些约束:

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

数字标签作用非常大,但是随着项目开发的不断发展,也许字段会有变化,但是建议不要轻易修改这些数字标签,修改之后如果没有同步客户端和服务器端会让一方解析出问题。

struct Report
{
  1: required string msg, //序列化中,必须有该字段
  2: optional i32 type = 0; //序列化中可以没有该字段
  3: i32 time //对于基本类型,会序列化到字节数组中,解析的时候不会校验。对于对象类型,如果不为空,则序列化到字节数组中
}
规范的struct定义中的每个域均会使用required或者optional关键字进行标识。如果required标识的域没有该字段,Thrift将给予提示;如果optional标识的域没有赋值,该域将不会被序列化传输;如果某个optional标识域有缺省值而用户没有重新赋值,则该域的值一直为缺省值;如果某个optional标识域有缺省值或者用户已经重新赋值,而不设置它的__isset为true,也不会被序列化传输。
 

3.容器类型:

  • list:对应 Java 的 ArrayList
  • set:对应 Java 的 HashSet
  • map:对应 Java 的 HashMap

容器中元素类型可以是除了service外的任何合法Thrift类型(包括结构体和异常)。为了最大的兼容性,map的key最好是thrift的基本类型,有些语言不支持复杂类型的key,JSON协议只支 持那些基本类型的key。

容器都是同构容器,不是异构容器。

struct Test {
  1: map<Number, UserId> user_map,
  2: set<Number> num_sets,
  3: list<Stusers> users
}

4.枚举(enum)

很多语言都有枚举,意义都一样。比如,当定义一个消息类型时,它只能是预定义的值列表中的一个,可以用枚举实现。
注意,不同于protocal buffer,thrift不支持枚举类嵌套,枚举常量必须是32位的正整数

enum EnOpType {
  CMD_OK = 0, // (0)
  CMD_NOT_EXIT = 2000, // (2000)
  CMD_EXIT = 2001, // (2001)
  CMD_ADD = 2002 // (2002)
}
struct StUser {
  1: required i32 userId;
  2: required string userName;
  3: optional EnOpType cmd_code = EnOpType.CMD_OK;
  4: optional string language = “english”
}


5.常量定义和类型定义

Thrift允许定义跨语言使用的常量,复杂的类型和结构体可使用JSON形式表示。

const i32 INT_CONST = 1234;
const EnOpType myEnOpType = EnOpType.CMD_EXIT; //2001

6.异常(Exceptions)

Thrift结构体在概念上类似于(similar to)C语言结构体类型—将相关属性封装在一起的简便方式。Thrift结构体将会被转换成面向对象语言的类。
异常在语法和功能上类似于结构体,差别是异常使用关键字exception,而且异常是继承每种语言的基础异常类。

exception UserException {
  1: i32 errorCode,
  2: string message,
  3: StUser userinfo
}

7.服务(Services)

   服务的定义方法在语义(semantically 上等同于面向对象语言中的接口。Thrift编译器会产生执行这些接口的client和server stub。 Thrift编译器会根据选择的目标语言为server产生服务接口代码,为client产生stubs。

service SeTest {
    void ping() throws (1:UserException e),
    bool postTweet(1: StUser user);
    StUser searchTweets(1:string name);
}
Service的约束

不支持多态
方法无法重载

8.名字空间(Namespace)

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

namespace cpp com.example.test
namespace java com.example.test
namespace php com.example.test
注意:现在统一规定为

namespace nova com.youzan.xxx

9.注释(Comment)

Thrift支持C多行风格和Java/C++单行风格。

/*
 * This is a multi-line comment.
 * Just like in C.
 */
// C++/Java style single-line comments work just as well.

10.依赖(Includes)

便于管理、重用和提高模块性/组织性,我们常常分割Thrift定义在不同的文件中。包含文件搜索方式与c++一样。Thrift允许文件包含其它thrift文件,用户需要使用thrift文件名作为前缀访问被包含的对象,如:

include "test.thrift"
...
struct StSearchResult {
 1: in32 uid;
 ...
}
thrift文件名要用双引号包含,末尾没有逗号或者分号

四:Java代码实现

1.服务端编码基本步骤:

  • 实现服务处理接口impl
  • 创建TProcessor
  • 创建TServerTransport
  • 创建TProtocol
  • 创建TServer
  • 启动Server

2.客户端编码基本步骤:

  • 创建Transport
  • 创建TProtocol
  • 基于TTransport和TProtocol创建 Client
  • 调用Client的相应方法

1.thrift生成代码

创建Thrift文件:G:\test\thrift\demoHello.thrift ,内容如下:

/*java 代码生成代码的语言,com.micmiu.thrift.demo 是生成代码的包路径*/
namespace java com.micmiu.thrift.demo  

/*服务的定义方法在语义(semantically 上等同于面向对象语言中的接口。Thrift编译器会产生执行这些接口的client和server stub。 Thrift编译器会根据选择的目标语言为server产生服务接口代码,为client产生stubs*/
service  HelloWorldService {
  string sayHello(1:string username)
}

用官网提供的windows下编译工具,运用这个工具生成相关代码

thrift-0.8.0.exe -r -gen java ./demoHello.thrift

生成后的目录结构如下:

G:\test\thrift>tree /F
卷 other 的文件夹 PATH 列表
卷序列号为 D238-BE47
G:.
│  demoHello.thrift
│  demouser.thrift
│  thrift-0.8.0.exe
│
└─gen-java
    └─com
        └─micmiu
            └─thrift
                └─demo
                        HelloWorldService.java

将生成的HelloWorldService.java 文件copy到自己测试的工程中,我的工程是用maven构建的,故在pom.xml中添加以下依赖:

<dependency>
	<groupId>org.apache.thrift</groupId>
	<artifactId>libthrift</artifactId>
	<version>0.8.0</version>
</dependency>
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-log4j12</artifactId>
	<version>1.5.8</version>
</dependency>

2. 实现接口Iface

java代码:HelloWorldImpl.java

package com.micmiu.thrift.demo;
 
import org.apache.thrift.TException;
 
/**
 * blog http://www.micmiu.com
 *
 * @author Michael
 *
 */
public class HelloWorldImpl implements HelloWorldService.Iface {
 
	public HelloWorldImpl() {
	}
 
	@Override
	public String sayHello(String username) throws TException {
		return "Hi," + username + " welcome to my blog www.micmiu.com";
	}
 
}

3.TSimpleServer服务端

简单的单线程服务模型,一般用于测试。

编写服务端server代码:HelloServerDemo.java

package com.micmiu.thrift.demo;

import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TJSONProtocol;
import org.apache.thrift.protocol.TSimpleJSONProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;

/**
 * blog http://www.micmiu.com
 *
 * @author Michael
 *
 */
public class HelloServerDemo {
	public static final int SERVER_PORT = 8090;

	public void startServer() {
		try {
			System.out.println("HelloWorld TSimpleServer start ....");

			TProcessor tprocessor = new HelloWorldService.Processor<HelloWorldService.Iface>(
					new HelloWorldImpl());
			// HelloWorldService.Processor<HelloWorldService.Iface> tprocessor =
			// new HelloWorldService.Processor<HelloWorldService.Iface>(
			// new HelloWorldImpl());

			// 简单的单线程服务模型,一般用于测试
			TServerSocket serverTransport = new TServerSocket(SERVER_PORT);
			TServer.Args tArgs = new TServer.Args(serverTransport);
			tArgs.processor(tprocessor);
			tArgs.protocolFactory(new TBinaryProtocol.Factory());
			// tArgs.protocolFactory(new TCompactProtocol.Factory());
			// tArgs.protocolFactory(new TJSONProtocol.Factory());
			TServer server = new TSimpleServer(tArgs);
			server.serve();

		} catch (Exception e) {
			System.out.println("Server start error!!!");
			e.printStackTrace();
		}
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		HelloServerDemo server = new HelloServerDemo();
		server.startServer();
	}

}

编写客户端Client代码:HelloClientDemo.java

package com.micmiu.thrift.demo;
 
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TJSONProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
 
/**
 * blog http://www.micmiu.com
 *
 * @author Michael
 *
 */
public class HelloClientDemo {
 
	public static final String SERVER_IP = "localhost";
	public static final int SERVER_PORT = 8090;
	public static final int TIMEOUT = 30000;
 
	/**
	 *
	 * @param userName
	 */
	public void startClient(String userName) {
		TTransport transport = null;
		try {
			transport = new TSocket(SERVER_IP, SERVER_PORT, TIMEOUT);
			// 协议要和服务端一致
			TProtocol protocol = new TBinaryProtocol(transport);
			// TProtocol protocol = new TCompactProtocol(transport);
			// TProtocol protocol = new TJSONProtocol(transport);
			HelloWorldService.Client client = new HelloWorldService.Client(
					protocol);
			transport.open();
			String result = client.sayHello(userName);
			System.out.println("Thrify client result =: " + result);
		} catch (TTransportException e) {
			e.printStackTrace();
		} catch (TException e) {
			e.printStackTrace();
		} finally {
			if (null != transport) {
				transport.close();
			}
		}
	}
 
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		HelloClientDemo client = new HelloClientDemo();
		client.startClient("Michael");
 
	}
 
}

4.TThreadPoolServer 服务模型

线程池服务模型,使用标准的阻塞式IO,预先创建一组线程处理请求。

编写服务端代码:HelloServerDemo.java

package com.micmiu.thrift.demo;
 
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;
 
/**
 * blog http://www.micmiu.com
 *
 * @author Michael
 *
 */
public class HelloServerDemo {
	public static final int SERVER_PORT = 8090;
 
	public void startServer() {
		try {
			System.out.println("HelloWorld TThreadPoolServer start ....");
 
			TProcessor tprocessor = new HelloWorldService.Processor<HelloWorldService.Iface>(
					new HelloWorldImpl());
 
			 TServerSocket serverTransport = new TServerSocket(SERVER_PORT);
			 TThreadPoolServer.Args ttpsArgs = new TThreadPoolServer.Args(
			 serverTransport);
			 ttpsArgs.processor(tprocessor);
			 ttpsArgs.protocolFactory(new TBinaryProtocol.Factory());
 
			// 线程池服务模型,使用标准的阻塞式IO,预先创建一组线程处理请求。
			 TServer server = new TThreadPoolServer(ttpsArgs);
			 server.serve();
 
		} catch (Exception e) {
			System.out.println("Server start error!!!");
			e.printStackTrace();
		}
	}
 
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		HelloServerDemo server = new HelloServerDemo();
		server.startServer();
	}
 
}

客户端Client代码和之前的一样,只要数据传输的协议一致即可,客户端测试成功。

还有其他几种工作模式,可以看文章http://www.micmiu.com/soa/rpc/thrift-sample/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值