RPC/Thrift总结

https://www.bilibili.com/video/BV1eF411E7SR?p=16

1.Thrift介绍

1.1Thrift定义

Thrift是一个轻量级、跨语言的RPC框架,主要用于各个服务之间的RPC通信。

1.2Thrift架构

Thrift技术栈分层从下向上分别为:传输层(Transport Layer)、协议层(Protocol Layer)、处理层
(Processor Layer)和服务层(Server Layer)。

  • 传输层(Transport Layer):传输层负责直接从网络中读取和写入数据,它定义了具体的网络传输
    协议;比如说TCP/IP传输等。
  • 协议层(Protocol Layer):协议层定义了数据传输格式,负责网络传输数据的序列化和反序列化;
    比如说JSON、XML、二进制数据等。
  • 处理层(Processor Layer):处理层是由具体的IDL(接口描述语言)生成的,封装了具体的底层网
    络传输和序列化方式,并委托给用户实现的Handler进行处理。
  • 服务层(Server Layer):整合上述组件,提供具体的网络IO模型(单线程/多线程/事件驱动),形成
    最终的服务

2.IDL详解

2.1介绍

Thrift 采用IDL(Interface Definition Language)来定义通用的服务接口,然后通过Thrift提供的编
译器,可以将服务接口编译成不同语言编写的代码,通过这个方式来实现跨语言的功能。

Thrift是一个典型的CS(客户端/服务端)结构,客户端和服务端可以使用不同的语言开发。既然客户端和
服务端能使用不同的语言开发,那么一定就要有一种中间语言来关联客户端和服务端的语言,这种语言
就是IDL (InterfaceDescription Language)

2.2IDL语法

基本类型

基本类型就是:不管哪一种语言,都支持的数据形式表现。Apache Thrift中支持以下几种基本类型:
在这里插入图片描述

特殊类型

binary: 未编码的字节序列,是string的一种特殊形式;这种类型主要是方便某些场景下JAVA调用。JAVA
中对应的是java.nio.ByteBuffer类型,GO中是[]byte。

集合容器(Containers)

有3种可用容器类型:
在这里插入图片描述
在使用容器类型时必须指定泛型,否则无法编译idl文件。其次,泛型中的基本类型,JAVA语言中会被替换为对应的包装类型。
集合中的元素可以是除了service之外的任何类型,包括exception。

struct Test { 
	1: map<string, User> usermap, 
	2: set<i32> intset, 
	3: list<double> doublelist 
}

常量及类型别名(Const&&Typedef)

//常量定义 
const i32 MALE_INT = 1 
const map<i32, string> GENDER_MAP = {1: "male", 2: "female"} 
//某些数据类型比较长可以用别名简化 typedef map<i32, string> gmp

struct类型

在面向对象语言中,表现为“类定义”;在弱类型语言、动态语言中,表现为“结构/结构体”。定义格式如
下:

struct <结构体名称> { <序号>:[字段性质] <字段类型> <字段名称> [= <默认值>] [;|,] }

例如:

struct User{ 
	1: required string name, //该字段必须填写 
	2: optional i32 age = 0; //默认值 
	3: bool gender //默认字段类型为optional 
}

struct bean{ 
	1: i32 number=10, 
	2: i64 bigNumber, 
	3: double decimals, 
	4: string name="thrifty"
}

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

枚举(enum)

Thrift不支持枚举类嵌套,枚举常量必须是32位的正整数

enum HttpStatus { 
	OK = 200, 
	NOTFOUND=404 
}

异常(Exceptions)

异常在语法和功能上类似于结构体,差别是异常使用关键字exception,而且异常是继承每种语言的基础
异常类。

exception MyException { 
	1: i32 errorCode 
	2: string message 
}
service ExampleService { 
	string GetName() throws (1: MyException e) 
}

Service (服务定义类型)

服务的定义方法在语义上等同于面向对象语言中的接口。

service HelloService { 
	i32 sayInt(1:i32 param) 
	string sayString(1:string param) 
	bool sayBoolean(1:bool param) 
	void sayVoid() 
}

编译后的Java代码

public class HelloService { 
	public interface Iface { 
		public int sayInt(int param) throws org.apache.thrift.TException; 
		public java.lang.String sayString(java.lang.String param) throws org.apache.thrift.TException; 
		public boolean sayBoolean(boolean param) throws org.apache.thrift.TException; 
		public void sayVoid() throws org.apache.thrift.TException; 
	}
	// ... 省略很多代码 
}

Namespace (名字空间)

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

namespace java com.example.test

转为

package com.example.test

实例:

namespace java com.tuling // 命名空间定义,规范:namespace + 语言 + 包路径 
service Hello{ // 接口定义,类似Java接口定义 
	string getWord(), // 方法定义,类似Java接口定义 
	void writeWold(1:string words) //参数类型指定
}

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.

Include

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

include "test.thrift" 
... 
struct StSearchResult { 
1: in32 uid; 
... 
}

3.Thrift应用

3.1Thrift安装

windows安装

下载地址:https://thrift.apache.org/download
选一个下载即可,建议exe,因为exe本身就是编译好给windows用户使用的。
在这里插入图片描述
下载完成后配置下环境变量,例如在D盘新建一个Thrift文件夹,将下载的thrift-0.16.0.exe重新命名为thrift.exe后放到Thrift文件夹下。
之后配置环境变量:
在这里插入图片描述
配置成功:
在这里插入图片描述
需要注意的是在cmd中成功配置后不一定在idea下的Terminal也能生效,如果不生效请重启,或是用带路径的普通方式访问。
在这里插入图片描述
centos 安装
参考文档:
官方文档
CSDN上的教程

3.2IDL文件编译

IDL文件可以直接用来生成各种语言的代码,下面给出常用的各种不同语言的代码生成命令:

# 生成java 
thrift -gen java user.thrift 
# 生成c++ 
thrift -gen cpp user.thrift 
# 生成php 
thrift -gen php user.thrift 
# 生成node.js
thrift -gen js:node user.thrift 
#可以通过以下命令查看生成命令的格式 
thrift -help 
//指定输出目录 
thrift --gen java -o target user.thrift

3.3Thrift的协议

Thrift可以让用户选择客户端与服务端之间传输通信协议的类别,在传输协议上总体划分为文本(text)和
二进制(binary)传输协议。为节约带宽,提高传输效率,一般情况下使用二进制类型的传输协议为多数,
有时还会使用基于文本类型的协议,这需要根据项目/产品中的实际需求。常用协议有以下几种:

  • TBinaryProtocol:二进制编码格式进行数据传输
  • TCompactProtocol:高效率的、密集的二进制编码格式进行数据传输
  • TJSONProtocol: 使用JSON文本的数据编码协议进行数据传输
  • TSimpleJSONProtocol:只提供JSON只写的协议,适用于通过脚本语言解析

3.4Thrift的传输层

常用的传输层有以下几种:

  • TSocket:使用阻塞式I/O进行传输,是最常见的模式
  • TNonblockingTransport:使用非阻塞方式,用于构建异步客户端
  • TFramedTransport:使用非阻塞方式,按块的大小进行传输,类似于Java中的NIO

3.5Java快速开始

1.编写user.thrift文件

namespace java com.tuling struct User{
   1:i32 id
   2:string name    3:i32 age=0 }
service  UserService {
	User getById(1:i32 id) bool isExist(1:string name)
}
  1. 通过编译器编译user.thrift文件,生成java接口类文件
#编译user.thrift
thrift -gen java user.thrift

由于未指定代码生成的目标目录,生成的类文件默认存放在 gen-java目录下,可将其复制至指定目录。
在这里插入图片描述
对于开发人员而言,使用原生的Thrift框架,仅需要关注以下四个核心内部接口/类:Iface, AsyncIface, Client和AsyncClient。

  • Iface:服务端通过实现UserService.Iface接口,向客户端的提供具体的同步业务逻辑。
  • AsyncIface:服务端通过实现UserService.Iface接口,向客户端的提供具体的异步业务逻辑。
  • Client:客户端通过UserService.Client的实例对象,以同步的方式访问服务端提供的服务方法。
  • AsyncClient:客户端通过UserService.AsyncClient的实例对象,以异步的方式访问服务端提供的 服务方法。

3.新建maven工程,引入thrift依赖

<dependency>
   <groupId>org.apache.thrift</groupId>    
   <artifactId>libthrift</artifactId>    
   <version>0.15.0</version> 
</dependency>

将生成类的UserService.java源文件拷贝进项目源文件目录中,并实现UserServiceService.Iface的定义 的getById()方法。

public class UserServiceImpl implements UserService.Iface {    @Override
   public User getById(int id) throws TException {        
   	   System.out.println("=====调用getById=====");
       //todo 模拟业务调用
       User user = new User();
       user.setId(id);
       user.setName("fox");
       user.setAge(30);
       return user;  }
   @Override
   public boolean isExist(String name) throws TException {
   	   return false;  
   }
}

4.服务器端程序编写
以TSimpleServer模型为例

/*
* 使用SimpleClient调用
* TSimpleServer的工作模式采用最简单的阻塞IO,实现方法简洁明了,便于理解。
* 但是一次只能接收和处理一个socket连接(同时启两个client,其中一个以debug方式启动,打上断点,另一个也将无法继续向下执行),效率比较低。
* 它主要用于演示Thrift的工作过程,在实际开发过程中很少
*
* */
public class SimpleService {

    public static void main(String[] args) {

        try {
            TServerTransport serverTransport = new TServerSocket(9090);

            //获取processor,处理业务
            UserService.Processor processor = new UserService.Processor(new UserServiceImpl());
            //指定TBinaryProtocol
            TBinaryProtocol.Factory protocolFactory = new TBinaryProtocol.Factory();

            //传入参数
            TSimpleServer.Args targs = new TSimpleServer.Args(serverTransport);
            targs.processor(processor);
            targs.protocolFactory(protocolFactory);

            //单线程服务模型,一般用于测试
            TServer server = new TSimpleServer(targs);

            System.out.println("Starting the simple server...");
            //暴露服务
            server.serve();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行服务端程序,服务端在指定端口监听客户端的连接请求。
在这里插入图片描述
5.客户端程序编写

package com.tuling;

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;

/**
 * @author Fox
 * consumer
 */
public class SimpleClient {

    public static void main(String[] args) {

        TTransport transport = null;
        try {
            // 使用阻塞io
            transport = new TSocket("localhost", 9090);
            //指定二进制编码格式
            TProtocol protocol = new TBinaryProtocol(transport);
            UserService.Client client = new UserService.Client(protocol);
            //建立连接
            transport.open();

            //发起rpc调用
            User result = client.getById(1);
            System.out.println("Result : " + result);

        } catch (TException e) {
            e.printStackTrace();
        } finally {
            if (null != transport) {
                transport.close();
            }
        }
    }
}

运行客户端程序,控制台输出返回结果
在这里插入图片描述
这里使用的一个基于单线程同步的简单服务模型,一般仅用于入门学习和测试!

3.6python跨语言调用服务

1.通过编译器编译user.thrift文件,生成python代码

thrift -gen py user.thrift

然后将生成的 python 代码 和 文件,放到新建的 python 项目中
2.python中使用thrift需要安装thrift模块

from thrift.transport import TSocket, TTransport
from thrift.protocol import TBinaryProtocol
from com.tuling import UserService
# Make socket
transport = TSocket.TSocket('localhost', 9090) transport.setTimeout(600)
# Buffering is critical. Raw sockets are very slow transport = TTransport.TBufferedTransport(transport)
# Wrap in a protocol
protocol = TBinaryProtocol.TBinaryProtocol(transport) # Create a client to use the protocol encoder
client = UserService.Client(protocol) # Connect!
transport.open()
result = client.getById(1) print(result)

在这里插入图片描述

4.网络服务模型详解

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

  • 阻塞服务模型:TSimpleServer、TThreadPoolServer。
  • 非阻塞服务模型:TNonblockingServer、THsHaServer和TThreadedSelectorServer。
    在这里插入图片描述

TServer

TServer定义了静态内部类Args,Args继承自抽象类AbstractServerArgs。AbstractServerArgs采用了建造者模式,向TServer提供各种工厂:
在这里插入图片描述
TServer的三个方法:serve()、stop()和isServing()。serve()用于启动服务,stop()用于关闭服务, isServing()用于检测服务的起停状态。

TSimpleServer

TSimpleServer的工作模式采用最简单的阻塞IO,实现方法简洁明了,便于理解,但是一次只能接收和 处理一个socket连接,效率比较低。它主要用于演示Thrift的工作过程,在实际开发过程中很少用到它。
工作流程
在这里插入图片描述
服务端使用

package com.tuling;

import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;

import com.tuling.service.UserServiceImpl;

/*
*使用SimpleClient调用
* TSimpleServer的工作模式采用最简单的阻塞IO,实现方法简洁明了,便于理解。
* 但是一次只能接收和处理一个socket连接(同时启两个client,其中一个以debug方式启动,打上断点,另一个也将无法继续向下执行),效率比较低。
* 它主要用于演示Thrift的工作过程,在实际开发过程中很少
*
* */
public class SimpleService {

    public static void main(String[] args) {

        try {
            TServerTransport serverTransport = new TServerSocket(9090);

            //获取processor,处理业务
            UserService.Processor processor = new UserService.Processor(new UserServiceImpl());
            //指定TBinaryProtocol
            TBinaryProtocol.Factory protocolFactory = new TBinaryProtocol.Factory();

            //传入参数
            TSimpleServer.Args targs = new TSimpleServer.Args(serverTransport);
            targs.processor(processor);
            targs.protocolFactory(protocolFactory);

            //单线程服务模型,一般用于测试
            TServer server = new TSimpleServer(targs);

            System.out.println("Starting the simple server...");
            //暴露服务
            server.serve();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

客户端使用

package com.tuling;

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;

/**
 * @author Fox
 * consumer
 */
public class SimpleClient {

    public static void main(String[] args) {

        TTransport transport = null;
        try {
            // 使用阻塞io
            transport = new TSocket("localhost", 9090);
            //指定二进制编码格式
            TProtocol protocol = new TBinaryProtocol(transport);
            UserService.Client client = new UserService.Client(protocol);
            //建立连接
            transport.open();

            //发起rpc调用
            User result = client.getById(1);
            System.out.println("Result : " + result);

        } catch (TException e) {
            e.printStackTrace();
        } finally {
            if (null != transport) {
                transport.close();
            }
        }
    }
}

TThreadPoolServer

TThreadPoolServer模式采用阻塞socket方式工作,主线程负责阻塞式监听是否有新socket到来,具体 的业务处理交由一个线程池来处理。
在这里插入图片描述
服务端使用

package com.tuling;

import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;

import com.tuling.service.UserServiceImpl;

/**
 * @author Fox
 */

/*
 * 使用SimpleClient调用
 * TThreadPoolServer模式采用阻塞socket方式工作,主线程负责阻塞式监听是否有新socket到来,具体的业务处理交由一个线程池来处理。
 * 所以即使有一个线程阻塞了,也可以从线程池中启动一个别的线程去调用。
 * 缺点是不适合高并发场景,底层线程池的最大量是Integer.MAX_VALUE约等于无限制,当大量客户端调用时会出现OOM(内存溢出)。
 * 线程池模式比较适合服务器端能预知最多有多少个客户端并发的情况,这时每个请求都能被业务线程池及时处理,性能也非常高。
 *
 * */
public class ThreadPoolServer {

    public static void main(String[] args) {

        try {
            TServerTransport serverTransport = new TServerSocket(9090);

            //获取processor
            UserService.Processor processor = new UserService.Processor(new UserServiceImpl());
            //指定TBinaryProtocol
            TBinaryProtocol.Factory protocolFactory = new TBinaryProtocol.Factory();

            //
            TThreadPoolServer.Args targs = new TThreadPoolServer.Args(serverTransport);
            targs.processor(processor);
            targs.protocolFactory(protocolFactory);

            // 线程池服务模型 使用标准的阻塞式IO 预先创建一组线程处理请求
            TServer server = new TThreadPoolServer(targs);

            System.out.println("Starting ThreadPool server...");
            server.serve();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

客户端使用

package com.tuling;

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;

/**
 * @author Fox
 * consumer
 */
public class SimpleClient {

    public static void main(String[] args) {

        TTransport transport = null;
        try {
            // 使用阻塞io
            transport = new TSocket("localhost", 9090);
            //指定二进制编码格式
            TProtocol protocol = new TBinaryProtocol(transport);
            UserService.Client client = new UserService.Client(protocol);
            //建立连接
            transport.open();

            //发起rpc调用
            User result = client.getById(1);
            System.out.println("Result : " + result);

        } catch (TException e) {
            e.printStackTrace();
        } finally {
            if (null != transport) {
                transport.close();
            }
        }
    }
}

ThreadPoolServer解决了TSimpleServer不支持并发和多连接的问题,引入了线程池。实现的模型是 One Thread Per Connection。
优缺点
TThreadPoolServer模式的优点

  • 拆分了监听线程(Accept Thread)和处理客户端连接的工作线程(Worker Thread),数据读取和业务处理 都交给线程池处理。因此在并发量较大时新连接也能够被及时接受。

  • 线程池模式比较适合服务器端能预知最多有多少个客户端并发的情况,这时每个请求都能被业务线程池 及时处理,性能也非常高。

TThreadPoolServer模式的缺点

  • 线程池模式的处理能力受限于线程池的工作能力,当并发请求数大于线程池中的线程数时,新请求也只 能排队等待。
  • 默认线程池允许创建的最大线程数量为Integer.MAX_VALUE,可能会创建出大量线程,导致OOM(内存 溢出)

TNonblockingServer

TNonblockingServer模式也是单线程工作,但是采用NIO的模式,利用io多路复用模型处理socket就绪 事件,对于有数据到来的socket进行数据读取操作,对于有数据发送的socket则进行数据发送操作,对于 监听socket则产生一个新业务socket并将其注册到selector上。
注意:TNonblockingServer要求底层的传输通道必须使用TFramedTransport。
工作流程
在这里插入图片描述

服务端使用

package com.tuling;

import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.TNonblockingServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TNonblockingServerSocket;
import org.apache.thrift.transport.layered.TFramedTransport;

import com.tuling.service.UserServiceImpl;

/**
 * @author Fox
 */
public class NonblockingServer {

    public static void main(String[] args) {

        try {
            TNonblockingServerSocket serverTransport = new TNonblockingServerSocket(9090);

            //获取processor
            UserService.Processor processor = new UserService.Processor(new UserServiceImpl());
            //指定TCompactProtocol
            TCompactProtocol.Factory protocolFactory = new TCompactProtocol.Factory();
            //指定TFramedTransport
            TFramedTransport.Factory tTransport = new TFramedTransport.Factory();

            TNonblockingServer.Args targs = new TNonblockingServer.Args(serverTransport);
            targs.processor(processor);
            targs.protocolFactory(protocolFactory);
            targs.transportFactory(tTransport);

            // 使用NIO服务端和客户端需要指定TFramedTransport数据传输的方式
            TServer server = new TNonblockingServer(targs);
            System.out.println("Starting Non-blocking server...");
            //暴露服务
            server.serve();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

客户端使用

package com.tuling;

import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.layered.TFramedTransport;

/**
 * @author Fox
 */
public class NonblockingClient {

    public static void main(String[] args) {

        TTransport transport = null;
        try {
            // 设置传输通道,对于NIO需要使用TFramedTransport(用于将数据分块发送)
            transport = new TFramedTransport(new TSocket("localhost", 9090));
            //协议和服务端一致
            TProtocol protocol = new TCompactProtocol(transport);
            UserService.Client client = new UserService.Client(protocol);
            //建立连接
            transport.open();

            //发起rpc调用
            User result = client.getById(1);
            System.out.println("Result : " + result);

        } catch (TException e) {
            e.printStackTrace();
        } finally {
            if (null != transport) {
                transport.close();
            }
        }

    }
}

优缺点
TNonblockingServer模式优点

  • 相比于TSimpleServer效率提升主要体现在IO多路复用上,TNonblockingServer采用非阻塞IO,对 accept/read/write等IO事件进行监控和处理,同时监控多个socket的状态变化。

TNonblockingServer模式缺点

  • TNonblockingServer模式在业务处理上还是采用单线程顺序来完成。在业务处理比较复杂、耗时的时候,例如某些接口函数需要读取数据库执行时间较长,会导致整个服务被阻塞住,此时该模式效率也不 高,因为多个调用请求任务依然是顺序一个接一个执行。源码如下
    在这里插入图片描述

THsHaServer

鉴于TNonblockingServer的缺点,THsHaServer继承于TNonblockingServer,引入了线程池提高了任务处理的并发能力。
注意:THsHaServer和TNonblockingServer一样,要求底层的传输通道必须使用 TFramedTransport。
工作流程
在这里插入图片描述

服务端使用

package com.tuling;

import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.THsHaServer;
import org.apache.thrift.server.TNonblockingServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TNonblockingServerSocket;
import org.apache.thrift.transport.layered.TFramedTransport;

import com.tuling.service.UserServiceImpl;

/**
 * @author Fox
 */
public class HsHaServer {

    public static void main(String[] args) {

        try {
            TNonblockingServerSocket serverTransport = new TNonblockingServerSocket(9090);

            //获取processor
            UserService.Processor processor = new UserService.Processor(new UserServiceImpl());
            //指定TCompactProtocol
            TCompactProtocol.Factory protocolFactory = new TCompactProtocol.Factory();
            //指定TFramedTransport
            TFramedTransport.Factory tTransport = new TFramedTransport.Factory();

            THsHaServer.Args targs = new THsHaServer.Args(serverTransport);
            targs.processor(processor);
            targs.protocolFactory(protocolFactory);
            targs.transportFactory(tTransport);

            // NIO 引入线程池处理业务
            TServer server = new THsHaServer(targs);
            System.out.println("Starting HsHa  server...");
            server.serve();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

优缺点
THsHaServer的优点

  • THsHaServer与TNonblockingServer模式相比,THsHaServer在完成数据读取之后,将业务处理过程交由一个线程池来完成,主线程直接返回进行下一次循环操作,效率大大提升。

THsHaServer的缺点

  • 主线程仍然需要完成所有socket的监听接收、数据读取和数据写入操作。当并发请求数较大时,且发送数据量较多时,监听socket上新连接请求不能被及时接受。

TThreadedSelectorServer

TThreadedSelectorServer是对THsHaServer的一种扩充,它将selector中的读写IO事件(read/write)从 主线程中分离出来。同时引入worker工作线程池。
TThreadedSelectorServer模式是目前Thrift提供的最高级的线程服务模型,它内部有如果几个部分构成:

  1. 一个AcceptThread专门用于处理监听socket上的新连接。
  2. 若干个SelectorThread专门用于处理业务socket的网络I/O读写操作,所有网络数据的读写均是由这些线程来完成。
  3. 一个负载均衡器SelectorThreadLoadBalancer对象,主要用于AcceptThread线程接收到一个新 socket连接请求时,决定将这个新连接请求分配给哪个SelectorThread线程。
  4. 一个ExecutorService类型的工作线程池,在SelectorThread线程中,监听到有业务socket中有调 用请求过来,则将请求数据读取之后,交给ExecutorService线程池中的线程完成此次调用的具体 执行。主要用于处理每个rpc请求的handler回调处理。
    工作流程
    在这里插入图片描述

服务端使用

package com.tuling;

import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.THsHaServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadedSelectorServer;
import org.apache.thrift.transport.TNonblockingServerSocket;
import org.apache.thrift.transport.layered.TFramedTransport;

import com.tuling.service.UserServiceImpl;

/**
 * @author Fox
 */
public class ThreadedSelectorServer {

    public static void main(String[] args) {

        try {
            TNonblockingServerSocket serverTransport = new TNonblockingServerSocket(9090);

            //获取processor
            UserService.Processor processor = new UserService.Processor(new UserServiceImpl());
            //指定TCompactProtocol
            TCompactProtocol.Factory protocolFactory = new TCompactProtocol.Factory();
            //指定TFramedTransport
            TFramedTransport.Factory tTransport = new TFramedTransport.Factory();

            TThreadedSelectorServer.Args targs = new TThreadedSelectorServer.Args(serverTransport);
            targs.processor(processor);
            targs.protocolFactory(protocolFactory);
            targs.transportFactory(tTransport);

            // NIO   主从reactor模型
            TServer server = new TThreadedSelectorServer(targs);
            System.out.println("Starting ThreadedSelector  server...");
            server.serve();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

客户端使用

package com.tuling;

import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.layered.TFramedTransport;

/**
 * @author Fox
 */
public class ThreadedSelectorClient {
    public static void main(String[] args) {

        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                handle();
            }).start();
        }

    }

    public static void handle(){
        TTransport transport = null;
        try {
            // 设置传输通道,对于NIO需要使用TFramedTransport(用于将数据分块发送)
            transport = new TFramedTransport(new TSocket("localhost", 9090));
            //协议和服务端一致
            TProtocol protocol = new TCompactProtocol(transport);
            UserService.Client client = new UserService.Client(protocol);
            transport.open();

            //RPC调用
            User result = client.getById(1);
            System.out.println("Result =: " + result);

            transport.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭传输通道
            transport.close();
        }

    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值