SpringBoot 结合 Thrift 构建远程服务调用

  |     |  

什么是Thrift?

Thrift是Facebook于2007年开发的跨语言的rpc服框架,提供多语言的编译功能,并提供多种服务器工作模式;用户通过Thrift的IDL(接口定义语言)来描述接口函数及数据类型,然后通过Thrift的编译环境生成各种语言类型的接口文件,用户可以根据自己的需要采用不同的语言开发客户端代码和服务器端代码。

Thrift 的跨语言特性

thrift通过一个中间语言IDL(接口定义语言)来定义RPC的数据类型和接口,这些内容写在以.thrift结尾的文件中,然后通过特殊的编译器来生成不同语言的代码,以满足不同需要的开发者,比如java开发者,就可以生成java代码,C++ 开发者可以生成c++代码,生成的代码中不但包含目标语言的接口定义,方法,数据类型,还包含有RPC协议层和传输层的实现代码.

Thrift 的协议栈结构


Thrift是一种c/s的架构体系.在最上层是用户自行实现的业务逻辑代码.第二层是由thrift编译器自动生成的代码,主要用于结构化数据的解析,发送和接收。
TServer主要任务是高效的接受客户端请求,并将请求转发给Processor处理。
Processor负责对客户端的请求做出响应,包括RPC请求转发,调用参数解析和用户逻辑调用,返回值写回等处理。从TProtocol以下部分是thirft的传输协议和底层I/O通信。
TProtocol是用于数据类型解析的,将结构化数据转化为字节流给TTransport进行传输。
TTransport是与底层数据传输密切相关的传输层,负责以字节流方式接收和发送消息体,不关注是什么数据类型。底层IO负责实际的数据传输,包括socket、文件和压缩数据流等。

入门小实例

使用IDEA,安装Thrift插件:

前往Thrift下载Thrift包,并配置环境变量。
idea中配置thrift的安装目录:

完事后,编写一个thrift小例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
namespace java com.dashuai.learning.thrift.service
service RPCDateService{
string getDate(1:string userName),

bool postStudent(1:Student student),

Student getStudent(1:i32 userId);
}
struct Student {
1: required i32 userId;
2: required string username;
3: required string text;
}

在项目结构main里配置一个Thritf的 ,在Generator list中选择添加一个Java,并配置输出目录。


接着右键编译:

就会生成对应的java服务类。
当然,你也可以使用命令编译:

1
thrift --gen java RPCDateService.thrift

类似下面的JAVA类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.12.0)", date = "2019-07-01")
public class RPCDateService {

public interface Iface {

public String getDate(String userName) throws org.apache.thrift.TException;

public boolean postStudent(Student student) throws org.apache.thrift.TException;

public Student getStudent(int userId) throws org.apache.thrift.TException;

}
//..............................................
}

导入对应的jar包,进行server端和client端的编码,我下载的是最新的0.12.0 版本的thrift,所以使用jar包0.12.0的。

1
compile 'org.apache.thrift:libthrift:0.12.0'

编写Server端

Thrift为服务器端程序提供了很多的工作模式,例如:线程池模型、非阻塞模型等等,可以根据自己的实际应用场景选择一种工作模式高效地对外提供服务。
下面的示例使用的是线程池模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@Component
public class RPCThriftServer {
private final Logger logger = LoggerFactory.getLogger(RPCThriftServer.class);

@Value("${thrift.port}")
private int port;
@Value("${thrift.minWorkerThreads}")
private int minThreads;
@Value("${thrift.maxWorkerThreads}")
private int maxThreads;

private TBinaryProtocol.Factory protocolFactory;
private TTransportFactory transportFactory;

@Autowired
private RPCDateServiceImpl rpcDateService;

public void init() {
protocolFactory = new TBinaryProtocol.Factory();
transportFactory = new TTransportFactory();
}

public void start() {
RPCDateService.Processor processor = new RPCDateService.Processor<RPCDateService.Iface>(rpcDateService);
init();
try {
TServerTransport transport = new TServerSocket(port);
TThreadPoolServer.Args tArgs = new TThreadPoolServer.Args(transport);
tArgs.processor(processor)
.protocolFactory(protocolFactory)
.transportFactory(transportFactory)
.minWorkerThreads(minThreads)
.maxWorkerThreads(maxThreads);
TServer server = new TThreadPoolServer(tArgs);
logger.info("thrift服务启动成功, 端口={}", port);
server.serve();
} catch (Exception e) {
logger.error("thrift服务启动失败", e);
}
}
}

实现生成的服务类接口,编写下服务提供者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Controller
public class RPCDateServiceImpl implements RPCDateService.Iface {
/**
* 模拟存储
*/
private Map<Integer, Student> studentMap = new HashMap<>();

@Override
public String getDate(String userName) throws TException {
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("今天是yyyy年MM月dd日 E kk点mm分");
String nowTime = simpleDateFormat.format(date);
return "Hello " + userName + "\n" + nowTime;
}

@Override
public boolean postStudent(Student student) throws TException {
try {
studentMap.put(student.getUserId(), student);
return true;
} catch (Exception e) {
return false;
}
}

@Override
public Student getStudent(int userId) throws TException {
return studentMap.get(userId);
}
}

OK,下面来编写一个客户端远程调用服务。

Thrift Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class RPCThriftClient {
private RPCDateService.Client client;
private TBinaryProtocol protocol;
private TSocket transport;
private String host;
private int port;

public String getHost() {
return host;
}

public void setHost(String host) {
this.host = host;
}

public int getPort() {
return port;
}

public void setPort(int port) {
this.port = port;
}

/**
* 初始化客户端
*/
public void init() {
transport = new TSocket(host, port);
//使用二进制协议
protocol = new TBinaryProtocol(transport);
client = new RPCDateService.Client(protocol);
}

public RPCDateService.Client getRPCThriftService() {
return client;
}

public void open() throws TTransportException {
transport.open();
}

public void close() {
transport.close();
}
}

编写几个API验证下是否调用成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@RestController
@Api(value = "SpringBoot集成Thrift实现RPC远程调用", tags = "RPCThriftController")
public class RPCThriftController {
private final Logger logger = LoggerFactory.getLogger(RPCThriftController.class);
@Autowired
RPCThriftClient rpcThriftClient;

@GetMapping(value = "/thrift/{name}")
@ApiOperation(value = "第一个接口Hello,World!", notes = "测试接口", response = String.class)
public String thriftTest(@PathVariable String name) {
try {
rpcThriftClient.open();
return rpcThriftClient.getRPCThriftService().getDate(name);
} catch (Exception e) {
logger.error("RPC调用失败", e);
return "error";
} finally {
rpcThriftClient.close();
}
}

@PostMapping(value = "/setStudent")
@ApiOperation(value = "添加一个学生对象", notes = "测试结构体添加...", response = ApiResponse.class)
@ApiImplicitParam(name = "student", value = "学生实体", required = true, dataType = "Student")
public ApiResult setStudent(@RequestBody Student student) {
try {
rpcThriftClient.open();
return ApiResult.prepare().success(rpcThriftClient.getRPCThriftService().postStudent(student));
} catch (Exception e) {
logger.error("RPC调用失败", e);
return ApiResult.prepare().error(null, 500, "添加失敗");
} finally {
rpcThriftClient.close();
}
}

@RequestMapping(value = "/getStudent", method = RequestMethod.GET)
@ApiOperation(value = "添加一个学生对象", notes = "测试接口", response = String.class)
@ApiImplicitParam(name = "userId", value = "获取学生信息", required = true, dataType = "String")
public ApiResult getStudent(String userId) {
try {
rpcThriftClient.open();
return ApiResult.prepare().success(rpcThriftClient.getRPCThriftService().getStudent(Integer.parseInt(userId)));
} catch (Exception e) {
logger.error("RPC调用失败", e);
return ApiResult.prepare().error(null, 500, "获取失敗");
} finally {
rpcThriftClient.close();
}
}
}


可以看到,客户端成功调用了服务。

server端和client端源码请前往github上查看,链接如下:
https://github.com/liaozihong/SpringBoot-Learning/tree/master/SpringBoot-Thrift-Server
https://github.com/liaozihong/SpringBoot-Learning/tree/master/SpringBoot-Thrift-Client

服务端处理服务调用如下错误,但不影响调用,google得知,thrift 0.12.0会出现这个问题,
如觉得烦,可以进行降级,如果有大佬了解错误的还望指出。

参考链接:
https://www.cnblogs.com/fingerboy/p/6424248.html
https://blog.csdn.net/houjixin/article/details/42778335
https://www.ibm.com/developerworks/cn/java/j-lo-apachethrift/index.html
Thrift语法参考

文章目录
  1. 1. 什么是Thrift?
  2. 2. Thrift 的跨语言特性
  3. 3. Thrift 的协议栈结构
  4. 4. 入门小实例
  5. 5. 编写Server端
  6. 6. Thrift Client
作者共写了53.1k个字 本站总访问量  |   您是访问本站的第个小伙伴