从Demo理解Thrift & Thrift和Dubbo的区别

发布时间:2024年01月14日


字节里的RPC框架都是用的Thrift,我猜这主要原因有2:

  1. Thrift是Facebook开源的项目,平台中立
  2. Thrift支持跨语言调用,这非常适合字节Java、Go语言都存在的环境,语言中立

但是我对它的使用还不是很了解,因此找了一篇文章想上手做一些demo,参考Thrift 初认识 & Thrift 和Dubbo 的区别_dubbo和thrift的区别-CSDN博客

安装

brew install thrift 在 mac上就可以安装,很无敌,但是公司里好像不是这么干的

demo尝试

  1. 创建一个服务接口Hello,创建文件Hello.thrift,符合thrift的IDL规范

thrift通过一个中间语言IDL(接口定义语言)来定义RPC的数据类型和接口,这些内容写在以.thrift结尾的文件中,然后通过特殊的编译器来生成不同语言的代码,以满足不同需要的开发者。比如java开发者,就可以生成java代码,c++开发者可以生成c++代码,生成的代码中不但包含目标语言的接口定义、方法、数据类型,还包含有RPC协议层和传输层的实现代码。

比如代码如下:

namespace java service.demo
service Hello{
    string helloString(1:string para)
}
  1. 终端进入Hello.thrift所在目录,执行命令
 thrift -r -gen java Hello.thrift

发现在当前目录下多了一个gen-java的目录,里面的有一个Hello.java的文件。这个java文件包含Hello服务的接口定义Hello.Iface,以及服务调用的底层通信细节,包括客户端的调用逻辑Hello.Client以及服务端的处理逻辑Hello.Processor

  1. 创建一个Maven管理的Java项目,pom.xml中添加相关的依赖,并将Hello.java文件复制到项目中
<dependency>
    <groupId>org.apache.thrift</groupId>
    <artifactId>libthrift</artifactId>
    <version>0.19.0</version>
</dependency>
  1. 创建HelloServiceImpl首先Hello.Iface接口
package com.jxz.thriftdemo;

import com.jxz.thriftdemo.idl.Hello;
import org.apache.thrift.TException;

/**
 * @Author jiangxuzhao
 * @Description
 * @Date 2024/1/14
 */
public class HelloServiceImpl implements Hello.Iface{
    @Override
    public String helloString(String para) throws TException {
        return "result = " + para;
    }
}

这里在编译的时候可能会找不到@javax.annotation.Generated,这是因为本地jdk版本较高导致的,可以直接删掉这个注解,它仅代表这个文件是自动生成的

  1. 创建服务端实现代码HelloServiceServer,把HelloServiceImpl作为一个具体的处理器传递给Thrift服务器
package com.jxz.thriftdemo.server;

import com.jxz.thriftdemo.idl.Hello;
import com.jxz.thriftdemo.impl.HelloServiceImpl;
import org.apache.thrift.TProcessor;
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.TTransportException;

/**
 * @Author jiangxuzhao
 * @Description
 * @Date 2024/1/14
 */
public class HelloServiceServer {
    public static void main(String[] args) {
        try {
            System.out.println("服务端开启...");
            // 1. 创建TProcessor,传入处理器
            TProcessor tprocessor = new Hello.Processor<>(new HelloServiceImpl());
            // 2. 创建Tprotocol,这里采用二进制格式
            TBinaryProtocol.Factory factory = new TBinaryProtocol.Factory();
            // 3. 创建TserverTransport,监听端口
            TServerSocket serverTransport = new TServerSocket(8083);

            TServer.Args tArgs = new TServer.Args(serverTransport);
            tArgs.processor(tprocessor);
            tArgs.protocolFactory(factory);
            // 4. 创建Tserver,传入需要的参数,server将以上内容集成在一起
            TSimpleServer server = new TSimpleServer(tArgs);
            // 5. 启动server
            server.serve();
        } catch (TTransportException e) {
            throw new RuntimeException(e);
        }
    }
}
  1. 创建客户端实现代码HelloServiceClient,调用Hello.client访问服务端的逻辑实现
package com.jxz.thriftdemo.client;

import com.jxz.thriftdemo.idl.Hello;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;

/**
 * @Author jiangxuzhao
 * @Description
 * @Date 2024/1/14
 */
public class HelloServiceClient {
    public static void main(String[] args) {
        System.out.println("客户端启动...");
        TTransport tTransport = null;
        try {
            // 1. 创建TTransport
            tTransport = new TSocket("127.0.0.1", 8083, 5000);
            // 2. 创建Tprotocol,这里采用二进制格式
            TBinaryProtocol tProtocol = new TBinaryProtocol(tTransport);
            // 3. 创建TProcessor,由于这里需要用到具体的方法,因此不能用泛型
            Hello.Client client = new Hello.Client(tProtocol);
            // 4. 打开传输层
            tTransport.open();
            // 5. 启动client
            String result = null;
            result = client.helloString("hello world");
            System.out.println(result);
        } catch (TTransportException e) {
            throw new RuntimeException(e);
        } catch (TException e) {
            throw new RuntimeException(e);
        } finally {
            // 6. 最后关闭传输流
            if (null != tTransport) {
                tTransport.close();
            }
        }
    }
}

先启动服务端,然后启动客户端,最后客户端输出如下:

客户端启动...
result = hello world

可以明显地看出来,客户端RPC调用了服务端的方法,服务器里面的具体处理器为HelloServiceImpl对象

Thrift协议栈

在这里插入图片描述

Thrift是一种c/s的架构体系。Server主要任务是高效的接受客户端请求,并将请求转发给Processor处理。

  • 最上层是用户自行实现的业务逻辑代码;

  • Processor是由thrift编译器自动生成的代码,它封装了从输入数据流中读数据和向数据流中写数据的操作,它的主要工作是:从连接中读取数据,把处理交给用户实现impl,最后把结果写到连接上。

  • TProtocol(协议层),用于数据类型解析的,将结构化数据转化为字节流给TTransport进行传输,例如:

    • TBinaryProtocol:二进制格式;
    • TCompactProtocol:压缩格式;
    • TJSONProtocol:JSON格式;
    • TSimpleJSONProtocol:提供JSON只写协议, 生成的文件很容易通过脚本语言解析;
    • TDebugProtocol:使用易懂的可读的文本格式,以便于debug
  • TTransport(传输层),定义数据传输方式,可以为TCP/IP传输,内存共享或者文件共享等)被用作运行时库

    • TSocket:阻塞式socker;
    • TFramedTransport:以frame为单位进行传输,非阻塞式服务中使用;
    • TFileTransport:以文件形式进行传输;
    • TMemoryTransport:将内存用于I/O,java实现时内部实际使用了简单的ByteArrayOutputStream;
    • TZlibTransport:使用zlib进行压缩, 与其他传输方式联合使用,当前无java实现
  • 底层IO负责实际的数据传输,包括socket、文件和压缩数据流等。

  • Thrift支持的服务模型

    • TSimpleServer:简单的单线程服务模型,常用于测试;
    • TThreadPoolServer:多线程服务模型,使用标准的阻塞式IO;
    • TNonblockingServer:多线程服务模型,使用非阻塞式IO(需使用TFramedTransport数据传输方式);

Thrift 与 Dubbo 的区别

  1. 透明化服务调用

Thrift:通过IDL(接口定义语言)实现,Thrift是支持跨语言的,而要实现跨语言机制,就需要一种中间语言来完成,那就是IDL。首先定义好了IDL之后(即定义好了一个service),由于server和Client都需要持有这个与之语言相对应的服务接口,那就需要thrift来将IDL编译成与之语言相对应的接口类,比如server端是java,client端是C#,则server端需要编译成java类,client编译成C#类。然后server端负责接口的具体实现,client只需要持有这个对象接口,进行调用即可,然后通过网络通信传给server。server负责解析client传递过来的数据,由于服务对象接口统一为IDL(thrift格式),所以统一了解析形式,只和本地的开发语言有关。

Dubbo:通过java反射和动态代理实现这一功能,由于只支持Java,所以Client和Server端开发语言机制一样,所以它们能够通过spring框架进行依赖,server端一般将对象方法接口注册发布到Zookeper中,然后Client可以直接从Zookeper中获取server端发布的对象方法接口,这样Client可以通过Spring进行自动装配获得server端发布的对象方法服务接口。

  1. 网络通信

thrift 参考上文

Thrift实际上是实现了C/S模式,通过代码生成工具将thrift文生成服务器端和客户端代码(可以为不同语言),从而实现服务端和客户端跨语言的支持。用户在Thirft文件中声明自己的服务,这些服务经过编译后会生成相应语言的代码文件,然后客户端调用服务,服务器端提服务便可以了。

dubbo网络通讯层主要实现了以下功能:

  • 多种网络通讯框架的抽象封装(netty,mina,grizzly)
  • 每个客户端主机和服务端保存单个长链接通信
  • 异步调用转同步
  • tcp链接的心跳和自动重连
  • 基于header头通讯协议,请求的编解码器

dubbo的网络通信基于NIO框架,一般基于事件的NIO网络框架都涉及到 channel , channelHandle核心概念,网络数据buffer, 网络数据编解码器,dubbo为了能够适配多种NIO框架,将以上概念全部又抽象了一层接口。如果有netty开发经验或者了解netty helloworld demo程序对于理解这个章节非常有帮助。

参考链接:

https://www.jianshu.com/p/1e0c8c08e89d

https://www.jianshu.com/p/1a1404ce2201

https://blog.csdn.net/qq418517226/article/details/51906357

  1. 序列化

thrift只支持对thrift协议描述的IDL进行序列化,包含如下几种序列化格式:

  • TBinaryProtocol:二进制格式;
  • TCompactProtocol:压缩格式;
  • TJSONProtocol:JSON格式;
  • TSimpleJSONProtocol:提供JSON只写协议, 生成的文件很容易通过脚本语言解析;
  • TDebugProtocol:使用易懂的可读的文本格式,以便于debug

dubbo支持各种协议的序列化,例如Hession,Jdk(实际上,dubbo没有IDL这一机制,因为实际上他就是通过java服务对象接口进行交互的)
例如他也可支持thrift协议,只需要thrift将IDL转换为一个java服务对象接口,那么dubbo就可以使用了

文章来源:https://blog.csdn.net/qq_44036439/article/details/135585283
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。