項(xiàng)目需要使用跨語言RPC服務(wù)辖所,RPC這里就不多講了,跨語言的框架目前比較有名的有Thrift磨德、gRPC缘回、Hessian這幾個。
Thrift原來是Facebook為大規(guī)模跨語言RPC服務(wù)開發(fā)的項(xiàng)目酥宴,后臺捐給Apache了揩环,官網(wǎng)鏈接https://thrift.apache.org/;
gRPC是Google開發(fā)的高性能幅虑、通用的開源RPC框架丰滑,官網(wǎng)鏈接https://www.grpc.io/
Hessian是一個輕量級的使用二進(jìn)制序列化的的RPC框架,基于HTTP倒庵,所以不存在什么語言問題褒墨,官網(wǎng)鏈接http://hessian.caucho.com
對于這三個框架的性能比較網(wǎng)上很多文章分析,總的來說還是Thrift性能占優(yōu)勢一點(diǎn)擎宝,但是Thrift官方維護(hù)并不太好郁妈,最新版本還是2018年12月發(fā)布的,而且github上連issue都沒開绍申,Hessian也是更新慢噩咪,而gRPC更新活躍,用的人也比較多极阅。至于使用哪個看個人吧胃碾。
說明一點(diǎn):Thrift有BIO(阻塞IO)方式和NIO(非阻塞IO)方式,下面先講BIO方式筋搏,然后再說NIO的仆百。
下面進(jìn)入主題:
一、下載Thrift接口生成工具
Windows版本下載地址:https://mirrors.tuna.tsinghua.edu.cn/apache/thrift/0.11.0/ (清華大學(xué)開源軟件鏡像站)
其他版本源碼下載地址: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奔脐,直接拿來用俄周,下載后可以把路徑加入到系統(tǒng)Path里面這樣在cmd里面就可以直接使用thrift命令來操作了。其他系統(tǒng)的安裝方法請參考文檔髓迎。
因?yàn)樽钚?.12版本有一個錯誤峦朗,在使用BIO多線程的情況下會一直打印一個異常,雖然并不影響服務(wù)排龄,但是每次都會打印一個Error日志很不好波势,錯誤情況這篇文章
后面我也會說NIO方式,是可以直接用0.12版本沒問題
二涣雕、創(chuàng)建TestService.thrift文件
thrift文件規(guī)范說明請看這篇文章艰亮。
創(chuàng)建一個很簡單的示例文件
namespace java com.example.thrift.service
service TestService {
string sayHello(1:string message);
}
三闭翩、生成Java代碼
在thrift文件目錄下使用命令
thrift --gen java TestService.thrift
執(zhí)行完之后挣郭,會在TestService.thrift目錄下生成一個gen-java目錄,里面有個生成好的TestService.java文件疗韵,這個就是用來實(shí)現(xiàn)遠(yuǎn)程調(diào)用的接口兑障,接下來把生成的Java文件復(fù)制到項(xiàng)目源碼里面。這里執(zhí)行命令可以直接在IDEA下面工具欄里的Terminal選項(xiàng)卡里面執(zhí)行,首先要進(jìn)入到TestService.thrift的目錄下面流译,然后直接執(zhí)行上面的命令就可以了逞怨,不用去切換CMD了。
四福澡、SpringBoot中引入Thrift
maven artifact:
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.11.0</version>
</dependency>
這里也要注意引入的版本跟上面生成代碼的工具版本要一致叠赦,這里也使用0.11的版本。然后看一下整個項(xiàng)目的結(jié)構(gòu):
加下來看幾個文件的代碼革砸。
五除秀、實(shí)現(xiàn)服務(wù)接口
上面生成的TestService源碼復(fù)制到service包下,然后在這里新建接口實(shí)現(xiàn)類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!";
}
}
六算利、編寫服務(wù)端
在server包下面新建ThriftServer服務(wù)端代碼:
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服務(wù)端開啟");
TServerSocket serverSocket = new TServerSocket(1998); //服務(wù)端口1998
TProcessor tProcessor = new TestService.Processor<TestService.Iface>(new TestServiceImpl());
//BIO單線程版
//TSimpleServer.Args sArgs = new TSimpleServer.Args(serverSocket);
//sArgs.processor(tProcessor); //注冊服務(wù)
//TServer server = new TSimpleServer(sArgs);
//BIO多線程版
TThreadPoolServer.Args tArgs = new TThreadPoolServer.Args(serverSocket);
tArgs.processor(tProcessor); //注冊服務(wù)
tArgs.minWorkerThreads(2); //設(shè)置線程池核心線程數(shù)量
tArgs.maxWorkerThreads(10); //設(shè)置線程池最大線程數(shù)量
TServer server = new TThreadPoolServer(tArgs);
server.serve();
} catch (Exception e) {
log.error("Thrift服務(wù)發(fā)生錯誤:", e);
}
}
}
注意Thrift提供了兩種線程版本册踩,一個是單線程的一個是多線程的。另外一點(diǎn)要注意的是服務(wù)端server是會阻塞主線程的效拭,所以在server開啟之后不要有任務(wù)其他操作暂吉,否則是運(yùn)行不到的。在SpringBoot初始化的過程中也不要啟動服務(wù)缎患,因?yàn)镾pringBoot默認(rèn)是以類名稱排序去掃描包下面的配置或者組件等等慕的,如果Thrift服務(wù)類名稱在其他配置前面,剛好你在初始化Thrift的時候順便也啟動了服務(wù)的話挤渔,那么其他的Spring配置就會不能初始化了业稼,所以可以在SpringBoot完全初始化之后再啟動Thrift,或者干脆起一個線程跑Thrift服務(wù)蚂蕴。
這里的單線程是共用主線程就是main線程低散,多線程則是另外開啟一個ThreadPoolExecutor線程池來處理socket的。這個看TThreadPoolServer源代碼就知道了:
七骡楼、編寫客戶端
在client包下面創(chuàng)建ThriftClient熔号,為了簡單,這里只用一個簡單Java類來當(dāng)做客戶端鸟整,代碼很少:
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 參數(shù)分別是:服務(wù)器地址引镊,端口,連接超時時間(毫秒)
transport = new TSocket("localhost", 1998, 30000);
TProtocol protocol = new TBinaryProtocol(transport);
TestService.Client client = new TestService.Client(protocol);
transport.open();
//這里得到返回數(shù)據(jù)是同步的
String reply = client.sayHello("Hello, I'm Client");
log.info(">>>收到回復(fù)消息:{}", reply);
//如果需要異步篮条,不要返回數(shù)據(jù)可以使用send_***方法
//client.send_sayHello("Hello, I'm Client ~ ");
//注意recv_***方法是同步的弟头,會阻塞到收完數(shù)據(jù)
//String reply = client.recv_sayHello();
//log.info(">>>收到回復(fù)消息:{}", reply);
} catch (Exception e) {
log.error("Thrift客戶端發(fā)生錯誤", e);
} finally {
if (transport != null) {
transport.close();
}
}
}
}
好了,BIO部分就到這里涉茧。下面來說說NIO部分赴恨,其實(shí)也差不多,只是換了個類“樗ǎ現(xiàn)在可以把Thrift的版本改到最新版本0.12.0伦连,接口生產(chǎn)工具Windows下載地址只要把0.11.0的地址改成0.12.0就可以了https://mirrors.tuna.tsinghua.edu.cn/apache/thrift/0.12.0/雨饺,對應(yīng)的maven資源版本也需要改成0.12.0,因?yàn)?.12版工具生成的代碼跟0.11版的有點(diǎn)不一樣惑淳,不過0.11版改一下也是可以用额港,但是最好還是跟著版本匹配來。下面的NIO的都是基于0.12版的
八歧焦、創(chuàng)建NIO服務(wù)端
在server包下創(chuàng)建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 服務(wù)端開啟");
TProcessor tprocessor = new TestService.Processor<TestService.Iface>(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服務(wù)端發(fā)生錯誤:", e);
}
}
}
這里也有單線程和多線程版本移斩。要注意的一點(diǎn)是NIO版本的transport默認(rèn)必須用TFramedTransport,所以Client里面也要用同樣的transport绢馍,否則服務(wù)端直接打印一個錯誤信息叹哭,不會拋異常,只是提醒客戶端使用的transport不正確:
ERROR 14012 --- [ Thread-5] .s.AbstractNonblockingServer$FrameBuffer : Read an invalid frame size of -2147418111. Are you using TFramedTransport on the client side?
客戶端則會直接拋異常痕貌,因?yàn)榉?wù)端根本就沒處理請求风罩。
九、創(chuàng)建NIO客戶端
在client包下創(chuàng)建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 參數(shù)分別是:服務(wù)器地址舵稠,端口超升,連接超時時間(毫秒),與BIO比較哺徊,只有這里不同室琢,
//就是在創(chuàng)建transport 的時候,包了一層TFramedTransport落追,和服務(wù)端匹配起來
transport = new TFramedTransport(new TSocket("localhost", 1998, 30000));
TProtocol protocol = new TBinaryProtocol(transport);
TestService.Client client = new TestService.Client(protocol);
transport.open();
//這里得到返回數(shù)據(jù)是同步的
String reply = client.sayHello("Hello, I'm NIO Client");
log.info(">>>收到回復(fù)消息:{}", reply);
//如果需要異步盈滴,不要返回數(shù)據(jù)可以使用send_***方法
//client.send_sayHello("Hello, I'm Client ~ ");
//注意recv_***方法是同步的,會阻塞到收完數(shù)據(jù)
//String replyAsync = client.recv_sayHello();
} catch (Exception e) {
log.error("Thrift客戶端發(fā)生錯誤", e);
} finally {
//NIO版本這一定要注意關(guān)閉transport
if (transport != null) {
transport.close();
}
}
}
}
在這里使用0.12的版本一切都很正常轿钠,如果換成是BIO多線程方式巢钓,這個版本會一直打印一個錯誤。
十疗垛、總結(jié)
總的來說Thrift的使用還是簡單的症汹,接口代碼可以生成不同語言平臺的代碼,這樣一個服務(wù)端可以對應(yīng)各種不同語言的客戶端贷腕,對于小型項(xiàng)目RPC來說背镇,確實(shí)是很方便的事情,至于服務(wù)治理泽裳、分布式這里就不討論了瞒斩。
在生產(chǎn)環(huán)境下服務(wù)端最好還是采用NIO方式。
====> 本文源碼
好了涮总,到此本文結(jié)束胸囱,感謝你的閱讀~~