參考文章RPC 基本原理與 Apach Thrift 初體驗
RPC基本原理
RPC(Remote Procedure Call),遠程過程調用,大部分的RPC框架都遵循如下三個開發步驟:
1. 定義一個接口說明文件:描述了對象(結構體)、對象成員、接口方法等一系列信息;
2. 通過RPC框架所提供的編譯器,將接口說明文件編譯成具體的語言文件;
3. 在客戶端和服務器端分別引入RPC編譯器所生成的文件,即可像調用本地方法一樣調用服務端代碼;
RPC通信過程如下圖所示
通信過程包括以下幾個步驟:
1、客戶過程以正常方式調用客戶樁(client stub,一段代碼);
2、客戶樁生成一個消息,然后調用本地操作系統;
3、客戶端操作系統將消息發送給遠程操作系統;
4、遠程操作系統將消息交給服務器樁(server stub,一段代碼);
5、服務器樁將參數提取出來,然后調用服務器過程;
6、服務器執行要求的操作,操作完成后將結果返回給服務器樁;
7、服務器樁將結果打包成一個消息,然后調用本地操作系統;
8、服務器操作系統將含有結果的消息發送回客戶端操作系統;
9、客戶端操作系統將消息交給客戶樁;
10、客戶樁將結果從從消息中提取出來,返回給調用它的客戶過程;
所有這些步驟的效果是,將客戶過程對客戶樁發出的本地調用轉換成對服務器過程的本地調用,而客戶端和服務器都不會意識到有中間步驟的存在。
這個時候,你可能會想,既然是調用另一臺機器的服務,使用 RESTful API 也可以實現啊,為什么要選擇 RPC 呢?我們可以從兩個方面對比:
- 資源粒度。RPC 就像本地方法調用,RESTful API 每一次添加接口都可能需要額外地組織開放接口的數據,這相當于在應用視圖中再寫了一次方法調用,而且它還需要維護開發接口的資源粒度、權限等;
- 流量消耗。RESTful API 在應用層使用 HTTP 協議,哪怕使用輕型、高效、傳輸效率高的 JSON 也會消耗較大的流量,而 RPC 傳輸既可以使用 TCP 也可以使用 UDP,而且協議一般使用二制度編碼,大大降低了數據的大小,減少流量消耗。
對接異構第三方服務時,通常使用 HTPP/RESTful 等公有協議,對于內部的服務調用,應用選擇性能更高的二進制私有協議。
Thrift架構
thrift主要用于各個服務之間的RPC通信,支持跨語言。thrift是一個典型的CS結構,客戶端和服務端可以使用不同的語言開發,thrift通過IDL(Interface Description Language)來關聯客戶端和服務端。thrift的整體架構圖如下圖所示
圖中Your Code是用戶實現的業務邏輯,接下來的FooService.Client
和Foo.write()/read()
是thrift根據IDL生成的客戶端和服務端的代碼,對應于RPC中Client stub和Server stub。TProtocol 用來對數據進行序列化與反序列化,具體方法包括二進制,JSON 或者 Apache Thrift 定義的格式。TTransport 提供數據傳輸功能,使用 Apache Thrift 可以方便地定義一個服務并選擇不同的傳輸協議。
如下圖所示為thrift的網絡棧結構
thirft使用socket進行數據傳輸,數據以特定的格式發送,接收方進行解析。我們定義好thrift的IDL文件后,就可以使用thrift的編譯器來生成雙方語言的接口、model,在生成的model以及接口代碼中會有解碼編碼的代碼。
TTransport層
代表thrift的數據傳輸方式,thrift定義了如下幾種常用數據傳輸方式
TSocket
: 阻塞式socket;TFramedTransport
: 以frame為單位進行傳輸,非阻塞式服務中使用;TFileTransport
: 以文件形式進行傳輸;
TProtocol層
代表thrift客戶端和服務端之間傳輸數據的協議,通俗來講就是客戶端和服務端之間傳輸數據的格式(例如json等),thrift定義了如下幾種常見的格式
TBinaryProtocol
: 二進制格式;TCompactProtocol
: 壓縮格式;TJSONProtocol
: JSON格式;TSimpleJSONProtocol
: 提供只寫的JSON協議;
thrift支持的Server模型
thrift主要支持以下幾種服務模型
TSimpleServer
: 簡單的單線程服務模型,常用于測試;TThreadPoolServer
: 多線程服務模型,使用標準的阻塞式IO;TNonBlockingServer
: 多線程服務模型,使用非阻塞式IO(需要使用TFramedTransport
數據傳輸方式);THsHaServer
:THsHa
引入了線程池去處理,其模型讀寫任務放到線程池去處理,Half-sync/Half-async
處理模式,Half-async
是在處理IO事件上(accept/read/write io),Half-sync
用于handler對rpc的同步處理;
thrift IDL文件
thrift IDL不支持無符號的數據類型,因為很多編程語言中不存在無符號類型,thrift支持一下幾種基本的數據類型
byte
: 有符號字節i16
: 16位有符號整數i32
: 32位有符號整數i64
: 63位有符號整數double
: 64位浮點數string
: 字符串
此外thrift還支持以下容器類型:
list
: 一系列由T類型的數據組成的有序列表,元素可以重復;set
: 一系列由T類型的數據組成的無序集合,元素不可重復;map
: 一個字典結構,Key為K類型,Value為V類型,相當于java中的HashMap;
thrift容器中元素的類型可以是除了service
之外的任何類型,包括exception
thirft支持struct類型,目的就是講一些數據聚合在一起,方便傳輸管理,struct定義形式如下:
struct People {1:string name;2:i32 age;3:string gender;
}
thrift支持枚舉類型,定義形式如下:
enum Gender {MALE,FEMALE
}
thrift支持自定義異常類型exception,異常定義形式如下:
exception RequestException {1:i32 code;2:string reason;
}
thrift定義服務相當于Java中創建接口一樣,創建的service經過代碼生thrift代碼生成工具編譯后就會生成客戶端和服務端的框架代碼,service的定義形式如下:
service HelloWorldService {// service中可以定義若干個服務,相當于Java Interface中定義的方法string doAction(1:string name, 2:i32 age);
}
thrift支持給類型定義別名,如下所示:
typedef i32 int
typedef i64 long
thrift也支持常量的定義,使用const
關鍵字:
const i32 MAX_RETRIES_TIME = 10;
const string MY_WEBSITE = "http://facebook.com";
thrift支持命名空間,命名空間相當于Java中的package,主要用于組織代碼,thrift使用關鍵字namespace
定義命名空間,格式是namespace 語言名 路徑
,如下示例所示:
namespace java com.test.thrift.demo
thrift也支持文件包含,相當于CPP中的include
,Java中的import
,使用關鍵字include
:
include "global.thrift"
#
、//
、/**/
都可以作為thrift文件中的注釋。
thrift提供兩個關鍵字required
和optional
,分別用于表示對應的字段是必填的還是可選的(推薦盡量使用optional
),如下所示:
struct People {1:required string name;2:optional i32 age;
}
thrift應用示例
本示例中我們使用java編寫thrift的服務端程序,使用python編寫thrift的客戶端程序。
首先定義thrift IDL文件
// data.thrift
namespace java thrift.generated
namespace py py.thrift.generatedtypedef i16 short
typedef i32 int
typedef i64 long
typedef bool boolean
typedef string String// struct關鍵字用于定義結構體,相當于面向對象編程語言中的類
struct Person {// 相當于定義類中的成員,并生成相應的get和set方法,optional表示username這個成員可以沒有1: optional String username,2: optional int age,3: optional boolean married
}// 定義一個異常類型,用于接口中可能拋出的異常
exception DataException {1: optional String message,2: optional String callStack,3: optional String date
}// 定義服務接口
service PersonService {Person getPersonByUsername(1: required String username) throws (1: DataException data),void savePerson(1: required Person person)
}
執行thrift --gen java src/thrift/data.thrift
生成對應的java
代碼,并引入到Java工程當中,代碼結構如下圖所示
編寫Java服務端代碼,data.thrift
的service中定義了兩個服務,我們需要定義相應服務的實現類(相當于handler),如下所示:
import thrift.generated.DataException;
import thrift.generated.Person;
import thrift.generated.PersonService;public class PersonServiceImpl implements PersonService.Iface {@Overridepublic Person getPersonByUsername(String username) throws DataException {System.out.println("Got Client Param: " + username);return new Person().setUsername(username).setAge(20).setMarried(false);}@Overridepublic void savePerson(Person person) throws DataException {System.out.println("Got Client Param:");System.out.println(person.username);System.out.println(person.age);System.out.println(person.married);}
}
同時我們需要借助thrift為我們提供的類庫實現一個服務器來監聽rpc請求,代碼如下所示:
import org.apache.thrift.TProcessorFactory;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.THsHaServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;
import thrift.generated.PersonService;public class ThriftServer {public static void main(String[] args) throws Exception {// 定義服務器使用的socket類型TNonblockingServerSocket tNonblockingServerSocket = new TNonblockingServerSocket(8899);// 創建服務器參數THsHaServer.Args arg = new THsHaServer.Args(tNonblockingServerSocket).minWorkerThreads(2).maxWorkerThreads(4);// 請求處理器PersonService.Processor<PersonServiceImpl> processor = new PersonService.Processor<>(new PersonServiceImpl());// 配置傳輸數據的格式arg.protocolFactory(new TCompactProtocol.Factory());// 配置數據傳輸的方式arg.transportFactory(new TFramedTransport.Factory());// 配置處理器用來處理rpc請求arg.processorFactory(new TProcessorFactory(processor));// 本示例中使用半同步半異步方式的服務器模型TServer server = new THsHaServer(arg);System.out.println("Thrift Server Started!");// 啟動服務server.serve();}
}
編寫python客戶端,執行thrift --gen py src/thrift/data.thrift
生成對應的python
代碼,并引入到python工程當中,代碼結構如下圖所示
python客戶端代碼如下所示:
# -*- coding:utf-8 -*-
__author__ = 'kpzhang'from py.thrift.generated import PersonService
from py.thrift.generated import ttypesfrom thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TCompactProtocolimport sysreload(sys)
sys.setdefaultencoding('utf8')try:tSocket = TSocket.TSocket("localhost", 8899)tSocket.setTimeout(900)transport = TTransport.TFramedTransport(tSocket)protocol = TCompactProtocol.TCompactProtocol(transport)client = PersonService.Client(protocol)transport.open()person = client.getPersonByUsername("張三")print person.usernameprint person.ageprint person.marriedprint '---------------------'newPerson = ttypes.Person();newPerson.username = "李四"newPerson.age = 30newPerson.married = Trueclient.savePerson(newPerson)transport.close()except Thrift.TException, tx:print '%s' % tx.message
客戶端可以向調用本地的方法一樣調用服務端的方法。
---------------------
作者:zkp_java
來源:CSDN
原文:https://blog.csdn.net/zkp_java/article/details/81879577
版權聲明:本文為作者原創文章,轉載請附上博文鏈接!
內容解析By:CSDN,CNBLOG博客文章一鍵轉載插件