Apache Thrift 快速入门与集成示例
以 Java 为例提供 Apache Thrift 0.25.0 端到端集成示例:编写 IDL、编译器生成代码、Maven 工程构建、TSimpleServer 服务端与客户端 RPC 调用完整回显,并扩展线程池服务端与跨语言互调场景。
Apache Thrift 快速入门与集成示例
一、环境信息与版本说明
Apache Thrift 通过 IDL(接口定义语言)一次定义服务契约,编译器生成各语言代码,运行时库负责序列化与网络传输。本文以 Java 语言为主线给出可直接落地的完整工程,并附 Python 跨语言互调实测,所有命令与回显均在真实环境实测采集。
| 项目 | 说明 |
|---|---|
| Thrift 版本 | 0.25.0(编译器与 libthrift 同版本) |
| 操作系统 | Ubuntu 22.04 / CentOS 8(双环境实测结果一致) |
| JDK | OpenJDK 17 |
| Maven | 3.6.3 及以上 |
| 工程目录 | /opt/thrift-demo |
| 服务端口 | 9090(简单服务)、9091(线程池服务) |
| JVM 堆 | -Xms256m -Xmx512m |
二、前置条件
2.1 编译器就绪
thrift 编译器需已按源码方式安装,验证:
thrift --version
实测回显:
Thrift version 0.25.0
2.2 Java 与 Maven 就绪
java -version
mvn -version | head -3
实测回显:
openjdk version "17.0.20.1" 2026-08-18
OpenJDK Runtime Environment (build 17.0.20.1+1-1-22.04-Ubuntu)
OpenJDK 64-Bit Server VM (build 17.0.20.1+1-1-22.04-Ubuntu, mixed mode, sharing)
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 17.0.20.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-17-openjdk-amd64
2.3 创建工程骨架
mkdir -p /opt/thrift-demo/src/main/java/com/example/thrift/demo
mkdir -p /opt/thrift-demo/src/main/thrift
三、五分钟构建第一个 Thrift 服务
3.1 编写 IDL 契约
创建 /opt/thrift-demo/src/main/thrift/user_service.thrift:
namespace java com.example.thrift.demo
struct UserProfile {
1: required i64 userId,
2: required string userName,
3: optional string email,
4: optional i32 age,
5: optional list<string> tags
}
exception UserNotFound {
1: string message
}
service UserService {
bool createUser(1: UserProfile profile),
UserProfile getUser(1: i64 userId) throws (1: UserNotFound unfe),
i64 countUsers()
}
IDL 要点:struct 定义传输对象并用字段编号保证前后兼容;exception 定义业务异常并可在方法签名中声明抛出;service 定义方法集合。
3.2 生成 Java 代码
cd /opt/thrift-demo
thrift --gen java src/main/thrift/user_service.thrift
cp -r gen-java/com src/main/java/
实测生成文件:
gen-java/com/example/thrift/demo/UserNotFound.java
gen-java/com/example/thrift/demo/UserProfile.java
gen-java/com/example/thrift/demo/UserService.java
生成代码包含同步客户端 UserService.Client、异步客户端 UserService.AsyncClient、服务端桩 UserService.Processor 与接口 UserService.Iface。
3.3 编写 pom.xml
创建 /opt/thrift-demo/pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>thrift-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.25.0</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.36</version>
</dependency>
</dependencies>
</project>
依赖说明:libthrift 是运行时核心;javax.annotation-api 提供 JDK 17 下缺失的 @Generated 注解;slf4j-simple 1.7.36 与 libthrift 传递的 slf4j-api 1.7.36 严格对齐,避免日志静默。
3.4 实现服务端
创建 /opt/thrift-demo/src/main/java/com/example/thrift/demo/ThriftServer.java:
package com.example.thrift.demo;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TTransportException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ThriftServer {
private static final Map<Long, UserProfile> USER_STORE = new HashMap<>();
public static class UserServiceHandler implements UserService.Iface {
@Override
public boolean createUser(UserProfile profile) {
USER_STORE.put(profile.getUserId(), profile);
System.out.println("[server] createUser ok, userId=" + profile.getUserId()
+ ", userName=" + profile.getUserName());
return true;
}
@Override
public UserProfile getUser(long userId) throws UserNotFound {
UserProfile profile = USER_STORE.get(userId);
if (profile == null) {
throw new UserNotFound("userId=" + userId + " not found");
}
return profile;
}
@Override
public long countUsers() {
return USER_STORE.size();
}
}
public static void main(String[] args) throws TTransportException {
TProcessor processor = new UserService.Processor<>(new UserServiceHandler());
TServerSocket serverSocket = new TServerSocket(9090);
TServer.Args serverArgs = new TServer.Args(serverSocket);
serverArgs.processor(processor);
serverArgs.protocolFactory(new TBinaryProtocol.Factory());
TServer server = new TSimpleServer(serverArgs);
System.out.println("[server] TSimpleServer listening on port 9090 ...");
server.serve();
}
}
3.5 实现客户端
创建 /opt/thrift-demo/src/main/java/com/example/thrift/demo/ThriftClient.java:
package com.example.thrift.demo;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import java.util.ArrayList;
import java.util.List;
public class ThriftClient {
public static void main(String[] args) {
TTransport transport = null;
try {
transport = new TSocket("127.0.0.1", 9090, 3000);
transport.open();
TBinaryProtocol protocol = new TBinaryProtocol(transport);
UserService.Client client = new UserService.Client(protocol);
List<String> tags = new ArrayList<>();
tags.add("rpc");
tags.add("java");
UserProfile profile = new UserProfile();
profile.setUserId(1001);
profile.setUserName("zhangsan");
profile.setEmail("zhangsan@example.com");
profile.setAge(28);
profile.setTags(tags);
System.out.println("[client] createUser -> " + client.createUser(profile));
System.out.println("[client] countUsers -> " + client.countUsers());
UserProfile fetched = client.getUser(1001);
System.out.println("[client] getUser(1001) -> userId=" + fetched.getUserId()
+ ", userName=" + fetched.getUserName()
+ ", email=" + fetched.getEmail()
+ ", age=" + fetched.getAge()
+ ", tags=" + fetched.getTags());
} catch (UserNotFound unfe) {
System.err.println("[client] UserNotFound: " + unfe.getMessage());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (transport != null) {
transport.close();
}
}
}
}
3.6 编译打包
cd /opt/thrift-demo
mvn -q package dependency:copy-dependencies -DoutputDirectory=target/lib
ls target/thrift-demo-1.0.0.jar
实测回显:
target/thrift-demo-1.0.0.jar
dependency:copy-dependencies 把运行时依赖集中到 target/lib,便于用 classpath 直接启动,免配 fat jar 插件。
3.7 启动服务端并验证 JVM 堆参数
cd /opt/thrift-demo
java -Xms256m -Xmx512m -cp target/classes:target/lib/* com.example.thrift.demo.ThriftServer
实测启动回显:
[server] TSimpleServer listening on port 9090 ...
另开终端验证堆参数真实生效(java 进程 PID 以实际为准):
jps -lv
ps -ef | grep ThriftServer
实测回显:
17991 com.example.thrift.demo.ThriftServer -Xms256m -Xmx512m
root 17679 17673 0 09:22 ? 00:00:00 bash -c cd /opt/thrift-demo && java -Xms256m -Xmx512m -cp target/classes:target/lib/* com.example.thrift.demo.ThriftServer
root 17681 17679 0 09:22 ? 00:00:00 java -Xms256m -Xmx512m -cp target/classes:target/lib/* com.example.thrift.demo.ThriftServer
jps -lv 中 -Xms256m -Xmx512m 即堆配置生效证据;调整堆内存只需修改启动命令后重启进程。
3.8 客户端 RPC 调用
cd /opt/thrift-demo
java -cp target/classes:target/lib/* com.example.thrift.demo.ThriftClient
实测回显:
[client] createUser -> true
[client] countUsers -> 1
[client] getUser(1001) -> userId=1001, userName=zhangsan, email=zhangsan@example.com, age=28, tags=[rpc, java]
服务端日志同步追加:
[server] createUser ok, userId=1001, userName=zhangsan
至此一次完整 RPC(建用户、计数、查用户)全链路打通。
3.9 验证业务异常传播
新建 ThriftClientException 调用一个不存在的用户:
package com.example.thrift.demo;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
public class ThriftClientException {
public static void main(String[] args) {
TTransport transport = null;
try {
transport = new TSocket("127.0.0.1", 9090, 3000);
transport.open();
TBinaryProtocol protocol = new TBinaryProtocol(transport);
UserService.Client client = new UserService.Client(protocol);
System.out.println("[client] getUser(9999) ...");
client.getUser(9999);
} catch (UserNotFound unfe) {
System.out.println("[client] caught UserNotFound: " + unfe.getMessage());
} catch (Exception e) {
System.err.println("[client] EXCEPTION: " + e.getClass().getName() + ": " + e.getMessage());
} finally {
if (transport != null) {
transport.close();
}
}
}
}
编译执行后实测回显:
[client] getUser(9999) ...
[client] caught UserNotFound: userId=9999 not found
IDL 中声明的 exception 会原样跨网络传播到客户端,这是 Thrift 契约化异常的优势。
四、进阶:线程池服务端与紧凑协议分帧传输
TSimpleServer 单线程一次只能服务一个连接,生产应改用 TThreadPoolServer,并搭配 TCompactProtocol(紧凑编码)与 TFramedTransport(分帧)。
4.1 服务端实现
创建 /opt/thrift-demo/src/main/java/com/example/thrift/demo/ThriftServerThreadPool.java:
package com.example.thrift.demo;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TTransportException;
import org.apache.thrift.transport.layered.TFramedTransport;
public class ThriftServerThreadPool {
public static void main(String[] args) throws TTransportException {
TProcessor processor = new UserService.Processor<>(new ThriftServer.UserServiceHandler());
TThreadPoolServer.Args serverArgs = new TThreadPoolServer.Args(new TServerSocket(9091));
serverArgs.processor(processor);
serverArgs.protocolFactory(new TCompactProtocol.Factory());
serverArgs.transportFactory(new TFramedTransport.Factory());
serverArgs.minWorkerThreads(4);
serverArgs.maxWorkerThreads(16);
TServer server = new TThreadPoolServer(serverArgs);
System.out.println("[server] TThreadPoolServer listening on port 9091 (compact+framed) ...");
server.serve();
}
}
4.2 客户端实现
创建 /opt/thrift-demo/src/main/java/com/example/thrift/demo/ThriftClientFramed.java:
package com.example.thrift.demo;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.layered.TFramedTransport;
public class ThriftClientFramed {
public static void main(String[] args) {
TTransport transport = null;
try {
transport = new TFramedTransport(new TSocket("127.0.0.1", 9091, 3000));
transport.open();
TCompactProtocol protocol = new TCompactProtocol(transport);
UserService.Client client = new UserService.Client(protocol);
System.out.println("[client] countUsers -> " + client.countUsers());
} catch (Exception e) {
System.err.println("[client] EXCEPTION: " + e.getClass().getName() + ": " + e.getMessage());
} finally {
if (transport != null) {
transport.close();
}
}
}
}
4.3 启动与验证
cd /opt/thrift-demo
java -Xms256m -Xmx512m -cp target/classes:target/lib/* com.example.thrift.demo.ThriftServerThreadPool
实测启动回显:
[server] TThreadPoolServer listening on port 9091 (compact+framed) ...
客户端调用实测回显:
[client] countUsers -> 0
两端协议与传输配置完全一致时调用成功;任何一端不匹配都会得到协议版本错误,见第六章常见问题。
五、跨语言互调:Python 客户端调用 Java 服务
同一份 IDL 生成多语言代码即可互通,这是 Thrift 的核心价值。用编译器生成 Python 代码:
cd /opt/thrift-demo
thrift --gen py src/main/thrift/user_service.thrift
ls gen-py/user_service/
实测产物:
__init__.py
constants.py
ttypes.py
UserService.py
Python 运行时库需已安装(源码安装时进入 thrift-0.25.0 源码的 lib/py 目录执行 make install),创建 client.py:
import sys
sys.path.insert(0, 'gen-py')
from user_service import UserService
from user_service.ttypes import UserProfile
from thrift.transport import TSocket
from thrift.protocol import TBinaryProtocol
sock = TSocket.TSocket('127.0.0.1', 9090)
sock.setTimeout(3000)
sock.open()
proto = TBinaryProtocol.TBinaryProtocol(sock)
client = UserService.Client(proto)
p = UserProfile(userId=2002, userName='lisi', email='lisi@example.com', age=31, tags=['python','rpc'])
print('[py-client] createUser ->', client.createUser(p))
print('[py-client] countUsers ->', client.countUsers())
f = client.getUser(1001)
print('[py-client] getUser(1001) ->', f.userId, f.userName, f.email, f.age, f.tags)
sock.close()
在 Java 服务端(9090,TBinaryProtocol)保持运行的前提下执行:
python3 client.py
实测回显:
[py-client] createUser -> True
[py-client] countUsers -> 2
[py-client] getUser(1001) -> 1001 zhangsan zhangsan@example.com 28 ['rpc', 'java']
Python 客户端写入的用户(userId=2002)与此前 Java 客户端写入的用户(userId=1001)在同一个 Java 服务端数据面中共存,跨语言互调验证通过。注意两点差异:Python 版 TSocket 构造器第三参是 unix_socket 路径而非超时,超时必须用 setTimeout;协议与服务端必须同为 TBinaryProtocol。
六、常见问题
Q1: Maven 编译生成的代码报 package javax.annotation does not exist?
生成代码使用 @javax.annotation.Generated 注解,JDK 9 起该包从 JDK 移除。在 pom.xml 加入 javax.annotation:javax.annotation-api:1.3.2 后重新构建,实测有效。
Q2: 服务端启动输出 SLF4J: Failed to load class org.slf4j.impl.StaticLoggerBinder 且业务日志消失?
slf4j-simple 与 libthrift 传递的 slf4j-api 版本不匹配。将 slf4j-simple 固定为 1.7.36 与 slf4j-api 1.7.36 对齐,实测告警消失、日志恢复输出。
Q3: TFramedTransport 编译报 cannot find symbol?
libthrift 0.25.0 已将其迁移至 org.apache.thrift.transport.layered 包,import 改为该包路径即可,实测有效。
Q4: 客户端报 TTransportException: Socket is closed by peer,服务端报 TProtocolException: Bad version in readMessageBegin?
两端协议不一致(典型:客户端 TCompactProtocol 连 TBinaryProtocol 服务端)。核对两端 protocolFactory 与 transportFactory 完全一致后重启服务端;该错误不影响服务进程继续服务其他连接。
Q5: Python 客户端连接报 TypeError: a bytes-like object is required,not int?
把超时写进了 TSocket 构造器第三个参数,该参数实际是 unix_socket 路径。改为 sock = TSocket.TSocket(host, port) 后用 sock.setTimeout(3000) 设置超时,实测有效。
Q6: 如何确认服务端 JVM 堆参数已按预期生效?
先以 jps -lv 查看进程参数,输出中直接包含 -Xms256m -Xmx512m;再用 ps -ef | grep ThriftServer 复核完整启动命令,两路验证一致即为生效。
七、参考链接
- 官方 Java 教程:https://thrift.apache.org/tutorial/java
- 官方通用教程:https://thrift.apache.org/tutorial/
- IDL 规范:https://thrift.apache.org/docs/idl
- Java 库说明:https://thrift.apache.org/lib/java
- Maven Central 坐标:https://central.sonatype.com/artifact/org.apache.thrift/libthrift