thrift学习笔记(一) thrift简介及第一个helloword程序

版权声明:本文为博主原创文章,未经博主允许不得转载,转载请注明出处. 博主博客地址是 http://blog.csdn.net/liubenlong007 https://blog.csdn.net/fgyibupi/article/details/54692467

简介

facebook开源的RPC框架,秉承了Facebook一贯的只管拉屎不管擦屁股的作风.
Thrift是一个软件框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎,以构建在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 等等编程语言间无缝结合的、高效的服务。
Thrift最初由facebook开发,07年四月开放源码,08年5月进入apache孵化器。thrift允许你定义一个简单的定义文件中的数据类型和服务接口。以作为输入文件,编译器生成代码用来方便地生成RPC客户端和服务器通信的无缝跨编程语言。

Windows下安装thrift

  1. 到官网下载Windows版本的编译器(thrift-0.9.3.exe):http://thrift.apache.org/download
  2. 下载完成后放到【C:\Program Files\Thrift】目录下,并将名字改为【thrift.exe】。注:这里改名只是为了敲命令行方便
  3. 将目录【C:\Program Files\Thrift】添加到环境变量即可

到此thrift安装完成,简单吧,我们来验证一下。
编写helloword服务接口描述文件代码:

Hello.thrift

namespace java service.demo
service Hello{
    string helloString(1:string para)
    i32 helloInt(1:i32 para)
    bool helloBoolean(1:bool para)
    void helloVoid()
    string helloNull()
}

命名为【Hello.thrift】放到【D:\workSpaceIdea\thrift】目录下,执行命令【thrift –gen java Hello.thrift】生成java文件:
这里写图片描述

创建第一个thrift程序

创建一个maven项目

目录结构:
这里写图片描述

pom 文件:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.lbl</groupId>
    <artifactId>thrift.demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.apache.thrift</groupId>
            <artifactId>libthrift</artifactId>
            <version>0.9.3</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.12</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

注:Hello.java是通过thrift自动生成的

HelloServiceImpl.java是实现类:

package service.demo;

import org.apache.thrift.TException;
 public class HelloServiceImpl implements Hello.Iface {
    @Override
 public boolean helloBoolean(boolean para) throws TException {
        return para;
  }
    @Override
 public int helloInt(int para) throws TException {
        try {
            Thread.sleep(20000);
 } catch (InterruptedException e) {
            e.printStackTrace();
 }
        return para;
 }
    @Override
 public String helloNull() throws TException {
        return null;
 }
    @Override
 public String helloString(String para) throws TException {
        System.out.println("入参:" + para);
 return "hello  " + para;
  }
    @Override
 public void helloVoid() throws TException {
        System.out.println("Hello World");
 }
 }

使用线程池模式的server:

HelloServiceServer.java

package server;

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;
import org.apache.thrift.transport.TTransportException;
import service.demo.Hello;
import service.demo.HelloServiceImpl;

public class HelloServiceServer {
    /**
     * 启动 Thrift 服务器
 * @param args
 */
 public static void main(String[] args) {
        try {
            // 设置服务端口为 7911
  TServerSocket serverTransport = new TServerSocket(7911);
  // 设置协议工厂为 TBinaryProtocol.Factory
  TBinaryProtocol.Factory proFactory = new TBinaryProtocol.Factory();
  // 关联处理器与 Hello 服务的实现
  TProcessor processor = new Hello.Processor(new HelloServiceImpl());
  TThreadPoolServer.Args args1 = new TThreadPoolServer.Args(serverTransport);
  args1.processor(processor);
  args1.protocolFactory(proFactory);

  TServer server = new TThreadPoolServer(args1);
  System.out.println("Start server on port 7911...");
  server.serve();
  } catch (TTransportException e) {
            e.printStackTrace();
  }
    }
 }

HelloServiceClient.java

package client;

import org.apache.thrift.TException;
 import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import service.demo.Hello;

 public class HelloServiceClient {
 /**
     * 调用 Hello 服务
 * @param args
 */
 public static void main(String[] args) {
        try {
            // 设置调用的服务地址为本地,端口为 7911
 TTransport transport = new TSocket("localhost", 7911);
 transport.open();
 // 设置传输协议为 TBinaryProtocol
 TProtocol protocol = new TBinaryProtocol(transport);
 Hello.Client client = new Hello.Client(protocol);
 // 调用服务的 helloVoid 方法
//            client.helloVoid();
  String resp = client.helloString("wertyuiopsdfghjkl;");
  System.out.println(resp);
  transport.close();
 } catch (TTransportException e) {
            e.printStackTrace();
 } catch (TException e) {
            e.printStackTrace();
 }
    }
 }

运行

运行server以后,运行client,会在server的console控制台看到输出内容
这里写图片描述
这里写图片描述

注意:
客户端和服务端要使用同一中 Protocol 和 Transport,否则会抛出异常

TNonblockingServer 服务模型 的 server

HelloServiceServer1.java

package server;

import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.TNonblockingServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;
import org.apache.thrift.transport.TTransportException;
import service.demo.Hello;
import service.demo.HelloServiceImpl;

public class HelloServiceServer1 {

    public static final int SERVER_PORT = 8090;

  /**
     * 启动 Thrift 服务器
 * @param args
 */
 public static void main(String[] args) {
        try {
            System.out.println("HelloWorld TNonblockingServer start ....");

  TProcessor tprocessor = new Hello.Processor(new HelloServiceImpl());

  TNonblockingServerSocket tnbSocketTransport = new TNonblockingServerSocket(SERVER_PORT);
  TNonblockingServer.Args tnbArgs = new TNonblockingServer.Args(tnbSocketTransport);
  tnbArgs.processor(tprocessor);
  tnbArgs.transportFactory(new TFramedTransport.Factory());
  tnbArgs.protocolFactory(new TCompactProtocol.Factory());

  // 使用非阻塞式IO,服务端和客户端需要指定TFramedTransport数据传输的方式
  TServer server = new TNonblockingServer(tnbArgs);
  server.serve();
  } catch (TTransportException e) {
            e.printStackTrace();
  }
    }
 }

HelloServiceClient1.java

package client;

import org.apache.thrift.TException;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import service.demo.Hello;

public class HelloServiceClient1 {

    public static final String SERVER_IP = "localhost";
 public static final int SERVER_PORT = 8090;
 public static final int TIMEOUT = 30000;

/**
 * 调用 Hello 服务 * @param args
  */
  public static void main(String[] args) {
       TTransport transport = null;
 try {
           transport = new TFramedTransport(new TSocket(SERVER_IP, SERVER_PORT, TIMEOUT));
  // 协议要和服务端一致
  TProtocol protocol = new TCompactProtocol(transport);
  Hello.Client client = new Hello.Client(protocol);
  transport.open();
  String result = client.helloString("hfjtydiyghfkjdhftgh");
  System.out.println("Thrify client result =: " + result);
  } catch (TTransportException e) {
           e.printStackTrace();
  } catch (TException e) {
           e.printStackTrace();
  } finally {
           if (null != transport) {
               transport.close();
  }
       }
   }
}

我在开发中遇到的错误及解决办法

  • java.lang.NoSuchMethodError: org.apache.thrift.EncodingUtils.setBit(BIZ)B

出现这个问题的原因是dubbox本身集成的thrift版本是0.8.0,我添加的版本是0.9.3导致的,解决方法:去掉对0.8.0版本的依赖
这里写图片描述

这个问题是因为我thrift的IDL描述文件多了个逗号,在thrift生产java文件的时候没有报错,但是在dubbox运行的时候就出错了。

IDL文件简介

  • 基本类型
    • 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
  • 结构体类型:
    • struct:定义公共的对象,类似于 C 语言中的结构体定义,在 Java 中是一个 JavaBean
  • 容器类型:
    • list:对应 Java 的 ArrayList
    • set:对应 Java 的 HashSet
    • map:对应 Java 的 HashMap
  • 异常类型:
    • exception:对应 Java 的 Exception
  • 服务类型:
    • service:对应服务的类

数据传输协议

不同版本稍有区别

这里写图片描述

  • TBinaryProtocol : 二进制格式.
  • TCompactProtocol : 压缩格式
  • TJSONProtocol : JSON格式
  • TSimpleJSONProtocol : 提供JSON只写协议, 生成的文件很容易通过脚本语言解析
数据传输格式 类型 优点 缺点
Xml 文本 1、良好的可读性。 2、序列化的数据包含完整的结构。3、调整不同属性的顺序对序列化/反序列化不影响 1、数据传输量大。2、不支持二进制数据类型
Json 文本 1、良好的可读性。2、调整不同属性的顺序对序列化/反序列化不影响 1、丢弃了类型信息, 比如”price”:100, 对price类型是int/double解析有二义性。2、不支持二进制数据类型
Thrift 二进制 高效 1、不宜读。2、向后兼容有一定的约定限制,采用id递增的方式标识并以optional修饰来添加
Google Protobuf 二进制 高效 1、不宜读。2、向后兼容有一定的约定限制

服务模型

这里写图片描述

Thrif 提供网络模型:单线程、多线程、事件驱动。从另一个角度划分为:阻塞服务模型、非阻塞服务模型。

  • 阻塞服务
    • TSimpleServer
    • TThreadPoolServer
  • 非阻塞服务模型
    • TNonblockingServer
    • THsHaServer
    • TThreadedSelectorServer

TSimpleServer

TSimpleServer实现是非常的简单,循环监听新请求的到来并完成对请求的处理,是个单线程阻塞模型。由于是一次只能接收和处理一个socket连接,效率比较低,在实际开发过程中很少用到它。

TThreadPoolServer

ThreadPoolServer为解决了TSimpleServer不支持并发和多连接的问题, 引入了线程池。但仍然是多线程阻塞模式即实现的模型是One Thread Per Connection。
线程池采用能线程数可伸缩的模式,线程池中的队列采用同步队列(SynchronousQueue)。
ThreadPoolServer拆分了监听线程(accept)和处理客户端连接的工作线程(worker), 监听线程每接到一个客户端, 就投给线程池去处理。
TThreadPoolServer模式优点:
线程池模式中,数据读取和业务处理都交由线程池完成,主线程只负责监听新连接,因此在并发量较大时新连接也能够被及时接受。线程池模式比较适合服务器端能预知最多有多少个客户端并发的情况,这时每个请求都能被业务线程池及时处理,性能也非常高。
TThreadPoolServer模式缺点:
线程池模式的处理能力受限于线程池的工作能力,当并发请求数大于线程池中的线程数时,新请求也只能排队等待。

TNonblockingServer

TNonblockingServer采用单线程非阻塞(NIO)的模式, 借助Channel/Selector机制, 采用IO事件模型来处理。所有的socket都被注册到selector中,在一个线程中通过seletor循环监控所有的socket,每次selector结束时,处理所有的处于就绪状态的socket,对于有数据到来的socket进行数据读取操作,对于有数据发送的socket则进行数据发送,对于监听socket则产生一个新业务socket并将其注册到selector中。
select代码里对accept/read/write等IO事件进行监控和处理, 唯一可惜的这个单线程处理. 当遇到handler里有阻塞的操作时, 会导致整个服务被阻塞住。
TNonblockingServer模式优点:
相比于TSimpleServer效率提升主要体现在IO多路复用上,TNonblockingServer采用非阻塞IO,同时监控多个socket的状态变化;
TNonblockingServer模式缺点:
TNonblockingServer模式在业务处理上还是采用单线程顺序来完成,在业务处理比较复杂、耗时的时候,例如某些接口函数需要读取数据库执行时间较长,此时该模式效率也不高,因为多个调用请求任务依然是顺序一个接一个执行。

THsHaServer

THsHaServer类是TNonblockingServer类的子类,为解决TNonblockingServer的缺点, THsHa引入了线程池去处理, 其模型把读写任务放到线程池去处理即多线程非阻塞模式。HsHa是: Half-sync/Half-async的处理模式, Half-aysnc是在处理IO事件上(accept/read/write io), Half-sync用于handler对rpc的同步处理上。因此可以认为THsHaServer半同步半异步。
THsHaServer的优点:
与TNonblockingServer模式相比,THsHaServer在完成数据读取之后,将业务处理过程交由一个线程池来完成,主线程直接返回进行下一次循环操作,效率大大提升;
THsHaServer的缺点:
主线程需要完成对所有socket的监听以及数据读写的工作,当并发请求数较大时,且发送数据量较多时,监听socket上新连接请求不能被及时接受。

TThreadedSelectorServer

TThreadedSelectorServer是大家广泛采用的服务模型,其多线程服务器端使用非堵塞式I/O模型,是对TNonblockingServer的扩充, 其分离了Accept和Read/Write的Selector线程, 同时引入Worker工作线程池。
(1)一个AcceptThread线程对象,专门用于处理监听socket上的新连接;
(2)若干个SelectorThread对象专门用于处理业务socket的网络I/O操作,所有网络数据的读写均是有这些线程来完成;
(3)一个负载均衡器SelectorThreadLoadBalancer对象,主要用于AcceptThread线程接收到一个新socket连接请求时,决定将这个新连接请求分配给哪个SelectorThread线程。
(4)一个ExecutorService类型的工作线程池,在SelectorThread线程中,监听到有业务socket中有调用请求过来,则将请求读取之后,交个ExecutorService线程池中的线程完成此次调用的具体执行
这里写图片描述

MainReactor就是Accept线程, 用于监听客户端连接, SubReactor采用IO事件线程(多个), 主要负责对所有客户端的IO读写事件进行处理. 而Worker工作线程主要用于处理每个rpc请求的handler回调处理(这部分是同步的)。因此其也是Half-Sync/Half-Async(半异步-半同步)的 。
TThreadedSelectorServer模式对于大部分应用场景性能都不会差,因为其有一个专门的线程AcceptThread用于处理新连接请求,因此能够及时响应大量并发连接请求;另外它将网络I/O操作分散到多个SelectorThread线程中来完成,因此能够快速对网络I/O进行读写操作,能够很好地应对网络I/O较多的情况。

猜你喜欢

转载自blog.csdn.net/fgyibupi/article/details/54692467