場景
Thrift
Thrift最初由Facebook研發,主要用於各個服務之間的RPC通信,支持跨語言,常用的語言比如C++, Java, Python,PHP, Ruby, Erlang,Perl,Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml都支持。
Thrift是一個典型的CS(客戶端/服務端)結構,客戶端和服務端可以使用不同的語言開發。既然客戶端和服務端能使用不同的語言開發,那么一定就要有一種中間語言來關聯客戶端和服務端的語言,這種語言就是IDL (Interface Description Language)。
RPC
RPC, 遠程過程調用,直觀說法就是A通過網絡調用B的過程方法。
簡單的說,RPC就是從一台機器(客戶端)上通過參數傳遞的方式調用另一台機器(服務器)上的一個函數或方法(可以統稱為服務)並得到返回的結果。
RPC 會隱藏底層的通訊細節(不需要直接處理Socket通訊或Http通訊) RPC 是一個請求響應模型。
客戶端發起請求,服務器返回響應(類似於Http的工作方式) RPC 在使用形式上像調用本地函數(或方法)一樣去調用遠程的函數(或方法)。
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
關注公眾號
霸道的程序猿
獲取編程相關電子書、教程推送與免費下載。
實現
Thrift數據類型
Thrift不支持無符號類型,因為很多編程語言不存在無符號類型,比如Java。
byte:有符號字節
i16:16位有符號整數
i32:32位有符號整數
i64:64位有符號整數
double:64位浮點數
string:字符串類型
Thrift容器類型
list:一系列由T類型的數據組成的有序列表,元素可以重復
set:一系列由T類型的數據組成的無序集合,元素不可重復
map:一個字典結構,key為K類型,value為V類型,相當於Java中的HashMap
Thrift工作原理
如何實現多語言之間的通信?
數據傳輸使用socket(多種語言均支持),數據再以特定的格式(String等)發送,接收方語言進行解析。
定義thrift的文件,由thrift文件(IDL)生成雙方語言的接口、model,在生成的model以及接口中會有解碼編碼的代碼。
Thrift結構體
就像C語言一樣,Thrift支持struct類型,目的就是將
一些數據聚合在一起,方便傳輸管理。struct的定義
形式如下:
struct People{ 1:string name; 2: i32 age; 3: string gender; }
Thrift枚舉
枚舉的定義形式和Java的Enum定義類似
enum Gender{ MALE, FEMALE }
Thrift異常
Thrift支持自定義exception,規則與struct一樣
exception RequestException{ 1:i32 code; 2: string reason: }
Thrift服務
Thrift定義服務相當於Java中創建Interface一樣,創建的service經過代碼生成命令之后就會生成客戶端和服務端的框架代碼。定義形式如下:、
service HelloWordService { // service中定義的函數,相當於Java interface中定義的方法 string doAction(1:string name,2: i32 age); }
Thrift類型定義
Thrift支持類似C++一樣的typedef定義:
typedef i32 int typedef i64 long
定義別名
Thrift常量
thrift也支持常量定義,使用const關鍵字:
const i32 MAX_RETRIES_TIME=10 const string MY_WEBSITE= "https://blog.csdn.net/BADAO_LIUMANG_QIZHI'
命名空間
Thrift的命名空間相當於Java中的package的意思,主要目的是組織代碼。thrift使用關鍵字namespace 定義命名空間:
namespace java com.bdao.thrift
格式是: namespace 語言名 路徑
文件包含
Thrift也支持文件包含,相當於C/C++中的include,Java中的import。使用關鍵字include定義:
include "global.thrift"
注釋
Thrift注釋方式支持shell風格的注釋,支持C/C++風格的注釋,即#和//開頭的語句都當做注釋,/**/包裹的語句也是注釋。
可選與必選
Thrift提供兩個關鍵字required, optional,分別用於表示對應的字段是必填的還是可選的
struct People{ 1:required string name; 2:optional i32 age; }
Thrift IDL示例文件
namespace c_glib TTest namespace cpp thrift.test namespace delphi Thrift.Test namespace go thrifttest namespace java thrift.test namespace js ThriftTest namespace lua ThriftTest namespace netstd ThriftTest namespace perl ThriftTest namespace php ThriftTest namespace py ThriftTest namespace py.twisted ThriftTest namespace rb Thrift.Test namespace st ThriftTest namespace xsd test (uri = 'http://thrift.apache.org/ns/ThriftTest') // Presence of namespaces and sub-namespaces for which there is // no generator should compile with warnings only namespace noexist ThriftTest namespace cpp.noexist ThriftTest namespace * thrift.test /** * Docstring! */ enum Numberz { ONE = 1, TWO, THREE, FIVE = 5, SIX, EIGHT = 8 } const Numberz myNumberz = Numberz.ONE; // the following is expected to fail: // const Numberz urNumberz = ONE; typedef i64 UserId struct Bonk { 1: string message, 2: i32 type } typedef map<string,Bonk> MapType struct Bools { 1: bool im_true, 2: bool im_false, } struct Xtruct { 1: string string_thing, 4: i8 byte_thing, 9: i32 i32_thing, 11: i64 i64_thing } struct Xtruct2 { 1: i8 byte_thing, // used to be byte, hence the name 2: Xtruct struct_thing, 3: i32 i32_thing } struct Xtruct3 { 1: string string_thing, 4: i32 changed, 9: i32 i32_thing, 11: i64 i64_thing } struct Insanity { 1: map<Numberz, UserId> userMap, 2: list<Xtruct> xtructs } (python.immutable= "") struct CrazyNesting { 1: string string_field, 2: optional set<Insanity> set_field, // Do not insert line break as test/go/Makefile.am is removing this line with pattern match 3: required list<map<set<i32> (python.immutable = ""), map<i32,set<list<map<Insanity,string>(python.immutable = "")> (python.immutable = "")>>>> list_field, 4: binary binary_field } union SomeUnion { 1: map<Numberz, UserId> map_thing, 2: string string_thing, 3: i32 i32_thing, 4: Xtruct3 xtruct_thing, 5: Insanity insanity_thing } exception Xception { 1: i32 errorCode, 2: string message } exception Xception2 { 1: i32 errorCode, 2: Xtruct struct_thing } struct EmptyStruct {} struct OneField { 1: EmptyStruct field } service ThriftTest { /** * Prints "testVoid()" and returns nothing. */ void testVoid(), /** * Prints 'testString("%s")' with thing as '%s' * @param string thing - the string to print * @return string - returns the string 'thing' */ string testString(1: string thing), /** * Prints 'testBool("%s")' where '%s' with thing as 'true' or 'false' * @param bool thing - the bool data to print * @return bool - returns the bool 'thing' */ bool testBool(1: bool thing), /** * Prints 'testByte("%d")' with thing as '%d' * The types i8 and byte are synonyms, use of i8 is encouraged, byte still exists for the sake of compatibility. * @param byte thing - the i8/byte to print * @return i8 - returns the i8/byte 'thing' */ i8 testByte(1: i8 thing), /** * Prints 'testI32("%d")' with thing as '%d' * @param i32 thing - the i32 to print * @return i32 - returns the i32 'thing' */ i32 testI32(1: i32 thing), /** * Prints 'testI64("%d")' with thing as '%d' * @param i64 thing - the i64 to print * @return i64 - returns the i64 'thing' */ i64 testI64(1: i64 thing), /** * Prints 'testDouble("%f")' with thing as '%f' * @param double thing - the double to print * @return double - returns the double 'thing' */ double testDouble(1: double thing), /** * Prints 'testBinary("%s")' where '%s' is a hex-formatted string of thing's data * @param binary thing - the binary data to print * @return binary - returns the binary 'thing' */ binary testBinary(1: binary thing), /** * Prints 'testStruct("{%s}")' where thing has been formatted into a string of comma separated values * @param Xtruct thing - the Xtruct to print * @return Xtruct - returns the Xtruct 'thing' */ Xtruct testStruct(1: Xtruct thing), /** * Prints 'testNest("{%s}")' where thing has been formatted into a string of the nested struct * @param Xtruct2 thing - the Xtruct2 to print * @return Xtruct2 - returns the Xtruct2 'thing' */ Xtruct2 testNest(1: Xtruct2 thing), /** * Prints 'testMap("{%s")' where thing has been formatted into a string of 'key => value' pairs * separated by commas and new lines * @param map<i32,i32> thing - the map<i32,i32> to print * @return map<i32,i32> - returns the map<i32,i32> 'thing' */ map<i32,i32> testMap(1: map<i32,i32> thing), /** * Prints 'testStringMap("{%s}")' where thing has been formatted into a string of 'key => value' pairs * separated by commas and new lines * @param map<string,string> thing - the map<string,string> to print * @return map<string,string> - returns the map<string,string> 'thing' */ map<string,string> testStringMap(1: map<string,string> thing), /** * Prints 'testSet("{%s}")' where thing has been formatted into a string of values * separated by commas and new lines * @param set<i32> thing - the set<i32> to print * @return set<i32> - returns the set<i32> 'thing' */ set<i32> testSet(1: set<i32> thing), /** * Prints 'testList("{%s}")' where thing has been formatted into a string of values * separated by commas and new lines * @param list<i32> thing - the list<i32> to print * @return list<i32> - returns the list<i32> 'thing' */ list<i32> testList(1: list<i32> thing), /** * Prints 'testEnum("%d")' where thing has been formatted into its numeric value * @param Numberz thing - the Numberz to print * @return Numberz - returns the Numberz 'thing' */ Numberz testEnum(1: Numberz thing), /** * Prints 'testTypedef("%d")' with thing as '%d' * @param UserId thing - the UserId to print * @return UserId - returns the UserId 'thing' */ UserId testTypedef(1: UserId thing), /** * Prints 'testMapMap("%d")' with hello as '%d' * @param i32 hello - the i32 to print * @return map<i32,map<i32,i32>> - returns a dictionary with these values: * {-4 => {-4 => -4, -3 => -3, -2 => -2, -1 => -1, }, 4 => {1 => 1, 2 => 2, 3 => 3, 4 => 4, }, } */ map<i32,map<i32,i32>> testMapMap(1: i32 hello), /** * So you think you've got this all worked out, eh? * * Creates a map with these values and prints it out: * { 1 => { 2 => argument, * 3 => argument, * }, * 2 => { 6 => <empty Insanity struct>, }, * } * @return map<UserId, map<Numberz,Insanity>> - a map with the above values */ map<UserId, map<Numberz,Insanity>> testInsanity(1: Insanity argument), /** * Prints 'testMulti()' * @param i8 arg0 - * @param i32 arg1 - * @param i64 arg2 - * @param map<i16, string> arg3 - * @param Numberz arg4 - * @param UserId arg5 - * @return Xtruct - returns an Xtruct with string_thing = "Hello2, byte_thing = arg0, i32_thing = arg1 * and i64_thing = arg2 */ Xtruct testMulti(1: i8 arg0, 2: i32 arg1, 3: i64 arg2, 4: map<i16, string> arg3, 5: Numberz arg4, 6: UserId arg5), /** * Print 'testException(%s)' with arg as '%s' * @param string arg - a string indication what type of exception to throw * if arg == "Xception" throw Xception with errorCode = 1001 and message = arg * else if arg == "TException" throw TException * else do not throw anything */ void testException(1: string arg) throws(1: Xception err1), /** * Print 'testMultiException(%s, %s)' with arg0 as '%s' and arg1 as '%s' * @param string arg - a string indicating what type of exception to throw * if arg0 == "Xception" throw Xception with errorCode = 1001 and message = "This is an Xception" * else if arg0 == "Xception2" throw Xception2 with errorCode = 2002 and struct_thing.string_thing = "This is an Xception2" * else do not throw anything * @return Xtruct - an Xtruct with string_thing = arg1 */ Xtruct testMultiException(1: string arg0, 2: string arg1) throws(1: Xception err1, 2: Xception2 err2) /** * Print 'testOneway(%d): Sleeping...' with secondsToSleep as '%d' * sleep 'secondsToSleep' * Print 'testOneway(%d): done sleeping!' with secondsToSleep as '%d' * @param i32 secondsToSleep - the number of seconds to sleep */ oneway void testOneway(1:i32 secondsToSleep) } service SecondService { /** * Prints 'testString("%s")' with thing as '%s' * @param string thing - the string to print * @return string - returns the string 'thing' */ string secondtestString(1: string thing) } struct VersioningTestV1 { 1: i32 begin_in_both, 3: string old_string, 12: i32 end_in_both } struct VersioningTestV2 { 1: i32 begin_in_both, 2: i32 newint, 3: i8 newbyte, 4: i16 newshort, 5: i64 newlong, 6: double newdouble 7: Bonk newstruct, 8: list<i32> newlist, 9: set<i32> newset, 10: map<i32, i32> newmap, 11: string newstring, 12: i32 end_in_both } struct ListTypeVersioningV1 { 1: list<i32> myints; 2: string hello; } struct ListTypeVersioningV2 { 1: list<string> strings; 2: string hello; } struct GuessProtocolStruct { 7: map<string,string> map_field, } struct LargeDeltas { 1: Bools b1, 10: Bools b10, 100: Bools b100, 500: bool check_true, 1000: Bools b1000, 1500: bool check_false, 2000: VersioningTestV2 vertwo2000, 2500: set<string> a_set2500, 3000: VersioningTestV2 vertwo3000, 4000: list<i32> big_numbers } struct NestedListsI32x2 { 1: list<list<i32>> integerlist } struct NestedListsI32x3 { 1: list<list<list<i32>>> integerlist } struct NestedMixedx2 { 1: list<set<i32>> int_set_list 2: map<i32,set<string>> map_int_strset 3: list<map<i32,set<string>>> map_int_strset_list } struct ListBonks { 1: list<Bonk> bonk } struct NestedListsBonk { 1: list<list<list<Bonk>>> bonk } struct BoolTest { 1: optional bool b = true; 2: optional string s = "true"; } struct StructA { 1: required string s; } struct StructB { 1: optional StructA aa; 2: required StructA ab; } struct OptionalSetDefaultTest { 1: optional set<string> with_default = [ "test" ] }
Thrift傳輸
thrift通過一個中間語言IDL(接口定義語言)來定義RPC的數據類型和接口,這些內容寫在以.thrift結尾的文件中,然后通過特殊的編譯器來生成不同語言的代碼
,以滿足不同需要的開發者,比如java開發者,就可以生成java代碼,c++開發者可以生成c++代碼,生成的代碼中不但包含目標語言的接口定義,方法,數據類型,
還包含有RPC協議層和傳輸層的實現代碼。
圖中,TProtocol(協議層),定義數據傳輸格式,例如:
TBinaryProtocol:二進制格式;
TCompactProtocol:壓縮格式;
TJSONProtocol:JSON格式;
TSimpleJSONProtocol:提供JSON只寫協議, 生成的文件很容易通過腳本語言解析;
TDebugProtocol:使用易懂的可讀的文本格式,以便於debug
TTransport(傳輸層),定義數據傳輸方式,可以為TCP/IP傳輸,內存共享或者文件共享等)被用作運行時庫。
TSocket:阻塞式socker;
TFramedTransport:以frame為單位進行傳輸,非阻塞式服務中使用;
TFileTransport:以文件形式進行傳輸;
TMemoryTransport:將內存用於I/O,java實現時內部實際使用了簡單的ByteArrayOutputStream;
TZlibTransport:使用zlib進行壓縮, 與其他傳輸方式聯合使用,當前無java實現;
Thrift支持的服務模型
TSimpleServer:簡單的單線程服務模型,常用於測試;
TThreadPoolServer:多線程服務模型,使用標准的阻塞式IO;
TNonblockingServer:多線程服務模型,使用非阻塞式IO(需使用TFramedTransport數據傳輸方式);
Java中使用Thrift實現RPC示例
首先需要下載並配置thrift的編譯器,下載地址
http://thrift.apache.org/download
這里下載Windows版本為例
下載thrift的exe文件
將其下載到磁盤中某目錄,然后在環境變量的的系統變量的Path中將exe所在的路徑添加。
這樣就能在任何地方使用thrift-0.13.0.exe了,為了使用命令方便,將此exe命名為thrift.exe
然后打開cmd ,輸入
thrift -version
出現如上則配置成功。
這里使用Gradle作為依賴管理,當然也可以使用Maven,只是管理方式不同而已。
Gradle在Windows下的下載安裝與配置以及在IDEA中配置以及修改jar包位置:
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/108578033
參照上面在IDEA中進行搭建Gradle項目
然后在build.gradle中引入thrift的相關依賴,在Maven中央倉庫中搜搜thrift
選擇與編譯器對應的版本這里是0.13.0
選擇gradle的依賴配置進行復制
然后復制到build.gradle中
dependencies { compile ( [group:'org.apache.thrift', name: 'libthrift', version: '0.13.0'] ) }
因為設置了自動導入,所以會在項目中自動引入thrift相關的依賴
然后在src下新建thrift目錄,在此目錄下新建MyData.thrift文件
namespace java thrift.generated 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 callback, 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 dataException) }
然后在IDEA中新建Terminal
thrift --gen java src/thrift/MyData.thrift
后面跟的目錄就是thrift文件所在的路徑
此時會在項目的根目錄下生成代碼,然后在src/main/java/com.badao下新建一個目錄Thrift,然后將上面生成的代碼復制到此路徑下。
此時代碼就已經生成好,
在此目錄下新建PersonServiceImpl並實現生成的PersonService接口
package com.badao.Thrift; import org.apache.thrift.TException; public class PersonServiceImpl implements PersonService.Iface { @Override public Person getPersonByUsername(String username) throws DataException, TException { System.out.println("getPersonByUsername被調用並收到參數:" + username); Person person = new Person(); person.setUsername("公眾號:霸道的程序猿"); person.setAge(100); person.setMarried(true); return person; } @Override public void savePerson(Person person) throws DataException, TException { System.out.println("savePerson方法被調用,接收到的參數為:"); System.out.println(person.getUsername()); System.out.println(person.getAge()); System.out.println(person.isMarried()); } }
並且實現其兩個方法,在根據用戶名獲取Person實體的方法中
輸出接收到的username參數並將一個Person對象返回。
在保存Person的方法中將接收到的Person對象進行輸出。
然后在此目錄下新建服務端類ThriftServer
package com.badao.Thrift; 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; public class ThriftServer { public static void main(String[] args) throws Exception { //設置服務器端口 TNonblockingServerSocket-非堵塞服務模型 TNonblockingServerSocket serverSocket = new TNonblockingServerSocket(8899); //參數設置 THsHaServer.Args arg = new THsHaServer.Args(serverSocket).minWorkerThreads(2).maxWorkerThreads(4); //處理器 PersonService.Processor<PersonServiceImpl> processor = new PersonService.Processor<>(new PersonServiceImpl()); 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.serve(); } }
綁定端口並啟動服務端。
然后在此路徑下新建客戶端ThriftClient
package com.badao.Thrift; import org.apache.thrift.protocol.TCompactProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.TFramedTransport; import org.apache.thrift.transport.TSocket; import org.apache.thrift.transport.TTransport; public class ThriftClient { public static void main(String[] args) { TTransport transport = new TFramedTransport(new TSocket("localhost", 8899), 600); TProtocol protocol = new TCompactProtocol(transport); PersonService.Client client = new PersonService.Client(protocol); try { transport.open(); Person person = client.getPersonByUsername("公眾號:霸道的程序猿"); System.out.println(person.getUsername()); System.out.println(person.getAge()); System.out.println(person.isMarried()); System.out.println("------------------------"); Person person1 = new Person(); person1.setUsername("公眾號:霸道的程序猿"); person1.setAge(50); person1.setMarried(true); client.savePerson(person1); }catch (Exception ex){ throw new RuntimeException(ex.getMessage(),ex); }finally { transport.close(); } } }
與服務端建立連接並調用服務端的兩個方法,然后運行服務端
然后再運行客戶端
此時客戶端已經調用getPersonByUsername並輸出數據並且調用了服務端的savePerson方法
示例代碼下載
https://download.csdn.net/download/BADAO_LIUMANG_QIZHI/12864856