SpringBoot與Apache Thrift整合,解決不同編程語言的微服務通信問題
作者:Java知識日歷
Apache Thrift是一種跨語言的服務開發框架,由 Facebook 開發并捐贈給 Apache 基金會。它允許開發者使用一種定義接口的方式,生成不同編程語言的代碼,從而實現不同語言之間的高效通信。
Apache Thrift是一種跨語言的服務開發框架,由 Facebook 開發并捐贈給 Apache 基金會。它允許開發者使用一種定義接口的方式,生成不同編程語言的代碼,從而實現不同語言之間的高效通信。
哪些公司使用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代碼實操
<!-- pom.xml -->
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/><!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Detailed demo project for Spring Boot and Apache Thrift integration</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.18.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
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<String> 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!
責任編輯:武曉燕
來源:
Java知識日歷