ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

跨语言RPC框架Thrift实战

跨语言RPC框架Thrift实战 Thrift框架定义Apache Thrift是一款由Facebook于2007年开源、后捐赠给Apache基金会的跨语言RPC远程过程调用框架。它通过一套独立的接口定义语言IDL来定义数据类型和服务接口并借助代码生成引擎在多种编程语言之间生成高效的网络通信代码。核心定位Thrift不只是一个通信协议更是一套完整的服务治理与通信解决方案涵盖了数据序列化、传输层协议、服务端/客户端模型以及多语言运行时支持。关键组成IDL编译器将.thrift文件编译成目标语言的源码。语言运行时库为各语言提供序列化、传输、IO处理的基础能力。传输与协议层可插拔的传输方式如Socket、HTTP和协议格式如Binary、Compact、JSON。Thrift框架优点2.1 跨语言支持能力突出支持超过25种编程语言包括C、Java、Python、Go、Node.js、PHP、Ruby等使异构系统间的服务调用变得透明且高效。2.2 性能优异资源开销低二进制序列化默认采用二进制编码数据体积小解析速度快。紧凑协议Compact Protocol进一步压缩整数和字符串节省带宽。相比于基于文本的协议如HTTPJSONThrift在CPU和内存消耗上具有明显优势。2.3 灵活的传输层与协议层设计通过分层架构Transport → Protocol → Processor → Server开发者可以自由组合传输方式阻塞Socket、非阻塞Socket、HTTP等。协议格式Binary、Compact、JSON、Multiplexed多服务复用。2.4 支持多种服务模型内置单线程、线程池、非阻塞NIO等多种服务端模式可适配不同并发场景。2.5 接口定义与实现解耦IDL文件作为“契约”将服务接口与具体实现彻底分离有利于前后端并行开发和版本管理。2.6为什么选择Thrift 而不选择Http对比维度ThriftHTTPREST/JSON协议类型二进制RPC协议可选JSON基于文本的HTTP/1.1或HTTP/2序列化效率高Binary/Compact体积小低JSON文本冗余大解析速度快无需复杂字符串解析慢需词法/语法解析跨语言性原生支持多语言代码生成依赖手动序列化/反序列化服务治理内置接口契约强类型约束需依赖OpenAPI等外部文档适用场景高性能微服务、高频内部调用对外API、浏览器/移动端兼容、缓存穿透敏感场景学习成本需学习IDL语法和框架机制通用标准上手容易选择建议内部服务间高并发、低延迟调用→ 优先Thrift。对外暴露API、需要浏览器或第三方系统兼容→ 优先HTTP。混合场景可在网关层提供HTTP接入后端内部使用Thrift。lDL详解IDLInterface Definition Language是Thrift的核心契约工具用于独立于具体编程语言描述服务接口和数据结构。作用作为服务提供方与消费方之间的共同约定。通过编译器自动生成各语言的服务端骨架Skeleton和客户端代理Stub。保证不同语言间的数据格式与调用方法一致。文件结构一个标准的.thrift文件基本上包含namespace → 定义生成代码的包/命名空间include → 引用其他thrift文件typedef → 类型别名常量定义结构体struct → 复杂数据类型枚举enum → 枚举类型异常exception → 自定义异常服务service → 接口方法定义IDL语法注释支持C/C风格//和/* */。命名空间namespace 语言 路径如namespace java com.example.demo。文件引入include shared.thrift。类型定义使用typedef为已有类型起别名如typedef i64 UserId。常量const i32 MAX_RETRY 3。必填与可选字段可标记为required必须传递或optional可选未标记时默认为optional但带有默认值行为。基本数据类型类型说明对应Java类型示例bool布尔值booleanbyte有符号8位整数bytei16有符号16位整数shorti32有符号32位整数inti64有符号64位整数longdouble64位浮点数doublestringUTF-8编码字符串Stringbinary字节序列BlobByteBuffer / byte[]集合数据类型类型定义方式说明listTlisti32有序列表可重复setTsetstring无序集合不可重复mapK,Vmapstring, i64键值对映射键类型可为基本类型定义常量// 数值型常量 const i32 DEFAULT_TIMEOUT 3000; const i64 MAX_FILE_SIZE 10485760; // 10MB const double PI 3.1415926; // 布尔常量 const bool ENABLE_CACHE true; const bool IS_DEBUG_MODE false; // 字符串常量 const string DEFAULT_ENCODING UTF-8; const string SERVICE_VERSION v2.1.0;Struct类型struct是Thrift中最核心的复合数据类型用于定义一组相关的字段集合类似于编程语言中的类Class或POJOPlain Old Java Object。结构体是Thrift中进行数据传输的基本单元几乎所有服务方法的请求参数和响应结果都通过结构体来承载。语法格式struct 结构体名称 { 字段序号: 可选性 字段类型 字段名 [ 默认值] [逗号或分号可选] }字段组成要素注意标有 required 的字段为必填项提交时请务必完整填写。官方不推荐用 required使用 required会固化接口契约限制接口的演进能力。一旦服务端变更容易导致老客户端调用失败引发线上事故。因此官方不推荐使用 required更推荐运行时条件校验的方式以保持接口的灵活性与兼容性。要素说明是否必须字段序号正整数从1开始用于序列化时的字段标识✅ 必须可选性required/optional/ 未标注默认optional❌ 可选字段类型基本类型、容器类型、其他struct、enum等✅ 必须字段名小驼峰命名如userName、userId✅ 必须默认值当字段未传递时使用的值❌ 可选使用案例// 用户信息结构体 struct User { 1: required i64 userId, // 必填字段 2: required string userName, // 必填字段 3: optional string email, // 可选字段 4: optional i32 age 0, // 可选字段带默认值 5: optional string phoneNumber, // 可选字段 6: optional bool isActive true // 可选字段默认true }嵌套类型使用案例// 地址信息 struct Address { 1: required string province, 2: required string city, 3: required string district, 4: optional string detailAddress, 5: optional string zipCode } // 订单信息嵌套Address struct Order { 1: required i64 orderId, 2: required i64 userId, 3: required listOrderItem items, // 容器嵌套 4: required Address shippingAddress, // 结构体嵌套 5: optional string remark , 6: optional i64 createTime }枚举枚举Enum用于定义一组具名的常量集合表示某个字段只能取预定义的一组值之一。枚举在Thrift中会被编译成各语言原生的枚举类型如Java的enum、Python的Enum类等提供类型安全和代码可读性。他的数值都是i32的整数类型。语法格式enum 枚举名称 { 常量名1 整数值1, 常量名2 整数值2, ... }使用案例// 订单状态枚举 enum OrderStatus { PENDING 0, // 待支付 PAID 1, // 已支付 SHIPPED 2, // 已发货 COMPLETED 3, // 已完成 CANCELLED 4 // 已取消 } // 用户角色枚举 enum UserRole { GUEST 0, // 游客 USER 1, // 普通用户 VIP 2, // VIP用户 ADMIN 3, // 管理员 SUPER_ADMIN 4 // 超级管理员 }枚举类型如果不指定后面的数值是它默认是递增的但是实际开发中还是推荐使用显示指定的方式。enum MixEnum { A 1, B, // 自动为2 C 10, D, // 自动为11 E 5 // ❌ 错误值必须递增不能小于前一个5 11 }异常异常Exception是Thrift中用于定义服务端业务异常的特殊结构体。当服务端处理请求时发生业务错误如用户不存在、订单已取消等可以通过抛出异常的方式将错误信息传递给客户端。语法格式exception 异常名称 { 字段序号: 可选性 字段类型 字段名 [ 默认值], ... }使用示例// 基础业务异常 exception BusinessException { 1: required i32 errorCode, 2: required string errorMessage, 3: optional string detailInfo, 4: optional i64 timestamp } // 用户不存在异常 exception UserNotFoundException { 1: required i64 userId, 2: required string message 用户不存在 } // 参数验证异常 exception ValidationException { 1: required string fieldName, 2: required string reason, 3: optional string actualValue } //服务中可以抛出多种异常 service OrderService { Order createOrder(1: CreateOrderRequest request) throws (1: UserException e, 2: OrderException e, 3: BaseException e) }Service服务定义类型Service服务是Thrift IDL中最核心的顶层定义用于声明一组远程可调用的方法接口。Service相当于传统面向对象编程中的接口Interface定义了服务端能够提供的所有RPC方法包括方法名、参数类型、返回值类型以及可能抛出的异常。核心定位Service是客户端与服务端之间的通信契约客户端通过Service定义生成代理Proxy/Stub服务端通过Service定义生成骨架Skeleton双方基于此契约进行透明的远程调用。语法格式service 服务名称 [extends 父服务名称] { 返回值类型 方法名(参数列表) [throws (异常列表)] 返回值类型 方法名(参数列表) [throws (异常列表)] ... }使用案例展示// 定义一个简单的服务包含一个方法 service HelloService { // 无参数返回字符串 string sayHello(), // 带参数返回字符串 string sayHelloTo(1: string name), // 带多个参数返回字符串类型 string sayHelloWithAge(1: string name, 2: i32 age) }namespace命名空间Namespace命名空间是Thrift IDL中用于指定生成代码的包名/模块名的关键字。它告诉Thrift编译器为不同编程语言生成的代码应该放在哪个包Package、命名空间Namespace或模块Module下。核心作用避免不同项目间的类名冲突组织和管理生成的代码结构符合各语言的项目规范和最佳实践语法格式namespace 语言标识 命名空间路径语言关键字命名空间格式生成代码示例Javajava包路径用.分隔package com.example.service;Pythonpy模块路径用.分隔生成在对应模块目录下Gogo包路径用/分隔package serviceCcpp命名空间用::分隔namespace com { namespace example { namespace service { } } }C#csharp命名空间用.分隔namespace Com.Example.ServicePHPphp命名空间用\分隔namespace Com\Example\Service;Node.jsjs模块路径生成在对应模块下Rubyrb模块路径module Com::Example::ServiceSwiftswift命名空间import ComExampleService使用案例// 为不同语言分别指定命名空间 namespace java com.example.thrift.demo namespace py com.example.thrift.demo namespace go com/example/thrift/demo namespace cpp com.example.thrift.demo namespace csharp Com.Example.Thrift.Demo namespace php Com\\Example\\Thrift\\Demo namespace js com.example.thrift.demo namespace rb Com::Example::Thrift::DemoThrift编译器核心作用Thrift编译器thrift命令是整个Thrift框架的核心引擎。它的作用可以概括为一句话把语言无关的IDL定义翻译成各语言专用的代码。具体来说它的核心功能就是根据.thrift文件自动生成你所需要的编程语言的代码。这些生成的代码替你完成了所有“脏活累活”包括数据结构的序列化与反序列化逻辑把你定义的struct变成自带读写方法的类。RPC服务的基础框架生成服务端骨架和客户端调用代理。这样一来你就不用手动编写大量繁琐的、用于网络传输和对象转换的样板代码可以把精力完全集中在业务逻辑上。安装安装Thrift编译器根据你的操作系统选择:Apache Thrift - Index of install/ 这个时下载的软件大家可以按着自己的操作系统进行安装Apache Download Mirrors windows的用户直接在这里下载就可以了。但是需要配置环境变量配置之后可以执行命令thrift --version如果这里显示失败的话就需要将我们的thrift-版本号.exe文件。请确保把它重命名为thrift.exe。执行就可以成功的执行了。编译编译命令thrift -gen 语言 你的IDL文件名.thrift //使用 -o 指定将文件生成到哪个文件夹下 thrift -gen java -o ./src/main/java user_service.thrift尝试执行//指定将文件放到哪里 namespace java com.hdk.rpc //执行之后创建出User类 struct User { 1: i32 id; 2: string name; 3: string password; 4: i32 age; } //创建出服务 service UserService { User getUser(1: string name); User getUserByPassword(1: string password); }执行命令之后编译出java代码Thrift协议协议决定了数据如何序列化/反序列化。就是把你的User对象变成二进制或 JSON 格式以及反过来。常用协议类型协议说明特点TBinaryProtocol二进制格式Thrift 默认协议高效、紧凑但不可读TCompactProtocol压缩二进制格式比 Binary 更省空间推荐使用TJSONProtocolJSON 格式可读性好适合调试或跨语言TSimpleJSONProtocol简化 JSON只写不读用于输出调试日志TDebugProtocol调试用可读文本开发测试时使用当前介绍了我们可以使用的协议类型但是数据的格式需要服务端和客户端统一才可以序列化和反序列化成功下面问大家展示一下在代码中是如何使用的。客户端构建方式TProtocol protocol new TBinaryProtocol(transport); //对于其他类型的其实就是换一个类 new就可以了服务端构建方式在服务端进行创建的时候我们要创建一个工厂的方式。TBinaryProtocol.Factory factory new TBinaryProtocol.Factory(); //对于其他类型的其实就是换一个类 new就可以了Thrift传输层传输层决定了数据如何从 A 点送到 B 点。它封装了底层的 I/O 操作屏蔽了网络细节。常用传输层类型传输层工作原理适用场景注意点TSocket阻塞式Socket一个连接一个线程开发测试、小流量内部工具QPS100连接数多时性能差TFramedTransport数据前加4字节长度头配合NIO线上高并发RPC、微服务调用非阻塞服务必须用这个两端要统一TMemoryTransport纯内存读写无网络I/O单元测试、序列化测试不能跨进程通信TFileTransport写入本地文件请求审计日志、流量回放受磁盘I/O限制TZlibTransportZlib压缩/解压跨机房调用、公网传输、大数据量CPU换带宽两端都要开启THttpTransport封装成HTTP POST请求穿透防火墙80/443端口、浏览器端调用HTTP头有额外开销客户端创建方式//TSocket TTransport transport new TSocket(localhost, 8080); //TFramedTransport TTransport transport new TSocket(localhost, 8080); TFramedTransport tFramedTransport new TFramedTransport(transport, 2048);服务端创建方式TServerTransport serverTransport new TServerSocket(8080); //TFramedTransport TServer.Args arg new TSimpleServer.Args(serverTransport) .transportFactory(new TFramedTransport.Factory()) // ← 这一步 .processor(userServiceProcessor) .protocolFactory(factory);重要规则协议和传输层必须匹配服务端客户端结果TBinaryProtocol TSocketTBinaryProtocol TSocket✅ 正常工作TCompactProtocol TSocketTCompactProtocol TSocket✅ 正常工作TBinaryProtocol TFramedTransportTBinaryProtocol TFramedTransport✅ 正常工作TCompactProtocol TSocketTBinaryProtocol TSocket❌ 协议不匹配报错TBinaryProtocol TSocketTBinaryProtocol TFramedTransport❌ 传输层不匹配报错快速开始接下来在这里为大家展示一个使用thrift的开发学习demo.下面的demo只是让大家感受一下。创建.thrift文件//指定将文件放到哪里 namespace java com.hdk.rpc //执行之后创建出User类 struct User { 1: i32 id; 2: string name; 3: string password; 4: i32 age; } //创建出服务 service UserService { User getUser(1: string name); User getUserByPassword(1: string password); }通过命令进行文件编译。编译之后将生成的文件copy到项目中我在这里直接将文件copy到了thrift-api文件中使用这个文件用户在实际开发中进行客户端和服务端之间的通信。服务端需要实现的Iface接口客户端使用client进行开发。引入依赖dependency groupIdorg.apache.thrift/groupId artifactIdlibthrift/artifactId version0.24.0/version /dependency服务端应用创建服务端的应用这个服务端的应用就是我们在实际开发中的server端。服务端中实现UserService.Iface接口public class UserServiceImpl implements UserService.Iface{ Override public User getUser(String name) throws TException { if(name null ||.equals(name)){ throw new RuntimeException(用户名不能为空); } return new User(new Random().nextInt(10000),name,123456,new Random().nextInt(100)); } Override public User getUserByPassword(String password) throws TException { if(password null ||.equals(password)){ throw new RuntimeException(密码不能为空); } return new User(new Random().nextInt(10000),getName(),123456,new Random().nextInt(100)); } private String getName() { String[] firstName {张,王,李,赵,刘,陈,李,章,璋}; String[] lastName {三,四,五,六,七,八,九,十}; return firstName[new Random().nextInt(firstName.length)]lastName[new Random().nextInt(lastName.length)]; } }客户端应用在实际开发中客户端通过依赖thrift-api模块中的接口定义使用生成的Client类与服务端进行通信。整个调用过程对开发者透明就像调用本地方法一样简单。public static void main(String[] args) throws TTransportException { // 1. 创建传输层监听 8080 端口等待客户端连接 TServerTransport serverTransport new TServerSocket(8080); // 2. 创建处理器将客户端请求路由到 UserServiceImpl 业务实现 UserService.ProcessorUserServiceImpl userServiceProcessor new UserService.Processor(new UserServiceImpl()); // 3. 创建协议工厂为什么用工厂 // 客户端只有 1 条连接 → 直接 new TBinaryProtocol(transport) // 服务端要处理 N 条并发连接 → 框架每 accept 一个连接就调用 factory.getProtocol(transport) // 工厂模式让框架控制创建时机为每条连接生成独立的 Protocol 实例 TBinaryProtocol.Factory factory new TBinaryProtocol.Factory(); // 4. 组装 TSimpleServer 参数传输 处理器 协议 TServer.Args arg new TSimpleServer.Args(serverTransport) .processor(userServiceProcessor) .protocolFactory(factory); // 5. 创建并启动服务端阻塞当前线程持续接收请求 TSimpleServer tSimpleServer new TSimpleServer(arg); tSimpleServer.serve(); }网络服务模型详解Thrift 提供了多种网络服务模型用于满足不同场景下的性能需求。从不同维度可以划分为线程模型维度单线程、多线程、事件驱动I/O 模型维度阻塞 I/O、非阻塞 I/OThrift 四种服务模型对比Server 模型I/O 模式处理线程模型一句话总结生产推荐TSimpleServer阻塞单线程一次一个纯玩具只适合测试❌TThreadPoolServer阻塞线程池一连接一线程连接多了线程数爆炸C10K扛不住⚠️ 谨慎使用TNonblockingServer非阻塞单线程NIO收发包但业务也在这个线程处理一阻塞全卡❌THsHaServer非阻塞线程池NIO Worker线程池解决了业务阻塞问题但 Selector 还是只有一个✅ 还行TThreadedSelectorServer非阻塞线程池多Selector Worker池I/O读写也并发吞吐最高✅✅最优解各层职责层级职责关注点服务模型如何处理并发请求线程策略线程、并发、性能传输层数据如何通过网络收发Socket、连接、网络协议层数据如何编码解码序列化格式、兼容性三层组合矩阵服务模型 × 传输层服务模型可用传输层说明TSimpleServerTSocket阻塞传输TThreadPoolServerTSocket阻塞传输TNonblockingServerTNonblockingSocket必须用非阻塞传输THsHaServerTNonblockingSocket必须用非阻塞传输服务模型 × 协议层完全自由组合服务模型可用协议层说明TSimpleServer任意协议✅ 自由组合TThreadPoolServer任意协议✅ 自由组合TNonblockingServer任意协议✅ 自由组合THsHaServer任意协议✅ 自由组合
返回列表