thritf
thrift是facebook开元的接口定义语言IDL(Interface Definition Languge),用于进行跨语言的接口定义从而实现不同语言之间的远程通讯。
不同的开发语言使用idl定义接口都会生成其本身的语法接口,idl忽略的语言的差异从而实现了远程调用。每个idl都有自己独特的语言法和工具,用于将自身代码生成其他语言的代码。
thrift、protobuf也提供了自己的工具将该语法编译成Java,python,Go等语言的代码,同时生成的代码中有构建server端与client端的方法,通过方法方法构建server服务器与client客户端,那么任意server和cliet都能通讯。
thrift的核心原理是序列化和反序列化,序列化就是将数据转化为字节流,反序列化就是将字节流恢复为数据。
namespace java demo
struct addStore{
1:i32 id
2:string storeName
3:i32 type
4:string uuid
5:string identitionTime
6:string createAt
7:i32 status
}
struct delete{
1:i32 id
}
struct update{
1:i32 id
2:string storeName
3:i32 type
4:string uuid
5:string identitionTime
6:string createAt
7:i32 status
}
struct getStore{
1:i32 id
2:string storeName
3:i32 type
4:string uuid
5:string identitionTime
6:string createAt
7:i32 status
}
service storeInterface{
i32 addStore(1:addStore req)
i32 delete(1:delete req)
i32 update(1:update req)
list<getStore >getStore(1:i32 id 2:string uuid)
}
上述使用thrift定义了storeInterface接口,其中需要注意的是namespace
声明了生成的语言为java。
thrift生成工具为thrift,如下所示:
kitex -module kitexdemo ./demo.thrift
需要注意的是namespace必须是要生成语言的表示,后一位是
文件夹名
thrift -r --gen java demo.thrift
-r
表示递归生成,--gen java
表示生成Java代码,该命令会生成一个gen-java
目录存放生成的代码。
thrift命令默认是在当前文件下生成,也可以使用
-o
来指定目录,-out
更多命令使用thrift -help
查看。
将生成的源码移到maven构建的源码目录,如下
打开文件发现全是报错
报错原因是在生成代码时只生成了源码而没有引入包,源码中import
关键字都没有出现。解决方案也很简单查询thrift生成源码是需要哪些工具包,在maven中引入然后刷新pom文件即可。
https://mvnrepository.com
也可以直接在idea中操作,鼠标点击红色报错Add Maven Dependency
Java实现
使用命令行只生成了代码和测设包名,要实际应用到maven中还需要改包名和引入依赖,如下
- 目录分层
- 引包引依赖
生成的源码和thrift定义的是一致的,那么实现接口,启动client和server端即可。如下:
public interface Iface {
public int addStore(addStoreReq req) throws org.apache.thrift.TException;
public int deleteStore(deleteStoreReq req) throws org.apache.thrift.TException;
public int updateStore(updateStoreReq req) throws org.apache.thrift.TException;
public java.util.List<getStoreReq> getStore(int id, java.lang.String uuid) throws org.apache.thrift.TException;
}
上述是服务端源码,首先要继承改接口,然后在实现接口方法:
package org.example.demo_test;
import org.apache.thrift.TException;
import org.example.demo.model.addStoreReq;
import org.example.demo.model.deleteStoreReq;
import org.example.demo.model.getStoreReq;
import org.example.demo.model.updateStoreReq;
import org.example.demo.storeInterface;
import java.util.List;
public class serverImpl implements storeInterface.Iface {
@Override
public int addStore(addStoreReq req) throws TException {
System.out.println("you add a store data");
return 0;
}
@Override
public int deleteStore(deleteStoreReq req) throws TException {
System.out.println("you delete a store data");
return 0;
}
@Override
public int updateStore(updateStoreReq req) throws TException {
System.out.println("you update a store data");
return 0;
}
@Override
public List<getStoreReq> getStore(int id, String uuid) throws TException {
System.out.println("show store lists");
return null;
}
}
方法已经实现接下来就是会数据传输了,原理可以看https://www.cnblogs.com/jpfss/p/10881220.html
thrift实现rpc大致分为三个过程,首先业务层代码的数据映射到idl的定义的同名接口中(内存复用);然后对映射过来的数据序列化操作,序列化协议是TProtocol
;最后实现数据流的传输,协议是TTransport
。
还有一点需要明白的是rpc传输和http传输过程类似,都是需要一个持续的非阻塞进程来响应客户端请求。
参考感谢作者!
服务端与客户端代码代码如下:
server
package org.example.demo_test;
import org.apache.thrift.TProcessorFactory;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.THsHaServer;
import org.apache.thrift.server.TSaslNonblockingServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TNonblockingServerSocket;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TTransportException;
import org.apache.thrift.transport.layered.TFramedTransport;
import org.example.demo.storeInterface;
public class server {
public static void main(String[] args) throws TTransportException {
//设置服务器端口 TNonblockingServerSocket-非堵塞服务模型
TNonblockingServerSocket serverSocket = new TNonblockingServerSocket(8899);
//参数设置
THsHaServer.Args arg = new THsHaServer.Args(serverSocket).minWorkerThreads(2).maxWorkerThreads(4);
//处理器
storeInterface.Processor<storeInterfaceImpl> processor = new storeInterface.Processor<>(new storeInterfaceImpl());
arg.protocolFactory(new TCompactProtocol.Factory());
arg.transportFactory(new TFramedTransport.Factory());
arg.processorFactory(new TProcessorFactory(processor));
TServer server = new THsHaServer(arg);
System.out.println("Thrift 服务端启动成功");
server.serve();
}
}
client
package org.example.demo_test;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import org.apache.thrift.transport.layered.TFramedTransport;
import org.example.demo.model.addStoreReq;
import org.example.demo.model.deleteStoreReq;
import org.example.demo.model.getStoreReq;
import org.example.demo.storeInterface;
import java.util.List;
public class client {
public static void main(String[] args) throws TException {
TTransport transport = new TFramedTransport(new TSocket("localhost", 9000), 600);
TProtocol protocol = new TCompactProtocol(transport);
storeInterface.Client client = new storeInterface.Client(protocol);
deleteStoreReq deleteStoreReq = new deleteStoreReq(1);
transport.open();
// 接口方法
client.deleteStore(deleteStoreReq);
List<getStoreReq> store = client.getStore(1, "333");
System.out.println(store);
return ;
}
}
demo地址:https://gitee.com/fireapproval/daxu/tree/master/thrift-demo
Go实现
thrift文件和上述一直,生成Go源码:thrift -r -gen go demo.thrift
。若对命令不熟悉通过thrift -help
查看 。
- 初始目录(仅包含thrift文件)
再次之前首先要确保安装了thrift插件,thrift -version
若未安装可参考如下步骤:
- 下载thrifthttps://archive.apache.org/dist/thrift/
- 系统高级设置配置环境变量(Linux修改配置文件即可)
thrift -r -out ./ --gen go demo.thrift
该命令生成了构建rpc的全部源码,如demo
文件夹
该文件夹是thrift
一键生成的,文件名取决于namespace
的命名。
==注意在生成的代码中可能会报错,注意观察如果是引入报包是正常的,因为生成的代码是将其本身的目录作为根目录,而实际上应该以项目目录为根目录,修改一下即可。另外要是生成的代码报错,确定引入包了情况下可能是thrift和go版本差太多了,相应减低版本即可。
如果在自动生成的代码中某些方法爆红就是版本问题了,在如下的网站找到相近的版本就行了
在生成的代码中,XXX_XXX-remote是测试代码无关核心功能,xxx_xxx是根据接口IDL的接口命名的。其中核心文件是和namespace
命名一样的文件。
在上述的核心文件中存在在thrift文件名称完全一样的接口,该接口就是rpc服务端接口,按照thrift的rpc传输原理,Processer、Protocal、Transport分阶段完成。
还要实现对服务端接口的重写,实现内存共用。
服务端实现:
package main
import (
"context"
"errors"
"fmt"
"github.com/apache/thrift/lib/go/thrift"
"thriftdemo/demo"
)
// 操作对象
type Store struct {}
// 实现服务端接口
// 以下所用到的参数均在thrift生成的很小代码中获取
func (s *Store) AddStore(ctx context.Context, req *demo.AddParam) (_r int32, _err error){
return 1,errors.New("add store fail")
}
func (s *Store) DeleteStore(ctx context.Context, req *demo.DeleteParam) (_r int32, _err error) {
return 1,errors.New("delete store fail")
}
func (s *Store) UpdateStore(ctx context.Context, req *demo.UpdateParam) (_r int32, _err error) {
return 1,nil
}
func (s Store) GetStore(ctx context.Context, id int32, uuid string) (_r []*demo.GetParam, _err error) {
P1 := demo.GetParam{ID: 1,StoreName: "XIAOXU",Type: 1,UUID: "33-55",IdentifyTime: "2023",CreateAt: "2023",Status: 1}
list :=make([]*demo.GetParam,1)
list[0]= &P1
return list,nil
}
//实现rpc服务端
func main(){
storeTmp := Store{}
// demo获取thrift传输各个阶段对象
process := demo.NewStoreInterfaceProcessor(&storeTmp)
socket, err := thrift.NewTServerSocket(":9000")
if err != nil{
panic(err)
}
factory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory())
factoryDefault := thrift.NewTBinaryProtocolFactoryDefault()
server := thrift.NewTSimpleServer4(process, socket, factory, factoryDefault)
fmt.Println("rpc服务端启动端口为9000")
server.Serve()
}
方法的实现类也可以拆分,这里都写在main函数了。
客户端实现:
package main
import (
"context"
"fmt"
"github.com/apache/thrift/lib/go/thrift"
"log"
"thriftdemo/demo"
)
func main() {
conf := &thrift.TConfiguration{}
transport := thrift.NewTSocketConf("localhost:9000", conf)
// 创建客户端协议
confN := &thrift.TConfiguration{}
protocolFactory := thrift.NewTBinaryProtocolFactoryConf(confN)
client := demo.NewStoreInterfaceClientFactory(transport, protocolFactory)
if err := transport.Open(); err != nil {
log.Fatalln("Error opening:", "localhost" + ":9000")
}
defer transport.Close()
//d, err := client.GetStore(context.Background(),1,"2233")
//if err!=nil{
//
//}
d ,err:= client.AddStore(context.Background(),&demo.AddParam{})
if err != nil{}
fmt.Println(d)
}
protobuf和thrift一样都是接口定义语言,通过工具生成各个语言的源码,他们的序列化协议不一样,用法都一样。
https://github.com/protocolbuffers/protobuf
https://protobuf.dev/getting-started/