Thrift原理简析(JAVA)

标签: thrift 原理 java | 发表时间:2013-12-09 19:42 | 作者:
出处:http://www.iteye.com

    Apache Thrift是一个跨语言的服务框架,本质上为RPC;当我们开发的service需要开放出去的时候,就会遇到跨语言调用的问题,JAVA语言开发了一个UserService用来提供获取用户信息的服务,如果服务消费端有PHP/Python/C++等,我们不可能为所有的语言都适配出相应的调用方式,有时候我们会很无奈的使用Http来作为访问协议;但是如果服务消费端不能使用HTTP,而且更加倾向于 以操作本地API的方式来使用服务,那么我们就需要Thrift来提供支持.

 

    本文以UserService为例,描述一下使用thrift的方式,以及其原理..

一. service.thrift

 

struct User{
	1:i64 id,
	2:string name,
	3:i64 timestamp,
	4:bool vip	
}

service UserService{
	User getById(1:i64 id)
}

二. 生成API文件

 

    首先下载和安装thrift客户端,比如在windows平台下,下载thrift.exe,不过此处需要提醒,不同的thrift客户端版本生成的API可能不兼容.本例使用thrift-0.9.0.exe;通过"--gen"指定生成API所适配的语言.本实例为生成java客户端API.

 

//windows平台下,将API文件输出在service目录下(此目录需要存在)
> thrift.exe --gen java -o service service.thrift

三. UserService实现类

 

 

public class UserServiceImpl implements UserService.Iface {
	@Override
	public User getById(long id){
		System.out.println("invoke...id:" + id);
		return new User();//for test
	}
}

 

 

四.原理简析

    1. User.java: thrift生成API的能力还是非常的有限,比如在struct中只能使用简单的数据类型(不支持Date,Collection<?>等),不过我们能从User中看出,它生成的类实现了"Serializable"接口和"TBase"接口.

    其中Serializable接口表明这个类的实例是需要序列化之后在网络中传输的,为了不干扰JAVA本身的序列化和反序列化机制,它还重写了readObject和writeObject方法.不过这对thrift本身并没有帮助.

    TBase接口是thrift序列化和反序列化时使用的,它的两个核心方法:read和write.在上述的thrift文件中,struct定义的每个属性都有一个序号,比如:1:id,那么thrift在序列化时,将会根据序号的顺序依次将属性的"名称 + 值"写入inputStream中,反序列化也是如此.(具体参见read和write的实现).

    因为thrift的序列化和反序列化实例数据时,是根据"属性序号"进行,这可以保证数据在inputstream和outputstream中顺序是严格的.这一点也要求API开发者,如果更改了thrift文件中的struct定义,需要重新生成客户端API,否则服务将无法继续使用(可能报错,也可能数据错误).thrift序列化/反序列化的过程和JAVA自带的序列化机制不同,它将不会携带额外的class结构,此外thrift这种序列化机制更加适合网络传输,而且性能更加高效.

    2. UserService.Client:  在生成的UserService中,有个Client静态类,这个类就是一个典型的代理类,此类已经实现了UserService的所有方法.开发者需要使用Client类中的API方法与Thrift server端交互,它将负责与Thrift server的Socket链接中,发送请求和接收响应.

    需要注意的时,每次Client方法调用,都会在一个Socket链接中进行,这就意味着,在使用Client消费服务之前,需要和Thrift server建立有效的TCP链接.(稍后代码示例)

    1) 发送请求:

//参见:TServiceClient
//API方法调用时,发送请求数据流
protected void sendBase(String methodName, TBase args) throws TException {
	oprot_.writeMessageBegin(new TMessage(methodName, TMessageType.CALL, ++seqid_));//首先写入"方法名称"和"seqid_"
	args.write(oprot_);//序列化参数
	oprot_.writeMessageEnd();
	oprot_.getTransport().flush();
}

protected void receiveBase(TBase result, String methodName) throws TException {
	TMessage msg = iprot_.readMessageBegin();//如果执行有异常
	if (msg.type == TMessageType.EXCEPTION) {
	  TApplicationException x = TApplicationException.read(iprot_);
	  iprot_.readMessageEnd();
	  throw x;
	}//检测seqid是否一致
	if (msg.seqid != seqid_) {
	  throw new TApplicationException(TApplicationException.BAD_SEQUENCE_ID, methodName + " failed: out of sequence response");
	}
	result.read(iprot_);//反序列化
	iprot_.readMessageEnd();
}

    Thrift提供了简单的容错方式:每次方法调用,都会在Client端标记一个seqid,这是一个自增的本地ID,在TCP请求时将此seqid追加到流中,同时Server端响应时,也将此seqid原样返回过来;这样客户端就可以根据此值用来判断"请求--响应"是对应的,如果出现乱序,将会导致此请求以异常的方式结束.

    2) 响应

//参考: TBaseProcessor.java
@Override
public boolean process(TProtocol in, TProtocol out) throws TException {
	TMessage msg = in.readMessageBegin();
	ProcessFunction fn = processMap.get(msg.name);
	if (fn == null) {
	  TProtocolUtil.skip(in, TType.STRUCT);
	  in.readMessageEnd();
	  TApplicationException x = new TApplicationException(TApplicationException.UNKNOWN_METHOD, "Invalid method name: '"+msg.name+"'");
	  out.writeMessageBegin(new TMessage(msg.name, TMessageType.EXCEPTION, msg.seqid));
	  x.write(out);//序列化响应结果,直接输出
	  out.writeMessageEnd();
	  out.getTransport().flush();
	  return true;
	}
	fn.process(msg.seqid, in, out, iface);
	return true;
}

    3) Server端Socket管理和执行策略

TThreadPoolServer
public void serve() {
	try {
	  //启动服务
	  serverTransport_.listen();
	} catch (TTransportException ttx) {
	  LOGGER.error("Error occurred during listening.", ttx);
	  return;
	}

	// Run the preServe event
	if (eventHandler_ != null) {
	  eventHandler_.preServe();
	}

	stopped_ = false;
	setServing(true);
	//循环,直到被关闭
	while (!stopped_) {
	  int failureCount = 0;
	  try {
		//accept客户端Socket链接,
		//对于每个新链接,将会封装成runnable,并提交给线程或者线程池中运行.
		TTransport client = serverTransport_.accept();
		WorkerProcess wp = new WorkerProcess(client);
		executorService_.execute(wp);
	  } catch (TTransportException ttx) {
		if (!stopped_) {
		  ++failureCount;
		  LOGGER.warn("Transport error occurred during acceptance of message.", ttx);
		}
	  }
	}
	//....
}

    Thrift Server端,设计思路也非常的直接...当前Service server启动之后,将会以阻塞的方式侦听Socket链接(代码参考TThreadPoolServer),每建立一个Socket链接,都会将此Socket经过封装之后,放入线程池中,本质上也是一个Socket链接对应一个Worker Thread.这个Thread只会处理此Socket中的所有数据请求,直到Socket关闭.

//参考:WorkerProcess
while (true) {

	if (eventHandler != null) {
	  eventHandler.processContext(connectionContext, inputTransport, outputTransport);
	}

	if(stopped_ || !processor.process(inputProtocol, outputProtocol)) {
	  break;
	}
}

 

    当有Socket链接不是很多的时候,TThreadPoolServer并不会有太大的性能问题,可以通过指定ThreadPool中线程的个数进行简单的调优..如果Socket链接很多,我们只能使用TThreadedSelectorServer来做支撑,TThreadedSelectorServer内部基于NIO + Socket模式,具有异步的特性;不过在绝大多数情况下,在thrift中使用"异步"似乎不太容易让人接受,毕竟这意味着Client端需要阻塞,并且这个阻塞时间是不可控的.单SelecorServer确实可以有效的提升Server的并发能力.

    3. Client端代码示例

public class UserServiceClient {

    public void startClient() {
        TTransport transport;
        try {
            transport = new TSocket("localhost", 1234);
            TProtocol protocol = new TBinaryProtocol(transport);
            UserService.Client client = new UserService.Client(protocol);
            transport.open();
            User user = client.getById(1000);
			////
            transport.close();
        } catch (TTransportException e) {
            e.printStackTrace();
        } catch (TException e) {
            e.printStackTrace();
        }
    }

}

    4. Server端代码示例 

public class Server {
    public void startServer() {
        try {
            TServerSocket serverTransport = new TServerSocket(1234);
            UserService.Processor process = new Processor(new UserServiceImpl());
            Factory portFactory = new TBinaryProtocol.Factory(true, true);
            Args args = new Args(serverTransport);
            args.processor(process);
            args.protocolFactory(portFactory);
            TServer server = new TThreadPoolServer(args);
            server.serve();
        } catch (TTransportException e) {
            e.printStackTrace();
        }
    }
}

 



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [thrift 原理 java] 推荐:

Thrift原理简析(JAVA)

- - ITeye博客
    本文以UserService为例,描述一下使用thrift的方式,以及其原理...     首先下载和安装thrift客户端,比如在windows平台下,下载thrift.exe,不过此处需要提醒,不同的thrift客户端版本生成的API可能不兼容.本例使用thrift-0.9.0.exe;通过"--gen"指定生成API所适配的语言.本实例为生成java客户端API..

Apache Thrift - java开发详解

- - 编程语言 - ITeye博客
2、编写IDL文件 Hello.thrift. 4、编写实现类、实现Hello.Iface:. 5、编写服务端,发布(阻塞式IO + 多线程处理)服务.      * 阻塞式、多线程处理 .             //设置传输通道,普通通道  .             //使用高密度二进制协议  .

浅谈Thrift内部实现原理

- - 董的博客
Dong | 新浪微博: 西成懂 | 可以转载, 但必须以超链接形式标明文章原始出处和作者信息及 版权声明. 网址: http://dongxicheng.org/search-engine/thrift-internals/. Thrift由两部分组成:编译器(在compiler目录下,采用C++编写)和服务器(在lib目录下),其中编译器的作用是将用户定义的thrift文件编译生成对应语言的代码,而服务器是事先已经实现好的、可供用户直接使用的RPC Server(当然,用户也很容易编写自己的server).

Thrift 原理与使用实例

- - 互联网旁观者
一、Thrift 框架介绍. Thrift是一个跨语言的服务部署框架,最初由Facebook于2007年开发,2008年进入Apache开源项目. Thrift通过一个中间语言(IDL, 接口定义语言)来定义RPC的接口和数据类型,然后通过一个编译器生成不同语言的代码(目前支持C++,Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk和OCaml),并由生成的代码负责RPC协议层和传输层的实现.

Apache Thrift入门2-Java代码实现例子

- - J2EE企业应用 顾问/咨询- H.E.'s Blog
  在上一篇文章中提到了Thrift的架构、传输协议( Ref),本篇文章将对Thrift的入门实例进行介绍. 分为如下5个部分: 运行环境、安装/配置、脚本文件、创建代码、运行程序. 二、安装/配置 (清单2).     1.下载thrift源文件.     2.编译thrift源文件.         1)解压 thrift-0.5.0.tar.gz.

【Thrift二】Thrift版的Hello World

- - 开源软件 - ITeye博客
本篇,不考虑细节问题和为什么,先照葫芦画瓢写一个Thrift版本的Hello World,了解Thrift RPC服务开发的基本流程. 在Intellij中创建一个Maven模块,加入对Thrift的依赖,同时还要加上slf4j依赖,如果不加slf4j依赖,在后面启动Thrift Server时会报错.

【Thrift一】Thrift编译安装

- - 开源软件 - ITeye博客
上面这段话选自Apache对Thrift的一句话介绍,Thrift是一个高性能的RPC服务框架. 在技术选型时,有如下三方面的需求时,考虑使用Thrift. 2.请求和响应的数据传输量大. 个人认为在没有特别strong的理由情况下,慎用Thrift. HTTP请求通常都能够满足需求,使用Thrift带来很多开发上的额外的工作量,一个简单的服务,就要写客户端代码,.thrift接口定义以及服务器端的服务响应代码,简单的事情复杂化.

java HashMap的原理

- - CSDN博客研发管理推荐文章
HashMap的数据结构:    在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外. HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体.    从上图中可以看出,HashMap底层就是一个数组结构,数组中的每一项又是一个链表.

java编译原理

- - 编程语言 - ITeye博客
1. 关于动态加载机制 . 学习Java比C++更容易理解OOP的思想,毕竟C++还混合了不少面向过程的成分. 很多人都能背出来Java语言的特点,所谓的动态加载机制等等. 当然概念往往是先记住而后消化的,可有多少人真正去体会过动态加载的机制,试图去寻找过其中的细节呢? 提供大家一个方法: 在命令行窗口运行Java程序的时候,加上这个很有用的参数: java verbose *.class .

Thrift入门试用

- - Java - 编程语言 - ITeye博客
在新的项目中公司在平台内部系统间使用Thrift通讯,都没有听说过. 然后听同事说,是跨语言Socket通讯的开源组件. 1.跨平台和语言的Socket通讯组件. 2.根据伪代码的结构语言定义对象和服务结构,然后生成各语言的代码和接口. 3.各语言根据组件提供的库,编写客户端和服务器端程序. 服务器端实现接口并编写业务逻辑.