參考文章RPC 基本原理與 Apach Thrift 初體驗
java
RPC(Remote Procedure Call),遠程過程調用,大部分的RPC框架都遵循以下三個開發步驟:python
1. 定義一個接口說明文件:描述了對象(結構體)、對象成員、接口方法等一系列信息; 2. 經過RPC框架所提供的編譯器,將接口說明文件編譯成具體的語言文件; 3. 在客戶端和服務器端分別引入RPC編譯器所生成的文件,便可像調用本地方法同樣調用服務端代碼;
RPC通訊過程以下圖所示
通訊過程包括如下幾個步驟:web
一、客戶過程以正常方式調用客戶樁(client stub,一段代碼); 二、客戶樁生成一個消息,而後調用本地操做系統; 三、客戶端操做系統將消息發送給遠程操做系統; 四、遠程操做系統將消息交給服務器樁(server stub,一段代碼); 五、服務器樁將參數提取出來,而後調用服務器過程; 六、服務器執行要求的操做,操做完成後將結果返回給服務器樁; 七、服務器樁將結果打包成一個消息,而後調用本地操做系統; 八、服務器操做系統將含有結果的消息發送回客戶端操做系統; 九、客戶端操做系統將消息交給客戶樁; 十、客戶樁將結果從從消息中提取出來,返回給調用它的客戶過程;
全部這些步驟的效果是,將客戶過程對客戶樁發出的本地調用轉換成對服務器過程的本地調用,而客戶端和服務器都不會意識到有中間步驟的存在。apache
這個時候,你可能會想,既然是調用另外一臺機器的服務,使用 RESTful API 也能夠實現啊,爲何要選擇 RPC 呢?咱們能夠從兩個方面對比:編程
對接異構第三方服務時,一般使用 HTPP/RESTful 等公有協議,對於內部的服務調用,應用選擇性能更高的二進制私有協議。json
thrift主要用於各個服務之間的RPC通訊,支持跨語言。thrift是一個典型的CS結構,客戶端和服務端可使用不一樣的語言開發,thrift經過IDL(Interface Description Language)來關聯客戶端和服務端。thrift的總體架構圖以下圖所示
服務器
FooService.Client
和
Foo.write()/read()
是thrift根據IDL生成的客戶端和服務端的代碼,對應於RPC中Client stub和Server stub。TProtocol 用來對數據進行序列化與反序列化,具體方法包括二進制,JSON 或者 Apache Thrift 定義的格式。TTransport 提供數據傳輸功能,使用 Apache Thrift 能夠方便地定義一個服務並選擇不一樣的傳輸協議。
thirft使用socket進行數據傳輸,數據以特定的格式發送,接收方進行解析。咱們定義好thrift的IDL文件後,就可使用thrift的編譯器來生成雙方語言的接口、model,在生成的model以及接口代碼中會有解碼編碼的代碼。markdown
表明thrift的數據傳輸方式,thrift定義了以下幾種經常使用數據傳輸方式網絡
TSocket
: 阻塞式socket;TFramedTransport
: 以frame爲單位進行傳輸,非阻塞式服務中使用;TFileTransport
: 以文件形式進行傳輸;表明thrift客戶端和服務端之間傳輸數據的協議,通俗來說就是客戶端和服務端之間傳輸數據的格式(例如json等),thrift定義了以下幾種常見的格式多線程
TBinaryProtocol
: 二進制格式;TCompactProtocol
: 壓縮格式;TJSONProtocol
: JSON格式;TSimpleJSONProtocol
: 提供只寫的JSON協議;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支持一下幾種基本的數據類型
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;
}
本示例中咱們使用java編寫thrift的服務端程序,使用python編寫thrift的客戶端程序。
首先定義thrift IDL文件
// data.thrift
namespace java thrift.generated
namespace py py.thrift.generated
typedef 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 {
@Override
public Person getPersonByUsername(String username) throws DataException {
System.out.println("Got Client Param: " + username);
return new Person().setUsername(username).setAge(20).setMarried(false);
}
@Override
public 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工程當中,代碼結構以下圖所示
# -*- coding:utf-8 -*-
__author__ = 'kpzhang'
from py.thrift.generated import PersonService
from py.thrift.generated import ttypes
from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TCompactProtocol
import sys
reload(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.username
print person.age
print person.married
print '---------------------'
newPerson = ttypes.Person();
newPerson.username = "李四"
newPerson.age = 30
newPerson.married = True
client.savePerson(newPerson)
transport.close()
except Thrift.TException, tx:
print '%s' % tx.message
客戶端能夠向調用本地的方法同樣調用服務端的方法。