先祭出官网。
昨天有个之前的同事来找我。说是新入职了某大厂,使用的是thrift请求,由于公司内部进行了thrift封装,导致他看不清整个流程,希望我能给他讲讲thrift请求应该是什么样的。。 突然我就很好奇,是什么使美团、头条等等大厂现在都加入了thrift的阵营呢?
特点
首先说一下thrift的特点。这个很多介绍thrift的文章里面也都会提到以下几点。我就说一下我个人的理解。
- 基于二进制的高性能的编解码框架
二进制:二进制协议比文本形式的协议,发送的数据量肯定是更小,传输效率更高的。
编解码框架:作为rpc框架,一般都是封装了方法名、方法参数这些方法调用的方式。客户端发送消息。服务端接收到客户端的消息之后,解码消息,然后通过方法调用模型来完成实际服务器端业务方法的调用。
其实这句话里的关键点应该就是高性能。网上也有很多性能测试能证明thrift的性能远远高于http。比如这篇文章。而这里面的原因,一部分是因为非阻塞io的机制,另一部分的原因也是因为http无连接无状态,而thrift是客户端提前建立连接,在这种测试中有先天优势。
- 基于NIO的底层通信
NIO,全称是NoneBlocking IO,非阻塞IO,和BIO(Blocking IO,阻塞IO)相对应。具体的解释和实现机制,不是这篇文章的重点,感兴趣的可以直接看这篇文章了解一下。
非阻塞IO,区别与BIO,BIO的全称是Blocking IO,阻塞IO。那这个阻塞是什么意思呢?Accept是阻塞的,只有新连接来了,Accept才会返回,主线程才能继Read是阻塞的,只有请求消息来了,Read才能返回,子线程才能继续处理Write是阻塞的,只有客户端把消息收了,Write才能返回,子线程才能继续读取下一个请求所以传统的多线程服务器是BlockingIO模式的,从头到尾所有的线程都是阻塞的。这些线程就干等在哪里,占用了操作系统的调度资源,什么事也不干,是浪费。那么NIO是怎么做到非阻塞的呢。它用的是事件机制。它可以用一个线程把Accept,读写操作,请求处理的逻辑全干了。如果什么事都没得做,它也不会死循环,它会将线程休眠起来,直到下一个事件来了再继续干活,这样的一个线程称之为NIO线程。
- 相对简单的服务调用模型
既然是相对简单那就是看跟谁比。和http比的话thrift是需要设计协议的,但是相对于xxx来说,。
- 使用IDL支持跨平台调用
到不同平台生成相应的代码。。目前支持了这些语言的使用吧。。
但是像java这种注解的使用方式的话。。生成idl可能(?)反而是另一个工作量。。(反正我真的见过不只一个写完代码反过来手拼idl的java工程狮。)
其他rpc框架
grpc
中文版文档。
没有协议层、服务端的什么TBinaryProtocal、TNonBlockingServer这些概念要了解。传输层使用http2协议。
整体使用起来和thrift非常像。同样有idl,protobuf的文件。同样需要启动客户端服务器。gprc生成的代码会简单易读很多,但是这个好像并没有什么用。。
也是支持多语言的,但是相对来讲没有thrift支持的那么全面。主要应用是对客户端的支持比较好。 目前提供C系列、Java和Go还有node语言版本。
优点最主要的是支持流式处理。支持OAuth鉴权。protobuf的高效序列化和反序列化。再就是支持 HTTP 2.0 标准化的协议。哦对还有文档相对健全。。
相比之下缺点主要集中在支持语言不太多,性能没有thrift好。这个性能测试我感觉比较全面中肯。
dubbo
中文官网。
捐给Apache之前是阿里爸爸的,意味着直接拥有最大语言社区,自带服务注册和服务治理。当时很多人选择dubbo估计也是看在这一点上吧。
非要和thrift比的话。只能说二者侧重点不同吧。thrift致力于多语言支持,dubbo专注java十几年。因此没有IDL这一机制,直接通过java服务对象接口进行交互。
和http对比
都是基于tcp协议实现的。 主要的区别是http是无连接、无状态的协议,而thrift在每次连接、关闭会比较耗性能。而且客户端主动关闭的Socket端会进入TIME_WAIT状态,并且TIME_WAIT状态一般维持在1-4分钟。
都说thrift省流量和性能好,但是我觉得不一定能体现出来。。 作为一个前端,调用thrift接口,虽然浏览器也可直接发thrift请求,但是出于建立连接性能方面考虑,大部分情况是通过node转发的,感觉比http要麻烦。 而且thrift省流量和性能好这一点其实体现不太出来。。因为浏览器发给node的始终还是http。
thrift还有一个缺点是完全静态化,所有用到的数据结构都必须事先定义,中途不能更改。若数据结构发生变化,必须重新定义,生成一个新的idl,重新编译。
详细的可以看这篇文章。
未来thrift的发展?
这个问题我也很好奇。thrift肯定不是最新的技术,会昙花一现还是会永久流传这个我真的很好奇。。
但是任何新技术都是需要时间落地的。目前来看,thrift的各项支持都很好,很多开源项目的周边支持都是thrift的,hbase提供thrift服务,hive,spark sql,cassandra等一系列对外的标准服务接口都是thrift的,以支持多语言。
thrift有他的优点但是也有他的局限,比如完全静态化就是他的优点也是局限。还有就是thrift不支持大量数据的流式传输。
thrift的使用流程【node角度】
首先说一下thrift的核心组成。。这个任何一个教程里都会提到,就简单列一下。
- TProtocol 协议和编解码组件
- TTransport 传输组件
- TProcessor 服务调用组件
- TServer,Client服务器和客户端组件
- IDL 接口定义
顺便说一下。如果想更详细的了解thrift的使用,或者不是前端开发同学,关注点不同的话,我会比较安利这篇文章。至于类型定义什么的也可以去官网查看。
安装
brew install thrift
网上有很多更具体的教程,可以参考。
idl文件
namespace java hello.test
enum THelloType {
HI = 1,
HELLO = 2,
WELCOME = 3,
}
struct Error {
1: i64 code,
2: string message,
}
struct THelloMessage {
1: string msg,
2: THelloType helloMsg,
}
struct TSayHelloResponse {
1: bool success,
2: Error error,
3: THelloMessage data
}
struct TSayHelloRequest {
1: string name,
2: list<string> friends,
}
service TSayHelloService {
TSayHelloResponse sayHello(1: TSayHelloRequest arg0),
map<string, set<i32>> searchWhateverByDate(1:string startDate, 2:string endDate, 3:i32 offset, 4:i32 limit)
}
生成js文件
thrift --gen js:node thrift_file
在koa项目中使用
首先安装依赖。
yarn add thrift
然后启动一个Client服务器。我们以上面那个idl举例。 一般这一步的话就是项目启动的时候执行一次就可以。TProtocol和TTransport选择自己要用的。
import thrift from 'thrift';
import HelloService from './gen-nodejs/TSayHelloService.js';
const connection = thrift.createConnection('127.0.0.1', 9527, {
transport : thrift.TBufferedTransport,
protocol : thrift.TBinaryProtocol
});
const client = thrift.createClient(HelloService, connection);
connection.on('error', function(e) {
console.log(e);
});
如果一个项目中需要连接多个service的话呢。就搞个连接池。。
const configs = [{
serviceName: 'hello',
service: require('./gen-nodejs/TSayHelloService.js'),
types: require('./gen-nodejs/hello_types'),
port: 9527,
}];
const pools = {};
configs.forEach(item => {
let connection = thrift.createConnection('127.0.0.1', item.port, {
transport : thrift.TBufferedTransport,
protocol : thrift.TBinaryProtocol
});
pools[item.serviceName] = thrift.createClient(item.service, connection);
});
发送请求。
const HELLOTYPES = require('./gen-nodejs/hello_types')
pools[hello].sayHello(new HELLOTYPES.TSayHelloRequest({
name: 'kikooo',
friends: ['xiaoa', 'xiaob'],
}), function(err, res) {
console.log('say hello success');
});
这里说的就只是使用原生的thrift包。当然各个厂子可能都有自己特殊的封装。
常见封装
一般情况下,在thrift发送请求的整个流程里,有以下几个点可能会被封装。初次使用的同学可以参照自己厂子的情况对照看一下。
建立连接阶段: 对createConnection进行封装,不暴露创建过程。例如:
new ConnectionPool({
serviceList: config,
retry: {
retries: 0, // 不重试
minTimeout: 3 * 1000,
maxTimeout: 10 * 1000,
randomize: false
}
});
配置和服务发现阶段: 大部分公司都会自建服务发现,有的公司甚至可能不暴露thrift文件,直接服务发现的时候获取。 配置可能就长这样:
const services = {
Hello: {
filename: 'hello.thrift',
}
};
// 或者这样
const services = [{
remoteKey: 'xxxxx',
service: HelloService,
serviceName: 'blablabla'
}];
// 或者直接没有本地配置,远程配了当前这个服务对应的需要的服务列表啊啥的,从服务发现把所有服务都拉来,调用的时候直接调。
发送请求阶段: 可能会被封装成中间件啊各种各样的形式,直接引包就用,例如:
app.use(thriftrequest({
config,
}));
const res = await ctx.thriftrequest.functionService.function(req);
// 或者这样。。
const HelloRequest = serviceExecuter({
remoteKey: 'global.hello',
service: HelloService,
});
const res = await HelloRequest(methodName, params);
广告时间
最后的最后,前些日子 (刚写这文的时候是2020年9月。我拖延症太严重了哭。) 刚写了个thrift接口mock的包,支持多种使用方法,不要脸的放一下链接,egg-thrift-mock,虽然叫这个名字但是不止能在egg里用的。 欢迎大家下载、试用、交流、提需求、提bug~~~
我知道很多大厂可能都会有自己的thrift-mock服务器。但是emmm。万一没有呢嘿嘿~~
参考
- Why Thrift, Why not HTTP RPC(JSON+gzip)
- Thrift RPC 框架分析
- Moving From Apache Thrift to gRPC: A Perspective From Alluxio
- Thrift入门 | RPC基础&&Thrift概念
- NIO相关基础篇
- RPC vs Thrift
- 由浅入深了解Thrift(二)——Thrift工作原理
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!