SpringBoot集成Thrift实现RPC服务基础示例-Java服务端和客户端

项目需要使用跨语言RPC服务,RPC这里就不多讲了,跨语言的框架目前比较有名的有Thrift、gRPC、Hessian这几个。

  • Thrift原来是Facebook为大规模跨语言RPC服务开发的项目,后台捐给Apache了,官网链接https://thrift.apache.org/;

  • gRPC是Google开发的高性能、通用的开源RPC框架,官网链接https://www.grpc.io/

  • Hessian是一个轻量级的使用二进制序列化的的RPC框架,基于HTTP,所以不存在什么语言问题,官网链接http://hessian.caucho.com

对于这三个框架的性能比较网上很多文章分析,总的来说还是Thrift性能占优势一点,但是Thrift官方维护并不太好,最新版本还是2018年12月发布的,而且github上连issue都没开,Hessian也是更新慢,而gRPC更新活跃,用的人也比较多。至于使用哪个看个人吧。

说明一点:Thrift有BIO(阻塞IO)方式和NIO(非阻塞IO)方式,下面先讲BIO方式,然后再说NIO的。

下面进入主题:

一、下载Thrift接口生成工具

Windows版本下载地址:https://mirrors.tuna.tsinghua.edu.cn/apache/thrift/0.11.0/ (清华大学开源软件镜像站)

其他版本源码下载地址:https://thrift.apache.org/download#maven-artifact 或者 https://github.com/apache/thrift
安装文档:https://thrift.apache.org/docs/install/ 或者 https://github.com/apache/thrift/tree/master/doc/install

我这里用的的是Windows版本的exe,直接拿来用,下载后可以把路径加入到系统Path里面这样在cmd里面就可以直接使用thrift命令来操作了。其他系统的安装方法请参考文档。


因为最新0.12版本有一个错误,在使用BIO多线程的情况下会一直打印一个异常,虽然并不影响服务,但是每次都会打印一个Error日志很不好,错误情况这篇文章
后面我也会说NIO方式,是可以直接用0.12版本没问题

二、创建TestService.thrift文件

thrift文件规范说明请看这篇文章。

创建一个很简单的示例文件

namespace java com.example.thrift.service

service TestService {
    string sayHello(1:string message);
}

三、生成Java代码

在thrift文件目录下使用命令

thrift --gen java TestService.thrift

执行完之后,会在TestService.thrift目录下生成一个gen-java目录,里面有个生成好的TestService.java文件,这个就是用来实现远程调用的接口,接下来把生成的Java文件复制到项目源码里面。这里执行命令可以直接在IDEA下面工具栏里的Terminal选项卡里面执行,首先要进入到TestService.thrift的目录下面,然后直接执行上面的命令就可以了,不用去切换CMD了。


TestService.thrift和生成的Java文件

四、SpringBoot中引入Thrift

maven artifact:


    org.apache.thrift
    libthrift
    0.11.0

这里也要注意引入的版本跟上面生成代码的工具版本要一致,这里也使用0.11的版本。然后看一下整个项目的结构:


示例代码结构

加下来看几个文件的代码。

五、实现服务接口

上面生成的TestService源码复制到service包下,然后在这里新建接口实现类TestServiceImpl:

package com.example.thrift.service;

import lombok.extern.slf4j.Slf4j;
import org.apache.thrift.TException;

@Slf4j
public class TestServiceImpl implements TestService.Iface {
    @Override
    public String sayHello(String message) throws TException {
        log.info("<<<收到消息:{}", message);
        return "Hi, I'm Server!";
    }
}

六、编写服务端

在server包下面新建ThriftServer服务端代码:

package com.example.thrift.server;

import com.example.thrift.service.TestService;
import com.example.thrift.service.TestServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.thrift.TProcessor;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;

@Slf4j
public class ThriftServer {

    public void start() {
        try {
            log.info(">>>Thrift服务端开启");
            TServerSocket serverSocket = new TServerSocket(1998); //服务端口1998
            TProcessor tProcessor = new TestService.Processor(new TestServiceImpl());

            //BIO单线程版
            //TSimpleServer.Args sArgs = new  TSimpleServer.Args(serverSocket);
            //sArgs.processor(tProcessor);  //注册服务
            //TServer server = new TSimpleServer(sArgs);

            //BIO多线程版
            TThreadPoolServer.Args tArgs = new TThreadPoolServer.Args(serverSocket);
            tArgs.processor(tProcessor); //注册服务
            tArgs.minWorkerThreads(2); //设置线程池核心线程数量
            tArgs.maxWorkerThreads(10); //设置线程池最大线程数量
            TServer server = new TThreadPoolServer(tArgs);

            server.serve();
        } catch (Exception e) {
            log.error("Thrift服务发生错误:", e);
        }
    }
}

注意Thrift提供了两种线程版本,一个是单线程的一个是多线程的。另外一点要注意的是服务端server是会阻塞主线程的,所以在server开启之后不要有任务其他操作,否则是运行不到的。在SpringBoot初始化的过程中也不要启动服务,因为SpringBoot默认是以类名称排序去扫描包下面的配置或者组件等等,如果Thrift服务类名称在其他配置前面,刚好你在初始化Thrift的时候顺便也启动了服务的话,那么其他的Spring配置就会不能初始化了,所以可以在SpringBoot完全初始化之后再启动Thrift,或者干脆起一个线程跑Thrift服务。
这里的单线程是共用主线程就是main线程,多线程则是另外开启一个ThreadPoolExecutor线程池来处理socket的。这个看TThreadPoolServer源代码就知道了:


TThreadPoolServer初始化源码片段

七、编写客户端

在client包下面创建ThriftClient,为了简单,这里只用一个简单Java类来当做客户端,代码很少:

package com.example.thrift.client;

import com.example.thrift.service.TestService;
import lombok.extern.slf4j.Slf4j;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;

@Slf4j
public class ThriftClient {
    public static void main(String[] args) {
        TTransport transport = null;
        try {
            //TSocket 参数分别是:服务器地址,端口,连接超时时间(毫秒)
            transport = new TSocket("localhost", 1998, 30000);
            TProtocol protocol = new TBinaryProtocol(transport);
            TestService.Client client = new TestService.Client(protocol);
            transport.open();

           //这里得到返回数据是同步的
            String reply = client.sayHello("Hello, I'm Client");
            log.info(">>>收到回复消息:{}", reply);

            //如果需要异步,不要返回数据可以使用send_***方法
            //client.send_sayHello("Hello, I'm Client ~ ");
            //注意recv_***方法是同步的,会阻塞到收完数据
            //String reply = client.recv_sayHello();
            //log.info(">>>收到回复消息:{}", reply);

        } catch (Exception e) {
            log.error("Thrift客户端发生错误", e);
        } finally {
            if (transport != null) {
                transport.close();
            }
        }
    }
}

好了,BIO部分就到这里。下面来说说NIO部分,其实也差不多,只是换了个类。现在可以把Thrift的版本改到最新版本0.12.0,接口生产工具Windows下载地址只要把0.11.0的地址改成0.12.0就可以了https://mirrors.tuna.tsinghua.edu.cn/apache/thrift/0.12.0/,对应的maven资源版本也需要改成0.12.0,因为0.12版工具生成的代码跟0.11版的有点不一样,不过0.11版改一下也是可以用,但是最好还是跟着版本匹配来。下面的NIO的都是基于0.12版的

八、创建NIO服务端

在server包下创建ThriftNIOServer:

package com.example.thrift.server;

import com.example.thrift.service.TestService;
import com.example.thrift.service.TestServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.thrift.TProcessor;
import org.apache.thrift.server.THsHaServer;
import org.apache.thrift.server.TNonblockingServer;
import org.apache.thrift.transport.TNonblockingServerSocket;

@Slf4j
public class ThriftNIOServer {
    public void start() {
        try {
            log.info(">>>Thrift NIO 服务端开启");
            TProcessor tprocessor = new TestService.Processor(new TestServiceImpl());
            TNonblockingServerSocket serverSocket = new TNonblockingServerSocket(1998);
            //NIO单线程版
            //TNonblockingServer.Args args = new TNonblockingServer.Args(serverSocket);
            //args.processor(tprocessor);
            //TNonblockingServer server = new TNonblockingServer(args);

            //NIO多线程版
            THsHaServer.Args args = new THsHaServer.Args(serverSocket);
            args.processor(tprocessor);
            args.minWorkerThreads(2);
            args.maxWorkerThreads(10);
            THsHaServer server = new THsHaServer(args);
            server.serve();
        } catch (Exception e) {
            log.error("Thrift服务端发生错误:", e);
        }
    }
}

这里也有单线程和多线程版本。要注意的一点是NIO版本的transport默认必须用TFramedTransport,所以Client里面也要用同样的transport,否则服务端直接打印一个错误信息,不会抛异常,只是提醒客户端使用的transport不正确:

ERROR 14012 --- [       Thread-5] .s.AbstractNonblockingServer$FrameBuffer : Read an invalid frame size of -2147418111. Are you using TFramedTransport on the client side?

客户端则会直接抛异常,因为服务端根本就没处理请求。

九、创建NIO客户端

在client包下创建ThriftNIOClient:

package com.example.thrift.client;

import com.example.thrift.service.TestService;
import lombok.extern.slf4j.Slf4j;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;

@Slf4j
public class ThriftNIOClient {
    public static void main(String[] args) {
        TTransport transport = null;
        try {
            //TSocket 参数分别是:服务器地址,端口,连接超时时间(毫秒),与BIO比较,只有这里不同,
            //就是在创建transport 的时候,包了一层TFramedTransport,和服务端匹配起来
            transport = new TFramedTransport(new TSocket("localhost", 1998, 30000));
            TProtocol protocol = new TBinaryProtocol(transport);
            TestService.Client client = new TestService.Client(protocol);
            transport.open();

            //这里得到返回数据是同步的
            String reply = client.sayHello("Hello, I'm NIO Client");
            log.info(">>>收到回复消息:{}", reply);

            //如果需要异步,不要返回数据可以使用send_***方法
            //client.send_sayHello("Hello, I'm Client ~ ");
            //注意recv_***方法是同步的,会阻塞到收完数据
            //String replyAsync = client.recv_sayHello();

        } catch (Exception e) {
            log.error("Thrift客户端发生错误", e);
        } finally {
            //NIO版本这一定要注意关闭transport
            if (transport != null) {
                transport.close();
            }
        }
    }
}

在这里使用0.12的版本一切都很正常,如果换成是BIO多线程方式,这个版本会一直打印一个错误。

十、总结

总的来说Thrift的使用还是简单的,接口代码可以生成不同语言平台的代码,这样一个服务端可以对应各种不同语言的客户端,对于小型项目RPC来说,确实是很方便的事情,至于服务治理、分布式这里就不讨论了。
在生产环境下服务端最好还是采用NIO方式。

====> 本文源码

好了,到此本文结束,感谢你的阅读~~

你可能感兴趣的:(SpringBoot集成Thrift实现RPC服务基础示例-Java服务端和客户端)