Apache Thrift 快速入门与集成示例

以 Java 为例提供 Apache Thrift 0.25.0 端到端集成示例:编写 IDL、编译器生成代码、Maven 工程构建、TSimpleServer 服务端与客户端 RPC 调用完整回显,并扩展线程池服务端与跨语言互调场景。

Apache ThriftApache Thrift2026/10/12

Apache Thrift 快速入门与集成示例

一、环境信息与版本说明

Apache Thrift 通过 IDL(接口定义语言)一次定义服务契约,编译器生成各语言代码,运行时库负责序列化与网络传输。本文以 Java 语言为主线给出可直接落地的完整工程,并附 Python 跨语言互调实测,所有命令与回显均在真实环境实测采集。

项目说明
Thrift 版本0.25.0(编译器与 libthrift 同版本)
操作系统Ubuntu 22.04 / CentOS 8(双环境实测结果一致)
JDKOpenJDK 17
Maven3.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 复核完整启动命令,两路验证一致即为生效。

七、参考链接