Apache Thrift RPC Server. Дружим C++ и Java

от автора

Привет, коллеги.
Хочу в этом топике выложить инструкцию, как быстро прикрутить Thrift, к своим поделкам.
Thrift — технология для организации межпроцессного взаимодействия между компонентами системы. Была разработана где то в недрах Facebook. Посути это кросс-языковой фреймворк для создания RPC сервисов, на бинарном протоколе. С помощью этого решения можно «подружить» компоненты написанные на разных языках C#, C++, Delphi, Erlang, Go, Java, PHP, Python, Ruby, итд. Описание сигнатур сервисов и данных осуществляется с помощью специального IDL — языка. Технология, по своей сути, похожа на COM, но без всей этой обвязки с регистрацией компонент. Так же не будем забывать, что COM это технология только для Windows, в то время как Thrift — кросплатформенна.

Вобщем решил поэкспериментировать, попробовать вынести часть нагруженной-вычислительной логики из Java в С++, в надежде что нативный С++ код будет немного производительней, и за одно опробовать Thrift RPC, в надежде что это быстрее чем REST.
Как и положено, без бубнов и граблей не обошлось!

И так, для начала надо всё это поставить:
1. Ставим поддержку для Boost, ибо всё завязано на нём

$ sudo apt-get install libboost-dev libboost-test-dev libboost-program-options-dev libevent-dev automake libtool flex bison pkg-config g++ libssl-dev 

2. качаем thrift tarball apache.softded.ru/thrift/0.9.0/thrift-0.9.0.tar.gz
распаковываем, запускаем configure, и затем собираем.

$ ./configure $ make $ sudo make install 

Вроде бы всё… Можно даже попробовать сгенерировать код из учебника, который идет вместе с thrift tarball

$ thrift --gen cpp tutorial.thrift 

команда thrift сгенерирует С++ обвертки, и бережно положит их в директорию gen-cpp. Тоже самое можно сделать для Java, PHP итд…
Пробуем скомпилировать и собрать нагенеренные исходники

$ g++ -Wall -I/usr/local/include/thrift *.cpp -L/usr/local/lib -lthrift -o something 

Упс, получите: error: ‘uint32_t’ does not name a type
Оказывается есть небольшой таракан в библиотеках thrift связанный с uint32_t. Лечится добавлением "#include <stdint.h>" в «Thrift.h», или (что лучше всего) специальными опциями компилятора -DHAVE_NETINET_IN_H -DHAVE_INTTYPES_H

Теперь это выглядит так:

$ g++ -DHAVE_INTTYPES_H -DHAVE_NETINET_IN_H -Wall -I/usr/local/include/thrift *.cpp -L/usr/local/lib -lthrift -o something 

Вот и всё, появился исполнимый файл, под названием something.
Запускаем, и получаем: error while loading shared libraries: libthrift.so.0: cannot open shared object file: No such file or directory
Возможно есть какие-то элегантные методы решения этой проблемы, но я решил её в лоб, копированием thrift файлов из /usr/local/lib в /lib

Всё, пример запустился. Значит, все на местах, и всё работает.

Теперь можно писать свой RPC сервер.
Его задача, быть key-value хранилищем. Хранить длинные (в несколько сот тысяч) битовые маски. Складывать их (AND), и отдавать клиенту массив индексов, в которых получились еденицы. Да, почти тоже самое умеет Redis, но он мне не подходит.

Полный код лежит здесь: github.com/2anikulin/fast-hands.git

Описываем сигнатуры данных и сервисов, в thrift definition file:

namespace cpp fasthands namespace java fasthands namespace php fasthands namespace perl fasthands   exception InvalidOperation {   1: i32 what,   2: string why }   service FastHandsService {    i32 put(1:i32 key, 2:binary value),     binary get(1:i32 key),     list <i32> bitAnd(1:list<i32> keys) throws (1:InvalidOperation ouch) } 

И генерируем обвертки.
Реализация на C++
Этот код, создает, и запускает RPC сервер

#define PORT 9090 #define THREAD_POOL_SIZE 15     int main() {     printf("FastHands Server started\n");     shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());   shared_ptr<FastHandsHandler> handler(new FastHandsHandler());   shared_ptr<TProcessor> processor(new FastHandsServiceProcessor(handler));     shared_ptr<ThreadManager> threadManager = ThreadManager::newSimpleThreadManager(THREAD_POOL_SIZE);   shared_ptr<PosixThreadFactory> threadFactory = shared_ptr<PosixThreadFactory>(new PosixThreadFactory());   threadManager->threadFactory(threadFactory);   threadManager->start();     TNonblockingServer server(processor, protocolFactory, PORT, threadManager);   server.serve();   printf("done.\n");     return 0; } 

В классе FastHandsHandler — имплементируется весь наш, прикладной функционал
Это заголовочный файл

class FastHandsHandler : virtual public FastHandsServiceIf {    public:   FastHandsHandler();   int32_t put(const int32_t key, const std::string& value);   void get(std::string& _return, const int32_t key);   void bitAnd(std::vector<int32_t> & _return, const std::vector<int32_t> & keys);    private:   void appendBitPositions(std::vector<int32_t> & positions, unsigned char bits, int offset);    private:   std::map<int32_t, std::string> m_store;   }; 

Пробуем собрать, и получаем очередную ошибку: c++ undefined reference to apache::thrift::server::TNonblockingServer
Дело в том, что, в отличии от учебника, мой сервер — ассинхронный, и использует класс TNonblockingServer. Чтобы код собирался, надо добавить дополнительные библиотеки -lthriftnb -levent

т.е сборка сейчас будет выглядеть так:

g++ -DHAVE_INTTYPES_H -DHAVE_NETINET_IN_H -Wall -I/usr/local/include/thrift *.cpp -L/usr/local/lib -lthrift -lthriftnb -levent -o something 

Теперь все хорошо. Код скомпилирован, слинкован, на выходе файл по имени something
Генерируем обвертки для java, и пишем вот такого клиента

import fasthands.FastHandsService; import fasthands.InvalidOperation; import org.apache.thrift.TException; import org.apache.thrift.transport.TFramedTransport; import org.apache.thrift.transport.TTransport; import org.apache.thrift.transport.TSocket; import org.apache.thrift.transport.TTransportException; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.protocol.TProtocol;   import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List;   public class JavaClient {       public static void main(String [] args) {         TTransport transport = new TFramedTransport(new TSocket("localhost", 9090));         TProtocol protocol = new TBinaryProtocol(transport);         final FastHandsService.Client client = new FastHandsService.Client(protocol);           final List<Integer> filters = new ArrayList<Integer>();           try {             transport.open();               int count = 12500;             byte bt[] = new byte[count];             for (int i =0; i < count; i++) {                 bt[i] = (byte)0xFF;             }               for (int i = 0; i < 50; i++) {                 client.put(i, ByteBuffer.wrap(bt)) ;                 filters.add(i);             }               List<Integer> list = client.bitAnd(filters);             System.out.println(list.size());             } catch (TTransportException e) {             e.printStackTrace();         } catch (TException e) {             e.printStackTrace();           }           transport.close();     } } 

Что в итоге.
Интересная технология, и не плохой способ прикрутить транспортный функционал к голому коду на С++. Но не скажу, что это намного быстрее чем REST, бинарные данные прекрасно передаются и по http. Что касается производительности кода, вынесенного из Java в С++, то чуда не произошло, он быстрее в 1.2 — 1.4 раза, ибо сериализация + расходы на транспортный уровень.

ссылка на оригинал статьи http://habrahabr.ru/post/179235/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *