哪些公司使用Apache Thrift?
Facebook:
- Facebook 是 Apache Thrift 的最初开发者之一,广泛用于其内部的微服务通信。
- 通过 Thrift,Facebook 能够实现不同语言之间的高效通信,并支持大规模的分布式系统。
Baidu:
- Baidu 使用 Thrift 来构建其搜索引擎和其他在线服务。
- Thrift 的高效性和可扩展性满足了 Baidu 对高性能的需求。
Yahoo!:
- Yahoo! 在其多个项目中使用 Thrift,包括搜索、广告和其他核心服务。
- Thrift 的可靠性和成熟度使其成为 Yahoo! 技术选择的一部分。
Twitter:
- Twitter 使用 Thrift 来处理大量的实时数据流和 API 请求。
- Thrift 的跨语言特性和高效的序列化能力帮助 Twitter 构建了高性能的服务架构。
Airbnb:
- Airbnb 利用 Thrift 实现其内部服务间的通信,特别是在需要高吞吐量和低延迟的应用场景中。
- Thrift 的灵活性和可扩展性使其成为 Airbnb 技术栈的重要组成部分。
Uber:
- Uber 在其后端服务中广泛使用 Thrift 来管理微服务之间的通信。
- Thrift 的高效性和可靠性确保了 Uber 系统的稳定运行。
Yelp:
- Yelp 使用 Thrift 来管理其评论、搜索和其他关键服务之间的通信。
- Thrift 的高效性和灵活性提升了 Yelp 的整体性能和可靠性。
选择Apache Thrift的好处
提高开发效率:
- 自动代码生成:Thrift 编译器根据 IDL 文件自动生成客户端和服务端代码,减少手动编码的工作量。
- 一致的数据结构:通过统一的 IDL 文件定义数据结构,确保前后端数据的一致性。
性能优化:
- 低延迟:由于使用高效的二进制协议和灵活的传输层,Thrift 在高性能场景下表现出色。
- 资源利用率高:较少的 CPU 和内存开销,适合大规模部署。
跨语言支持:
- 多语言兼容性:Thrift 支持多种编程语言(如 Java、C++、Python、PHP、Ruby、JavaScript、Node.js、Go、Delphi 等),这对于需要在不同语言之间进行通信的分布式系统尤为重要。
- 简化开发:开发者可以使用自己熟悉的语言编写服务端和客户端代码,而不需要担心底层协议的复杂性。
高效的序列化和反序列化:
- 二进制协议:Thrift 使用高效的二进制协议进行数据传输,相比于 JSON 或 XML 更加紧凑,减少了带宽消耗并提高了传输速度。
- 多种协议选项:除了默认的二进制协议外,Thrift 还提供了其他协议选项(如 TCompactProtocol、TJSONProtocol),可以根据具体需求选择合适的协议。
灵活的传输层:
- 多种传输方式:Thrift 支持多种传输层实现,包括 TCP/IP、HTTP、内存缓冲区等,可以轻松集成到现有的网络架构中。
- 可扩展性:易于扩展和定制,可以根据项目需求调整传输层的行为。
安全性:
- 加密传输:可以通过 SSL/TLS 加密传输层,保护数据安全。
- 身份验证和授权:结合其他安全机制,可以实现细粒度的身份验证和授权控制。
RPC 模式:
- 同步和异步调用:Thrift 支持同步和异步 RPC 调用,适用于各种客户端-服务器模型的应用场景。
- 简单易用:通过定义 IDL 文件,可以快速生成客户端和服务端代码,减少重复工作。
创建service.thrift文件
首先,在项目的根目录下创建一个名为idl的文件夹,然后在其中创建service.thrift文件。
// idl/service.thrift
namespace java com.example.service
namespace go service
enum ErrorCode {
SUCCESS = 0, // 成功
UNKNOWN_ERROR = 1, // 未知错误
INVALID_REQUEST = 2, // 请求无效
}
struct Request {
1: required string message, // 请求消息
}
struct Response {
1: required ErrorCode errorCode, // 错误码
2: optional string errorMessage, // 错误信息
3: optional string result, // 结果
}
service MyService {
Response processRequest(1: Request request) throws (1: Exception e), // 处理请求的方法
}
使用Thrift编译器生成Java和Go代码
# 生成Java代码
thrift --gen java -out src/main/java idl/service.thrift
# 生成Go代码
thrift --gen go -out ./gen-go idl/service.thrift
之后,src/main/java目录下会生成Java代码,而在当前目录下生成gen-go文件夹,包含Go代码。
Java代码实操
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.7.5
com.example
demo
0.0.1-SNAPSHOT
demo
Detailed demo project for Spring Boot and Apache Thrift integration
11
org.springframework.boot
spring-boot-starter-web
org.apache.thrift
libthrift
0.18.1
org.slf4j
slf4j-api
ch.qos.logback
logback-classic
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-maven-plugin
Thrift客户端配置
// src/main/java/com/example/demo/config/ThriftConfig.java
package com.example.demo.config;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import com.example.service.MyService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Configuration
publicclass ThriftConfig {
privatestaticfinal Logger logger = LoggerFactory.getLogger(ThriftConfig.class);
@Bean
public MyService.Client thriftClient() {
// 创建Thrift传输层,连接到本地9090端口
TTransport transport = new TSocket("localhost", 9090);
try {
// 打开传输层连接
transport.open();
// 返回MyService客户端实例,使用二进制协议
returnnew MyService.Client(new TBinaryProtocol(transport));
} catch (TTransportException e) {
// 记录错误日志并抛出异常
logger.error("Failed to open Thrift client connection", e);
thrownew RuntimeException(e);
}
}
}
Controller
接收HTTP请求并通过Thrift客户端调用远程服务:
// src/main/java/com/example/demo/controller/ApiController.java
package com.example.demo.controller;
import com.example.service.Request;
import com.example.service.Response;
import com.example.service.MyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@RestController
@RequestMapping("/api")
publicclass ApiController {
privatestaticfinal Logger logger = LoggerFactory.getLogger(ApiController.class);
privatefinal MyService.Client client;
@Autowired
public ApiController(MyService.Client client) {
this.client = client;
}
@PostMapping("/process")
public ResponseEntity process(@RequestBody String message) {
// 创建请求对象
Request request = new Request(message);
try {
// 调用Thrift客户端的processRequest方法
Response response = client.processRequest(request);
// 根据响应结果返回不同的HTTP状态码和消息
if (response.getErrorCode() == com.example.service.ErrorCode.SUCCESS) {
returnnew ResponseEntity<>(response.getResult(), HttpStatus.OK);
} else {
logger.warn("Error processing request: {}", response.getErrorMessage());
returnnew ResponseEntity<>(response.getErrorMessage(), HttpStatus.BAD_REQUEST);
}
} catch (Exception e) {
// 捕获异常并记录错误日志,返回内部服务器错误
logger.error("Exception while processing request", e);
returnnew ResponseEntity<>("Internal Server Error", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
启动主类
// src/main/java/com/example/demo/DemoApplication.java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
实现Go的服务端
// gen-go/server.go
package main
import (
"context"
"fmt"
"log"
"net"
"github.com/apache/thrift/lib/go/thrift"
service "path/to/gen-go/service"
)
type myHandler struct{}
// ProcessRequest 处理请求的方法
func (h *myHandler) ProcessRequest(ctx context.Context, request *service.Request) (*service.Response, error) {
log.Printf("Received request: %s\n", request.Message)
iflen(request.Message) == 0 {
// 如果请求消息为空,返回INVALID_REQUEST错误
return &service.Response{
ErrorCode: service.ErrorCode_INVALID_REQUEST,
ErrorMessage: "Invalid request",
}, nil
}
// 返回成功响应
return &service.Response{
ErrorCode: service.ErrorCode_SUCCESS,
Result: fmt.Sprintf("Processed: %s", request.Message),
}, nil
}
func main() {
handler := &myHandler{}
processor := service.NewMyServiceProcessor(handler)
// 创建Thrift服务器监听9090端口
serverTransport, err := thrift.NewTServerSocket(":9090")
if err != nil {
log.Fatalf("Error opening server socket: %s", err.Error())
}
tFactory := thrift.NewTFramedTransportFactory(thrift.NewTBufferedTransportFactory(8192))
pFactory := thrift.NewTBinaryProtocolFactoryDefault()
// 创建Thrift简单服务器
server := thrift.NewTSimpleServer4(processor, serverTransport, tFactory, pFactory)
fmt.Println("Starting the simple server... on port 9090...")
err = server.Serve()
if err != nil {
log.Fatalf("Error starting Thrift server: %s", err.Error())
}
}
运行Go服务端
在终端中运行Go服务端:
cd gen-go
go run server.go
测试
curl -X POST http://localhost:8080/api/process -H "Content-Type: text/plain" -d "Hello from Go!"
Respons:
Processed: Hello from Go!