由于公司现在的开发业务模块中,有使用到Java作为客户端调用python服务器端业务处理,因此在底下研究了下,结合了网上的优质文章,在此做一下记录。
- thrift是一个软件框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎,以构建在C++,Java,Go,Python,PHP,Ruby,Erlang,Perl,C#,Cocoa,JavaScript,Node.js,Smalltalk,and OCaml这些变成语言间无缝结合的。高效的服务。
- thrift最初由facebook开发用作系统内个语言之间的RPC通信,2007年由facebook贡献到Apache基金,08年5月进入Apache卵化器。支持多种语言之间的RPC方式的通信:Java语言client可以构造一个对象,调用相应服务方法来调用python语言的服务,跨越语言的C/S RPC调用。
- thrift允许定义一个简单的定义文件中的数据类型和服务接口,以作为输入文件,编译器生成代码用来方便地生成RPC客户端和服务端通信的无缝跨编程语言。
环境准备
本文是在window上进行演示的。有关在linux上安装Python环境,请参考(https://blog.csdn.net/gdkyxy2013/article/details/79457590),个人感觉非常nice的。
本文使用的环境演示
- thrift-0.9.3
- thrift-0.9.3.tar.gz 下载地址:http://archive.apache.org/dist/thrift/0.9.3/
- hrift-0.9.3.exe 下载地址:http://archive.apache.org/dist/thrift/0.9.3/
- Python-3.8.3
thrift安装
- 在D盘(任意盘符)新建一个Thrift文件夹,将下载的thrift-0.9.3重新命名为thrift.exe后放到该文件夹下。
- 配置环境变量,如图
- 接下来测试thrift的环境变量是否安装正确,Ctrl+R输入cmd打开DOS窗口,在窗口输入“thrift -version”
Python环境安装
- 双击下载的Python-3.8.3安装包,勾选"添加到环境变量",一步步默认安装即可。
- 打开命令窗口,输入python --version,可查看当前安装的版本,如图
thrift支持的数据类型
- 基本数据类型
- bool:布尔值(true或者false)
- byte:8位的有符号字节(java的byte类型)
- i16:16位的有符号整数(java的short类型)
- i32:32位的有符号整数(java的int类型)
- i64:64位的有符号长整型(java的long类型)
- double:一个64位的浮点数(java的double类型)
- string: 一个utf8编码的字符串文本(java的String)
- Structs
- Thrift的structs用来定义一个通用对象,但是没有继承关系。
- 集合类型
- list:一个有序的元素列表。元素可以重复。
- set:一个无序的元素集合,集合中元素不能重复。
- map:一个键值对的数据结构,相当于Java中的HashMap。
- 异常类型Exceptions
- Thrift的异常类型,除了是继承于静态异常基类以外,其他的跟struct是类似的。表示的是一个异常对象。
- 服务类型Services
- Thrift的service类型相当于定义一个面向对象编程的一个接口。Thrift的编译器会根据这个接口定义来生成服务端和客户端的接口实现代码。
案例实现
- 依赖引用
java 的maven依赖
<!--thrift-->
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.9.3</version>
</dependency>
首先使用Thrift之前需要定义一个符合Thrift语言规范的.thrift格式的接口文件,data.thrift
namespace java thrift.generated
namespace py py.thrift.generated
typedef i16 short
typedef i32 int
typedef i64 long
typedef bool boolean
typedef string String
struct Person {
1: optional String username,
2: optional int age,
3: optional boolean married
}
exception DataException {
1: optional String message,
2: optional String callStack,
3: optional String date
}
service PersonService {
Person getPersonByUsername(1: required String username) throws (1: DataException dataException),
void savePerson(1: required Person person) throws (1: DataException dataException)
}
使用thrift的编译器,生成客户端和服务端的代码
生成Java的客户端服务端代码:
thrift -gen java data.thrift所在的路径
生成Python的客户端服务端代码
thrift -gen py data.thrift所在的路径
Thrift的调用
java服务端接口服务代码:
public class PersonServiceImpl implements PersonService.Iface {
@Override
public Person getPersonByUsername(String username) throws DataException, TException {
System.out.println("Got client param: " + username);
Person person = new Person();
person.setUsername(username);
person.setAge(20);
person.setMarried(false);
return person;
}
@Override
public void savePerson(Person person) throws DataException, TException {
System.out.println("Got client param: ");
System.out.println(person.getUsername());
System.out.println(person.getAge());
System.out.println(person.isMarried());
}
}
开启服务(在实际应用中保证只有一个)
public class ThriftServer {
public static void main(String[] args) throws Exception {
TNonblockingServerSocket serverSocket = new TNonblockingServerSocket(8899);
THsHaServer.Args arg = new THsHaServer.Args(serverSocket).minWorkerThreads(2).maxWorkerThreads(4);
PersonService.Processor<PersonServiceImpl> processor = new PersonService.Processor<>(new PersonServiceImpl());
arg.protocolFactory(new TCompactProtocol.Factory());
arg.transportFactory(new TFramedTransport.Factory());
arg.processorFactory(new TProcessorFactory(processor));
TServer server = new THsHaServer(arg);
System.out.println("Thrift Server Started!");
server.serve();
}
}
Java客户端代码
public class ThriftClient {
public static void main(String[] args) {
TTransport transport = new TFramedTransport(new TSocket("localhost", 8899), 600);
TProtocol protocol = new TCompactProtocol(transport);
PersonService.Client client = new PersonService.Client(protocol);
try {
transport.open();
Person person = client.getPersonByUsername("张三");
System.out.println(person.getUsername());
System.out.println(person.getAge());
System.out.println(person.isMarried());
System.out.println("------------");
Person person1 = new Person();
person1.setUsername("李四");
person1.setAge(30);
person1.setMarried(true);
client.savePerson(person1);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
} finally {
transport.close();
}
}
}
Python客户端代码
__author__ = '作者'
from py.thrift.generated import PersonService
from py.thrift.generated import ttypes
from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TCompactProtocol
import sys
reload(sys)
sys.setdefaultencoding('utf8')
try:
tSocket = TSocket.TSocket('localhost', 8899)
tSocket.setTimeout(600)
transport = TTransport.TFramedTransport(tSocket)
protocol = TCompactProtocol.TCompactProtocol(transport)
client = PersonService.Client(protocol)
transport.open()
person = client.getPersonByUsername('张三')
print person.username
print person.age
print person.married
print '------------------'
newPerson = ttypes.Person()
newPerson.username = '李四'
newPerson.age = 30
newPerson.married = True
client.savePerson(newPerson)
except Thrift.TException, tx:
print '%s' % tx.message
Thrift的传输格式(协议层)
Thrift之所以被称为一种高效的RPC框架,其中一个重要的原因就是它提供了高效的数据传输。
以下是Thrift的传输格式种类:
- TBinaryProtocol: 二进制格式。效率显然高于文本格式。
- TBinaryProtocol: TCompactProtocol:压缩格式。在二进制基础上进一步压缩。
- TBinaryProtocol: TJSONProtocol:JSON格式。
- TBinaryProtocol: TSimpleJSONProtocol:提供JSON只写协议(缺少元数据信息),生成的文件很容易用过脚本语言解析。
- TBinaryProtocol: TDebugProtocol:使用易懂的刻度文本格式,以便于调试。
以上可以看到,在线上环境,使用TCompactProtocol格式效率是最高的,同等数据传输占用网络带宽是最少的。
Thrift的传输格式(传输层)
- TSocket:阻塞式socket。
- TFramedTransport:以frame为单位进行传输,非阻塞式服务中使用。
- TFileTransport:以文件形式进行传输。
- TMemoryTransport:将内存用于I/O,Java是现实内部实际使用了简单的ByteArrayOutputStream。
- TZlibTransport:使用zlib进行压缩,与其他传输方式联合使用。当前无java实现。
Thrift的服务模型
- TSimpleServer
简单的单线程服务模型,常用于测试。只在一个单独的线程中以阻塞I/O的方式来提供服务。所以它只能服务一个客户端连接,其他的所有客户端在被服务器端接受之前都只能等待。 - TNonblockingServer
它使用了非阻塞式I/O,使用了java.nio.channels.Selector,通过调用select(),它使得程序阻塞在多个连接上,而不是单一的一个连接上。TNonblockingServer处理这些连接的时候,要么接受它,要么从它那读数据,要么把数据写到它那里,然后再次调用select()来等待下一个准备好的可用的连接。通过使用这种方式,server可同时服务多个客户端,而不会出现一个客户端把其他客户端全部“饿死”的情况。缺点是所有消息是被调用select()方法的同一个线程处理的,服务端同一时间只会处理一个消息,并没有实现并行处理。 - THsHaServer(半同步半异步server)
针对TNonblockingServer存在的问题,THsHaServer应运而生,它使用一个单独的线程专门负责I/O,同样使用java.nio.channels.Selector,通过调用select()。然后再利用一个独立的worker线程池来处理消息。只要有空闲的worker线程,消息就会被立即处理,因此多条消息能被并行处理。效率进一步得到了提高。 - TThreadSelectorServer
它与THsHaServer的主要区别在于,TThreadSelectorServer允许你用多个线程来处理网络I/O。它维护了两个线程池,一个用来处理网络I/O,另一个用来进行请求的处理。 - TThreadPoolServer
它使用的是一种多线程服务模型,使用标准的阻塞式I/O。它会使用一个单独的线程来接受连接,一旦接收了一个连接,它会被放入ThreadPoolExecutor中的一个worker线程里处理。worker线程被绑定到特定的客户端连接上,直到它关闭。一旦关闭,该worker线程就又回到了线程池中。
这意味着,如果有1万个并发的客户端连接,你就需要运行1万个线程,所以它对系统资源的消耗不像其他类型的server一样那么"友好"。此外,如果客户端数量超过了线程池中的最大线程数,在有一个worker线程可用之前,请求将被阻塞在哪里。如果提前知道了将要连接到服务器上的客户端数量,并且不介意运行大量线程的话,TThreadPoolServer可能是个很好的选择。
由于一直在做JAVA方面的开发,Python一直没有接触。搭建开发环境试运行时没有运行起来(实在抱歉),准备的个人例子无法在本文演示。
本文来自:https://blog.csdn.net/zw19910924/article/details/78178539