项目需要使用跨语言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了。
四、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源代码就知道了:
七、编写客户端
在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方式。
====> 本文源码
好了,到此本文结束,感谢你的阅读~~