1. 程式人生 > >由淺入深瞭解Thrift(一)——Thrift介紹與用法

由淺入深瞭解Thrift(一)——Thrift介紹與用法

2.6、  編寫客戶端程式碼

Thrift的客戶端程式碼同樣需要伺服器開頭的那兩步:新增三個jar包和生成的java介面檔案TestThriftService.java。

		 m_transport = new TSocket(THRIFT_HOST, THRIFT_PORT,2000);
		 TProtocol protocol = new TBinaryProtocol(m_transport);
		 TestThriftService.Client testClient = new TestThriftService.Client(protocol);
		
		 try {
			 m_transport.open();
			 
			 String res = testClient.getStr("test1", "test2");
			 System.out.println("res = " + res);
			 m_transport.close();
		} catch (TException e){
				// TODO Auto-generated catch block
				e.printStackTrace();
		}


程式碼2.4

由程式碼2.4可以看到編寫客戶端程式碼非常簡單,只需下面幾步即可:

[1]建立一個傳輸層物件(TTransport),具體採用的傳輸方式是TFramedTransport,要與伺服器端保持一致,即:

m_transport =new TFramedTransport(newTSocket(THRIFT_HOST,THRIFT_PORT, 2000));

這裡的THRIFT_HOST, THRIFT_PORT分別是Thrift伺服器程式的主機地址和監聽埠號,這裡的2000是socket的通訊超時時間;

[2]建立一個通訊協議物件(TProtocol),具體採用的通訊協議是二進位制協議,這裡要與伺服器端保持一致,即:

TProtocolprotocol =new TBinaryProtocol(m_transport);

[3]建立一個Thrift客戶端物件(TestThriftService.Client),Thrift的客戶端類TestThriftService.Client已經在檔案TestThriftService.java中,由Thrift編譯器自動為我們生成,即:

TestThriftService.ClienttestClient =new TestThriftService.Client(protocol);

[4]開啟socket,建立與伺服器直接的socket連線,即:

m_transport.open();

[5]通過客戶端物件呼叫伺服器服務函式getStr,即:

String res = testClient.getStr("test1","test2");

                System.out.println("res = " +res);

[6]使用完成關閉socket,即:

m_transport.close();

        這裡有以下幾點需要說明:

[1]在同步方式使用客戶端和伺服器的時候,socket是被一個函式呼叫獨佔的,不能多個呼叫同時使用一個socket,例如通過m_transport.open()開啟一個socket,此時建立多個執行緒同時進行函式呼叫,這時就會報錯,因為socket在被一個呼叫佔著的時候不能再使用;

[2]可以分時多次使用同一個socket進行多次函式呼叫,即通過m_transport.open()開啟一個socket之後,你可以發起一個呼叫,在這個次呼叫完成之後,再繼續呼叫其他函式而不需要再次通過m_transport.open()開啟socket;

2.7、  需要注意的問題

(1)Thrift的伺服器端和客戶端使用的通訊方式要一樣,否則便無法進行正常通訊;

Thrift的伺服器端的種模式所使用的通訊方式並不一樣,因此,伺服器端使用哪種通訊方式,客戶端程式也要使用這種方式,否則就無法進行正常通訊了。例如,上面的程式碼2.3中,伺服器端使用的工作模式為TNonblockingServer,在該工作模式下需要採用的傳輸方式為TFramedTransport,也就是在通訊過程中會將tcp的位元組流封裝成一個個的幀,此時就需要客戶端程式也這麼做,否則便會通訊失敗。出現如下問題:

伺服器端會爆出如下出錯log:

2015-01-06 17:14:52.365 ERROR [Thread-11] [AbstractNonblockingServer.java:348] - Read an invalid frame size of -2147418111. Are you using TFramedTransport on the client side?

客戶端則會報出如下出錯log:

org.apache.thrift.transport.TTransportException: java.net.SocketException: Connection reset
	at org.apache.thrift.transport.TIOStreamTransport.read(TIOStreamTransport.java:129)
	at org.apache.thrift.transport.TTransport.readAll(TTransport.java:84)
	at org.apache.thrift.protocol.TBinaryProtocol.readAll(TBinaryProtocol.java:362)
	at org.apache.thrift.protocol.TBinaryProtocol.readI32(TBinaryProtocol.java:284)
	at org.apache.thrift.protocol.TBinaryProtocol.readMessageBegin(TBinaryProtocol.java:191)
	at org.apache.thrift.TServiceClient.receiveBase(TServiceClient.java:69)
	at com.browan.freepp.dataproxy.service.DataProxyService$Client.recv_addLiker(DataProxyService.java:877)
	at com.browan.freepp.dataproxy.service.DataProxyService$Client.addLiker(DataProxyService.java:862)
	at com.browan.freepp.dataproxy.service.DataProxyServiceTest.test_Likers(DataProxyServiceTest.java:59)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:606)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: java.net.SocketException: Connection reset
	at java.net.SocketInputStream.read(SocketInputStream.java:196)
	at java.net.SocketInputStream.read(SocketInputStream.java:122)
	at java.io.BufferedInputStream.fill(BufferedInputStream.java:235)
	at java.io.BufferedInputStream.read1(BufferedInputStream.java:275)
	at java.io.BufferedInputStream.read(BufferedInputStream.java:334)
	at org.apache.thrift.transport.TIOStreamTransport.read(TIOStreamTransport.java:127)
	... 31 more


(2)在伺服器端或者客戶端直接使用IDL生成的介面檔案時,可能會遇到下面兩個問題:

[1]、Cannotreduce the visibility of the inherited method fromProcessFunction<I,TestThriftService.getStr_args>

[2]、The typeTestThriftService.Processor<I>.getStr<I> must implement theinherited abstract methodProcessFunction<I,TestThriftService.getStr_args>.isOneway()

問題產生的原因:

問題[1] 是繼承類的訪問許可權縮小所造成的;

問題[2] 是因為存在抽象函式isOneWay所致;

解決辦法:

問題[1]的訪問許可權由protected修改為public;

問題[2]的解決辦法是為抽象函式新增一個空的函式體即可。

2.8、  一些的應用技巧

(1)  為呼叫加上一個事務ID

在分散式服務開發過程中,一次事件(事務)的執行可能跨越位於不同機子上多個服務程式,在後續維護過程中跟蹤log將變得非常麻煩,因此在系統設計的時候,系統的一個事務產生之處應該產生一個系統唯一的事務ID,該ID在各服務程式之間進行傳遞,讓一次事務在所有服務程式輸出的log都以此ID作為標識。

在使用Thrift開發伺服器程式的時候,也應該為每個介面函式提供一個事務ID的引數,並且在伺服器程式開發過程中,該ID應該在內部函式呼叫過程中也進行傳遞,並且在日誌輸出的時候都加上它,以便問題跟蹤。

(2)  封裝返回結果

Thrift提供的RPC方式的服務,使得呼叫方可以像呼叫自己的函式一樣呼叫Thrift服務提供的函式;在使用Thrift開發過程中,儘量不要直接返回需要的資料,而是將返回結果進行封裝,例如上面的例子中的getStr函式就是直接返回了結果string,見Thrift檔案test_service.thrift中對該函式的描述:

stringgetStr(1:string srcStr1, 2:string srcStr2)

在實際開發過程中,這是一種很不好的行為,在返回結果為null的時候還可能造成呼叫方產生異常,需要對返回結果進行封裝,例如:

/*String型別返回結果*/
struct ResultStr
{
  1: ThriftResult result,
  2: string value
}


其中ThriftResult是自己定義的列舉型別的返回結果,在這裡可以根據自己的需要新增任何自己需要的返回結果型別:
enum ThriftResult
{
  SUCCESS,           /*成功*/
  SERVER_UNWORKING,  /*伺服器處於非Working狀態*/
  NO_CONTENT,  		 /*請求結果不存在*/
  PARAMETER_ERROR,	 /*引數錯誤*/
  EXCEPTION,	 	 /*內部出現異常*/
  INDEX_ERROR,		 /*錯誤的索引或者下標值*/
  UNKNOWN_ERROR, 	 /*未知錯誤*/
  DATA_NOT_COMPLETE, 	 /*資料不完全*/
  INNER_ERROR, 	 /*內部錯誤*/
}


此時可以將上述定義的getStr函式修改為:

ResultStr  getStr(1:string srcStr1, 2:string srcStr2)

在此函式中,任何時候都會返回一個ResultStr物件,無論異常還是正常情況,在出錯時還可以通過ThriftResult返回出錯的型別。

(3)  將服務與資料型別分開定義

在使用Thrift開發一些中大型專案的時候,很多情況下都需要自己封裝資料結構,例如前面將返回結果進行封裝的時候就定義了自己的資料型別ResultStr,此時,將資料結構和服務分開定義到不通的檔案中,可以增加thrift檔案的易讀性。例如:

在thrift檔案:thrift_datatype.thrift中定義資料型別,如:

namespace java com.browan.freepp.thriftdatatype
const string VERSION = "1.0.1"
/**為ThriftResult新增資料不完全和內部錯誤兩種型別
*/

/****************************************************************************************************
* 定義返回值,
* 列舉型別ThriftResult,表示返回結果,成功或失敗,如果失敗,還可以表示失敗原因
* 每種返回型別都對應一個封裝的結構體,該結構體其命名遵循規則:"Result" + "具體操作結果型別",結構體都包含兩部分內容:
* 第一部分為列舉型別ThriftResult變數result,表示操作結果,可以 表示成功,或失敗,失敗時可以給出失敗原因
* 第二部分的變數名為value,表示返回結果的內容;
*****************************************************************************************************/
enum ThriftResult
{
  SUCCESS,           /*成功*/
  SERVER_UNWORKING,  /*伺服器處於非Working狀態*/
  NO_CONTENT,  		 /*請求結果不存在*/
  PARAMETER_ERROR,	 /*引數錯誤*/
  EXCEPTION,	 	 /*內部出現異常*/
  INDEX_ERROR,		 /*錯誤的索引或者下標值*/
  UNKNOWN_ERROR 	 /*未知錯誤*/
  DATA_NOT_COMPLETE 	 /*資料不完全*/
  INNER_ERROR 	 /*內部錯誤*/
}

/*bool型別返回結果*/
struct ResultBool 
{
  1: ThriftResult result,
  2: bool value
}

/*int型別返回結果*/
struct ResultInt
{
  1: ThriftResult result,
  2: i32 value
}

/*String型別返回結果*/
struct ResultStr
{
  1: ThriftResult result,
  2: string value
}

/*long型別返回結果*/
struct ResultLong
{
  1: ThriftResult result,
  2: i64 value
}



/*double型別返回結果*/
struct ResultDouble
{
  1: ThriftResult result,
  2: double value
}

/*list<string>型別返回結果*/
struct ResultListStr 
{
  1: ThriftResult result,
  2: list<string> value
}

/*Set<string>型別返回結果*/
struct ResultSetStr 
{
  1: ThriftResult result,
  2: set<string> value
}

/*map<string,string>型別返回結果*/
struct ResultMapStrStr 
{
  1: ThriftResult result,
  2: map<string,string> value
}


程式碼2.5

在另外一個檔案test_service.thrift中定義服務介面函式,如下所示:

namespace java com.test.service

include "thrift_datatype.thrift"

service TestThriftService
{

	/**
	*value 中存放兩個字串拼接之後的字串
	*/
	thrift_datatype.ResultStr getStr(1:string srcStr1, 2:string srcStr2),
	
	thrift_datatype.ResultInt getInt(1:i32 val)
	
}


程式碼 2.6

由於在介面服務定義的thrift檔案test_service.thrift中要用到對資料型別定義的thrift檔案:thrift_datatype.thrift,因此需要在其檔案前通過include把自己所使用的thrift檔案包含進來,另外在使用其他thrift檔案中定義的資料型別時要加上它的檔名,如:thrift_datatype.ResultStr

(4)  為Thrift檔案新增版本號

在實際開發過程中,還可以為Thrift檔案加上版本號,以方便對thrift的版本進行控制,如程式碼2.5所示。