作者發現,本文被多個博客和網站轉發。贈人玫瑰,手有余香!
本文版權歸作者和博客園共有,歡迎轉載,轉載請注明出處: https://www.cnblogs.com/buguge
【org.apache.thrift.TException家族】

【Thrift架構】
以下是thrift的客戶端和服務端交互的一個原理圖。可以看到遵循了rpc框架的傳輸層、協議層和應用層三層。本文提到的異常就是與這三層相對應的傳輸異常TTransportException(ConnectException、SocketTimeoutException)、協議異常TProtocolException和應用異常TApplicationException。

■ org.apache.thrift.transport.TTransportException: java.net.SocketException: Connection reset
既然是Connection reset,即“連接被重置”,從字面意思就可以判斷出來,是連接的問題。那么,Thrift框架底層就是傳輸層,自然就是TTransport的問題了。什么問題呢?這個異常是由於client端指定的TTransport與服務端不一致導致的。demo中服務端是TFramedTransport,client端的TTransport實例是TSocket。
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:86) at org.apache.thrift.protocol.TBinaryProtocol.readAll(TBinaryProtocol.java:425) at org.apache.thrift.protocol.TBinaryProtocol.readI32(TBinaryProtocol.java:321) at org.apache.thrift.protocol.TBinaryProtocol.readMessageBegin(TBinaryProtocol.java:225) at org.apache.thrift.TServiceClient.receiveBase(TServiceClient.java:77) at com.emaxcard.route.thrift.quickpay.TBatchPayQueryService$Client.recv_batchPayQuery(TBatchPayQueryService.java:61) at com.emaxcard.route.thrift.quickpay.TBatchPayQueryService$Client.batchPayQuery(TBatchPayQueryService.java:48) at com.emaxcard.ThriftTest.main(ThriftTest.java:38) Caused by: java.net.SocketException: Connection reset at java.net.SocketInputStream.read(SocketInputStream.java:209) at java.net.SocketInputStream.read(SocketInputStream.java:141) at java.io.BufferedInputStream.fill(BufferedInputStream.java:246) at java.io.BufferedInputStream.read1(BufferedInputStream.java:286) at java.io.BufferedInputStream.read(BufferedInputStream.java:345) at org.apache.thrift.transport.TIOStreamTransport.read(TIOStreamTransport.java:127) ... 8 more
■ org.apache.thrift.transport.TTransportException: java.net.SocketTimeoutException: connect timed out
在執行transport.open()與服務端建立連接時,超時了。服務端響應時間超出了客戶端設置的connectTimeout值。BTW,因為thrift多應用於局域網分布式系統,所以通常情況下不會出現連接超時,可能是所指定的服務壓根兒就不存在(需檢查IP和端口是否正確)
org.apache.thrift.transport.TTransportException: java.net.SocketTimeoutException: connect timed out at org.apache.thrift.transport.TSocket.open(TSocket.java:226) at com.emaxcard.ThriftTest.main(ThriftTest.java:30) Caused by: java.net.SocketTimeoutException: connect timed out at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method) at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:345) at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) at java.net.Socket.connect(Socket.java:589) at org.apache.thrift.transport.TSocket.open(TSocket.java:221) ... 1 more
■ org.apache.thrift.transport.TTransportException: java.net.ConnectException: Connection refused: connect
連接被拒絕
- 服務端服務停止,客戶端無法建立soket連接,最終會出現這個TTransportException異常。
- 我TSocket指定的是192.168.40.212的9898端口,在212上通過lsof -i:9898命令發現這個端口並沒有開放。也會報這個異常。
注意到這個異常是java.net.ConnectException的Connection refused: connect。由此聯想一下當我們發起一個http請求時,如果http接口的服務端是上面兩種情況,那么,也會出現這個異常。
org.apache.thrift.transport.TTransportException: java.net.ConnectException: Connection refused: connect at org.apache.thrift.transport.TSocket.open(TSocket.java:226) at HelloServiceClient.main(HelloServiceClient.java:26) Caused by: java.net.ConnectException: Connection refused: connect at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method) at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339) at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200) at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182) at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) at java.net.Socket.connect(Socket.java:579) at org.apache.thrift.transport.TSocket.open(TSocket.java:221) ... 1 more
■ org.apache.thrift.transport.TTransportException: Cannot write to null outputstream

原因:客戶端未調用transport的open()方法,或者open失敗了,因socket輸出流是null而報TTransportException。一種情況是指定的遠程服務的地址(ip+端口)/節點名(zk負載情況下)或服務名壓根都不對,必然無法建立socket連接。
■ org.apache.thrift.transport.TTransportException: java.net.SocketTimeoutException: Read timed out
這個socketTimeout異常就很容易理解了。客戶端設置了socketTimeout,而服務端方法未能在這個時間內響應。
TTransport transport = new TSocket("localhost", 9898, socketTimeout, connectTimeout);
示例中我設置socketTimeout=2000,讓服務端方法線程sleep3秒,結果就會出現這個異常。監測客戶端調用的duration=2027,大於設定的2000。
org.apache.thrift.transport.TTransportException: java.net.SocketTimeoutException: Read timed out at org.apache.thrift.transport.TIOStreamTransport.read(TIOStreamTransport.java:129) at org.apache.thrift.transport.TTransport.readAll(TTransport.java:86) at org.apache.thrift.protocol.TBinaryProtocol.readAll(TBinaryProtocol.java:425) at org.apache.thrift.protocol.TBinaryProtocol.readI32(TBinaryProtocol.java:321) at org.apache.thrift.protocol.TBinaryProtocol.readMessageBegin(TBinaryProtocol.java:225) at org.apache.thrift.TServiceClient.receiveBase(TServiceClient.java:77) at com.zhanggz.test.rpc.service.AgentPayService$Client.recv_apply(AgentPayService.java:61) at com.zhanggz.test.rpc.service.AgentPayService$Client.apply(AgentPayService.java:48) at HelloServiceClient.main(HelloServiceClient.java:36) Caused by: java.net.SocketTimeoutException: Read timed out at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.read(SocketInputStream.java:152) 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) ... 8 more
■ org.apache.thrift.transport.TTransportException
我在demo里如下2種調用rpc方法的情況報了這個異常。
- 客戶端指向本機127.0.0.1的8080端口,在調用rpc方法時,報如下異常。因為本機Tomcat的8080端口雖然存在,但並未暴露所指定的thrift服務。
org.apache.thrift.transport.TTransportException at org.apache.thrift.transport.TIOStreamTransport.read(TIOStreamTransport.java:132) at org.apache.thrift.transport.TTransport.readAll(TTransport.java:86) at org.apache.thrift.protocol.TBinaryProtocol.readStringBody(TBinaryProtocol.java:379) at org.apache.thrift.protocol.TBinaryProtocol.readMessageBegin(TBinaryProtocol.java:236) at org.apache.thrift.TServiceClient.receiveBase(TServiceClient.java:77) at com.zhanggz.test.rpc.service.AgentPayService$Client.recv_apply(AgentPayService.java:61) at com.zhanggz.test.rpc.service.AgentPayService$Client.apply(AgentPayService.java:48) at HelloServiceClient.main(HelloServiceClient.java:36)
- 服務端協議層使用的傳輸格式是TMultiplexedProtocol,而client端調用時指定的是TCompactProtocol,在調用rpc方法時出現了異常。
org.apache.thrift.transport.TTransportException at org.apache.thrift.transport.TIOStreamTransport.read(TIOStreamTransport.java:132) at org.apache.thrift.transport.TTransport.readAll(TTransport.java:86) at org.apache.thrift.transport.TFramedTransport.readFrame(TFramedTransport.java:132) at org.apache.thrift.transport.TFramedTransport.read(TFramedTransport.java:100) at org.apache.thrift.transport.TTransport.readAll(TTransport.java:86) at org.apache.thrift.protocol.TCompactProtocol.readByte(TCompactProtocol.java:637) at org.apache.thrift.protocol.TCompactProtocol.readMessageBegin(TCompactProtocol.java:505) at org.apache.thrift.TServiceClient.receiveBase(TServiceClient.java:77) at com.emaxcard.route.thrift.TBatchPayQueryService$Client.recv_batchPayQuery(TBatchPayQueryService.java:61) at com.emaxcard.route.thrift.TBatchPayQueryService$Client.batchPayQuery(TBatchPayQueryService.java:48) at com.emaxcard.ThriftTest.main(ThriftTest.java:43)
■ org.apache.thrift.TApplicationException: Internal error processing *方法名*
當服務端出現未經捕獲的異常時,客戶端會收到這個異常。
這就要求thrift接口服務端一定要規避異常的拋出。
org.apache.thrift.TApplicationException: Internal error processing apply at org.apache.thrift.TServiceClient.receiveBase(TServiceClient.java:79) at com.zhanggz.test.rpc.service.AgentPayService$Client.recv_apply(AgentPayService.java:61) at com.zhanggz.test.rpc.service.AgentPayService$Client.apply(AgentPayService.java:48) at HelloServiceClient.main(HelloServiceClient.java:35)
■ org.apache.thrift.TApplicationException: *方法名* failed: unknown result
當服務端響應值為null時,客戶端會收到這個異常。其中,TApplicationException是TException的一個派生類。
這就要求thrift接口服務端是不允許返回null的。
org.apache.thrift.TApplicationException: apply failed: unknown result at com.zhanggz.test.rpc.service.AgentPayService$Client.recv_apply(AgentPayService.java:65) at com.zhanggz.test.rpc.service.AgentPayService$Client.apply(AgentPayService.java:48) at HelloServiceClient.main(HelloServiceClient.java:35)
■ org.apache.thrift.TApplicationException: Invalid method name: '*方法名*'
字面意思來理解是:客戶端調用的遠程方法,服務端並未暴露出來,導致這個異常。
實際上是什么情況呢?因為一個interface的方法默認都是public的,所以並不存在一個interface的某個方法不能被訪問。之所以拋出這個異常,實際上是客戶端所調用的thrift接口.Client實例,服務端並未暴露thrift接口.Processor。
見如下這種情況:
服務端暴露的接口(Processor):TProcessor tprocessor = new AgentPayService.Processor<AgentPayService.Iface>(new AgentPayServiceImpl());
---么么噠(incaseof 服務端提供的thrift接口jar包里有AgentPayService和HelloService)---
客戶端調用的接口(Client): HelloService.Client client = new HelloService.Client(protocol);
又見如下這種情況:
thrift接口定義了方法,但是服務端實現類並未實現這個方法。 (我們現在的項目是thrfit接口單獨放在一個jar里,當給某個接口新加了方法后,server端沒有獲取最新jar是不會報錯的,而這時如果client端獲取最新jar了,那么在rpc調用時就會出現這個異常)
org.apache.thrift.TApplicationException: Invalid method name: 'helloString' at org.apache.thrift.TServiceClient.receiveBase(TServiceClient.java:79) at com.zhanggz.test.rpc.service.HelloService$Client.recv_helloString(HelloService.java:61) at com.zhanggz.test.rpc.service.HelloService$Client.helloString(HelloService.java:48) at HelloServiceClient.main(HelloServiceClient.java:36)
■ org.apache.thrift.protocol.TProtocolException: Required field '***' was not present!
thrift定義了參數為required。而程序在請求或返回時未對其賦值,會出現這個異常。
struct BatchPayQueryResponseVO{ /**返回碼*/ 1: required i32 responseCode; ~~~ ~~~ /**上游渠道paymentId*/ 9:required string channelPaymentId;
org.apache.thrift.protocol.TProtocolException: Required field 'channelPaymentId' was not present! Struct: BatchPayQueryResponseVO(responseCode:1002, responseMsg:渠道處理失敗, payStatus:null, payStatusText:null, paymentId:32300, amount:0, fee:0, bankSerTime:null, channelPaymentId:null, channelBatchId:null, pyerBankSerialNo:null, pyeeBankSerialNo:null) at com.emaxcard.route.thrift.quickpay.BatchPayQueryResponseVO.validate(BatchPayQueryResponseVO.java:1292) ~[gateway_thrift-1.0-SNAPSHOT.jar:?] at com.emaxcard.route.thrift.quickpay.TBatchPayQueryService$batchPayQuery_result.validate(TBatchPayQueryService.java:856) ~[gateway_thrift-1.0-SNAPSHOT.jar:?] at com.emaxcard.route.thrift.quickpay.TBatchPayQueryService$batchPayQuery_result$batchPayQuery_resultStandardScheme.write(TBatchPayQueryService.java:915) ~[gateway_thrift-1.0-SNAPSHOT.jar:?] at com.emaxcard.route.thrift.quickpay.TBatchPayQueryService$batchPayQuery_result$batchPayQuery_resultStandardScheme.write(TBatchPayQueryService.java:882) ~[gateway_thrift-1.0-SNAPSHOT.jar:?] at com.emaxcard.route.thrift.quickpay.TBatchPayQueryService$batchPayQuery_result.write(TBatchPayQueryService.java:833) ~[gateway_thrift-1.0-SNAPSHOT.jar:?] at org.apache.thrift.ProcessFunction.process(ProcessFunction.java:57) ~[libthrift-0.11.0.jar:0.11.0] at org.apache.thrift.TBaseProcessor.process(TBaseProcessor.java:39) ~[libthrift-0.11.0.jar:0.11.0] at org.apache.thrift.TMultiplexedProcessor.process(TMultiplexedProcessor.java:134) ~[libthrift-0.11.0.jar:0.11.0] at org.apache.thrift.server.AbstractNonblockingServer$FrameBuffer.invoke(AbstractNonblockingServer.java:518) [libthrift-0.11.0.jar:0.11.0] at org.apache.thrift.server.Invocation.run(Invocation.java:18) [libthrift-0.11.0.jar:0.11.0] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_181] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_181] at java.lang.Thread.run(Thread.java:748) [?:1.8.0_181]
■ NPE

原因:thrift服務端可能停了
☞ Stay Hungry,Stay Foolish. 如果對閣下有幫助,就動動手指,點一下“推薦”喲~
