背景
在某專案中,我們廣泛使用thrift作為我們內部介面呼叫的RPC框架,而且基本上都是使用多執行緒請求等待應答的同步模式。但是在一些情況下(例如大資料量同步),如果可以使用非同步模式,可以優化程式結構和提高模組效能。
分析
thrift有提供一套非同步模式供我們使用,首先我們像往常一樣編寫thrift協議檔案。
1 2 3 4 5 |
namespace cpp uctest service Test{ string pingpong(1: string data); } |
不同的是,需要加入cpp:cob_type來生成程式碼。生成的程式碼檔案外表與之前的基本相同,但在Test.h和Test.cpp中內涵就豐富了,增加了非同步客戶端和非同步伺服器使用的類。
非同步客戶端程式碼有TestCobClient以及它繼承的虛擬類。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class TestCobClient : virtual public TestCobClIf { public: TestCobClient(boost::shared_ptr< ::apache::thrift::async::TAsyncChannel> channel, ::apache::thrift::protocol::TProtocolFactory* protocolFactory) : channel_(channel), itrans_(new ::apache::thrift::transport::TMemoryBuffer()), otrans_(new ::apache::thrift::transport::TMemoryBuffer()), piprot_(protocolFactory->getProtocol(itrans_)), poprot_(protocolFactory->getProtocol(otrans_)) { iprot_ = piprot_.get(); oprot_ = poprot_.get(); } boost::shared_ptr< ::apache::thrift::async::TAsyncChannel> getChannel() { return channel_; } virtual void completed__(bool /* success */) {} void pingpong(std::tr1::function<void(TestCobClient* client)> cob, const std::string& data); void send_pingpong(const std::string& data); void recv_pingpong(std::string& _return); protected: boost::shared_ptr< ::apache::thrift::async::TAsyncChannel> channel_; boost::shared_ptr< ::apache::thrift::transport::TMemoryBuffer> itrans_; boost::shared_ptr< ::apache::thrift::transport::TMemoryBuffer> otrans_; boost::shared_ptr< ::apache::thrift::protocol::TProtocol> piprot_; boost::shared_ptr< ::apache::thrift::protocol::TProtocol> poprot_; ::apache::thrift::protocol::TProtocol* iprot_; ::apache::thrift::protocol::TProtocol* oprot_; }; |
從原始檔上看,非同步功能的核心在於TAsyncChannel,它是用於回撥函式註冊和非同步收發資料。send_pingpong和 recv_pingpong分別向緩衝區(TMemoryBuffer)寫入和讀取資料。而pingpong則通過呼叫TAsyncChannel的 sendAndRecvMessage介面註冊回撥函式。
TAsyncChannel作為介面類定義了三個介面函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
/** * Send a message over the channel. */ virtual void sendMessage(const VoidCallback& cob, apache::thrift::transport::TMemoryBuffer* message) = 0; /** * Receive a message from the channel. */ virtual void recvMessage(const VoidCallback& cob, apache::thrift::transport::TMemoryBuffer* message) = 0; /** * Send a message over the channel and receive a response. */ virtual void sendAndRecvMessage(const VoidCallback& cob, apache::thrift::transport::TMemoryBuffer* sendBuf, apache::thrift::transport::TMemoryBuffer* recvBuf); |
TAsyncChannel目前為止(0.9.1版本)只有一種客戶端實現類TEvhttpClientChannel,顧名思義它是基於 libevent和http協議實現的。 使用libevent的方法就不在這裡累贅了,主要看下sendAndRecvMessage的實現。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
void TEvhttpClientChannel::sendAndRecvMessage( const VoidCallback& cob, apache::thrift::transport::TMemoryBuffer* sendBuf, apache::thrift::transport::TMemoryBuffer* recvBuf) { cob_ = cob; recvBuf_ = recvBuf; struct evhttp_request* req = evhttp_request_new(response, this); uint8_t* obuf; uint32_t sz; sendBuf->getBuffer(&obuf, &sz); rv = evbuffer_add(req->output_buffer, obuf, sz); rv = evhttp_make_request(conn_, req, EVHTTP_REQ_POST, path_.c_str()); } |
通過向evhttp_request中註冊相應回撥函式respones和傳入回撥例項本身的指標,在相應時候回撥函式中呼叫TEvhttpClientChannel例項的finish介面完成資料接收,並寫入快取中,供應用層獲取使用。
實驗
有文章認為:使用Thrift非同步客戶端需要配合使用對應非同步伺服器才能工作。如果這個觀點成立,我們改造目前程式程式碼的成本就會很高,而且可能會喪失使用Thrift的便捷性。
通過上述程式碼的閱讀,發現唯一的侷限性是伺服器必須使用Http的傳輸層,此外只需要協議層保持一致,並不需要一定使用非同步伺服器。
下面我們通過簡單程式碼基於上文“uctest”的協議實現了一個非同步客戶端和同步伺服器。
伺服器程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class TestHandler : virtual public TestIf { public: TestHandler() { } void pingpong(std::string& _return, const std::string& data) { if(data=="ping") printf("[%d] recv ping\n", (int)time(NULL)); _return = "pong"; printf("[%d] send pong\n", (int)time(NULL)); } }; int main(int argc, char **argv) { int port = 9091; shared_ptr<TestHandler> handler(new TestHandler()); shared_ptr<TProcessor> processor(new TestProcessor(handler)); shared_ptr<TServerTransport> serverTransport(new TServerSocket(port)); shared_ptr<TTransportFactory> transportFactory(new THttpServerTransportFactory()); shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory()); TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory); server.serve(); return 0; } |
客戶端程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
void my_ping_pong(TestCobClient* client) { std::string pong; client->recv_pingpong(pong); printf("[%d] recv:%s\n", (int)time(NULL), pong.c_str()); } int main(int argc, char **argv) { try{ event_base* evbase = event_base_new(); boost::shared_ptr<TAsyncChannel> channel(new TEvhttpClientChannel("127.0.0.1", "/", "127.0.0.1", 9091, evbase)); TestCobClient client(channel, new TBinaryProtocolFactory()); function<void(TestCobClient* client)> cob = bind(&my_ping_pong,_1); client.pingpong(cob, "ping"); printf("[%d] ping\n", (int)time(NULL)); for(int i=0;i<5;i++) { printf("[%d] running...\n", (int)time(NULL)); sleep(1); } event_base_dispatch(evbase); event_base_free(evbase); } catch(...) { printf("exception"); return 1; } return 0; }; |
執行結果如下:
[tangzheng@dev10 server]$ ./demo.serv
[1388639886] recv ping
[1388639886] send pong
[tangzheng@dev10 client]$ ./demo.client
[1388639881] ping
[1388639881] running…
[1388639882] running…
[1388639883] running…
[1388639884] running…
[1388639885] running…
[1388639886] recv:pong
達到非同步客戶端預期的效果。
結果
初步掌握了thrift非同步客戶端的用法,我們即可在需要的時候使用,或者優化當前的程式。 由於這種提供的非同步模式必須基於HTTP傳輸層,使用有一定的侷限性。之後將會繼續研究是否可以在TAsyncChannel的基礎上,開發支援其他傳輸層的介面。