1、thrift的基本介绍

1.1 thrift的定义

Thrift是一个轻量级、跨语言的RPC框架,主要用于各个服务之间的RPC通信,最初由Facebook于2007年开发,2008年进入Apache开源项目。它通过自身的IDL中间语言, 并借助代码生成引擎生成各种主流语言的RPC服务端/客户端模板代码。Thrift支持多种不同的编程语言,包括C++, Java, Python,PHP,Ruby, Erlang, Haskell, C#, Cocoa, Javascript, Node.js, Smalltalk, OCaml, Golang等,本系列主要讲述基于Java语言的Thrift的配置方式和具体使用。

1.2 thrift架构

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

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

1.3 thrift的基本特性

1.3.1 开发速度快
通过编写RPC接口Thrift IDL文件,利用编译生成器自动生成服务端骨架(Skeletons)和客户端桩(Stubs)。从而省去开发者自定义和维护接口编解码、消息传输、服务器多线程模型等基础工作。服务端:只需要按照服务骨架即接口,编写好具体的业务处理程序(Handler)即实现类即可。客户端:只需要拷贝IDL定义好的客户端桩和服务对象,然后就像调用本地对象的方法一样调用远端服务。
1.3.2 接口维护简单
通过维护Thrift格式的IDL(接口描述语言)文件(注意写好注释),即可作为给Client使用的接口文档使用,也自动生成接口代码,始终保持代码和文档的一致性。且Thrift协议可灵活支持接口的可扩展性。
1.3.3 学习成本低
因为其来自Google Protobuf开发团队,所以其IDL文件风格类似Google Protobuf,且更加易读易懂;特别是RPC服务接口的风格就像写一个面向对象的Class一样简单。初学者只需参照:http://thrift.apache.org/,一个多小时就可以理解Thrift IDL文件的语法使用。
1.3.4 多语言/跨语言支持
Thrift支持C++、 Java、Python、PHP、Ruby、Erlang、Perl、Haskell、C#、Cocoa、JavaScript、Node.js、Smalltalk等多种语言,即可生成上述语言的服务器端和客户端程序。
1.3.5 稳定/广泛使用
Thrift在很多开源项目中已经被验证是稳定和高效的,例如Cassandra、Hadoop、HBase等;国外在Facebook中有广泛使用,国内包括百度、美团小米、和饿了么等公司。

2、thrift的基本语法

2.1 thrift的IDL介绍

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

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

2.2 IDL的基本语法——基础类型

在这里插入图片描述

2.3 特殊类型、集合容器

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

目前有三种容器类型:

图片.png

在使用容器类型时必须指定泛型,否则无法编译idl文件。其次,泛型中的基本类型,JAVA语言中会被替换为对应的包装类型。

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

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

2.5 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引入

2.6 枚举、异常

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

    enum HttpStatus {
        OK = 200,
        NOTFOUND=404
    }

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

exception MyException {
    1: i32 errorCode
    2: string message
}

service ExampleService {
    string GetName() throws (1: MyException e)
}

2.7 Service (服务定义类型)

service UserService {
User getById(1:i32 id)
bool isExist(1:string name)
}

编译后的内容

图片.png

2.8 Namespace (名字空间)

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

定义namespace

namespace java com.yogurt.test

转化后

package com.yogurt.test

3、thrift的快速使用

3.1 环境待建

thrift编译器的安装
参考文档:https://thrift.apache.org/docs/install/

windows 安装
下载地址:https://thrift.apache.org/download

centos 安装
参考文档:https://thrift.apache.org/docs/install/centos.html

本人是Windows系统,所以这里搭建Windows环境

3.1.1 下载windows的文件,重命名文件为thrift.exe

图片.png

3.1.2 环境变量配置

图片.png

配置PATH路径,我的路径为:E:\installation\thrift

图片.png

3.1.3 测试

图片.png

安装成功!

3.2 快速入门尝试下

3.2.1 编写Thrift文件
namespace java com.yogurt
    struct User{
        1:i32 id
        2:string name
        3:i32 age=0
    }
    
service UserService {
    User getById(1:i32 id)
    bool isExist(1:string name)
}

Thrift的相关命令:

# 生成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

进行编译:

图片.png

默认会放在当前路径下,我当前的路径是/User/asus

图片.png

3.2.2 编写代码

客户端代码

package com.yogurt;

import com.yogurt.Service.User;
import com.yogurt.Service.UserService;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.transport.TSocket;

/**
 * @author yogurt
 * @Date 2023/2/26 - 18:36 - 2023
 */
public class client {

    public static void main(String[] args) {

        try{
            TSocket socket = new TSocket("localhost", 9000);
            // 指定二进制编码
            TBinaryProtocol protocol = new TBinaryProtocol(socket);
            UserService.Client client = new UserService.Client(protocol);
            socket.open();

            // RPC 调用
            User byId = client.getById(9000);
            System.out.println("byId = " + byId);
        }catch (Exception e){
            System.out.println(e);
        }
    }
}

服务端代码

package com.yogurt;

import com.yogurt.Service.Impl.UserServiceImpl;
import com.yogurt.Service.UserService;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;

/**
 * @author yogurt
 * @Date 2023/2/26 - 18:27 - 2023
 */
public class SimpleService {

    public static void main(String[] args) {
        try{
            TServerSocket socket = new TServerSocket(9000);
            // 获取process
            UserService.Processor processor = new UserService.Processor(new UserServiceImpl());
            // 指定TBinaryProtocol
            TBinaryProtocol.Factory factory = new TBinaryProtocol.Factory();

            TServer.Args args1 = new TSimpleServer.Args(socket);
            args1.processor(processor);
            args1.protocolFactory(factory);

            TSimpleServer tSimpleServer = new TSimpleServer(args1);

            tSimpleServer.serve();
        }catch (Exception e){
            System.out.println(e);
        }
    }
}

结果

图片.png

图片.png

这是在单线程场景下的,后续将详细介绍多线程下的RPC调用

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐