Apache Thrift - 實現服務



在 Apache Thrift 中實現服務

Apache Thrift 允許您在介面定義語言 (IDL) 中定義服務和資料型別,併為各種程式語言生成程式碼。典型的服務實現涉及提供服務的伺服器和使用服務的客戶端。

本教程將引導您完成使用生成的程式碼實現服務的流程,重點介紹伺服器端和客戶端的實現。

設定您的環境

在實現服務之前,請確保您擁有以下內容

  • Apache Thrift 編譯器:已安裝並配置。您可以從 Apache Thrift 網站下載它。
  • 生成的程式碼:使用 Thrift 編譯器為您的目標程式語言生成必要的程式碼。
  • 程式設計環境:使用適當的依賴項(例如,Java、Python 等的 Thrift 庫)設定您的程式設計環境。

生成服務程式碼

在 Thrift IDL 檔案中定義服務後,下一步是在目標程式語言中為伺服器和客戶端生成相應的程式碼。

此程式碼生成過程非常重要,因為它提供了在伺服器端實現服務邏輯和在客戶端與服務互動所需的類和介面。

瞭解 Thrift 編譯器的作用

Thrift 編譯器(“thrift”命令)是一個讀取 Thrift IDL 檔案並在您指定的程式語言中生成程式碼的工具。此生成的程式碼包括以下內容

  • 資料結構:對應於 IDL 檔案中定義的結構體、列舉、聯合體和其他資料型別的類或型別。
  • 服務介面:IDL 中定義的每個服務的介面或基類,您必須在伺服器應用程式中實現這些介面或基類。
  • 客戶端存根:客戶端類,提供透過呼叫服務中定義的遠端過程來與伺服器互動的方法。

示例:Thrift IDL 檔案

以下 Thrift IDL 檔案定義了一個“User”結構體、一個具有兩種方法的“UserService”服務和一個“UserNotFoundException”異常

namespace java com.example.thrift
namespace py example.thrift

struct User {
  1: i32 id
  2: string name
  3: bool isActive
}

service UserService {
  User getUserById(1: i32 id) throws (1: UserNotFoundException e)
  void updateUser(1: User user)
}

exception UserNotFoundException {
  1: string message
}

使用 Thrift 編譯器生成程式碼

thrift --gen java example.thrift
thrift --gen py example.thrift

這將生成您將用於實現服務的 Java 和 Python 中的必要類和介面。

在 Java 中實現服務

從 Thrift IDL 檔案生成必要的 Java 程式碼後,下一步是實現服務。這涉及建立處理客戶端請求的伺服器端邏輯以及開發與服務互動的客戶端程式碼。

伺服器端實現

在伺服器端實現中,您首先需要實現服務介面:Thrift 編譯器為每個服務生成一個 Java 介面。實現此介面以定義服務的行為

public class UserServiceHandler implements UserService.Iface {
   @Override
   public User getUserById(int id) throws UserNotFoundException, TException {
      // Implement the logic to retrieve the user by ID
      if (id == 1) {
         return new User(id, "John Doe", true);
      } else {
         throw new UserNotFoundException("User not found");
      }
   }
   @Override
   public void updateUser(User user) throws TException {
      // Implement the logic to update the user
      System.out.println("Updating user: " + user.name);
   }
}

然後,我們需要設定伺服器:建立一個伺服器,偵聽客戶端請求並呼叫服務處理程式上的適當方法

public class UserServiceServer {
   public static void main(String[] args) {
      try {
         UserServiceHandler handler = new UserServiceHandler();
         UserService.Processor<UserServiceHandler> processor = new UserService.Processor<>(handler);
         TServerTransport serverTransport = new TServerSocket(9090);
         TServer server = new TSimpleServer(new TServer.Args(serverTransport).processor(processor));

         System.out.println("Starting the server...");
         server.serve();
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}

其中,

  • 伺服器傳輸:指定通訊傳輸(例如,套接字)。
  • 處理器:透過將其委託給服務處理程式來處理傳入請求。
  • 伺服器:伺服器偵聽請求並將它們傳遞給處理器。

客戶端實現

在客戶端實現中,您首先需要建立客戶端:Thrift 編譯器為每個服務生成一個客戶端類。使用此類來呼叫伺服器上的方法

public class UserServiceClient {
   public static void main(String[] args) {
      try {
         TTransport transport = new TSocket("localhost", 9090);
         transport.open();

         TProtocol protocol = new TBinaryProtocol(transport);
         UserService.Client client = new UserService.Client(protocol);

         User user = client.getUserById(1);
         System.out.println("User retrieved: " + user.name);

         user.isActive = false;
         client.updateUser(user);

         transport.close();
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}

其中,

  • 傳輸:管理與伺服器的連線。
  • 協議:指定資料如何序列化(例如,二進位制協議)。
  • 客戶端:提供呼叫遠端服務的方法。

在 Python 中實現服務

在 Python 中實現 Thrift 服務時,該過程涉及幾個類似於 Java 等其他語言中的步驟。

您將需要實現服務邏輯,設定伺服器以處理客戶端請求,並確保服務平穩執行。

伺服器端實現

在伺服器端實現中,您首先需要實現服務介面:在 Python 中,Thrift 編譯器為每個服務生成一個基類。子類化此基類以實現您的服務邏輯

from example.thrift.UserService import Iface
from example.thrift.ttypes import User, UserNotFoundException

class UserServiceHandler(Iface):
   def getUserById(self, id):
      if id == 1:
         return User(id=1, name="John Doe", isActive=True)
      else:
         raise UserNotFoundException(message="User not found")

   def updateUser(self, user):
      print(f"Updating user: {user.name}")

然後,我們需要設定伺服器:建立一個 Thrift 伺服器以偵聽傳入請求並將它們傳遞給服務處理程式

from thrift.Thrift import TProcessor
from thrift.transport import TSocket, TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TSimpleServer
from example.thrift.UserService import Processor

if __name__ == "__main__":
   handler = UserServiceHandler()
   processor = Processor(handler)
   transport = TSocket.TServerSocket(port=9090)
   tfactory = TTransport.TBufferedTransportFactory()
   pfactory = TBinaryProtocol.TBinaryProtocolFactory()

   server = TSimpleServer(processor, transport, tfactory, pfactory)

   print("Starting the server...")
   server.serve()

其中,

  • 處理器:管理請求到處理程式的委託。
  • 傳輸和協議工廠:設定伺服器的通訊和資料序列化方法。
  • 伺服器:啟動伺服器以處理客戶端請求。

客戶端實現

在客戶端實現中,您首先需要建立客戶端:使用生成的客戶端類連線到伺服器並呼叫其方法

from thrift.transport import TSocket, TTransport
from thrift.protocol import TBinaryProtocol
from example.thrift.UserService import Client

if __name__ == "__main__":
   transport = TSocket.TSocket('localhost', 9090)
   transport = TTransport.TBufferedTransport(transport)
   protocol = TBinaryProtocol.TBinaryProtocol(transport)
   client = Client(protocol)

   transport.open()

   try:
      user = client.getUserById(1)
      print(f"User retrieved: {user.name}")

      user.isActive = False
      client.updateUser(user)
   except Exception as e:
      print(f"Error: {e}")

   transport.close()

其中,

  • 傳輸和協議:管理通訊和資料格式。
  • 客戶端:提供對遠端服務的介面,允許您呼叫伺服器上的方法。

處理異常

正確處理異常可確保您的服務能夠平穩地管理錯誤並向客戶端提供有意義的反饋。

在 Apache Thrift 中,可以在 IDL 檔案中定義異常,並在服務實現和客戶端程式碼中進行處理。處理異常包括

  • 在 Thrift IDL 中定義異常:在 Thrift IDL 檔案中指定異常,以便伺服器和客戶端都瞭解可能發生的錯誤型別。
  • 在服務實現中丟擲異常:在服務方法中實現邏輯,以便在必要時丟擲異常。
  • 在伺服器端處理異常:在伺服器實現中管理異常,以確保服務能夠從錯誤中恢復並提供有意義的響應。
  • 在客戶端處理異常:在客戶端程式碼中實現錯誤處理,以管理伺服器丟擲的異常並做出相應的響應。

在 Thrift IDL 中定義異常

使用exception關鍵字在 Thrift IDL 檔案中定義異常。您可以指定服務方法可以丟擲的自定義異常型別

示例:帶有異常的 Thrift IDL 檔案

exception InvalidOperationException {
   1: string message
}

service CalculatorService {
   i32 add(1: i32 num1, 2: i32 num2) throws (1: InvalidOperationException e)
   i32 divide(1: i32 num1, 2: i32 num2) throws (1: InvalidOperationException e)
}

其中,

  • 異常定義:“InvalidOperationException”是一個帶有單個欄位“message”的自定義異常。
  • 方法簽名:“add”和“divide”方法指定丟擲“InvalidOperationException”。使用“throws”關鍵字在方法簽名中包含異常。

在服務實現中丟擲異常

在您的服務實現中,您需要根據方法的邏輯丟擲異常。這涉及使用 IDL 中定義的異常

from thrift.Thrift import TException

class InvalidOperationException(TException):
   def __init__(self, message):
      self.message = message

class CalculatorServiceHandler:
   def add(self, num1, num2):
      return num1 + num2

   def divide(self, num1, num2):
      if num2 == 0:
         raise InvalidOperationException("Cannot divide by zero")
      return num1 / num2

其中,

  • 自定義異常類:“InvalidOperationException”繼承自“TException”幷包含一個“message”屬性。
  • 丟擲異常:在“divide”方法中,如果除數為零,則會引發“InvalidOperationException”。

在伺服器端處理異常

在伺服器端,您應該處理異常以確保服務能夠管理錯誤並提供適當的響應。

Python 伺服器程式碼中的異常處理

from thrift.server import TSimpleServer
from thrift.transport import TSocket, TTransport
from thrift.protocol import TBinaryProtocol
from calculator_service import CalculatorService, CalculatorServiceHandler

if __name__ == "__main__":
   handler = CalculatorServiceHandler()
   processor = CalculatorService.Processor(handler)
   transport = TSocket.TServerSocket(port=9090)
   tfactory = TTransport.TBufferedTransportFactory()
   pfactory = TBinaryProtocol.TBinaryProtocolFactory()

   server = TSimpleServer.TSimpleServer(processor, transport, tfactory, pfactory)

   print("Starting the Calculator service on port 9090...")
   try:
      server.serve()
   except InvalidOperationException as e:
      print(f"Handled exception: {e.message}")
   except Exception as e:
      print(f"Unexpected error: {str(e)}")

其中,

  • 異常處理塊:“try”塊啟動伺服器,“except”塊處理異常。“InvalidOperationException”被顯式捕獲並處理,而其他異常則被通用“Exception”塊捕獲。

在客戶端處理異常

在客戶端,您需要處理伺服器丟擲的異常。這可確保客戶端能夠管理錯誤並做出相應的反應。

帶有異常處理的 Python 客戶端程式碼示例

from thrift.transport import TSocket, TTransport
from thrift.protocol import TBinaryProtocol
from calculator_service import CalculatorService, InvalidOperationException

try:
   transport = TSocket.TSocket('localhost', 9090)
   transport = TTransport.TBufferedTransport(transport)
   protocol = TBinaryProtocol.TBinaryProtocol(transport)
   client = CalculatorService.Client(protocol)

   transport.open()
    
   try:
      result = client.divide(10, 0)  # This will raise an exception
   except InvalidOperationException as e:
      print(f"Exception caught from server: {e.message}")
   finally:
      transport.close()

except Exception as e:
   print(f"Client-side error: {str(e)}")

其中,

  • 異常處理塊:“try”塊圍繞與伺服器互動的程式碼。“except”塊捕獲伺服器丟擲的“InvalidOperationException”,而通用“Exception”塊處理任何客戶端錯誤。

同步與非同步處理

在服務架構中,任務的處理和執行方式會顯著影響效能、響應能力和使用者體驗。

同步和非同步處理是兩種基本方法,它們在處理操作的方式上有所不同,尤其是在網路或分散式系統中。

同步處理

同步處理是一種按順序執行任務的方法。在此模型中,每個任務必須在下一個任務開始之前完成。這意味著系統在繼續執行下一個操作之前會等待一個操作完成。

以下是同步處理的特點

  • 阻塞呼叫:每個操作都會阻塞後續操作的執行,直到它完成。例如,如果呼叫服務方法,則呼叫方會在方法返回結果之前等待。
  • 簡單的流程:執行流程簡單易懂,因為操作是逐個執行的。它更容易實現和除錯,因為程式碼按線性順序執行。
  • 可預測的效能:效能是可預測的,因為操作按請求順序完成。
  • 資源利用率:如果操作正在等待外部資源(例如,網路響應),則可能導致資源利用率低下,因為系統在此期間處於空閒狀態。

示例

考慮一個同步 Thrift 服務實現,其中客戶端呼叫一個方法,伺服器處理請求並在客戶端可以繼續之前返回結果

# Client-side synchronous call
# Client waits until the server responds with the result
result = client.add(5, 10)  
print(f"Result: {result}")

在此示例中,對“client.add”的客戶端呼叫會阻塞,直到伺服器響應結果。客戶端在等待時無法執行其他任務。

非同步處理

非同步處理允許任務同時執行,而不會阻塞其他任務的執行。在此模型中,可以啟動操作,然後獨立於主執行流程執行。

以下是非同步處理的特徵

  • 非阻塞呼叫:啟動操作並在後臺執行,允許主執行緒或程序繼續執行其他任務。例如,服務方法呼叫可以立即返回,而操作在後臺完成。
  • 複雜流程:執行流程可能更復雜,因為任務是同時處理的。這通常需要回調、Promise 或 Future 物件來管理完成。
  • 效能提升:非同步處理可以透過更有效地利用系統資源來提高效能,尤其是在 I/O 密集型操作中,這些操作通常需要等待外部響應。
  • 併發性:允許同時執行多個任務,這在高延遲環境或處理許多併發請求時非常有用。

示例

考慮一個非同步 Thrift 服務實現,其中客戶端在等待伺服器響應時不會阻塞

import asyncio
from thrift.transport import TSocket, TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TAsyncServer

async def call_add(client):
   result = await client.add(5, 10)  # Non-blocking call
   print(f"Result: {result}")

async def main():
   transport = TSocket.TSocket('localhost', 9090)
   transport = TTransport.TBufferedTransport(transport)
   protocol = TBinaryProtocol.TBinaryProtocol(transport)
   client = CalculatorService.Client(protocol)

   await transport.open()
   await call_add(client)
   await transport.close()

asyncio.run(main())

在此示例中,“call_add”是一個非同步函式,它不會阻塞其他任務的執行。“await”關鍵字用於執行對“client.add”的非阻塞呼叫,允許程式繼續執行其他程式碼。

廣告

© . All rights reserved.