Хочу в этом топике выложить инструкцию, как быстро прикрутить 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/
Добавить комментарий