[Thrift]学习使用Thrift

Thrift

在接触到Thrift之前我只接触过阿里的Dubbo,但是Dubbo不支持跨语言,所以我在找一门跨语言的RPC框架,最近接触到的有gRPC,Thrift,自己也用Netty实现了一下简单的调用,不过那充其量只是一个玩具。这次打算把之前自己项目里的HTTP调用换掉,换成Thrift。这里来记录一下Thrift的学习。

简介

Thrift是Facebook开发的,现在已经是APACHE的开源项目了,它是一种接口描述语言和二进制通讯协议,用来定义和创建跨语言的服务,也正因为跨语言这个特点,用它来做开发的时候,需要使用它的一个自带工具来生成自己需要的代码。这和以往的非跨语言的RPC框架还是有一点不同的。

Thrift是一套完整的栈,什么是完整的栈了,其实只要用过了就知道,它里面有TProtocol,TTransport这样的类,也就是可以设置传输协议而不需要重新进行编码。除此之外,在服务端,也做了阻塞,非阻塞以及多线程这样的机制。

  • Thrift支持的通讯协议
    • TBinaryProtocol 一种简单的二进制格式,之所以简单,是因为并没有对空间效率进行优化,但比文本处理起来更快,不易调试。
    • TCompactProtocol 顾名思义,是一种带压缩的更紧凑的二进制格式,处理起来更高效。
    • TDebugProtocol 一种人类可以理解的文本协议,可以用来协助调试。
    • TDenseProtocol 和第二个相似,将传输数据的原信息剥离。
    • TJSONProtocol 使用JSON格式。
    • TSimpleJSONProtocol 一种只写协议,不能被Thrift解析,适合用脚本语言来解析。
  • Thrift支持的传输协议
    • TFileTransport 该协议会写文件。
    • TFramedTransport 当使用一个非阻塞服务器的时候,需要使用这个协议,顾名思义按帧来发送数据。
    • TMemoryTransport 使用阻塞的套接字来进行传输。
    • TZlibTransport 用zlib进行压缩,用于连接另一个传输协议。
  • Thrift支持的服务模型
    • TNonblockingServer 一个多线程的服务器,顾名思义是非阻塞的,上面的TFRamedTransport必须和这个模型一块使用。
    • TSimpleServer 一个单线程的服务器,是阻塞的。
    • TThreadPoolServer 阻塞的多线程服务器
    • THsHaServer 把读写任务放到线程池处理,半同步半异步的处理模式,半同步是在处理IO时间上,比如Socket的读写,连接,半异步是用于Handler对RPC的同步处理。

demo

定义接口和实体

需要用Thrift的IDL来定义一个简单的实体,代表一个学生,此次demo主要就是实现学生信息的查和保存,信息都存在HashMap里。

//指明代码生成之后所处的文件路径,相当于java里的package
namespace java cn.izzer.thriftdemo.common

//将shrift的数据类型格式转换为java习惯的格式
typedef i16 short
typedef i32 int
typedef i64 long
typedef string String
typedef bool boolean

//定义对象
struct Student {
    1:optional String name,
    2:optional int age,
    3:optional String address
}

//定义异常
exception DataException {
    //optional 可选 非必传
    1:optional int code,
    2:optional String message,
    3:optional String dateTime
}

//定义后台业务接口
service StudentService {
    //根据名称获取学生信息 返回一个学生对象  抛出DataException异常
    //required 必传项
    Student getStudentByName(1:required String name) throws (1:DataException dataException),

    //保存一个学生信息 无返回 抛出DataException异常
    void save(1:required Student student) throws (1:DataException dataException)
}

用Thrift工具生成代码

(https://mirror.bit.edu.cn/apache/thrift/0.13.0/thrift-0.13.0.exe

使用命令:

thrift-0.13.0.exe --gen java <path>/Student.thrift

在当前路径下就会产生thrift生成的代码。这个包作为公共模块即可。

服务端

实现上面的生成的StudentService.Iface的接口,初始化的方法里放了两个对象来做测试。

@Service
public class StudentServiceImpl implements cn.izzer.thriftdemo.common.StudentService.Iface {

    private HashMap<String,Student> studentMap;

    @PostConstruct
    public void init(){
        studentMap = new HashMap<>();
        Student s1 = new Student();
        s1.setName("thyin");
        s1.setAddress("Shanghai");
        s1.setAge(22);
        Student s2 = new Student();
        s2.setName("abc");
        s2.setAddress("Shanghai");
        s2.setAge(22);
        studentMap.put(s1.getName(),s1);
        studentMap.put(s2.getName(),s2);
    }


    @Override
    public Student getStudentByName(String name) {
        return studentMap.get(name);
    }

    @Override
    public void save(Student student) throws DataException, TException {
        studentMap.put(student.getName(),student);
    }
}

然后就是Thrift的服务编写。

@Component
public class ThriftServer {

    @Value("${thrift.port}")
    private Integer port;

    @Value("${thrift.min-thread-pool}")
    private Integer minThreadPool;

    @Value("${thrift.max-thread-pool}")
    private Integer maxThreadPool;

    @Autowired
    private StudentServiceImpl studentService;

    public void start(){
        try {
            TNonblockingServerSocket serverSocket = new TNonblockingServerSocket(port);
            THsHaServer.Args args = new THsHaServer.Args(serverSocket)
                    .minWorkerThreads(minThreadPool)
                    .maxWorkerThreads(maxThreadPool);
            StudentService.Processor<StudentServiceImpl> processor = new StudentService.Processor<StudentServiceImpl>(studentService);
            //二进制压缩协议
            args.protocolFactory(new TCompactProtocol.Factory());
            args.transportFactory(new TFramedTransport.Factory());
            args.processorFactory(new TProcessorFactory(processor));
            TServer server = new THsHaServer(args);
            System.out.println("Thrift Server Started at port:"+port);
            server.serve();
        }catch (TTransportException ex){
            ex.printStackTrace();
        }
    }
}

端口和IP设置还是不能忘。

thrift:
  port: 7001
  min-thread-pool: 100
  max-thread-pool: 200

在Springboot启动类里启动Thrift服务。

SpringBootApplication
public class ServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServerApplication.class, args);
    }

    @Bean(initMethod = "start")
    public ThriftServer init(){
        ThriftServer server = new ThriftServer();
        return server;
    }
}

客户端

定义StudentService接口,然后实现。

public interface StudentService {
    Student getStudentByName(String name);
    void save(Student student);
}
@Service
public class StudentServiceImpl implements StudentService {

    @Autowired
    private ThriftClient client;

    @Override
    public Student getStudentByName(String name) {
        try {
            client.open();
            Student student = client.getService().getStudentByName(name);
            System.out.println("获取用户成功,用户信息为:"+student);
            return student;
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            client.close();
        }
        return null;
    }

    @Override
    public void save(Student student) {
        try {
            client.open();
            client.getService().save(student);
            System.out.println("客户端保存用户信息成功,信息为:"+student);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            client.close();
        }
    }
}

定义客户端操作的API。

public class ThriftClient {

    private Integer port;

    private String host;

    private TTransport transport;

    private TProtocol protocol;

    private StudentService.Client client;

    private void initClient(){
        transport = new TFastFramedTransport(new TSocket(host,port),1000);
        protocol = new TCompactProtocol(transport);
        client = new StudentService.Client(protocol);
    }

    public void setPort(Integer port) {
        this.port = port;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public StudentService.Client getService(){
        return client;
    }

    public void open() throws TTransportException{
        if(null!=transport&&!transport.isOpen()){
            transport.open();
        }
    }

    public void close(){
        if(null!=transport&&!transport.isOpen()){
            transport.close();
        }
    }
}

然后用Spring来进行管理。

@Configuration
public class ThrifClientConfig {

    @Value("${thrift.port}")
    private Integer port;

    @Value("${thrift.host}")
    private String host;

    @Bean(initMethod = "initClient")
    public ThriftClient init(){
        ThriftClient client = new ThriftClient();
        client.setHost(host);
        client.setPort(port);
        return client;
    }
}

thrift的端口和ip设置,这里我使用HTTP接口去调用客户端,所以多一个HTTP服务的端口

thrift:
  port: 7001
  host: localhost
server:
  port: 8001

最后就是业务类的编写了,也没啥太复杂的。

@Controller
@RequestMapping("/rpc")
public class StudentController {

    @Autowired
    private StudentService studentService;

    @RequestMapping("/get/{name}")
    @ResponseBody
    public Student getStudent(@PathVariable("name")String name){
        return studentService.getStudentByName(name);
    }

    @RequestMapping("/save/{name}/{age}")
    @ResponseBody
    public String saveStudent(@PathVariable("name")String name,@PathVariable("age")Integer age){
        Student student = new Student();
        student.setName(name);
        student.setAge(age);
        studentService.save(student);
        return "Success";
    }

}

测试

服务端客户端都启动之后,就可以通过POSTMAN或者浏览器来进行调用了。

先调用获取信息接口。

avatar

可以看到请求正常执行。那再来试试保存。

avatar

再来获取被保存的人的信息。

avatar

原文地址:https://www.cnblogs.com/Yintianhao/p/14260089.html