RPC框架 之 Apach thrift
Thrift
1,Apache Thrift 主要用於各個服務之間的RPC通訊,支援跨語言,常用語言:C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi都支援。
2,Thrift 是一個典型的CS(客戶端/伺服器)結構,客戶端和服務端可以使用不同的語言開發,既然客戶端和服務端都能使用不同的語言開發,那麼一定就要有一種中間語言來關聯客戶端和服務端的語言,這種語言就是IDL(Interface Description Language), .thrift 就是一種IDL語言
Trift 資料型別(Base Types)
Trift 不支援無符號型別,因為很多程式語言不存在無符號型別,比如java,php
基礎資料型別
byte: 有符號位元組。
i16: 16位有符號整數。
i32:32位有符號整數。
i64:64位有符號整數。
double: 64位 浮點數。
string: 字串。
1:optional string username,
2:optional i32 age,
特殊資料型別
binary:一系列未編碼的位元組
N.B.。
Structs結構體
Thrift structs 定義了公共物件,也就是oop語言中的常說的類,但是他不具有繼承性.結構體可以包含很多欄位,欄位包含的內容: numeric field IDs, optional default values, 結構體的目的就是將一些資料聚合在一起,方便傳輸管理
struct Person {
1:optional string username,
2:optional i32 age,
3:optional bool married
}
容器型別(Containers)
list: 一系列由T型別的資料組成的有序列表,元素可以重複。
set: 一系列由T型別的資料組成的無序集合,元素不可重複。
map: 一個字典結構,key為K 型別,value為V 型別,相當於java中的HashMap.
1: list<string> strings,
2: list<i32> newlist,
3 : set<i32> newset,
4: set<string> a_set2500,
5: map<i32, i32> newmap,
6: map<string,string> map_field,
異常(Exceptions)
thift支援自定義exception,規則與struct一樣,
exception NotFoundException {
}
exception InvalidRequestException {
1: required string why
}
exception DataException{
1:optional string message;
2:optional string callStack,
3:optional string date
}
服務(Services)
Thrift 定義服務相當於java中建立Interface一樣,建立的service經過程式碼生成命令之後就會生成客戶端和伺服器端的框架程式碼。
- 一個服務(Services)可以定義多個函式。
service FacebookService {
string getName(),
map<string, i64> getCounters(),
i64 getCounter(1: string key),
}
service PersonService{
Person getPersonByUsername(1:required string username) throws(1:DataException dataException),
void savePerson(1:required Person person) throws(1:DataException dataException)
}
型別定義(typedef)
thirift 支援類似c++ 一樣的typedef定義,相當於改了別名,語法:
typedef DefinitionType Identifier
typedef i16 short
typedef i32 int
typedef i64 long
typedef bool boolean
typedef string String
使用
struct Person {
1: optional String username,
2: optional int age,
3: optional boolean married
}
常量(const)
thrift 也支援常量定義,使用const 關鍵字,語法:
const FieldType Identifier = ConstValue
const i32 INT32CONSTANT = 9853
const map<string,string> MAPCONSTANT = {'hello':'world', 'goodnight':'moon'}
名稱空間
Thirft的名稱空間相當於java中package的意思,主要目的是組織程式碼。thrift使用namespace定義名稱空間:
namespace 語言名 路徑
namespace java thrift.generated
namespace php tutorial
檔案包含
Trift也支援檔案包含,相當於php/c/c++中的include,java中的import.使用關鍵字include定義:
include “檔名”
include "paratent.thrift"
註釋
Trifit註釋方式支援shell風格的註釋,支援c/c++ 風格的註釋,即#和//開頭的語句都當做註釋,/**/包含的語句就是註釋。
可選與必選
thrift 提供兩個關鍵字required,option,分別用於表示對應的欄位是必填的還是可選的。預設是可選
struct People{
1: required string name;
2: optional i32 age;
}
一個完整的thrift 檔案
namespace java thrift.generated
namespace php tutorial
typedef i16 short
typedef i32 int
typedef i64 long
typedef bool boolean
typedef string String
struct Person {
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 dataException),
void savePerson(1:required Person person) throws(1: DataException dataEception),
String testSave(1:required String name)
}
Thrift 工作原理
如何實現多語言之間的通訊?
1,資料傳輸使用socket(多種語言均支援),資料再以特定的格式傳送,接收語言進行解析。
2,定義thrift 的檔案,由thrift檔案(IDL)生成雙方語言的介面,model,在生成的model以及介面中會有解碼編碼的程式碼。
安裝thrift
brew install thrift
生成程式碼
thrift -r --gen java src/thrift/person.thrift
thrift -r --java php src/thrift/person.thrift
或者
thrift -r --java php:server src/thrift/person.thrift
java thrift示例
idl users.thrift
namespace java com.lihao.netty.thrift2
namespace php thrift
typedef i16 short
typedef i32 int
typedef i64 long
typedef bool boolean
typedef string String
struct User{
1: String username,
2: int age,
3: int id,
}
struct UserRequest{
1:int id;
2:String token;
3:String username;
4:String passworld;
}
exception DataException {
1: String message;
2: String callStack;
3: String date
}
service UserService{
User login(1:required UserRequest userRequst) throws(1: DataException dataException);
void logOut(1:required UserRequest userRequst) throws(1: DataException dataException);
list<User> userList() throws(1: DataException dataException);
}
生成程式碼
thrift -r --gen java src/thrift/users.thrift
實現service 介面方法 UserServiceIml.java
public class UserServiceIml implements UserService.Iface {
@Override
public User login(UserRequest userRequst) throws DataException, TException {
System.out.println("--------伺服器:login--------------");
System.out.println(userRequst);
User user = new User();
user.setId(2);
user.setUsername("張三");
user.setAge(20);
return user;
}
@Override
public void logOut(UserRequest userRequst) throws DataException, TException {
System.out.println("--------伺服器:logOut--------------");
System.out.println(userRequst);
}
@Override
public List<User> userList() throws DataException, TException {
System.out.println("--------伺服器:userList--------------");
List userList = new ArrayList<>();
User user = new User();
user.setId(2);
user.setUsername("我會回來的");
user.setAge(20);
User user1 = new User();
user1.setId(2);
user1.setUsername("張三-2---");
user1.setAge(20);
User user2 = new User();
user2.setId(2);
user2.setUsername("張三---123");
user2.setAge(20);
userList.add(user);
userList.add(user1);
userList.add(user2);
return userList;
}
}
服務端 Server.java
public class Server {
public static void main(String... args) throws Exception {
/**
* +-------------------------------------------+
| Server |
| (single-threaded, event-driven etc) |
+-------------------------------------------+
| Processor |
| (compiler generated) |
+-------------------------------------------+
| Protocol |
| (JSON, compact etc) |
+-------------------------------------------+
| Transport |
| (raw TCP, HTTP etc) |
+-------------------------------------------+
*/
TNonblockingServerSocket socket = new TNonblockingServerSocket(7788);
THsHaServer.Args arg = new THsHaServer.Args(socket).minWorkerThreads(2).maxWorkerThreads(4);
UserService.Processor<UserServiceIml> processor = new UserService.Processor<>(new UserServiceIml());
//協議 二進位制壓縮
arg.protocolFactory(new TCompactProtocol.Factory());
//傳輸 幀
arg.transportFactory(new TFramedTransport.Factory());
// 處理器
arg.processorFactory(new TProcessorFactory(processor));
TServer server = new THsHaServer(arg);
System.out.println("Thrift Server Started");
server.serve();
}
}
java客戶端 client
public class Client {
public static void main(String ...args) {
TTransport transport = new TFastFramedTransport(new TSocket("localhost",7788),600);
TCompactProtocol protocol = new TCompactProtocol(transport);
UserService.Client client = new UserService.Client(protocol);
try {
transport.open();
UserRequest request = new UserRequest();
request.setUsername("li si");
request.setPassworld("****");
User user = client.login(request);
System.out.println("--------------------------------");
System.out.println(user);
System.out.println("--------------------------------");
List<User> list = client.userList();
System.out.println(list);
System.out.println("--------------------------------");
} catch (Exception ex){
ex.printStackTrace();
} finally {
transport.close();
}
}
}
php客戶端
- 生成php程式碼
thrift -r --gen php src/thrift/users.thrift
UserClient.php
header("Content-Type: text/html; charset=UTF-8");
ini_set("display_errors", 'On');
error_reporting(E_ALL);
require_once __DIR__ . '/lib/Thrift/ClassLoader/ThriftClassLoader.php';
use Thrift\ClassLoader\ThriftClassLoader;
use \Thrift\Transport\TSocket;
use \Thrift\Transport\TFramedTransport;
use \Thrift\Protocol\TCompactProtocol;
use \thrift\UserServiceClient;
$GEN_DIR = __DIR__ . '/gen-php';
$loader = new ThriftClassLoader();
$loader->registerNamespace('Thrift', __DIR__ . '/lib');
$loader->registerDefinition('thrift', $GEN_DIR);
$loader->register();
$socket = new TSocket("localhost", 7788);
$transport = new TFramedTransport($socket);
$protoc = new TCompactProtocol($transport);
$client = new UserServiceClient($protoc);
echo 1;
try {
$transport->open();
echo 2;
$request = new \thrift\UserRequest();
$request->username = "張三";
$user = $client->login($request);
print_r($user);
echo 3;
$list = $client->userList();
echo $list[0]->username."ok";
} catch (\tutorial\DataException $ex) {
echo 'error';
print 'TException: ' . $ex->getMessage() . "\n";
}
$transport->close();
程式碼步驟
thfit伺服器端程式碼
建立Handler,用於處理業務邏輯,資料處理介面.
基於Handler建立Processor,資料處理物件
建立Transport(通訊方式),資料傳輸方式
建立Protocol方式(設定傳輸格式),資料傳輸協議
基於Processor, Transport和Protocol建立Server
執行Server
thrift客戶端:
建立Transport
建立Protocol方式
基於Transport和Protocol建立Client
執行Client的方法
Thrift 深入瞭解
Thrift 架構圖
client
表示與服務端連線的那個物件,進行方法呼叫,
write/read
thift 幫助我們自動生成的, write 是將客戶端的資料寫到socket(伺服器端),read 從socket讀到客戶端
TProtocal
應用層 表示協議 資料格式 比如json
TTransport
傳輸層
TCompactProtocol 即使使用charlse 抓包 拿到資料也是二進位制
Thrift 傳輸格式Protocol
TBinaryProtocol 二進位制格式
TCompactProtocol 壓縮二進位制格式(常用的方式)
TJSONProtocol JSON格式
TSimpleJSONProtocal 提供json只寫協議,生成的檔案很容易通過指令碼語言解析。
TDebugProtocal 使用易懂的可讀的文字格式,以便於debug
Thrift 傳輸方式Transport
TSocket 阻塞式socket
TFramedTransport 以frame 為單位進行傳輸,非阻塞式服務中使用(常用的方式)
TFileTransport 以檔案形式進行傳輸
TMemoryTransport 將記憶體用於I/O , java 實現時內部實際使用了簡單的ByteArrayOutputStream。
TZlibTransport 使用zlib進行壓縮,與其他傳輸方式聯合使用。當前無java實現。
Thrift 支援的服務模型
TSimpleServer 簡單的單執行緒服務模型,常用於測試
TThreadPoolServer 多執行緒服務模型,使用標準的阻塞式IO
TNonbolockingServer - 多執行緒服務模型,使用非阻塞式IO(需使用TFramedTransport資料傳輸方式)
THsHaServer - THsHa 引入了執行緒池去處理,其模型把讀寫任務放倒執行緒池去處理;
Half-sync/Half-async的處理模式,Half-async是在處理IO事件上(accept/read/write io),
Half-sync用於handler對rpc同步處理(常用方式)