Thrift原理简析(JAVA)

2023-11-13

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

 

    不过,如果你的JAVA服务,并没有跨语言调用的需求,那么使用thrift作为RPC框架,似乎不是最好的选择,不管thrift是否性能优越,但是它使用起来确实没有类似于Hessian/CXF等那样便捷和易于使用.

 

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

一. service.thrift

Java代码   收藏代码
  1. struct User{  
  2.     1:i64 id,  
  3.     2:string name,  
  4.     3:i64 timestamp,  
  5.     4:bool vip    
  6. }  
  7.   
  8. service UserService{  
  9.     User getById(1:i64 id)  
  10. }  

    你可以将自己的JAVA服务通过".thrift"文件描述出来,并提供给服务消费端,那么消费端即可以生成自己的API文件..Thrift框架目前已经支持大部分主流的语言,.需要注意,因为Thrift考虑到struct/service定义需要兼容多种语言的"风格",所以它只支持一些基本的数据类型(比如i32,i64,string等),以及service定义的方法不能重名,即使参数列表不同.(并不是所有的语言都能像JAVA一样支持重载)

 

二. 生成API文件

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

 

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

    需要明确的是:Thrift和其他RPC框架不同,thrift在生成的API文件中,已经描述了"调用过程"(即硬编码),而不是像其他RPC那样在运行时(runtime)动态解析方法调用或者参数.

三. UserService实现类

Java代码   收藏代码
  1. public class UserServiceImpl implements UserService.Iface {  
  2.     @Override  
  3.     public User getById(long id){  
  4.         System.out.println("invoke...id:" + id);  
  5.         return new User();//for test  
  6.     }  
  7. }  

    实现类,需要放在Thrift server端.  

 

四.原理简析

    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的实现).

Java代码   收藏代码
  1. //read方法逐个读取字段,按照"索引",最终将"struct"对象封装完毕.  
  2. //write方法也非常类似,按照"索引"顺序逐个输出到流中.  
  3. while (true){  
  4.         schemeField = iprot.readFieldBegin();  
  5.         if (schemeField.type == org.apache.thrift.protocol.TType.STOP) {   
  6.           break;  
  7.         }  
  8.         switch (schemeField.id) {  
  9.           case 1// ID  
  10.             if (schemeField.type == org.apache.thrift.protocol.TType.I32) {  
  11.               struct.id = iprot.readI32();  
  12.               struct.setIdIsSet(true);  
  13.             } else {   
  14.               org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);  
  15.             }  
  16.             break;  
  17.           case 2// NAME  
  18.           ..  
  19.         }  
  20. }  

    因为thrift的序列化和反序列化实例数据时,是根据"属性序号"进行,这可以保证数据在inputstream和outputstream中顺序是严格的,此外每个struct中"序号"不能重复,但是可以不需要从"1"开始.如果"序号"有重复,将导致无法生成API文件.这一点也要求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) 发送请求:

Java代码   收藏代码
  1. //参见:TServiceClient  
  2. //API方法调用时,发送请求数据流  
  3. protected void sendBase(String methodName, TBase args) throws TException {  
  4.     oprot_.writeMessageBegin(new TMessage(methodName, TMessageType.CALL, ++seqid_));//首先写入"方法名称"和"seqid_"  
  5.     args.write(oprot_);//序列化参数  
  6.     oprot_.writeMessageEnd();  
  7.     oprot_.getTransport().flush();  
  8. }  
  9.   
  10. protected void receiveBase(TBase result, String methodName) throws TException {  
  11.     TMessage msg = iprot_.readMessageBegin();//如果执行有异常  
  12.     if (msg.type == TMessageType.EXCEPTION) {  
  13.       TApplicationException x = TApplicationException.read(iprot_);  
  14.       iprot_.readMessageEnd();  
  15.       throw x;  
  16.     }//检测seqid是否一致  
  17.     if (msg.seqid != seqid_) {  
  18.       throw new TApplicationException(TApplicationException.BAD_SEQUENCE_ID, methodName + " failed: out of sequence response");  
  19.     }  
  20.     result.read(iprot_);//反序列化  
  21.     iprot_.readMessageEnd();  
  22. }  

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

    2) 响应

Java代码   收藏代码
  1. //参考: TBaseProcessor.java  
  2. @Override  
  3. public boolean process(TProtocol in, TProtocol out) throws TException {  
  4.     TMessage msg = in.readMessageBegin();  
  5.     ProcessFunction fn = processMap.get(msg.name);//根据方法名,查找"内部类"  
  6.     if (fn == null) {  
  7.       TProtocolUtil.skip(in, TType.STRUCT);  
  8.       in.readMessageEnd();  
  9.       TApplicationException x = new TApplicationException(TApplicationException.UNKNOWN_METHOD, "Invalid method name: '"+msg.name+"'");  
  10.       out.writeMessageBegin(new TMessage(msg.name, TMessageType.EXCEPTION, msg.seqid));  
  11.       x.write(out);//序列化响应结果,直接输出  
  12.       out.writeMessageEnd();  
  13.       out.getTransport().flush();  
  14.       return true;  
  15.     }  
  16.     fn.process(msg.seqid, in, out, iface);  
  17.     return true;  
  18. }  

    thrift生成的UserService.Processor类,就是server端用来处理请求过程的"代理类";server端从socket中读取请求需要调用的"方法名" +参数列表,并交付给Processor类处理;和其他的RPC调用不同的时,thrift并没有使用类似于"反射机制"的方式来调用方法,而是将UserService的每个方法生成一个"内部类":

Java代码   收藏代码
  1. public static class getById<I extends Iface> extends org.apache.thrift.ProcessFunction<I, getById_args> {  
  2.   public getById() {  
  3.     super("getById");//其中getById为标识符  
  4.   }  
  5.   
  6.   public getById_args getEmptyArgsInstance() {  
  7.     return new getById_args();  
  8.   }  
  9.   
  10.   protected boolean isOneway() {  
  11.     return false;  
  12.   }  
  13.   //实际处理方法  
  14.   public getById_result getResult(I iface, getById_args args) throws org.apache.thrift.TException {  
  15.     getById_result result = new getById_result();  
  16.     result.success = iface.getById(args.id);//直接调用实例的具体方法,硬编码  
  17.     return result;  
  18.   }  
  19. }  

    这个"内部类",将会在Processor初始化的时候,放入到一个map中,此后即可以通过"方法名"查找,然后调用其"getResult"方法了.由此可见,thrift客户端与server端均没有使用到“反射机制”,全程都是硬编码实现,之所以这么做的原因可能是考虑到性能,也可能是考虑到开发者可以在生成的代码基础上任意调整以适应特殊的情况;如果使用反射机制,将会无法兼顾上述2个情况。

Java代码   收藏代码
  1. public static class Processor<I extends Iface> extends org.apache.thrift.TBaseProcessor<I> implements org.apache.thrift.TProcessor {  
  2.   
  3.     public Processor(I iface) {  
  4.       super(iface, getProcessMap(new HashMap<String, org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>>()));  
  5.     }  
  6.   
  7.     protected Processor(I iface, Map<String,  org.apache.thrift.ProcessFunction<I, ? extends  org.apache.thrift.TBase>> processMap) {  
  8.       super(iface, getProcessMap(processMap));  
  9.     }  
  10.   
  11.     private static <I extends Iface> Map<String,  org.apache.thrift.ProcessFunction<I, ? extends  org.apache.thrift.TBase>> getProcessMap(Map<String,  org.apache.thrift.ProcessFunction<I, ? extends  org.apache.thrift.TBase>> processMap) {  
  12.       //放入map  
  13.       processMap.put("getById"new getById());  
  14.       return processMap;  
  15.     }  
  16.     ....  
  17. }  

 

 

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

Java代码   收藏代码
  1. TThreadPoolServer  
  2. public void serve() {  
  3.     try {  
  4.       //启动服务  
  5.       serverTransport_.listen();  
  6.     } catch (TTransportException ttx) {  
  7.       LOGGER.error("Error occurred during listening.", ttx);  
  8.       return;  
  9.     }  
  10.   
  11.     // Run the preServe event  
  12.     if (eventHandler_ != null) {  
  13.       eventHandler_.preServe();  
  14.     }  
  15.   
  16.     stopped_ = false;  
  17.     setServing(true);  
  18.     //循环,直到被关闭  
  19.     while (!stopped_) {  
  20.       int failureCount = 0;  
  21.       try {  
  22.         //accept客户端Socket链接,  
  23.         //对于每个新链接,将会封装成runnable,并提交给线程或者线程池中运行.  
  24.         TTransport client = serverTransport_.accept();  
  25.         WorkerProcess wp = new WorkerProcess(client);  
  26.         executorService_.execute(wp);  
  27.       } catch (TTransportException ttx) {  
  28.         if (!stopped_) {  
  29.           ++failureCount;  
  30.           LOGGER.warn("Transport error occurred during acceptance of message.", ttx);  
  31.         }  
  32.       }  
  33.     }  
  34.     //....  
  35. }  

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

Java代码   收藏代码
  1. //参考:WorkerProcess  
  2. while (true) {  
  3.   
  4.     if (eventHandler != null) {  
  5.       eventHandler.processContext(connectionContext, inputTransport, outputTransport);  
  6.     }  
  7.   
  8.     if(stopped_ || !processor.process(inputProtocol, outputProtocol)) {  
  9.       break;  
  10.     }  
  11. }  

 

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

    3. Client端代码示例

Java代码   收藏代码
  1. public class UserServiceClient {  
  2.   
  3.     public void startClient() {  
  4.         TTransport transport;  
  5.         try {  
  6.             transport = new TSocket("localhost"1234);  
  7.             TProtocol protocol = new TBinaryProtocol(transport);  
  8.             UserService.Client client = new UserService.Client(protocol);  
  9.             transport.open();  
  10.             User user = client.getById(1000);  
  11.               
  12.             transport.close();  
  13.         } catch (TTransportException e) {  
  14.             e.printStackTrace();  
  15.         } catch (TException e) {  
  16.             e.printStackTrace();  
  17.         }  
  18.     }  
  19.   
  20. }  

    4. Server端代码示例 

Java代码   收藏代码
  1. public class Server {  
  2.     public void startServer() {  
  3.         try {  
  4.             TServerSocket serverTransport = new TServerSocket(1234);  
  5.             UserService.Processor process = new Processor(new UserServiceImpl());//入口  
  6.             Factory portFactory = new TBinaryProtocol.Factory(truetrue);  
  7.             Args args = new Args(serverTransport);  
  8.             args.processor(process);  
  9.             args.protocolFactory(portFactory);  
  10.             TServer server = new TThreadPoolServer(args);  
  11.             server.serve();  
  12.         } catch (TTransportException e) {  
  13.             e.printStackTrace();  
  14.         }  
  15.     }  
  16. }  

    到这里,你就会发现,一个service,需要server端启动一个ServerSocket,而且还需要指定一个Processor,此Processor通常已经有thrift编译器生成好了。那么Processor在初始化(参见上述源码),将会为每个方法创建一个内部类的实例,并保存在map中,那么当socket中有请求时,将会反序列化字节流并解析出“方法名”、参数列表,然后根据方法名,从map中查询出方法对应的实例(有点command模式),我们从上述源码中已经知道,每个方法对应的内部类都实现了统一的接口,此时只需要执行相应的方法即可(比如getResult方法),这些方法内部都是硬编码直接调用service实现类的实际方法,而且service的实例在创建Processor实例时已经传递过去。

 

    如果你有很多service,它们需要使用不同的端口,不过通常需要让这些service尽可能的分布在不同的物理server上,否则一个物理server上运行太多的ServerSocket进程并不是一件让人愉快的事情. 或者你让几个service整合成一个.

 

    问题总没有想象的那么简单,其实service被拆分的粒度越细,越容易被部署和扩展,对于负载均衡就更加有利.如何让一个service分布式部署,稍后再继续分享.

    总结:

    1) thrift文件定义struct和serivice API,此文件可以被其他语言生成API文件或者类文件.

    2) 使用thrift客户端生成API文件

    3) JAVA服务端(即服务提供端),实现service功能.

    4) 服务端将server发布成一个Thrift server: 即将service嵌入到一个serverSocket中.

    5) 客户端启动Socket,并和Thrift server建立TCP连接.并使用Client代理类操作远程接口.









http://blog.csdn.net/z69183787/article/details/53005696









本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Thrift原理简析(JAVA) 的相关文章

  • thrift例程编译报错原因和解决方法总结

    thrift里自带的turoral xff0c 使用make编译时经常会报错 xff0c 总结如下 xff1a 1 如果出现如下错误 xff1a error uint8 t does not name a type error uint32
  • Python 使用 Thrift 连接 HBASE 进行操作

    在工作中想要使用Python对HBASE进行操作 主要用来获取数据进行分析 HBASE提供了 Thrift 借口 通过查看API 进行了一些的尝试 下面就是使用Python的相关代码 在使用之前需要启动 HBASE的Thrift和安装pyt
  • Eclipse 部署Thrift 实例 & 服务模型实例演示(java)

    一 Eclipse 部署Thrift 实例 注 需要1 工具包thrift 0 9 0 ext 下载地址http download csdn net detail xyw eliot 5414527 2 Java语言Thrift工程需要的j
  • Thrift快速入门

    文章目录 Thrift的安装 windows下安装 Linux下安装 Thrift的使用 编写IDL文件 命名空间 namespace 基本数据类型 类型定义 typedef 结构体类型 struct 枚举类型 enum 异常类型 exce
  • 在 Thrift IDL 中,如何说客户端应该在结构中包含一组字段中的一个?

    假设我在 Apache Thrift IDL 文件中定义了一个包含两个字段的结构 例如 struct Thing 1 optional string name 2 optional i32 size 这意味着客户端可以提供没有字段 名称 大
  • 循环遍历maven中的特定资源文件生成源

    我使用 maven antrun plugin 从 thrift IDL 生成源代码 我有一个单独的项目 和 jar 来保存这些生成的源 并且这个插件不支持通配符替换 所以我不能说 thrift 我使用执行任务来生成源代码并将它们复制到 s
  • apache thrift C++ 异步客户端

    我正在寻找 C 异步客户端和非阻塞 C 服务器实现 我在 apache 中看到一些邮件档案 但该活动是 2009 年末的 想知道最新的 thrift 是否支持它 我正在对 C 代码使用 cob style 选项 但生成的代码无法编译 将不胜
  • Apache Thrift 教程中的 C++ 链接器错误 - 未定义的符号

    我正在运行 Apache 的 Thrift 教程 http wiki apache org thrift ThriftUsageC 2B 2B我的 Thrift 版本是 0 9 1 我使用的是 OS X 我在本教程中搜索了类似的问题 虽然其
  • Thrift HBase 客户端 - 支持过滤器和协处理器

    遗憾的是 我的 hbase 客户端语言是 Python 我现在使用 happybase 它基于 thrift AFAIK 我知道到目前为止 thrift 仍然不支持过滤器 协处理器 如果我错了 请纠正我 有人可以给我指出任何可以跟踪计划 进
  • Thrift TSimpleServer 在多次成功请求后变得无响应

    我有一个 Thrift API 由在 Linux 上运行的 Java 应用程序提供服务 我正在使用 NET 客户端连接到 API 并执行操作 对该服务的前几次调用工作正常 没有错误 但随后 看似随机 调用将 挂起 如果我强制退出客户端并尝试
  • Cassandra + PHP + Thrift + 检索多行性能不佳

    我是 Cassandra 的新手 我正在尝试使用 php 恢复多行 但性能确实很差 这是我正在使用的代码
  • 从 erlang 插入 cassandra

    我正在尝试从 Erlang R14B02 通过 thrift 0 6 1 将一些内容插入到 cassandra 0 7 6 中 我正在做以下事情 读取记录定义 rr cassandra types 连接到卡桑德拉 ok C thrift c
  • 在 C++ 中将序列化的 Thrift 结构序列化到 Kafka

    我有一套structs定义于Thrift例如以下内容 struct Foo 1 i32 a 2 i64 b 我需要执行以下操作C a 序列化实例Foo转换为 Thrift 兼容字节 使用Binary or Compact节俭协议 b 将字节
  • HBase 上的 Thrift 有性能基准吗?

    我有一个可以将大量数据写入 hbase 的系统 系统是用c 编写的 发现hbase有其他语言的thrift接口 我的问题是 HBase 上的 Thrift 有性能基准吗 与java原生api相比 最劣势是什么 我推荐最近关于这个主题的两篇博
  • Apache Thrift 中的对称加密 (AES)

    我有两个使用 Thrift 进行交互的应用程序 他们共享相同的密钥 我需要加密他们的消息 使用对称算法 例如 AES 是有意义的 但我还没有找到任何库来执行此操作 所以我做了一个研究并看到了以下选项 使用内置 SSL 支持 我可以使用内置的
  • Haskell Thrift 库在性能测试中比 C++ 慢 300 倍

    我正在构建一个包含两个组件的应用程序 用 Haskell 编写的服务器和用 Qt C 编写的客户端 我正在使用 thrift 来传达它们 我想知道为什么它运行得这么慢 我做了性能测试 这是我机器上的结果 Results C server a
  • thrift:generate-python 在 SBT 中不生成 Python 文件

    I use sbt 节俭 https github com bigtoast sbt thrift 0 6我在构建定义中有以下内容 thriftPythonEnabled true thriftPythonOutputDir lt lt s
  • Thrift 将可选转换为默认或必需

    我有一个节俭的结构 struct Message 1 optional int userID 将其更改为默认需求是否安全 struct Message 1 int userID 如果我知道它总是已设置 那么 需要 呢 如中所述这个答案 ht
  • 使用 Thrift 通过共享内存进行 IPC 通信

    我找不到关于如何使用 apache thrift 通过共享内存进行 ipc 通信的足够示例 我的目标是在 thrift 的帮助下序列化现有的类 然后通过共享内存发送到另一个进程 在该进程中我在 thrift 的帮助下再次反序列化它 现在我正
  • 微服务之间的通信

    假设您有微服务 A B 和 C 它们当前都通过 HTTP 进行通信 假设服务 A 向服务 B 发送请求 服务 B 得到响应 然后 该响应中返回的数据必须发送到服务 C 进行一些处理 然后最终返回到服务 A 服务 A 现在可以在网页上显示结果

随机推荐